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$");
13
14#include <sys/param.h>
15#include <sys/conf.h>
16#include <sys/kernel.h>
17#include <sys/systm.h>
18#include <sys/limits.h>
19#include <sys/malloc.h>
20#include <sys/ctype.h>
21#include <sys/sbuf.h>
22#include <sys/queue.h>
23#include <dev/led/led.h>
24#include <sys/uio.h>
25#include <sys/sx.h>
26
27struct ledsc {
28	LIST_ENTRY(ledsc)	list;
29	char			*name;
30	void			*private;
31	int			unit;
32	led_t			*func;
33	struct cdev *dev;
34	struct sbuf		*spec;
35	char			*str;
36	char			*ptr;
37	int			count;
38	time_t			last_second;
39};
40
41static struct unrhdr *led_unit;
42static struct mtx led_mtx;
43static struct sx led_sx;
44static LIST_HEAD(, ledsc) led_list = LIST_HEAD_INITIALIZER(led_list);
45static struct callout led_ch;
46static int blinkers = 0;
47
48static MALLOC_DEFINE(M_LED, "LED", "LED driver");
49
50static void
51led_timeout(void *p)
52{
53	struct ledsc	*sc;
54
55	LIST_FOREACH(sc, &led_list, list) {
56		if (sc->ptr == NULL)
57			continue;
58		if (sc->count > 0) {
59			sc->count--;
60			continue;
61		}
62		if (*sc->ptr == '.') {
63			sc->ptr = NULL;
64			blinkers--;
65			continue;
66		} else if (*sc->ptr == 'U' || *sc->ptr == 'u') {
67			if (sc->last_second == time_second)
68				continue;
69			sc->last_second = time_second;
70			sc->func(sc->private, *sc->ptr == 'U');
71		} else if (*sc->ptr >= 'a' && *sc->ptr <= 'j') {
72			sc->func(sc->private, 0);
73			sc->count = (*sc->ptr & 0xf) - 1;
74		} else if (*sc->ptr >= 'A' && *sc->ptr <= 'J') {
75			sc->func(sc->private, 1);
76			sc->count = (*sc->ptr & 0xf) - 1;
77		}
78		sc->ptr++;
79		if (*sc->ptr == '\0')
80			sc->ptr = sc->str;
81	}
82	if (blinkers > 0)
83		callout_reset(&led_ch, hz / 10, led_timeout, p);
84}
85
86static int
87led_state(struct ledsc *sc, struct sbuf **sb, int state)
88{
89	struct sbuf *sb2 = NULL;
90
91	sb2 = sc->spec;
92	sc->spec = *sb;
93	if (*sb != NULL) {
94		sc->str = sbuf_data(*sb);
95		if (sc->ptr == NULL) {
96			blinkers++;
97			callout_reset(&led_ch, hz / 10, led_timeout, NULL);
98		}
99		sc->ptr = sc->str;
100	} else {
101		sc->str = NULL;
102		if (sc->ptr != NULL)
103			blinkers--;
104		sc->ptr = NULL;
105		sc->func(sc->private, state);
106	}
107	sc->count = 0;
108	*sb = sb2;
109	return(0);
110}
111
112static int
113led_parse(const char *s, struct sbuf **sb, int *state)
114{
115	int i, error;
116
117	/*
118	 * Handle "on" and "off" immediately so people can flash really
119	 * fast from userland if they want to
120	 */
121	if (*s == '0' || *s == '1') {
122		*state = *s & 1;
123		return (0);
124	}
125
126	*state = 0;
127	*sb = sbuf_new_auto();
128	if (*sb == NULL)
129		return (ENOMEM);
130	switch(s[0]) {
131		/*
132		 * Flash, default is 100msec/100msec.
133		 * 'f2' sets 200msec/200msec etc.
134		 */
135		case 'f':
136			if (s[1] >= '1' && s[1] <= '9')
137				i = s[1] - '1';
138			else
139				i = 0;
140			sbuf_printf(*sb, "%c%c", 'A' + i, 'a' + i);
141			break;
142		/*
143		 * Digits, flashes out numbers.
144		 * 'd12' becomes -__________-_-______________________________
145		 */
146		case 'd':
147			for(s++; *s; s++) {
148				if (!isdigit(*s))
149					continue;
150				i = *s - '0';
151				if (i == 0)
152					i = 10;
153				for (; i > 1; i--)
154					sbuf_cat(*sb, "Aa");
155				sbuf_cat(*sb, "Aj");
156			}
157			sbuf_cat(*sb, "jj");
158			break;
159		/*
160		 * String, roll your own.
161		 * 'a-j' gives "off" for n/10 sec.
162		 * 'A-J' gives "on" for n/10 sec.
163		 * no delay before repeat
164		 * 'sAaAbBa' becomes _-_--__-
165		 */
166		case 's':
167			for(s++; *s; s++) {
168				if ((*s >= 'a' && *s <= 'j') ||
169				    (*s >= 'A' && *s <= 'J') ||
170				    *s == 'U' || *s <= 'u' ||
171					*s == '.')
172					sbuf_bcat(*sb, s, 1);
173			}
174			break;
175		/*
176		 * Morse.
177		 * '.' becomes _-
178		 * '-' becomes _---
179		 * ' ' becomes __
180		 * '\n' becomes ____
181		 * 1sec pause between repeats
182		 * '... --- ...' -> _-_-_-___---_---_---___-_-_-__________
183		 */
184		case 'm':
185			for(s++; *s; s++) {
186				if (*s == '.')
187					sbuf_cat(*sb, "aA");
188				else if (*s == '-')
189					sbuf_cat(*sb, "aC");
190				else if (*s == ' ')
191					sbuf_cat(*sb, "b");
192				else if (*s == '\n')
193					sbuf_cat(*sb, "d");
194			}
195			sbuf_cat(*sb, "j");
196			break;
197		default:
198			sbuf_delete(*sb);
199			return (EINVAL);
200	}
201	error = sbuf_finish(*sb);
202	if (error != 0 || sbuf_len(*sb) == 0) {
203		*sb = NULL;
204		return (error);
205	}
206	return (0);
207}
208
209static int
210led_write(struct cdev *dev, struct uio *uio, int ioflag)
211{
212	struct ledsc	*sc;
213	char *s;
214	struct sbuf *sb = NULL;
215	int error, state = 0;
216
217	if (uio->uio_resid > 512)
218		return (EINVAL);
219	s = malloc(uio->uio_resid + 1, M_DEVBUF, M_WAITOK);
220	s[uio->uio_resid] = '\0';
221	error = uiomove(s, uio->uio_resid, uio);
222	if (error) {
223		free(s, M_DEVBUF);
224		return (error);
225	}
226	error = led_parse(s, &sb, &state);
227	free(s, M_DEVBUF);
228	if (error)
229		return (error);
230	mtx_lock(&led_mtx);
231	sc = dev->si_drv1;
232	if (sc != NULL)
233		error = led_state(sc, &sb, state);
234	mtx_unlock(&led_mtx);
235	if (sb != NULL)
236		sbuf_delete(sb);
237	return (error);
238}
239
240int
241led_set(char const *name, char const *cmd)
242{
243	struct ledsc	*sc;
244	struct sbuf *sb = NULL;
245	int error, state = 0;
246
247	error = led_parse(cmd, &sb, &state);
248	if (error)
249		return (error);
250	mtx_lock(&led_mtx);
251	LIST_FOREACH(sc, &led_list, list) {
252		if (strcmp(sc->name, name) == 0)
253			break;
254	}
255	if (sc != NULL)
256		error = led_state(sc, &sb, state);
257	else
258		error = ENOENT;
259	mtx_unlock(&led_mtx);
260	if (sb != NULL)
261		sbuf_delete(sb);
262	return (0);
263}
264
265static struct cdevsw led_cdevsw = {
266	.d_version =	D_VERSION,
267	.d_write =	led_write,
268	.d_name =	"LED",
269};
270
271struct cdev *
272led_create(led_t *func, void *priv, char const *name)
273{
274
275	return (led_create_state(func, priv, name, 0));
276}
277struct cdev *
278led_create_state(led_t *func, void *priv, char const *name, int state)
279{
280	struct ledsc	*sc;
281
282	sc = malloc(sizeof *sc, M_LED, M_WAITOK | M_ZERO);
283
284	sx_xlock(&led_sx);
285	sc->name = strdup(name, M_LED);
286	sc->unit = alloc_unr(led_unit);
287	sc->private = priv;
288	sc->func = func;
289	sc->dev = make_dev(&led_cdevsw, sc->unit,
290	    UID_ROOT, GID_WHEEL, 0600, "led/%s", name);
291	sx_xunlock(&led_sx);
292
293	mtx_lock(&led_mtx);
294	sc->dev->si_drv1 = sc;
295	LIST_INSERT_HEAD(&led_list, sc, list);
296	sc->func(sc->private, state != 0);
297	mtx_unlock(&led_mtx);
298
299	return (sc->dev);
300}
301
302void
303led_destroy(struct cdev *dev)
304{
305	struct ledsc *sc;
306
307	mtx_lock(&led_mtx);
308	sc = dev->si_drv1;
309	dev->si_drv1 = NULL;
310	if (sc->ptr != NULL)
311		blinkers--;
312	LIST_REMOVE(sc, list);
313	if (LIST_EMPTY(&led_list))
314		callout_stop(&led_ch);
315	mtx_unlock(&led_mtx);
316
317	sx_xlock(&led_sx);
318	free_unr(led_unit, sc->unit);
319	destroy_dev(dev);
320	if (sc->spec != NULL)
321		sbuf_delete(sc->spec);
322	free(sc->name, M_LED);
323	free(sc, M_LED);
324	sx_xunlock(&led_sx);
325}
326
327static void
328led_drvinit(void *unused)
329{
330
331	led_unit = new_unrhdr(0, INT_MAX, NULL);
332	mtx_init(&led_mtx, "LED mtx", NULL, MTX_DEF);
333	sx_init(&led_sx, "LED sx");
334	callout_init_mtx(&led_ch, &led_mtx, 0);
335}
336
337SYSINIT(leddev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, led_drvinit, NULL);
338