From 8fb1d1f7d1c754f3df3a76678b7b911a3ebe2d23 Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Mon, 26 May 2014 11:52:36 +0200
Subject: [PATCH 4/4] WIP led support

---
 share/man/man4/Makefile                      |   1 +
 share/man/man4/acpi_thinkpad.4               |  40 ++--
 share/man/man4/led.4                         | 193 +++++++++++++++
 sys/conf/files                               |   1 +
 sys/dev/acpica/acpi_asus/acpi_asus.c         |  14 +-
 sys/dev/acpica/acpi_thinkpad/acpi_thinkpad.c |   9 +-
 sys/dev/misc/led/led.c                       | 338 +++++++++++++++++++++++++++
 sys/dev/misc/led/led.h                       |  23 ++
 8 files changed, 578 insertions(+), 41 deletions(-)
 create mode 100644 share/man/man4/led.4
 create mode 100644 sys/dev/misc/led/led.c
 create mode 100644 sys/dev/misc/led/led.h

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index adfafd1..b9cf7a9 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -153,6 +153,7 @@ MAN=	aac.4 \
 	km.4 \
 	ktr.4 \
 	kue.4 \
+	led.4 \
 	lge.4 \
 	lgue.4 \
 	lm.4 \
diff --git a/share/man/man4/acpi_thinkpad.4 b/share/man/man4/acpi_thinkpad.4
index 32d6003..1f7c88a 100644
--- a/share/man/man4/acpi_thinkpad.4
+++ b/share/man/man4/acpi_thinkpad.4
@@ -25,7 +25,7 @@
 .\"
 .\" $FreeBSD: head/share/man/man4/acpi_ibm.4 237509 2012-06-23 20:44:45Z joel $
 .\"
-.Dd March 30, 2014
+.Dd May 26, 2014
 .Dt ACPI_THINKPAD 4
 .Os
 .Sh NAME
@@ -145,17 +145,17 @@ Mute
 .It Li 0x18
 Access IBM Button
 .El
-.\".Ss Xr led 4 Ss Interface
-.\"The
-.\".Nm
-.\"driver provides a
-.\".Xr led 4
-.\"interface for the ThinkLight.
-.\"The ThinkLight can be made to blink by writing
-.\".Tn ASCII
-.\"strings to the
-.\".Pa /dev/led/thinklight
-.\"device.
+.Ss Xr led 4 Ss Interface
+The
+.Nm
+driver provides a
+.Xr led 4
+interface for the ThinkLight.
+The ThinkLight can be made to blink by writing
+.Tn ASCII
+strings to the
+.Pa /dev/led/thinklight
+device.
 .Sh SYSCTL VARIABLES
 The following sysctls are currently implemented:
 .Bl -tag -width indent
@@ -359,13 +359,13 @@ Defaults for these sysctls can be set in
 .Xr sysctl.conf 5 .
 Sensors can be monitored by
 .Xr sensorsd 8 .
-.\".Sh FILES
-.\".Bl -tag -width ".Pa /dev/led/thinklight"
-.\".It Pa /dev/led/thinklight
-.\"ThinkLight
-.\".Xr led 4
-.\"device node
-.\".El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/led/thinklight"
+.It Pa /dev/led/thinklight
+ThinkLight
+.Xr led 4
+device node
+.El
 .Sh EXAMPLES
 The following can be added to
 .Xr devd.conf 5
@@ -471,7 +471,7 @@ hw.acpi.thinkpad.handlerevents=0x04\\ 0x10\\ 0x11
 .Ed
 .Sh SEE ALSO
 .Xr acpi 4 ,
-.\" .Xr led 4 ,
+.Xr led 4 ,
 .Xr sysctl.conf 5 ,
 .Xr devd 8 ,
 .Xr sensorsd 8 ,
