Hi,

I have a ThingM blink(1) USB RGB device that shows up as uhid(4).
The tooling is "interesting", especially with all those libusb and
HID libraries doing the abstraction.  This introduces ublink(4), a
dedicated kernel driver for that device.  There are two LEDs on the
device, which can be modified seperately.  The firmware is impress-
ive and provides features like playing sequences and adjusting how
long it should take to fade from one colour to another.  Obviously
this also increases the complexity of the tools involved to toggle
those RGB LEDs.  Thus, the driver is kept simple (for now).

In addition to providing a way to set RGB LEDs, we can also use it
as a watchdog.  If we don't "tickle" it in a specific timeframe it
will play a (unless otherwise programmed) random sequence, which for
instance can be used to see that the machine has paniced.  This has
been quite useful while debugging the USB stack, because once the
magic sequence started you know that you're in deep trouble.  All
other features are unimplemented.  Gamma correction would be nice
to have.

Since there is no abstraction layer for LEDs yet, this also intro-
duces led(4), which attaches to ublink(4), and provides /dev/ledX.

uhidev1 at uhub0 port 3 configuration 1 interface 0 "ThingM blink(1) mk2" rev 
2.00/0.02 addr 2
uhidev1: iclass 3/0, 1 report id
ublink0 at uhidev1 reportid 1
led0 at ublink0: 2 LEDs

led(4) can be improved even further.  We can attach the ThinkPad
LEDs to it to control it.  There are also plenty of mouses that
have controllable RGBs. We could add "human readable descrip-
tions", for instance "upper side LED" or "docking station LED",
so that users can find out more easily which LED they have to
toggle.  A fading or blinking mechanism, including hardware off-
loading, can probably be added as well.

To be able to control those devices, there's ledctl(1), a simple
program that can turn on/off /dev/ledX devices and also set RGB
colours.

I only did the MAKEDEV stuff for amd64, and also there are no
manpages yet.  Also I haven't run it through make build yet,
sorry.  And I don't often do userland tools, so feel free to
let me know how to improve it.

Thanks,
Patrick

diff --git a/etc/MAKEDEV.common b/etc/MAKEDEV.common
index 6c48a46a74a..1ee654feb4b 100644
--- a/etc/MAKEDEV.common
+++ b/etc/MAKEDEV.common
@@ -519,6 +519,8 @@ __devitem(ipmi, ipmi*, IPMI BMC access)dnl
 _mkdev(ipmi, ipmi*, {-M ipmi$U c major_ipmi_c $U 600-})dnl
 __devitem(gpio, gpio*, General Purpose Input/Output)dnl
 _mcdev(gpio, gpio*, gpio, {-major_gpio_c-}, 600)dnl
+__devitem(led, led*, Generic LED devices)dnl
+_mcdev({-led-}, led*, {-led-}, {-major_led_c-}, 660)dnl
 __devitem(vmm, vmm, Virtual Machine Monitor)dnl
 _mkdev(vmm, vmm, {-M vmm c major_vmm_c 0 600-})dnl
 __devitem(pvbus, pvbus*, paravirtual device tree root)dnl
diff --git a/etc/etc.amd64/MAKEDEV b/etc/etc.amd64/MAKEDEV
index 0bf05aa2fb7..63920f63329 100644
--- a/etc/etc.amd64/MAKEDEV
+++ b/etc/etc.amd64/MAKEDEV
@@ -78,6 +78,7 @@
 #      gpr*    GPR400 smartcard reader
 #      hotplug devices hot plugging
 #      ipmi*   IPMI BMC access
+#      led*    Generic LED devices
 #      nvram   NVRAM access
 #      kcov    Kernel code coverage tracing
 #      pci*    PCI bus devices
@@ -329,6 +330,10 @@ nvram)
        M nvram c 85 0 440 kmem
        ;;
 
