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