diff --git a/share/man/man4/led.4 b/share/man/man4/led.4
new file mode 100644
index 0000000..9bf89d2
--- /dev/null
+++ b/share/man/man4/led.4
@@ -0,0 +1,193 @@
+.\" Copyright (c) 2003 Sergey A. Osokin <osa@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD: head/share/man/man4/led.4 213573 2010-10-08 12:40:16Z uqs $
+.\"
+.Dd April 24, 2007
+.Dt LED 4
+.Os
+.Sh NAME
+.Nm led
+.Nd API for manipulating LED's, lamps and other annunciators
+.Sh SYNOPSIS
+.In dev/led/led.h
+.Pp
+.Fd "typedef void led_t(void *priv, int onoff);"
+.Ft struct cdev *
+.Fn led_create_state "led_t *func" "void *priv" "char const *name" "int state"
+.Ft struct cdev *
+.Fn led_create "led_t *func" "void *priv" "char const *name"
+.Ft void
+.Fn led_destroy "struct cdev *"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides generic support for handling LEDs, lamps and other
+annunciators.
+.Pp
+The hardware driver must supply a function to turn the annunciator on and off
+and the device
+.Fa name
+of the annunciator relative to
+.Pa /dev/led/ .
+The
+.Fa priv
+argument is passed back to this on/off function and can be used however
+the hardware driver sees fit.
+.Pp
+The lamp can be controlled by opening and writing
+.Tn ASCII
+strings to the
+.Pa /dev/led/bla
+device.
+.Pp
+In the following, we will use this special notation to indicate the resulting
+output of the annunciator:
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Ic *
+The annunciator is on for 1/10th second.
+.It Ic _
+The annunciator is off for 1/10th second.
+.El
+.Pp
+State can be set directly, and since the change happens immediately,
+it is possible to flash the annunciator with very short periods and
+synchronize it with program events.
+It should be noted that there is a non-trivial overhead, so this may
+not be usable for benchmarking or measuring short intervals.
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Ic 0
+Turn the annunciator off immediately.
+.It Ic 1
+Turn the annunciator on immediately.
+.El
+.Pp
+Flashing can be set with a given period.
+The pattern continues endlessly.
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Ic f
+_*
+.It Ic f1
+_*
+.It Ic f2
+__**
+.It Ic f3
+___***
+.It ...
+.It Ic f9
+_________*********
+.El
+.Pp
+Three high-level commands are available:
+.Bl -tag -width indent -offset indent
+.It Ic d%d
+Numbers.
+Each digit is blinked out at 1/10th second, zero as ten pulses.
+Between digits a one second pause and after the last
+digit a two second pause after which the sequence is repeated.
+.It Ic s%s
+String.
+This gives full control over the annunciator.
+Letters
+.Ql A
+.No ...
+.Ql J
+turn the annunciator on for from 1/10th to one full
+second.
+Letters
+.Ql a
+.No ...
+.Ql j
+turn the annunciator off for 1/10th
+to one full second.
+Letters
+.Ql u
+and
+.Ql U
+turn the annunciator off and on respectively when the next
+UTC second starts.
+Unless terminated with a
+.Ql \&. ,
+the sequence is immediately repeated.
+.It Ic m%s
+Morse.
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Ql \&.
+becomes
+.Ql _*
+.It Ql -
+becomes
+.Sq Li _***
+.It Ql "\ "
+becomes
+.Sq Li __
+.It Ql \en
+becomes
+.Sq Li ____
+.El
+.El
+.Pp
+The sequence is repeated after a one second pause.
+.Sh FILES
+.Bl -tag -width ".Pa /dev/led/*"
+.It Pa /dev/led/*
+.El
+.Sh EXAMPLES
+A
+.Sq Li d12
+flashes the lamp
+.Pp
+.Dl *__________*_*______________________________
+.Pp
+A
+.Sq Li sAaAbBa
+flashes
+.Pp
+.Dl *_*__**_
+.Bd -literal
+/usr/games/morse -l "Soekris rocks" > /dev/led/error
+.Ed
+.Sh SEE ALSO
+.Xr morse 6
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 5.2 .
+.Sh AUTHORS
+.An -nosplit
+This software was written by
+.An Poul-Henning Kamp
+.Aq phk@FreeBSD.org .
+.Pp
+This manual page was written by
+.An Sergey A. Osokin
+.Aq osa@FreeBSD.org
+and
+.An Poul-Henning Kamp
+.Aq phk@FreeBSD.org .
diff --git a/sys/conf/files b/sys/conf/files
index e6af227..9e09946 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -665,6 +665,7 @@ rt2860.fw			optional ralfw				\
 	no-obj no-implicit-rule						\
 	clean		"rt2860.fw"
 dev/netif/lge/if_lge.c	optional lge
+dev/misc/led/led.c		standard
 dev/disk/md/md.c		optional md
 dev/raid/mfi/mfi.c		optional mfi
 dev/raid/mfi/mfi_debug.c	optional mfi
diff --git a/sys/dev/acpica/acpi_asus/acpi_asus.c b/sys/dev/acpica/acpi_asus/acpi_asus.c
index bc63088..c1b71a3 100644
--- a/sys/dev/acpica/acpi_asus/acpi_asus.c
+++ b/sys/dev/acpica/acpi_asus/acpi_asus.c
@@ -50,9 +50,7 @@
 #include "accommon.h"
 #include "acpivar.h"
 
-#if defined(__FreeBSD__)
-#include <dev/led/led.h>
-#endif
+#include <dev/misc/led/led.h>
 
 /* Methods */
 #define ACPI_ASUS_METHOD_BRN	1
