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