led.c revision 121941
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 121941 2003-11-03 09:47:30Z 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			}
177			sbuf_cat(sb, "J");
178			break;
179		default:
180			break;
181	}
182	sbuf_finish(sb);
183	free(s, M_DEVBUF);
184	if (sbuf_overflowed(sb)) {
185		sbuf_delete(sb);
186		return (ENOMEM);
187	}
188	if (sbuf_len(sb) == 0) {
189		sbuf_delete(sb);
190		return (0);
191	}
192
193	mtx_lock(&led_mtx);
194	sb2 = sc->spec;
195	sc->spec = sb;
196	sc->str = sbuf_data(sb);
197	sc->ptr = sc->str;
198	sc->count = 0;
199	mtx_unlock(&led_mtx);
200	if (sb2 != NULL)
201		sbuf_delete(sb2);
202	return(0);
203}
204
205static struct cdevsw led_cdevsw = {
206        .d_write =      led_write,
207        .d_name =       "LED",
208};
209
210dev_t
211led_create(led_t *func, void *priv, char const *name)
212{
213	struct ledsc	*sc;
214	struct sbuf *sb;
215
216	if (next_minor == 0) {
217		mtx_init(&led_mtx, "LED mtx", MTX_DEF, 0);
218		timeout(led_timeout, NULL, hz / 10);
219	}
220
221	sb = sbuf_new(NULL, NULL, SPECNAMELEN, SBUF_FIXEDLEN);
222	if (sb == NULL)
223		return (NODEV);
224	sbuf_cpy(sb, "led/");
225	sbuf_cat(sb, name);
226	sbuf_finish(sb);
227	if (sbuf_overflowed(sb)) {
228		sbuf_delete(sb);
229		return (NODEV);
230	}
231
232	sc = malloc(sizeof *sc, M_LED, M_WAITOK | M_ZERO);
233	sc->private = priv;
234	sc->func = func;
235	sc->dev = make_dev(&led_cdevsw, unit2minor(next_minor),
236	    UID_ROOT, GID_WHEEL, 0600, sbuf_data(sb));
237	sc->dev->si_drv1 = sc;
238	next_minor++;
239	sbuf_delete(sb);
240	mtx_lock(&led_mtx);
241	LIST_INSERT_HEAD(&led_list, sc, list);
242	mtx_unlock(&led_mtx);
243	return (sc->dev);
244}
245
246void
247led_destroy(dev_t dev)
248{
249	struct ledsc *sc;
250
251	sc = dev->si_drv1;
252	mtx_lock(&led_mtx);
253	LIST_REMOVE(sc, list);
254	mtx_unlock(&led_mtx);
255	sbuf_delete(sc->spec);
256	destroy_dev(dev);
257	free(sc, M_LED);
258}
259