@@ -123,14 +121,12 @@ struct acpi_asus_softc {
 	struct acpi_asus_model	*model;
 	struct sysctl_ctx_list	sysctl_ctx;
 	struct sysctl_oid	*sysctl_tree;
-#if defined(__FreeBSD__)
 	struct acpi_asus_led	s_bled;
 	struct acpi_asus_led	s_dled;
 	struct acpi_asus_led	s_gled;
 	struct acpi_asus_led	s_mled;
 	struct acpi_asus_led	s_tled;
 	struct acpi_asus_led	s_wled;
-#endif
 
 	int			s_brn;
 	int			s_disp;
@@ -515,10 +511,8 @@ static int	acpi_asus_probe(device_t dev);
 static int	acpi_asus_attach(device_t dev);
 static int	acpi_asus_detach(device_t dev);
 
-#if defined(__FreeBSD__)
 static void	acpi_asus_led(struct acpi_asus_led *led, int state);
 static void	acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused);
-#endif
 
 static int	acpi_asus_sysctl(SYSCTL_HANDLER_ARGS);
 static int	acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method);
@@ -753,7 +747,6 @@ acpi_asus_attach(device_t dev)
 		    acpi_asus_sysctls[i].description);
 	}
 
-#if defined(__FreeBSD__)
 	/* Attach leds */
 	if (sc->model->bled_set) {
 		sc->s_bled.busy = 0;
@@ -805,7 +798,6 @@ acpi_asus_attach(device_t dev)
 		    led_create_state((led_t *)acpi_asus_led, &sc->s_wled,
 			"wled", 1);
 	}
-#endif
 
 	/* Activate hotkeys */
 	AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
@@ -844,7 +836,6 @@ acpi_asus_detach(device_t dev)
 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
 
 	sc = device_get_softc(dev);
-#if defined(__FreeBSD)
 	/* Turn the lights off */
 	if (sc->model->bled_set)
 		led_destroy(sc->s_bled.cdev);
@@ -863,7 +854,6 @@ acpi_asus_detach(device_t dev)
 
 	if (sc->model->wled_set)
 		led_destroy(sc->s_wled.cdev);
-#endif
 
 	/* Remove notify handler */
 	AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
@@ -882,7 +872,6 @@ acpi_asus_detach(device_t dev)
 	return (0);
 }
 
-#if defined(__FreeBSD__)
 static void
 acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused)
 {
@@ -943,7 +932,6 @@ acpi_asus_led(struct acpi_asus_led *led, int state)
 
 	AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)acpi_asus_led_task, led);
 }
-#endif
 
 static int
 acpi_asus_sysctl(SYSCTL_HANDLER_ARGS)
diff --git a/sys/dev/acpica/acpi_thinkpad/acpi_thinkpad.c b/sys/dev/acpica/acpi_thinkpad/acpi_thinkpad.c
index 61fc911..7a73ded 100644
--- a/sys/dev/acpica/acpi_thinkpad/acpi_thinkpad.c
+++ b/sys/dev/acpica/acpi_thinkpad/acpi_thinkpad.c
@@ -48,6 +48,7 @@
 #include <sys/lock.h>
 #include <sys/thread2.h>
 #include <machine/clock.h>
+#include <dev/misc/led/led.h>
 
 #include "acpi.h"
 #include "accommon.h"
@@ -282,10 +283,8 @@ static int	acpi_thinkpad_attach(device_t dev);
 static int	acpi_thinkpad_detach(device_t dev);
 static int	acpi_thinkpad_resume(device_t dev);
 
-#if 0 /* XXX */
 static void	thinkpad_led(void *softc, int onoff);
 static void	thinkpad_led_task(struct acpi_thinkpad_softc *sc, int pending __unused);