+led*)
+       M led$U c 55 $U 660
+       ;;
+
 ipmi*)
        M ipmi$U c 96 $U 600
        ;;
diff --git a/etc/etc.amd64/MAKEDEV.md b/etc/etc.amd64/MAKEDEV.md
index 1aa4f673524..03f696e8c5a 100644
--- a/etc/etc.amd64/MAKEDEV.md
+++ b/etc/etc.amd64/MAKEDEV.md
@@ -76,6 +76,7 @@ _DEV(gpr, 80)
 _DEV(hotplug, 82)
 _DEV(ipmi, 96)
 dnl _DEV(joy, 26)
+_DEV(led, 55)
 _DEV(nvram, 85)
 _DEV(kcov, 19)
 _DEV(pci, 72)
diff --git a/share/man/man8/man8.amd64/MAKEDEV.8 
b/share/man/man8/man8.amd64/MAKEDEV.8
index f43b5cc341f..a1c08a9738e 100644
--- a/share/man/man8/man8.amd64/MAKEDEV.8
+++ b/share/man/man8/man8.amd64/MAKEDEV.8
@@ -1,10 +1,10 @@
-.\" $OpenBSD: MAKEDEV.8,v 1.83 2018/08/19 11:51:04 anton Exp $
+.\" $OpenBSD$
 .\"
 .\" THIS FILE AUTOMATICALLY GENERATED.  DO NOT EDIT.
 .\" generated from:
 .\"
 .\"    OpenBSD: etc.amd64/MAKEDEV.md,v 1.70 2018/08/19 11:42:33 anton Exp 
-.\"    OpenBSD: MAKEDEV.common,v 1.100 2018/08/19 11:42:33 anton Exp 
+.\"    OpenBSD: MAKEDEV.common,v 1.103 2019/06/11 14:48:56 jcs Exp 
 .\"    OpenBSD: MAKEDEV.man,v 1.9 2017/06/06 08:11:23 tb Exp 
 .\"    OpenBSD: MAKEDEV.mansub,v 1.2 2004/02/20 19:13:01 miod Exp 
 .\"
@@ -23,7 +23,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: August 19 2018 $
+.Dd $Mdocdate: June 6 2017 $
 .Dt MAKEDEV 8 amd64
 .Os
 .Sh NAME
@@ -230,6 +230,9 @@ devices hot plugging, see
 .It Ar ipmi*
 IPMI BMC access, see
 .Xr ipmi 4 .
+.It Ar led*
+Generic LED devices, see
+.Xr led 4 .
 .It Ar nvram
 NVRAM access, see
 .Xr nvram 4 .
diff --git a/sys/arch/amd64/amd64/conf.c b/sys/arch/amd64/amd64/conf.c
index a8c10783f11..307287ea096 100644
--- a/sys/arch/amd64/amd64/conf.c
+++ b/sys/arch/amd64/amd64/conf.c
@@ -163,6 +163,7 @@ cdev_decl(nvram);
 cdev_decl(drm);
 #include "viocon.h"
 cdev_decl(viocon);
+#include "led.h"
 
 #include "wsdisplay.h"
 #include "wskbd.h"
@@ -247,7 +248,7 @@ struct cdevsw       cdevsw[] =
        cdev_midi_init(NMIDI,midi),     /* 52: MIDI I/O */
        cdev_notdef(),                  /* 53 was: sequencer I/O */
        cdev_notdef(),                  /* 54 was: RAIDframe disk driver */
-       cdev_notdef(),                  /* 55: */
+       cdev_led_init(NLED,led),        /* 55: led(4) */
        /* The following slots are reserved for isdn4bsd. */
        cdev_notdef(),                  /* 56: i4b main device */
        cdev_notdef(),                  /* 57: i4b control device */
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 64d390905d9..1fd43d81507 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -280,6 +280,8 @@ ucom*       at ucycom?
 uslhcom* at uhidev?            # Silicon Labs CP2110 USB HID UART
 ucom*  at uslhcom?
 uhid*  at uhidev?              # USB generic HID support
