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