-#endif
 
 static int	acpi_thinkpad_sysctl(SYSCTL_HANDLER_ARGS);
 static int	acpi_thinkpad_sysctl_init(struct acpi_thinkpad_softc *sc,
@@ -331,7 +330,6 @@ DRIVER_MODULE(acpi_thinkpad, acpi, acpi_thinkpad_driver,
 MODULE_DEPEND(acpi_thinkpad, acpi, 1, 1, 1);
 static char    *thinkpad_ids[] = {"IBM0068", "LEN0068", NULL};
 
-#if 0 /* XXX */
 static void
 thinkpad_led(void *softc, int onoff)
 {
@@ -359,7 +357,6 @@ thinkpad_led_task(struct acpi_thinkpad_softc *sc, int pending __unused)
 
 	sc->led_busy = 0;
 }
-#endif
 
 static int
 acpi_thinkpad_probe(device_t dev)
@@ -474,11 +471,9 @@ acpi_thinkpad_attach(device_t dev)
 
 	sensordev_install(&sc->sensordev);
 
-#if 0 /* XXX */
 	/* Hook up light to led(4) */
 	if (sc->light_set_supported)
 		sc->led_dev = led_create_state(thinkpad_led, sc, "thinklight", sc->light_val);
-#endif
 
 	return (0);
 }
@@ -510,10 +505,8 @@ acpi_thinkpad_detach(device_t dev)
 		sensor_detach(&sc->sensordev, &sc->sensors[i]);
 	sensor_task_unregister(sc);
 
-#if 0 /* XXX */
 	if (sc->led_dev != NULL)
 		led_destroy(sc->led_dev);
-#endif
 
 	return (0);
 }