+ublink*        at uhidev?              # ThingM blink(1)
+led*   at ublink?
 upd*   at uhidev?              # USB Power Devices sensors
 aue*   at uhub?                # ADMtek AN986 Pegasus Ethernet
 atu*   at uhub?                # Atmel AT76c50x based 802.11b
diff --git a/sys/conf/files b/sys/conf/files
index 0cef7842919..ab0db72178a 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -56,6 +56,11 @@ define       i2c_bitbang
 # 1-Wire bus bit-banging
 define onewire_bitbang
 
+define led {}
+device led
+file   dev/ic/led.c            led needs-flag
+attach led at led
+
 # net device attributes - we have generic code for ether(net)
 define crypto
 define ether
diff --git a/sys/dev/ic/led.c b/sys/dev/ic/led.c
new file mode 100644
index 00000000000..3bc8c214394
--- /dev/null
+++ b/sys/dev/ic/led.c
@@ -0,0 +1,139 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2019 Patrick Wildt <patr...@blueri.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/vnode.h>
+#include <sys/conf.h>
+
+#include <dev/ic/led.h>
+
+#ifdef LED_DEBUG
+#define DPRINTF(x)     do { if (leddebug) printf x; } while (0)
+#define DPRINTFN(n,x)  do { if (leddebug>(n)) printf x; } while (0)
+int    leddebug = 0;
+#else
+#define DPRINTF(x)
+#define DPRINTFN(n,x)
+#endif
+
+struct led_softc {
+       struct device    sc_dev;
+       unsigned int     sc_nled;
+
+       void            *sc_cookie;
+       struct led_ctl  *sc_ctl;
+};
+
+#define        LEDUNIT(dev)    (minor(dev))
+
+int led_match(struct device *, void *, void *);
+void led_attach(struct device *, struct device *, void *);
+int led_detach(struct device *, int);
+
+struct cfdriver led_cd = {
+       NULL, "led", DV_DULL
+};
+
+struct cfattach led_ca = {
+       sizeof(struct led_softc), led_match, led_attach, led_detach,
+};
+
+int
+led_match(struct device *parent, void *match, void *aux)
+{
+       return 1;
+}
+
+void
+led_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct led_softc *sc = (struct led_softc *)self;
+       struct led_attach_args *laa = (struct led_attach_args *)aux;
+
+       sc->sc_cookie = laa->cookie;
+       sc->sc_ctl = laa->ctl;
+       sc->sc_nled = laa->nled;
+
+       printf(": %u LEDs\n", sc->sc_nled);
+}
+
+int
+led_detach(struct device *self, int flags)
+{
+       int maj, mn;
+
+       /* locate the major number */
+       for (maj = 0; maj < nchrdev; maj++)
+               if (cdevsw[maj].d_open == ledopen)
+                       break;
+
+       /* Nuke the vnodes for any open instances (calls close). */
+       mn = self->dv_unit;
+       vdevgone(maj, mn, mn, VCHR);
+
+       return 0;
+}
+
+int
+ledopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+       struct led_softc *sc;
+
+       if (LEDUNIT(dev) >= led_cd.cd_ndevs)
+               return ENXIO;
+       sc = led_cd.cd_devs[LEDUNIT(dev)];
+       if (sc == NULL)
+               return ENXIO;
+
+       return 0;
+}
+
+int
+ledclose(dev_t dev, int flag, int mode, struct proc *p)
+{
+       return 0;
+}
+
+int
+ledioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
+{
+       struct led_softc *sc;
+       struct led_set *set;
+
+       if (LEDUNIT(dev) >= led_cd.cd_ndevs)
+               return ENXIO;
+       sc = led_cd.cd_devs[LEDUNIT(dev)];
+       if (sc == NULL)
+               return ENXIO;
+
+       switch (cmd) {
+       case LED_GET_NUM:
+               *(unsigned int *)addr = sc->sc_nled;
+               break;
+       case LED_SET:
+               set = (struct led_set *)addr;
+               if (set->led >= sc->sc_nled)
+                       return ENXIO;
+               sc->sc_ctl->set_led(sc->sc_cookie, set->led, set->rgb);
+               break;
+       default:
+               return EINVAL;
+       }
+       return 0;
+}
diff --git a/sys/dev/ic/led.h b/sys/dev/ic/led.h
new file mode 100644
index 00000000000..d738692e35f
--- /dev/null
+++ b/sys/dev/ic/led.h
@@ -0,0 +1,36 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2019 Patrick Wildt <patr...@blueri.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/ioctl.h>
+
+struct led_ctl {
+       void    (*set_led)(void *, unsigned int led, uint32_t);
+};
+
+struct led_attach_args {
+       void            *cookie;
+       struct led_ctl  *ctl;
+       unsigned int     nled;
+};
+
+struct led_set {
+       unsigned int     led;
+       uint32_t         rgb;
+};
+
+#define LED_GET_NUM            _IOR ('L', 0, unsigned int)
+#define LED_SET                        _IOW ('L', 1, struct led_set)
diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb
index 2a68642a9a9..12a6a087824 100644
--- a/sys/dev/usb/files.usb
+++ b/sys/dev/usb/files.usb
@@ -468,3 +468,8 @@ file        dev/usb/uwacom.c                uwacom
 
 attach bwfm at uhub with bwfm_usb: firmload
 file   dev/usb/if_bwfm_usb.c           bwfm_usb
