On Fri, Dec 13, 2019 at 10:34:59PM +0100, Patrick Wildt wrote:
> 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

Updated diff, changes:

 * All the manpages!
 * /dev/led{0,1,2,...} are now by default 600.
 * Doesn't panic when you remove the stick, I think I
   might have already had that fixed in the first diff.
 * Some small improvements here and there, nothing special.

Thanks to jan for ledctl(1) manpage and review.  And thanks to
cheloha@!

Patrick

diff --git a/etc/MAKEDEV.common b/etc/MAKEDEV.common
index c187df748d6..dc67b54ec5a 100644
--- a/etc/MAKEDEV.common
+++ b/etc/MAKEDEV.common
@@ -521,6 +521,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-}, 600)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 543b0ec731b..50f7ac53acb 100644
--- a/etc/etc.amd64/MAKEDEV
+++ b/etc/etc.amd64/MAKEDEV
@@ -78,6 +78,7 @@
 #      gpio*   General Purpose Input/Output
 #      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 600
+       ;;
+
 ipmi*)
        M ipmi$U c 96 $U 600
        ;;
diff --git a/etc/etc.amd64/MAKEDEV.md b/etc/etc.amd64/MAKEDEV.md
index f46b52bd7d6..66e85a16da8 100644
--- a/etc/etc.amd64/MAKEDEV.md
+++ b/etc/etc.amd64/MAKEDEV.md
@@ -76,6 +76,7 @@ _DEV(gpio, 88)
 _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/man4/Makefile b/share/man/man4/Makefile
index 307bd44b66b..45ab7539c60 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -41,8 +41,8 @@ MAN=  aac.4 abcrtc.4 ac97.4 acphy.4 acrtc.4 \
        ip.4 ip6.4 ipcomp.4 ipgphy.4 ipmi.4 ips.4 ipsec.4 ipw.4 \
        isa.4 isagpio.4 isapnp.4 islrtc.4 it.4 itherm.4 iwi.4 iwn.4 iwm.4 \
        ix.4 ixgb.4 ixl.4 jmb.4 jme.4 jmphy.4 \
-       kate.4 kcov.4 km.4 ksmn.4 ksyms.4 kubsan.4 kue.4 lc.4 lge.4 lii.4 \
-       lisa.4 lm.4 lmenv.4 lmn.4 lmtemp.4 lo.4 lpt.4 lxtphy.4 luphy.4 \
+       kate.4 kcov.4 km.4 ksmn.4 ksyms.4 kubsan.4 kue.4 lc.4 led.4 lge.4 \
+       lii.4 lisa.4 lm.4 lmenv.4 lmn.4 lmtemp.4 lo.4 lpt.4 lxtphy.4 luphy.4 \
        maestro.4 mainbus.4 malo.4 maxds.4 maxrtc.4 maxtmp.4 mbg.4 \
        mcprtc.4 mcx.4 midi.4 mii.4 mfi.4 \
        mfii.4 mlphy.4 moscom.4 mos.4 mpe.4 mpath.4 mpi.4 mpii.4 \
@@ -75,8 +75,8 @@ MAN=  aac.4 abcrtc.4 ac97.4 acphy.4 acrtc.4 \
        tcic.4 tcp.4 termios.4 tht.4 ti.4 tipmic.4 tl.4 \
        tlphy.4 thmc.4 tpm.4 tpmr.4 tqphy.4 trm.4 trunk.4 tsl.4 tty.4 \
        tun.4 tap.4 twe.4 \
-       txp.4 txphy.4 uaudio.4 uark.4 uath.4 ubcmtp.4 uberry.4 ubsa.4 \
-       ubsec.4 ucom.4 uchcom.4 ucrcom.4 ucycom.4 ukspan.4 uslhcom.4 \
+       txp.4 txphy.4 uaudio.4 uark.4 uath.4 ubcmtp.4 uberry.4 ublink.4 \
+       ubsa.4 ubsec.4 ucom.4 uchcom.4 ucrcom.4 ucycom.4 ukspan.4 uslhcom.4 \
        udav.4 udcf.4 udl.4 udp.4 udsbr.4 \
        uftdi.4 ugen.4 ugl.4 ugold.4 uguru.4 uhci.4 uhid.4 uhidev.4 uipaq.4 \
        uk.4 ukbd.4 \