diff --git a/sys/dev/misc/led/led.c b/sys/dev/misc/led/led.c
new file mode 100644
index 0000000..ff2022d
--- /dev/null
+++ b/sys/dev/misc/led/led.c
@@ -0,0 +1,338 @@
+/*-
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD: head/sys/dev/led/led.c 247008 2013-02-19 19:25:50Z mav $
+ */
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/systm.h>
+#include <sys/limits.h>
+#include <sys/malloc.h>
+#include <sys/ctype.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+#include <dev/misc/led/led.h>
+#include <sys/uio.h>
+#include <sys/device.h>
+
+struct ledsc {
+	LIST_ENTRY(ledsc)	list;
+	char			*name;
+	void			*private;
+	int			unit;
+	led_t			*func;
+	struct cdev *dev;
+	struct sbuf		*spec;
+	char			*str;
+	char			*ptr;
+	int			count;
+	time_t			last_second;
+};
+
+static struct unrhdr *led_unit;
+static struct lock led_lock;
+static struct lock led_lock2;
+static LIST_HEAD(, ledsc) led_list = LIST_HEAD_INITIALIZER(led_list);
+static struct callout led_ch;
+static int blinkers = 0;
+
+static MALLOC_DEFINE(M_LED, "LED", "LED driver");
+
+static void
+led_timeout(void *p)
+{
+	struct ledsc	*sc;
+
+	lockmgr(&led_lock, LK_EXCLUSIVE);
+	LIST_FOREACH(sc, &led_list, list) {
+		if (sc->ptr == NULL)
+			continue;
+		if (sc->count > 0) {
+			sc->count--;
+			continue;
+		}
+		if (*sc->ptr == '.') {
+			sc->ptr = NULL;
+			blinkers--;
+			continue;
+		} else if (*sc->ptr == 'U' || *sc->ptr == 'u') {
+			if (sc->last_second == time_second)
+				continue;
+			sc->last_second = time_second;
+			sc->func(sc->private, *sc->ptr == 'U');
+		} else if (*sc->ptr >= 'a' && *sc->ptr <= 'j') {
+			sc->func(sc->private, 0);
+			sc->count = (*sc->ptr & 0xf) - 1;
+		} else if (*sc->ptr >= 'A' && *sc->ptr <= 'J') {
+			sc->func(sc->private, 1);
+			sc->count = (*sc->ptr & 0xf) - 1;
+		}
+		sc->ptr++;
+		if (*sc->ptr == '\0')
+			sc->ptr = sc->str;
+	}
+	if (blinkers > 0)
+		callout_reset(&led_ch, hz / 10, led_timeout, p);
+	lockmgr(&led_lock, LK_RELEASE);
+}
+
+static int
+led_state(struct ledsc *sc, struct sbuf **sb, int state)
+{
+	struct sbuf *sb2 = NULL;
+
+	sb2 = sc->spec;
+	sc->spec = *sb;
+	if (*sb != NULL) {
+		sc->str = sbuf_data(*sb);
+		if (sc->ptr == NULL) {
+			blinkers++;
+			callout_reset(&led_ch, hz / 10, led_timeout, NULL);
+		}
+		sc->ptr = sc->str;
+	} else {
+		sc->str = NULL;
+		if (sc->ptr != NULL)
+			blinkers--;
+		sc->ptr = NULL;
+		sc->func(sc->private, state);
+	}
+	sc->count = 0;
+	*sb = sb2;
+	return(0);
+}
+
+static int
+led_parse(const char *s, struct sbuf **sb, int *state)
+{
+	int i, error;
+
+	/*
+	 * Handle "on" and "off" immediately so people can flash really
+	 * fast from userland if they want to
+	 */
+	if (*s == '0' || *s == '1') {
+		*state = *s & 1;
+		return (0);
+	}
+
+	*state = 0;
+	*sb = sbuf_new_auto();
+	if (*sb == NULL)
+		return (ENOMEM);
+	switch(s[0]) {
+		/*
+		 * Flash, default is 100msec/100msec.
+		 * 'f2' sets 200msec/200msec etc.
+		 */
+		case 'f':
+			if (s[1] >= '1' && s[1] <= '9')
+				i = s[1] - '1';
+			else
+				i = 0;
+			sbuf_printf(*sb, "%c%c", 'A' + i, 'a' + i);
+			break;
+		/*
+		 * Digits, flashes out numbers.
+		 * 'd12' becomes -__________-_-______________________________
+		 */
+		case 'd':
+			for(s++; *s; s++) {
+				if (!isdigit(*s))
+					continue;
+				i = *s - '0';
+				if (i == 0)
+					i = 10;
+				for (; i > 1; i--) 
+					sbuf_cat(*sb, "Aa");
+				sbuf_cat(*sb, "Aj");
+			}
+			sbuf_cat(*sb, "jj");
+			break;
+		/*
+		 * String, roll your own.
+		 * 'a-j' gives "off" for n/10 sec.
+		 * 'A-J' gives "on" for n/10 sec.
+		 * no delay before repeat
+		 * 'sAaAbBa' becomes _-_--__-
+		 */
+		case 's':
+			for(s++; *s; s++) {
+				if ((*s >= 'a' && *s <= 'j') ||
+				    (*s >= 'A' && *s <= 'J') ||
+				    *s == 'U' || *s <= 'u' ||
+					*s == '.')
+					sbuf_bcat(*sb, s, 1);
+			}
+			break;
+		/*
+		 * Morse.
+		 * '.' becomes _-
+		 * '-' becomes _---
+		 * ' ' becomes __
+		 * '\n' becomes ____
+		 * 1sec pause between repeats
+		 * '... --- ...' -> _-_-_-___---_---_---___-_-_-__________
+		 */
+		case 'm':
+			for(s++; *s; s++) {
+				if (*s == '.')
+					sbuf_cat(*sb, "aA");
+				else if (*s == '-')
+					sbuf_cat(*sb, "aC");
+				else if (*s == ' ')
+					sbuf_cat(*sb, "b");
+				else if (*s == '\n')
+					sbuf_cat(*sb, "d");
+			}
+			sbuf_cat(*sb, "j");
+			break;
+		default:
+			sbuf_delete(*sb);
+			return (EINVAL);
+	}
+	error = sbuf_finish(*sb);
+	if (error != 0 || sbuf_len(*sb) == 0) {
+		*sb = NULL;
+		return (error);
+	}
+	return (0);
+}
+
+static int
+led_write(struct dev_write_args *ap)
+{
+	struct uio *uio = ap->a_uio;
+	cdev_t dev = ap->a_head.a_dev;
+	struct ledsc	*sc;
+	char *s;
+	struct sbuf *sb = NULL;
+	int error, state = 0;
+
+	if (uio->uio_resid > 512)
+		return (EINVAL);
+	s = kmalloc(uio->uio_resid + 1, M_DEVBUF, M_WAITOK);
+	s[uio->uio_resid] = '\0';
+	error = uiomove(s, uio->uio_resid, uio);
+	if (error) {
+		kfree(s, M_DEVBUF);
+		return (error);
+	}
+	error = led_parse(s, &sb, &state);
+	kfree(s, M_DEVBUF);
+	if (error)
+		return (error);
+	lockmgr(&led_lock, LK_EXCLUSIVE);
+	sc = dev->si_drv1;
+	if (sc != NULL)
+		error = led_state(sc, &sb, state);
+	lockmgr(&led_lock, LK_RELEASE);
+	if (sb != NULL)
+		sbuf_delete(sb);
+	return (error);
+}
+
+int
+led_set(char const *name, char const *cmd)
+{
+	struct ledsc	*sc;
+	struct sbuf *sb = NULL;
+	int error, state = 0;
+
+	error = led_parse(cmd, &sb, &state);
+	if (error)
+		return (error);
+	lockmgr(&led_lock, LK_EXCLUSIVE);
+	LIST_FOREACH(sc, &led_list, list) {
+		if (strcmp(sc->name, name) == 0)
+			break;
+	}
+	if (sc != NULL)
+		error = led_state(sc, &sb, state);
+	else
+		error = ENOENT;
+	lockmgr(&led_lock, LK_RELEASE);
+	if (sb != NULL)
+		sbuf_delete(sb);
+	return (0);
+}
+
+static struct dev_ops led_ops = {
+	{ "LED", 0, 0 },
+	.d_write =	led_write,
+};
+
+struct cdev *
+led_create(led_t *func, void *priv, char const *name)
+{
+
+	return (led_create_state(func, priv, name, 0));
+}
+struct cdev *
+led_create_state(led_t *func, void *priv, char const *name, int state)
+{
+	struct ledsc	*sc;
+
+	sc = kmalloc(sizeof *sc, M_LED, M_WAITOK | M_ZERO);
+
+	lockmgr(&led_lock2, LK_EXCLUSIVE);
+	sc->name = kstrdup(name, M_LED);
+	sc->unit = alloc_unr(led_unit);
+	sc->private = priv;
+	sc->func = func;
+	sc->dev = make_dev(&led_ops, sc->unit,
+	    UID_ROOT, GID_WHEEL, 0600, "led/%s", name);
+	lockmgr(&led_lock2, LK_RELEASE);
+
+	lockmgr(&led_lock, LK_EXCLUSIVE);
+	sc->dev->si_drv1 = sc;
+	LIST_INSERT_HEAD(&led_list, sc, list);
+	sc->func(sc->private, state != 0);
+	lockmgr(&led_lock, LK_RELEASE);
+
+	return (sc->dev);
+}
+
+void
+led_destroy(struct cdev *dev)
+{
+	struct ledsc *sc;
+
+	lockmgr(&led_lock, LK_EXCLUSIVE);
+	sc = dev->si_drv1;
+	dev->si_drv1 = NULL;
+	if (sc->ptr != NULL)
+		blinkers--;
+	LIST_REMOVE(sc, list);
+	if (LIST_EMPTY(&led_list))
+		callout_stop(&led_ch);
+	lockmgr(&led_lock, LK_RELEASE);
+
+	lockmgr(&led_lock2, LK_EXCLUSIVE);
+	free_unr(led_unit, sc->unit);
+	destroy_dev(dev);
+	if (sc->spec != NULL)
+		sbuf_delete(sc->spec);
+	kfree(sc->name, M_LED);
+	kfree(sc, M_LED);
+	lockmgr(&led_lock2, LK_RELEASE);
+}
+
+static void
+led_drvinit(void *unused)
+{
+
+	led_unit = new_unrhdr(0, INT_MAX, NULL);
+	lockinit(&led_lock, "LED lock", 0, LK_CANRECURSE);
+	lockinit(&led_lock2, "LED lock2", 0, LK_CANRECURSE);
+	callout_init_mp(&led_ch);
+}
+
+SYSINIT(leddev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, led_drvinit, NULL);
diff --git a/sys/dev/misc/led/led.h b/sys/dev/misc/led/led.h
new file mode 100644
index 0000000..dc2baa0
--- /dev/null
+++ b/sys/dev/misc/led/led.h
@@ -0,0 +1,23 @@
+/*-
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD: head/sys/dev/led/led.h 219951 2011-03-24 08:56:12Z mav $
+ */
+
+#ifndef _DEV_LED_H
+#define _DEV_LED_H
+
+typedef	void led_t(void *, int);
+
+struct cdev *led_create_state(led_t *, void *, char const *, int);
+struct cdev *led_create(led_t *, void *, char const *);
+void	led_destroy(struct cdev *);
+
+int	led_set(char const *, char const *);
+
+#endif
-- 
1.8.2.2