+
+# ThingM blink(1)
+device ublink: led
+attach ublink at uhidbus
+file   dev/usb/ublink.c                ublink
diff --git a/sys/dev/usb/ublink.c b/sys/dev/usb/ublink.c
new file mode 100644
index 00000000000..4b749c6a88e
--- /dev/null
+++ b/sys/dev/usb/ublink.c
@@ -0,0 +1,169 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2019 Patrick Wildt <patr...@blueri.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/timeout.h>
+
+#include <dev/ic/led.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdevs.h>
+#include <dev/usb/uhidev.h>
+
+#define BLINK_FADE_MS          300
+#define BLINK_WATCHDOG_MS      15000
+#define BLINK_TIMEOUT_MS       10000
+
+#define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname)
+
+#ifdef UBLINK_DEBUG
+#define DPRINTF(x)     printf x
+#else
+#define DPRINTF(x)
+#endif
+
+struct ublink_softc {
+       struct uhidev            sc_hdev;
+       struct usbd_device      *sc_udev;
+
+       struct timeout           sc_wdog_to;
+};
+
+int ublink_match(struct device *, void *, void *);
+void ublink_attach(struct device *, struct device *, void *);
+int ublink_detach(struct device *, int);
+
+void ublink_watchdog(void *);
+
+void ublink_set_led(void *, unsigned int, uint32_t);
+
+struct led_ctl ublink_ctl = {
+       ublink_set_led
+};
+
+struct cfdriver ublink_cd = {
+       NULL, "ublink", DV_DULL
+};
+
+struct cfattach ublink_ca = {
+       sizeof(struct ublink_softc), ublink_match, ublink_attach, ublink_detach
+};
+
+struct usb_devno ublink_devs[] = {
+       { USB_VENDOR_THINGM, USB_PRODUCT_THINGM_BLINK1 },
+};
+
+int
+ublink_match(struct device *parent, void *match, void *aux)
+{
+       struct uhidev_attach_arg *uha = aux;
+
+       if (uha->reportid == UHIDEV_CLAIM_ALLREPORTID)
+               return UMATCH_NONE;
+
+       if (usb_lookup(ublink_devs, uha->uaa->vendor,
+           uha->uaa->product) == NULL)
+               return UMATCH_NONE;
+
+       return UMATCH_VENDOR_PRODUCT;
+}
+
+void
+ublink_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct ublink_softc *sc = (struct ublink_softc *)self;
+       struct uhidev_attach_arg *uha = aux;
+       struct led_attach_args la;
+
+       sc->sc_udev = uha->parent->sc_udev;
+       sc->sc_hdev.sc_parent = uha->parent;
+       sc->sc_hdev.sc_report_id = uha->reportid;
+
+       printf("\n");
+
+       /* Turn on watchdog and repeatedly re-arm. */
+       timeout_set(&sc->sc_wdog_to, ublink_watchdog, sc);
+       timeout_add_msec(&sc->sc_wdog_to, BLINK_TIMEOUT_MS);
+
+       /* Attach led(4). */
+       la.cookie = self;
+       la.ctl = &ublink_ctl;
+       la.nled = 2;
+       config_found(self, &la, NULL);
+}
+
+int
+ublink_detach(struct device *self, int flags)
+{
+       struct ublink_softc *sc = (struct ublink_softc *)self;
+
+       timeout_del(&sc->sc_wdog_to);
+       return config_detach_children(self, flags);
+}
+
+void
+ublink_watchdog(void *self)
+{
+       struct ublink_softc *sc = self;
+       uint8_t buf[7];
+
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       /* "serverdown" */
+       buf[0] = 'D';
+       buf[1] = 1;
+       buf[2] = ((BLINK_WATCHDOG_MS / 10) >> 8);
+       buf[3] = ((BLINK_WATCHDOG_MS / 10) % 0xff);
+       buf[4] = 1;
+       buf[5] = 0;
+       buf[6] = 15;
+
+       uhidev_set_report_async(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
+            sc->sc_hdev.sc_report_id, buf, sizeof(buf));
+       timeout_add_msec(&sc->sc_wdog_to, BLINK_TIMEOUT_MS);
+}
+
+void
+ublink_set_led(void *self, unsigned int led, uint32_t rgb)
+{
+       struct ublink_softc *sc = self;
+       uint8_t buf[7];
+
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       if (led > 1)
+               return;
+
+       /* fade to RGB */
+       buf[0] = 'c';
+       buf[1] = (rgb >> 16) & 0xff;
+       buf[2] = (rgb >> 8) & 0xff;
+       buf[3] = rgb & 0xff;
+       buf[4] = ((BLINK_FADE_MS / 10) >> 8);
+       buf[5] = (BLINK_FADE_MS / 10) % 0xff;
+       buf[6] = led + 1;
+
+       uhidev_set_report_async(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT,
+            sc->sc_hdev.sc_report_id, buf, sizeof(buf));
+}
diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs
index c506a7bcc28..afd673b6b7e 100644
--- a/sys/dev/usb/usbdevs
+++ b/sys/dev/usb/usbdevs
@@ -630,6 +630,7 @@ vendor TRIPPLITE    0x2478  Tripp-Lite
 vendor ARUBA           0x2626  Aruba
 vendor XIAOMI          0x2717  Xiaomi
 vendor NHJ             0x2770  NHJ
