led.c revision 122963
1/*-
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
8 *
9 */
10
11#include <sys/cdefs.h>
12__FBSDID("$FreeBSD: head/sys/dev/led/led.c 122963 2003-11-23 10:22:51Z phk $");
13
14#include <sys/param.h>
15#include <sys/conf.h>
16#include <sys/kernel.h>
17#include <sys/systm.h>
18#include <sys/malloc.h>
19#include <sys/ctype.h>
20#include <sys/sbuf.h>
21#include <sys/queue.h>
22#include <dev/led/led.h>
23#include <sys/uio.h>
24
25struct ledsc {
26	LIST_ENTRY(ledsc)	list;
27	void			*private;
28	led_t			*func;
29	dev_t			dev;
30	struct sbuf		*spec;
31	char			*str;
32	char			*ptr;
33	int			count;
34};
35
36static unsigned next_minor;
37static struct mtx led_mtx;
38static LIST_HEAD(, ledsc) led_list = LIST_HEAD_INITIALIZER(&led_list);
39
40MALLOC_DEFINE(M_LED, "LED", "LED driver");
41
42static void
43led_timeout(void *p)
44{
45	struct ledsc	*sc;
46
47	mtx_lock(&led_mtx);
48	LIST_FOREACH(sc, &led_list, list) {
49		if (sc->ptr == NULL)
50			continue;
51		if (sc->count > 0) {
52			sc->count--;
53			continue;
54		}
55		sc->func(sc->private, sc->ptr[0] >= 'a' ? 1 : 0);
56		sc->count = sc->ptr[0] & 0xf;
57		if (*(++sc->ptr) == '\0')
58			sc->ptr = sc->str;
59	}
60	mtx_unlock(&led_mtx);
61	timeout(led_timeout, p, hz / 10);
62	return;
63}
64
65static int
66led_write(dev_t dev, struct uio *uio, int ioflag)
67{
68	int error;
69	char *s;
70	struct ledsc *sc;
71	struct sbuf *sb;
72	struct sbuf *sb2;
73	int i;
74
75	sc = dev->si_drv1;
76
77	if (uio->uio_resid > 512)
78		return (EINVAL);
79	s = malloc(uio->uio_resid + 1, M_DEVBUF, M_WAITOK);
80	if (s == NULL)
81		return (ENOMEM);
82	s[uio->uio_resid] = '\0';
83	error = uiomove(s, uio->uio_resid, uio);
84	if (error) {
85		free(s, M_DEVBUF);
86		return (error);
87	}
88
89	/*
90	 * Handle "on" and "off" immediately so people can flash really
91	 * fast from userland if they want to
92	 */
93	if (*s == '0' || *s == '1') {
94		mtx_lock(&led_mtx);
95		sb2 = sc->spec;
96		sc->spec = NULL;
97		sc->str = NULL;
98		sc->ptr = NULL;
99		sc->count = 0;
100		sc->func(sc->private, *s & 1);
101		mtx_unlock(&led_mtx);
102		if (sb2 != NULL)
103			sbuf_delete(sb2);
104		free(s, M_DEVBUF);
105		return(0);
106	}
107
108	sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
109	if (sb == NULL) {
110		free(s, M_DEVBUF);
111		return (ENOMEM);
112	}
113
114	switch(s[0]) {
115		/*
116		 * Flash, default is 100msec/100msec.
117		 * 'f2' sets 200msec/200msec etc.
118		 */
119		case 'f':
120			if (s[1] >= '1' && s[1] <= '9')
121				i = s[1] - '1';
122			else
123				i = 0;
124			sbuf_printf(sb, "%c%c", 'a' + i, 'A' + i);
125			break;
126		/*
127		 * Digits, flashes out numbers.
128		 * 'd12' becomes -__________-_-______________________________
129		 */
130		case 'd':
131			for(s++; *s; s++) {
132				if (!isdigit(*s))
133					continue;
134				i = *s - '0';
135				if (i == 0)
136					i = 10;
137				for (; i > 1; i--)
138					sbuf_cat(sb, "aA");
139				sbuf_cat(sb, "aJ");
140			}
141			sbuf_cat(sb, "JJ");
142			break;
143		/*
144		 * String, roll your own.
145		 * 'A-J' gives "off" for n/10 sec.
146		 * 'a-j' gives "on" for n/10 sec.
147		 * no delay before repeat
148		 * 'sAaAbBa' becomes _-_--__-
149		 */
150		case 's':
151			for(s++; *s; s++) {
152				if ((*s & 0x0f) > 10)
153					continue;
154				if ((*s & 0xf0) < ' ')
155					continue;
156				sbuf_bcat(sb, s, 1);
157			}
158			break;
159		/*
160		 * Morse.
161		 * '.' becomes _-
162		 * '-' becomes _---
163		 * ' ' becomes __
164		 * '\n' becomes ____
165		 * 1sec pause between repeats
166		 * '... --- ...' -> _-_-_-___---_---_---___-_-_-__________
167		 */
168		case 'm':
169			for(s++; *s; s++) {
170				if (*s == '.')
171					sbuf_cat(sb, "Aa");
172				else if (*s == '-')
173					sbuf_cat(sb, "Ac");
174				else if (*s == ' ')
175					sbuf_cat(sb, "B");
176				else if (*s == '\n')
177					sbuf_cat(sb, "D");
178			}
179			sbuf_cat(sb, "J");
180			break;
181		default:
182			break;
183	}
184	sbuf_finish(sb);
185	free(s, M_DEVBUF);
186	if (sbuf_overflowed(sb)) {
187		sbuf_delete(sb);
188		return (ENOMEM);
189	}
190	if (sbuf_len(sb) == 0) {
191		sbuf_delete(sb);
192		return (0);
193	}
194
195	mtx_lock(&led_mtx);
196	sb2 = sc->spec;
197	sc->spec = sb;
198	sc->str = sbuf_data(sb);
199	sc->ptr = sc->str;
200	sc->count = 0;
201	mtx_unlock(&led_mtx);
202	if (sb2 != NULL)
203		sbuf_delete(sb2);
204	return(0);
205}
206
207static struct cdevsw led_cdevsw = {
208        .d_write =      led_write,
209        .d_name =       "LED",
210};
211
212dev_t
213led_create(led_t *func, void *priv, char const *name)
214{
215	struct ledsc	*sc;
216	struct sbuf *sb;
217
218	if (next_minor == 0) {
219		mtx_init(&led_mtx, "LED mtx", MTX_DEF, 0);
220		timeout(led_timeout, NULL, hz / 10);
221	}
222
223	sb = sbuf_new(NULL, NULL, SPECNAMELEN, SBUF_FIXEDLEN);
224	if (sb == NULL)
225		return (NODEV);
226	sbuf_cpy(sb, "led/");
227	sbuf_cat(sb, name);
228	sbuf_finish(sb);
229	if (sbuf_overflowed(sb)) {
230		sbuf_delete(sb);
231		return (NODEV);
232	}
233
234	sc = malloc(sizeof *sc, M_LED, M_WAITOK | M_ZERO);
235	sc->private = priv;
236	sc->func = func;
237	sc->dev = make_dev(&led_cdevsw, unit2minor(next_minor),
238	    UID_ROOT, GID_WHEEL, 0600, sbuf_data(sb));
239	sc->dev->si_drv1 = sc;
240	next_minor++;
241	sbuf_delete(sb);
242	mtx_lock(&led_mtx);
243	LIST_INSERT_HEAD(&led_list, sc, list);
244	mtx_unlock(&led_mtx);
245	return (sc->dev);
246}
247
248void
249led_destroy(dev_t dev)
250{
251	struct ledsc *sc;
252
253	sc = dev->si_drv1;
254	mtx_lock(&led_mtx);
255	LIST_REMOVE(sc, list);
256	mtx_unlock(&led_mtx);
257	if (sc->spec != NULL)
258		sbuf_delete(sc->spec);
259	destroy_dev(dev);
260	free(sc, M_LED);
261}
262