diff --git a/share/man/man4/led.4 b/share/man/man4/led.4
new file mode 100644
index 00000000000..fb06edee73a
--- /dev/null
+++ b/share/man/man4/led.4
@@ -0,0 +1,78 @@
+.\"    $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.
+.\"
+.Dd $Mdocdate$
+.Dt LED 4
+.Os
+.Sh NAME
+.Nm led
+.Nd Generic LED device
+.Sh SYNOPSIS
+.Cd "led* at ublink?"
+.Sh DESCRIPTION
+The
+.Nm
+device attaches to the LED controller and provices a uniform
+programming interface to its LEDs.
+.Pp
+Each LED controller with an attached
+.Nm
+device has an associated device file under the
+.Pa /dev
+directory, e.g.\&
+.Pa /dev/led0 .
+Access from userland is performed through
+.Xr ioctl 2
+calls on these devices.
+.Sh IOCTL INTERFACE
+The following structures and constants are defined in the
+.In sys/gpio.h
+header file:
+.Bl -tag -width XXXX
+.It Dv LED_GET_NUM Fa "unsigned int"
+Returns the number of LEDs on this LED controller.
+.It Dv LED_SET Fa "struct led_set"
+Sets a single LED's state using the
+.Fa led_set
+structur:
+.Bd -literal
+struct led_set {
+       unsigned int led;       /* total number of LEDs available */
+       uint32_t rgb;           /* colour value to set */
+};
+.Ed
+.El
+.Sh FILES
+.Bl -tag -width "/dev/ledu" -compact
+.It /dev/led Ns Ar u
+LED device unit
+.Ar u
+file.
+.El
+.Sh SEE ALSO
+.Xr ledctl 1 ,
+.Xr ioctl 2
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Ox 6.7 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Patrick Wildt Aq Mt patr...@blueri.se .
diff --git a/share/man/man4/ublink.4 b/share/man/man4/ublink.4
new file mode 100644
index 00000000000..3cbc33856d9
--- /dev/null
+++ b/share/man/man4/ublink.4
@@ -0,0 +1,50 @@
+.\"    $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.
+.\"
+.Dd $Mdocdate$
+.Dt UBLINK 4
+.Os
+.Sh NAME
+.Nm ublink
+.Nd ThingM blink(1) USB RGB LED notification light
+.Sh SYNOPSIS
+.Cd "ublink* at uhidev?"
+.Cd "led* at ublink?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the ThingM blink(1) notification light.
+It provides access to its two LEDs using the
+.Xr led 4
+device.
+.Pp
+Additionally it sets up a default watchdog.
+If the host does not respond in a given time, the LEDs will start
+playing a pre-programmed sequence.
+.Sh SEE ALSO
+.Xr led 4 ,
+.Xr uhidev 4
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Ox 6.7 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Patrick Wildt Aq Mt patr...@blueri.se .
diff --git a/share/man/man4/uhidev.4 b/share/man/man4/uhidev.4
index 7dafba8fa22..4e93bf6e624 100644
--- a/share/man/man4/uhidev.4
+++ b/share/man/man4/uhidev.4
@@ -37,6 +37,7 @@
 .Sh SYNOPSIS
 .Cd "uhidev*  at uhub?"
 .Cd "ubcmtp*  at uhidev?"
+.Cd "ublink*  at uhidev?"
 .Cd "ucycom*  at uhidev?"
 .Cd "ugold*   at uhidev?"
 .Cd "uhid*    at uhidev?"
@@ -69,6 +70,7 @@ only dispatches data to them based on the report id.
 .Sh SEE ALSO
 .Xr intro 4 ,
 .Xr ubcmtp 4 ,
+.Xr ublink 4 ,
 .Xr ucycom 4 ,
 .Xr ugold 4 ,
 .Xr uhid 4 ,
diff --git a/share/man/man8/man8.amd64/MAKEDEV.8 
b/share/man/man8/man8.amd64/MAKEDEV.8
index 02d448eed25..2836f490cf5 100644
--- a/share/man/man8/man8.amd64/MAKEDEV.8
+++ b/share/man/man8/man8.amd64/MAKEDEV.8
@@ -1,4 +1,4 @@
-.\" $OpenBSD: MAKEDEV.8,v 1.86 2019/12/17 13:18:05 reyk Exp $
+.\" $OpenBSD$
 .\"
 .\" THIS FILE AUTOMATICALLY GENERATED.  DO NOT EDIT.
 .\" generated from:
@@ -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: December 17 2019 $
+.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 6330f6e442d..84b9d2c7ab3 100644
--- a/sys/arch/amd64/amd64/conf.c
+++ b/sys/arch/amd64/amd64/conf.c
@@ -164,6 +164,7 @@ cdev_decl(nvram);
 cdev_decl(drm);
 #include "viocon.h"
 cdev_decl(viocon);
+#include "led.h"
 
 #include "wsdisplay.h"
 #include "wskbd.h"