+vendor THINGM          0x27b8  ThingM
 vendor ASUSTEK         0x2821  ASUSTeK Computer
 vendor PLANEX          0x2c02  Planex Communications
 vendor LINKINSTRUMENTS 0x3195  Link Instruments
@@ -4249,6 +4250,9 @@ product TI NEXII          0x5409  Nex II Digital
 product TI MSP430_JTAG         0xf430  MSP-FET430UIF JTAG
 product TI MSP430              0xf432  MSP-FET430UIF
 
+/* ThingM products */
+product THINGM BLINK1          0x01ed  blink(1)
+
 /* Thrustmaster products */
 product THRUST FUSION_PAD      0xa0a3  Fusion Digital Gamepad
 
diff --git a/sys/dev/usb/usbdevs.h b/sys/dev/usb/usbdevs.h
index e520ee87431..b74272773b0 100644
--- a/sys/dev/usb/usbdevs.h
+++ b/sys/dev/usb/usbdevs.h
@@ -1,4 +1,4 @@
-/*     $OpenBSD: usbdevs.h,v 1.714 2019/12/07 08:47:20 kevlo Exp $     */
+/*     $OpenBSD$       */
 
 /*
  * THIS FILE IS AUTOMATICALLY GENERATED.  DO NOT EDIT.
@@ -637,6 +637,7 @@
 #define        USB_VENDOR_ARUBA        0x2626          /* Aruba */
 #define        USB_VENDOR_XIAOMI       0x2717          /* Xiaomi */
 #define        USB_VENDOR_NHJ  0x2770          /* NHJ */
