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/cdefs.h> 14 __FBSDID("$FreeBSD$"); 15 16 #include <sys/param.h> 17 #include <sys/conf.h> 18 #include <sys/kernel.h> 19 #include <sys/systm.h> 20 #include <sys/limits.h> 21 #include <sys/malloc.h> 22 #include <sys/ctype.h> 23 #include <sys/sbuf.h> 24 #include <sys/queue.h> 25 #include <dev/led/led.h> 26 #include <sys/uio.h> 27 #include <sys/sx.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 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 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 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 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 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 * 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 * 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 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 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