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/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 25 struct 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 36 static unsigned next_minor; 37 static struct mtx led_mtx; 38 static LIST_HEAD(, ledsc) led_list = LIST_HEAD_INITIALIZER(&led_list); 39 40 MALLOC_DEFINE(M_LED, "LED", "LED driver"); 41 42 static void 43 led_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 if (*sc->ptr >= 'a' && *sc->ptr <= 'j') 56 sc->func(sc->private, 0); 57 else if (*sc->ptr >= 'A' && *sc->ptr <= 'J') 58 sc->func(sc->private, 1); 59 sc->count = *sc->ptr & 0xf; 60 sc->ptr++; 61 if (*sc->ptr == '\0') 62 sc->ptr = sc->str; 63 } 64 mtx_unlock(&led_mtx); 65 timeout(led_timeout, p, hz / 10); 66 return; 67 } 68 69 static int 70 led_write(dev_t dev, struct uio *uio, int ioflag) 71 { 72 int error; 73 char *s, *s2; 74 struct ledsc *sc; 75 struct sbuf *sb; 76 struct sbuf *sb2; 77 int i; 78 79 sc = dev->si_drv1; 80 81 if (uio->uio_resid > 512) 82 return (EINVAL); 83 s2 = s = malloc(uio->uio_resid + 1, M_DEVBUF, M_WAITOK); 84 if (s == NULL) 85 return (ENOMEM); 86 s[uio->uio_resid] = '\0'; 87 error = uiomove(s, uio->uio_resid, uio); 88 if (error) { 89 free(s2, M_DEVBUF); 90 return (error); 91 } 92 93 /* 94 * Handle "on" and "off" immediately so people can flash really 95 * fast from userland if they want to 96 */ 97 if (*s == '0' || *s == '1') { 98 mtx_lock(&led_mtx); 99 sb2 = sc->spec; 100 sc->spec = NULL; 101 sc->str = NULL; 102 sc->ptr = NULL; 103 sc->count = 0; 104 sc->func(sc->private, *s & 1); 105 mtx_unlock(&led_mtx); 106 if (sb2 != NULL) 107 sbuf_delete(sb2); 108 free(s2, M_DEVBUF); 109 return(0); 110 } 111 112 sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND); 113 if (sb == NULL) { 114 free(s2, M_DEVBUF); 115 return (ENOMEM); 116 } 117 118 switch(s[0]) { 119 /* 120 * Flash, default is 100msec/100msec. 121 * 'f2' sets 200msec/200msec etc. 122 */ 123 case 'f': 124 if (s[1] >= '1' && s[1] <= '9') 125 i = s[1] - '1'; 126 else 127 i = 0; 128 sbuf_printf(sb, "%c%c", 'A' + i, 'a' + i); 129 break; 130 /* 131 * Digits, flashes out numbers. 132 * 'd12' becomes -__________-_-______________________________ 133 */ 134 case 'd': 135 for(s++; *s; s++) { 136 if (!isdigit(*s)) 137 continue; 138 i = *s - '0'; 139 if (i == 0) 140 i = 10; 141 for (; i > 1; i--) 142 sbuf_cat(sb, "Aa"); 143 sbuf_cat(sb, "Aj"); 144 } 145 sbuf_cat(sb, "jj"); 146 break; 147 /* 148 * String, roll your own. 149 * 'a-j' gives "off" for n/10 sec. 150 * 'A-J' gives "on" for n/10 sec. 151 * no delay before repeat 152 * 'sAaAbBa' becomes _-_--__- 153 */ 154 case 's': 155 for(s++; *s; s++) { 156 if ((*s & 0x0f) > 10) 157 continue; 158 if ((*s & 0xf0) < ' ') 159 continue; 160 sbuf_bcat(sb, s, 1); 161 } 162 break; 163 /* 164 * Morse. 165 * '.' becomes _- 166 * '-' becomes _--- 167 * ' ' becomes __ 168 * '\n' becomes ____ 169 * 1sec pause between repeats 170 * '... --- ...' -> _-_-_-___---_---_---___-_-_-__________ 171 */ 172 case 'm': 173 for(s++; *s; s++) { 174 if (*s == '.') 175 sbuf_cat(sb, "aA"); 176 else if (*s == '-') 177 sbuf_cat(sb, "aC"); 178 else if (*s == ' ') 179 sbuf_cat(sb, "b"); 180 else if (*s == '\n') 181 sbuf_cat(sb, "d"); 182 } 183 sbuf_cat(sb, "j"); 184 break; 185 default: 186 sbuf_delete(sb); 187 free(s2, M_DEVBUF); 188 return (EINVAL); 189 } 190 sbuf_finish(sb); 191 free(s2, M_DEVBUF); 192 if (sbuf_overflowed(sb)) { 193 sbuf_delete(sb); 194 return (ENOMEM); 195 } 196 if (sbuf_len(sb) == 0) { 197 sbuf_delete(sb); 198 return (0); 199 } 200 201 mtx_lock(&led_mtx); 202 sb2 = sc->spec; 203 sc->spec = sb; 204 sc->str = sbuf_data(sb); 205 sc->ptr = sc->str; 206 sc->count = 0; 207 mtx_unlock(&led_mtx); 208 if (sb2 != NULL) 209 sbuf_delete(sb2); 210 return(0); 211 } 212 213 static struct cdevsw led_cdevsw = { 214 .d_write = led_write, 215 .d_name = "LED", 216 }; 217 218 dev_t 219 led_create(led_t *func, void *priv, char const *name) 220 { 221 struct ledsc *sc; 222 struct sbuf *sb; 223 224 if (next_minor == 0) { 225 mtx_init(&led_mtx, "LED mtx", NULL, MTX_DEF); 226 timeout(led_timeout, NULL, hz / 10); 227 } 228 229 sb = sbuf_new(NULL, NULL, SPECNAMELEN, SBUF_FIXEDLEN); 230 if (sb == NULL) 231 return (NODEV); 232 sbuf_cpy(sb, "led/"); 233 sbuf_cat(sb, name); 234 sbuf_finish(sb); 235 if (sbuf_overflowed(sb)) { 236 sbuf_delete(sb); 237 return (NODEV); 238 } 239 240 sc = malloc(sizeof *sc, M_LED, M_WAITOK | M_ZERO); 241 sc->private = priv; 242 sc->func = func; 243 sc->dev = make_dev(&led_cdevsw, unit2minor(next_minor), 244 UID_ROOT, GID_WHEEL, 0600, sbuf_data(sb)); 245 sc->dev->si_drv1 = sc; 246 next_minor++; 247 sbuf_delete(sb); 248 mtx_lock(&led_mtx); 249 LIST_INSERT_HEAD(&led_list, sc, list); 250 mtx_unlock(&led_mtx); 251 return (sc->dev); 252 } 253 254 void 255 led_destroy(dev_t dev) 256 { 257 struct ledsc *sc; 258 259 sc = dev->si_drv1; 260 mtx_lock(&led_mtx); 261 LIST_REMOVE(sc, list); 262 mtx_unlock(&led_mtx); 263 if (sc->spec != NULL) 264 sbuf_delete(sc->spec); 265 destroy_dev(dev); 266 free(sc, M_LED); 267 } 268