+#define        USB_VENDOR_THINGM       0x27b8          /* ThingM */
 #define        USB_VENDOR_ASUSTEK      0x2821          /* ASUSTeK Computer */
 #define        USB_VENDOR_PLANEX       0x2c02          /* Planex 
Communications */
 #define        USB_VENDOR_LINKINSTRUMENTS      0x3195          /* Link 
Instruments */
@@ -4256,6 +4257,9 @@
 #define        USB_PRODUCT_TI_MSP430_JTAG      0xf430          /* 
MSP-FET430UIF JTAG */
 #define        USB_PRODUCT_TI_MSP430   0xf432          /* MSP-FET430UIF */
 
+/* ThingM products */
+#define        USB_PRODUCT_THINGM_BLINK1       0x01ed          /* blink(1) */
+
 /* Thrustmaster products */
 #define        USB_PRODUCT_THRUST_FUSION_PAD   0xa0a3          /* Fusion 
Digital Gamepad */
 
diff --git a/sys/dev/usb/usbdevs_data.h b/sys/dev/usb/usbdevs_data.h
index 21b61b04d5a..b2a46a8ef79 100644
--- a/sys/dev/usb/usbdevs_data.h
+++ b/sys/dev/usb/usbdevs_data.h
@@ -1,4 +1,4 @@
-/*     $OpenBSD: usbdevs_data.h,v 1.708 2019/12/07 08:47:20 kevlo Exp $        
*/
+/*     $OpenBSD$       */
 
 /*
  * THIS FILE IS AUTOMATICALLY GENERATED.  DO NOT EDIT.
@@ -10901,6 +10901,10 @@ const struct usb_known_product usb_known_products[] = {
            USB_VENDOR_TI, USB_PRODUCT_TI_MSP430,
            "MSP-FET430UIF",
        },
+       {
+           USB_VENDOR_THINGM, USB_PRODUCT_THINGM_BLINK1,
+           "blink(1)",
+       },
        {
            USB_VENDOR_THRUST, USB_PRODUCT_THRUST_FUSION_PAD,
            "Fusion Digital Gamepad",
@@ -14041,6 +14045,10 @@ const struct usb_known_vendor usb_known_vendors[] = {
            USB_VENDOR_NHJ,
            "NHJ",
        },
+       {
+           USB_VENDOR_THINGM,
+           "ThingM",
+       },
        {
            USB_VENDOR_ASUSTEK,
            "ASUSTeK Computer",
diff --git a/sys/sys/conf.h b/sys/sys/conf.h
index 48118f7f670..9782ffab7be 100644
--- a/sys/sys/conf.h
+++ b/sys/sys/conf.h
@@ -432,6 +432,13 @@ extern struct cdevsw cdevsw[];
        (dev_type_stop((*))) enodev, 0, selfalse, \
        (dev_type_mmap((*))) enodev }
 
+/* open, close, ioctl */
+#define cdev_led_init(c,n) { \
+       dev_init(c,n,open), dev_init(c,n,close), (dev_type_read((*))) enodev, \
+       (dev_type_write((*))) enodev, dev_init(c,n,ioctl), \
+       (dev_type_stop((*))) enodev, 0, selfalse, \
+       (dev_type_mmap((*))) enodev }
+
 /* open, close, ioctl */
 #define       cdev_bio_init(c,n) { \
        dev_init(c,n,open), dev_init(c,n,close), (dev_type_read((*))) enodev, \