@@ -248,7 +249,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 17e88a9eb51..aae4a516aad 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?
 fido*  at uhidev?              # FIDO/U2F security key support
 upd*   at uhidev?              # USB Power Devices sensors
 aue*   at uhub?                # ADMtek AN986 Pegasus Ethernet
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 0f697127e92..c3b7ac5a73a 100644
--- a/sys/dev/usb/files.usb
+++ b/sys/dev/usb/files.usb
@@ -473,3 +473,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..4129694a5fd
--- /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 = 3;
+       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 > 2)
+               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;
+
+       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 b43c8374fa5..bf5cac8a98e 100644
--- a/sys/sys/conf.h
+++ b/sys/sys/conf.h
@@ -439,6 +439,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, \
@@ -586,6 +593,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..35338c8219c
--- /dev/null
+++ b/usr.bin/ledctl/Makefile
@@ -0,0 +1,6 @@
+#      $OpenBSD$
+
+PROG=  ledctl
+SRCS=  ledctl.c
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/ledctl/ledctl.1 b/usr.bin/ledctl/ledctl.1
new file mode 100644
index 00000000000..300968310a4
--- /dev/null
+++ b/usr.bin/ledctl/ledctl.1
@@ -0,0 +1,93 @@
+.\"    $OpenBSD$
+.\"
+.\" Copyright (c) 2019 Jan Klemkow <j...@openbsd.org>
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate$
+.Dt LEDCTL 1
+.Os
+.Sh NAME
+.Nm ledctl
+.Nd sets the colors of LEDs
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar device
+.Op Fl n Ar led
+.Op Ar on|off|rgb
+.Sh DESCRIPTION
+The
+.Nm
+utility turns LEDs
+.Ar on ,
+.Ar off
+or sets their color.
+The color value
+.Ar rgb
+has to be in hex format.
+The parameters
+.Ar on
+and
+.Ar off
+are shortcuts for the color values 0xffffff and 0x000000.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl f Ar device
+.Nm
+uses
+the device file
+.Ar device .
+Default is
+.Pa /dev/led0 .
+.It Fl n Ar led
+A
+.Xr led 4
+device may consists of multiple LEDs.
+Sets
+.Ar led
+to the index of the right LED.
+Default is 0.
+.El
+.Sh FILES
+.Pa /dev/led0
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Sets the first LED of
+.Pa /dev/led0
+to red:
+.Bd -literal -offset indent
+$ ledctl 0xff0000
+.Ed
+.Pp
+Sets the second LED of
+.Pa /dev/led0
+to green:
+.Bd -literal -offset indent
+$ ledctl -n 1 0x00ff00
+.Ed
+.Pp
+Sets the first LED of
+.Pa /dev/led2
+to blue:
+.Bd -literal -offset indent
+$ ledctl -f /dev/led2 0x0000ff
+.Ed
+.\".Sh SEE ALSO
+.\".Xr led 4
+.Sh AUTHORS
+The
+.Nm
+program was written by
+.An Patrick Wildt Aq Mt patr...@blueri.se .
diff --git a/usr.bin/ledctl/ledctl.c b/usr.bin/ledctl/ledctl.c
new file mode 100644
index 00000000000..e3275c42db0
--- /dev/null
+++ b/usr.bin/ledctl/ledctl.c
@@ -0,0 +1,101 @@
+/*     $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, nled;
+       struct led_set set;
+       int ch, fd;
+       long rgb;
+       char *ep;
+
+       led = 0;
+       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 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);
+
+       if (ioctl(fd, LED_GET_NUM, &nled) == -1)
+               err(1, "ioctl LED_GET_NUM");
+
+       if (led >= nled)
+               errx(1, "bogus led: %u (max %u)", led, nled);
+
+       memset(&set, 0, sizeof(set));
+       set.led = led;
+       if (strcmp(argv[0], "on") == 0) {
+               set.rgb = 0xffffff;
+       } else if (strcmp(argv[0], "off") == 0) {
+               set.rgb = 0x000000;
+       } else {
+               errno = 0;
+               rgb = strtol(argv[0], &ep, 16);
+               if (*argv[0] == '\0' || *ep != '\0')
+                       errx(1, "bogus color: %s", argv[0]);
+               if (errno == ERANGE && (rgb == LONG_MAX || rgb == LONG_MIN))
+                       errx(1, "bogus color: %s", argv[0]);
+               set.rgb = rgb;
+       }
+
+       if (ioctl(fd, LED_SET, &set) == -1)
+               err(1, "ioctl LED_SET");
+
+       return 0;
+}

Reply via email to