@@ -579,6 +586,8 @@ cdev_decl(diskmap);
 
 cdev_decl(bpf);
 
+cdev_decl(led);
+
 cdev_decl(pf);
 
 cdev_decl(tun);
diff --git a/usr.bin/Makefile b/usr.bin/Makefile
index 35a3c00d69f..79b180eefcc 100644
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -11,8 +11,8 @@ SUBDIR= apply arch at aucat audioctl awk banner \
        find fgen finger fmt fold from fstat ftp gencat getcap \
        getconf getent getopt gprof grep head hexdump htpasswd id indent \
        infocmp ipcrm ipcs \
-       join jot kdump keynote ktrace lam last lastcomm ldap leave less lex \
-       libtool lndir \
+       join jot kdump keynote ktrace lam last lastcomm ldap leave ledctl \
+       less lex libtool lndir \
        locale locate lock logger login logname look lorder \
        m4 mail make mandoc mesg mg \
        midicat mixerctl mkdep mklocale mktemp nc netstat \
diff --git a/usr.bin/ledctl/Makefile b/usr.bin/ledctl/Makefile
new file mode 100644
index 00000000000..2d19fa44551
--- /dev/null
+++ b/usr.bin/ledctl/Makefile
@@ -0,0 +1,8 @@
+#      $OpenBSD$
+
+PROG=  ledctl
+SRCS=  ledctl.c
+
+NOMAN=
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/ledctl/ledctl.c b/usr.bin/ledctl/ledctl.c
new file mode 100644
index 00000000000..90dc2a531e3
--- /dev/null
+++ b/usr.bin/ledctl/ledctl.c
@@ -0,0 +1,94 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2019 Patrick Wildt <patr...@blueri.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <dev/ic/led.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void
+usage(void)
+{
+       fprintf(stderr, "usage: %s -f device -n led [on|off|rgb]\n",
+           getprogname());
+       exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+       const char *dev, *errstr;
+       unsigned int led = 0;
+       struct led_set set;
+       int ch, fd;
+       long rgb;
+       char *ep;
+
+       dev = "/dev/led0";
+       while ((ch = getopt(argc, argv, "f:n:")) != -1) {
+               switch (ch) {
+               case 'f':
+                       dev = optarg;
+                       break;
+               case 'n':
+                       led = strtonum(optarg, 0, 64, &errstr);
+                       if (errstr != NULL)
+                               errx(1, "led index is %s: %s", errstr, optarg);
+                       break;
+               default:
+                       usage();
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (dev == NULL || argc != 1)
+               usage();
+
+       fd = open(dev, O_RDWR);
+       if (fd == -1)
+               err(1, "%s", dev);
+
+       memset(&set, 0, sizeof(set));
+       set.led = led;
+       if (strcmp(argv[0], "on") == 0) {
+               set.rgb = 0xffffff;
+               ioctl(fd, LED_SET, &set);
+       } else if (strcmp(argv[0], "off") == 0) {
+               set.rgb = 0x000000;
+               ioctl(fd, LED_SET, &led);
+       } else {
+               errno = 0;
+               rgb = strtol(argv[0], &ep, 16);
+               if (*argv[0] == '\0' || *ep != '\0')
+                       errx(1, "led rgb is %s: %s", errstr, argv[0]);
+               if (errno == ERANGE && (rgb == LONG_MAX || rgb == LONG_MIN))
+                       errx(1, "led rgb is %s: %s", errstr, argv[0]);
+               set.rgb = rgb;
+               ioctl(fd, LED_SET, &set);
+       }
+
+       exit(0);
+}

Reply via email to