1 /* $NetBSD: spkr.c,v 1.4 2016/12/13 20:20:34 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1990 Eric S. Raymond (esr@snark.thyrsus.com) 5 * Copyright (c) 1990 Andrew A. Chernov (ache@astral.msk.su) 6 * Copyright (c) 1990 Lennart Augustsson (lennart@augustsson.net) 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed by Eric S. Raymond 20 * 4. The name of the author may not be used to endorse or promote products 21 * derived from this software without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 * POSSIBILITY OF SUCH DAMAGE. 34 */ 35 36 /* 37 * spkr.c -- device driver for console speaker on 80386 38 * 39 * v1.1 by Eric S. Raymond (esr@snark.thyrsus.com) Feb 1990 40 * modified for 386bsd by Andrew A. Chernov <ache@astral.msk.su> 41 * 386bsd only clean version, all SYSV stuff removed 42 * use hz value from param.c 43 */ 44 45 #include <sys/cdefs.h> 46 __KERNEL_RCSID(0, "$NetBSD: spkr.c,v 1.4 2016/12/13 20:20:34 christos Exp $"); 47 48 #include <sys/param.h> 49 #include <sys/systm.h> 50 #include <sys/kernel.h> 51 #include <sys/errno.h> 52 #include <sys/device.h> 53 #include <sys/malloc.h> 54 #include <sys/module.h> 55 #include <sys/uio.h> 56 #include <sys/proc.h> 57 #include <sys/ioctl.h> 58 #include <sys/conf.h> 59 60 #include <sys/bus.h> 61 62 #include <dev/spkrio.h> 63 #include <dev/spkrvar.h> 64 65 dev_type_open(spkropen); 66 dev_type_close(spkrclose); 67 dev_type_write(spkrwrite); 68 dev_type_ioctl(spkrioctl); 69 70 const struct cdevsw spkr_cdevsw = { 71 .d_open = spkropen, 72 .d_close = spkrclose, 73 .d_read = noread, 74 .d_write = spkrwrite, 75 .d_ioctl = spkrioctl, 76 .d_stop = nostop, 77 .d_tty = notty, 78 .d_poll = nopoll, 79 .d_mmap = nommap, 80 .d_kqfilter = nokqfilter, 81 .d_discard = nodiscard, 82 .d_flag = D_OTHER 83 }; 84 85 static void playinit(struct spkr_softc *); 86 static void playtone(struct spkr_softc *, int, int, int); 87 static void playstring(struct spkr_softc *, const char *, size_t); 88 89 /**************** PLAY STRING INTERPRETER BEGINS HERE ********************** 90 * 91 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 92 * M[LNS] are missing and the ~ synonym and octave-tracking facility is added. 93 * Requires spkr_tone(), spkr_rest(). String play is not interruptible 94 * except possibly at physical block boundaries. 95 */ 96 97 #define dtoi(c) ((c) - '0') 98 99 /* 100 * Magic number avoidance... 101 */ 102 #define SECS_PER_MIN 60 /* seconds per minute */ 103 #define WHOLE_NOTE 4 /* quarter notes per whole note */ 104 #define MIN_VALUE 64 /* the most we can divide a note by */ 105 #define DFLT_VALUE 4 /* default value (quarter-note) */ 106 #define FILLTIME 8 /* for articulation, break note in parts */ 107 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */ 108 #define NORMAL 7 /* 7/8ths of note interval is filled */ 109 #define LEGATO 8 /* all of note interval is filled */ 110 #define DFLT_OCTAVE 4 /* default octave */ 111 #define MIN_TEMPO 32 /* minimum tempo */ 112 #define DFLT_TEMPO 120 /* default tempo */ 113 #define MAX_TEMPO 255 /* max tempo */ 114 #define NUM_MULT 3 /* numerator of dot multiplier */ 115 #define DENOM_MULT 2 /* denominator of dot multiplier */ 116 117 /* letter to half-tone: A B C D E F G */ 118 static const int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; 119 120 /* 121 * This is the American Standard A440 Equal-Tempered scale with frequencies 122 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... 123 * our octave 0 is standard octave 2. 124 */ 125 #define OCTAVE_NOTES 12 /* semitones per octave */ 126 static const int pitchtab[] = 127 { 128 /* C C# D D# E F F# G G# A A# B*/ 129 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, 130 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 131 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 132 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 133 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, 134 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 135 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, 136 }; 137 #define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES) 138 139 static void 140 playinit(struct spkr_softc *sc) 141 { 142 sc->sc_octave = DFLT_OCTAVE; 143 sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 144 sc->sc_fill = NORMAL; 145 sc->sc_value = DFLT_VALUE; 146 sc->sc_octtrack = false; 147 sc->sc_octprefix = true;/* act as though there was an initial O(n) */ 148 } 149 150 /* play tone of proper duration for current rhythm signature */ 151 static void 152 playtone(struct spkr_softc *sc, int pitch, int val, int sustain) 153 { 154 int sound, silence, snum = 1, sdenom = 1; 155 156 /* this weirdness avoids floating-point arithmetic */ 157 for (; sustain; sustain--) { 158 snum *= NUM_MULT; 159 sdenom *= DENOM_MULT; 160 } 161 162 if (pitch == -1) { 163 (*sc->sc_rest)(sc->sc_dev, sc->sc_whole 164 * snum / (val * sdenom)); 165 return; 166 } 167 168 int fac = sc->sc_whole * (FILLTIME - sc->sc_fill); 169 int fval = FILLTIME * val; 170 sound = (sc->sc_whole * snum) / (val * sdenom) - fac / fval; 171 silence = fac * snum / (fval * sdenom); 172 173 #ifdef SPKRDEBUG 174 aprint_debug_dev(sc->sc_dev, 175 "%s: pitch %d for %d ticks, rest for %d ticks\n", __func__, 176 pitch, sound, silence); 177 #endif /* SPKRDEBUG */ 178 179 (*sc->sc_tone)(sc->sc_dev, pitchtab[pitch], sound); 180 if (sc->sc_fill != LEGATO) 181 (*sc->sc_rest)(sc->sc_dev, silence); 182 } 183 184 /* interpret and play an item from a notation string */ 185 static void 186 playstring(struct spkr_softc *sc, const char *cp, size_t slen) 187 { 188 int pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 189 190 #define GETNUM(cp, v) \ 191 for (v = 0; slen > 0 && isdigit((unsigned char)cp[1]); ) { \ 192 v = v * 10 + (*++cp - '0'); \ 193 slen--; \ 194 } 195 196 for (; slen--; cp++) { 197 int sustain, timeval, tempo; 198 char c = toupper((unsigned char)*cp); 199 200 #ifdef SPKRDEBUG 201 aprint_debug_dev(sc->sc_dev, "%s: %c (%x)\n", __func__, c, c); 202 #endif /* SPKRDEBUG */ 203 204 switch (c) { 205 case 'A': case 'B': case 'C': case 'D': 206 case 'E': case 'F': case 'G': 207 /* compute pitch */ 208 pitch = notetab[c - 'A'] + sc->sc_octave * OCTAVE_NOTES; 209 210 /* this may be followed by an accidental sign */ 211 if (slen > 0 && (cp[1] == '#' || cp[1] == '+')) { 212 ++pitch; 213 ++cp; 214 slen--; 215 } else if (slen > 0 && cp[1] == '-') { 216 --pitch; 217 ++cp; 218 slen--; 219 } 220 221 /* 222 * If octave-tracking mode is on, and there has been no 223 * octave- setting prefix, find the version of the 224 * current letter note * closest to the last 225 * regardless of octave. 226 */ 227 if (sc->sc_octtrack && !sc->sc_octprefix) { 228 int d = abs(pitch - lastpitch); 229 if (d > abs(pitch + OCTAVE_NOTES - lastpitch)) { 230 if (sc->sc_octave < NOCTAVES - 1) { 231 ++sc->sc_octave; 232 pitch += OCTAVE_NOTES; 233 } 234 } 235 236 if (d > abs(pitch - OCTAVE_NOTES - lastpitch)) { 237 if (sc->sc_octave > 0) { 238 --sc->sc_octave; 239 pitch -= OCTAVE_NOTES; 240 } 241 } 242 } 243 sc->sc_octprefix = false; 244 lastpitch = pitch; 245 246 /* 247 * ...which may in turn be followed by an override 248 * time value 249 */ 250 GETNUM(cp, timeval); 251 if (timeval <= 0 || timeval > MIN_VALUE) 252 timeval = sc->sc_value; 253 254 /* ...and/or sustain dots */ 255 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) { 256 slen--; 257 sustain++; 258 } 259 260 /* time to emit the actual tone */ 261 playtone(sc, pitch, timeval, sustain); 262 break; 263 264 case 'O': 265 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) { 266 sc->sc_octprefix = sc->sc_octtrack = false; 267 ++cp; 268 slen--; 269 } else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) { 270 sc->sc_octtrack = true; 271 ++cp; 272 slen--; 273 } else { 274 GETNUM(cp, sc->sc_octave); 275 if (sc->sc_octave >= NOCTAVES) 276 sc->sc_octave = DFLT_OCTAVE; 277 sc->sc_octprefix = true; 278 } 279 break; 280 281 case '>': 282 if (sc->sc_octave < NOCTAVES - 1) 283 sc->sc_octave++; 284 sc->sc_octprefix = true; 285 break; 286 287 case '<': 288 if (sc->sc_octave > 0) 289 sc->sc_octave--; 290 sc->sc_octprefix = true; 291 break; 292 293 case 'N': 294 GETNUM(cp, pitch); 295 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) { 296 slen--; 297 sustain++; 298 } 299 playtone(sc, pitch - 1, sc->sc_value, sustain); 300 break; 301 302 case 'L': 303 GETNUM(cp, sc->sc_value); 304 if (sc->sc_value <= 0 || sc->sc_value > MIN_VALUE) 305 sc->sc_value = DFLT_VALUE; 306 break; 307 308 case 'P': 309 case '~': 310 /* this may be followed by an override time value */ 311 GETNUM(cp, timeval); 312 if (timeval <= 0 || timeval > MIN_VALUE) 313 timeval = sc->sc_value; 314 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) { 315 slen--; 316 sustain++; 317 } 318 playtone(sc, -1, timeval, sustain); 319 break; 320 321 case 'T': 322 GETNUM(cp, tempo); 323 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) 324 tempo = DFLT_TEMPO; 325 sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; 326 break; 327 328 case 'M': 329 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) { 330 sc->sc_fill = NORMAL; 331 ++cp; 332 slen--; 333 } else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) { 334 sc->sc_fill = LEGATO; 335 ++cp; 336 slen--; 337 } else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's')) { 338 sc->sc_fill = STACCATO; 339 ++cp; 340 slen--; 341 } 342 break; 343 } 344 } 345 } 346 347 /******************* UNIX DRIVER HOOKS BEGIN HERE ************************** 348 * 349 * This section implements driver hooks to run playstring() and the spkr_tone() 350 * and spkr_rest() functions defined above. 351 */ 352 extern struct cfdriver spkr_cd; 353 #define spkrenter(d) device_lookup_private(&spkr_cd, d) 354 355 void 356 spkr_attach(device_t self, void (*tone)(device_t, u_int, u_int), 357 void (*rest)(device_t, int)) 358 { 359 struct spkr_softc *sc = device_private(self); 360 361 sc->sc_dev = self; 362 sc->sc_tone = tone; 363 sc->sc_rest = rest; 364 sc->sc_inbuf = NULL; 365 } 366 367 int 368 spkropen(dev_t dev, int flags, int mode, struct lwp *l) 369 { 370 #ifdef SPKRDEBUG 371 aprint_debug("%s: entering with dev = %"PRIx64"\n", __func__, dev); 372 #endif /* SPKRDEBUG */ 373 struct spkr_softc *sc = spkrenter(minor(dev)); 374 375 if (sc == NULL) 376 return ENXIO; 377 if (sc->sc_inbuf) 378 return EBUSY; 379 380 sc->sc_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK); 381 playinit(sc); 382 return 0; 383 } 384 385 int 386 spkrwrite(dev_t dev, struct uio *uio, int flags) 387 { 388 #ifdef SPKRDEBUG 389 aprint_debug("%s: entering with dev = %"PRIx64", count = %zu\n", 390 __func__, dev, uio->uio_resid); 391 #endif /* SPKRDEBUG */ 392 struct spkr_softc *sc = spkrenter(minor(dev)); 393 394 if (sc == NULL) 395 return ENXIO; 396 if (!sc->sc_inbuf) 397 return EINVAL; 398 399 size_t n = min(DEV_BSIZE, uio->uio_resid); 400 int error = uiomove(sc->sc_inbuf, n, uio); 401 if (error) 402 return error; 403 playstring(sc, sc->sc_inbuf, n); 404 return 0; 405 } 406 407 int 408 spkrclose(dev_t dev, int flags, int mode, struct lwp *l) 409 { 410 #ifdef SPKRDEBUG 411 aprint_debug("%s: entering with dev = %"PRIx64"\n", __func__, dev); 412 #endif /* SPKRDEBUG */ 413 struct spkr_softc *sc = spkrenter(minor(dev)); 414 415 if (sc == NULL) 416 return ENXIO; 417 if (!sc->sc_inbuf) 418 return EINVAL; 419 420 sc->sc_tone(sc->sc_dev, 0, 0); 421 free(sc->sc_inbuf, M_DEVBUF); 422 sc->sc_inbuf = NULL; 423 424 return 0; 425 } 426 427 static void 428 playonetone(struct spkr_softc *sc, tone_t *tp) 429 { 430 if (tp->frequency == 0) 431 (*sc->sc_rest)(sc->sc_dev, tp->duration); 432 else 433 (*sc->sc_tone)(sc->sc_dev, tp->frequency, tp->duration); 434 } 435 436 int 437 spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 438 { 439 tone_t *tp; 440 tone_t ttp; 441 int error; 442 #ifdef SPKRDEBUG 443 aprint_debug("%s: entering with dev = %"PRIx64", cmd = %lx\n", 444 __func__, dev, cmd); 445 #endif /* SPKRDEBUG */ 446 447 struct spkr_softc *sc = spkrenter(minor(dev)); 448 449 if (sc == NULL) 450 return ENXIO; 451 if (!sc->sc_inbuf) 452 return EINVAL; 453 454 switch (cmd) { 455 case SPKRTONE: 456 playonetone(sc, data); 457 return 0; 458 case SPKRTUNE: 459 for (tp = *(void **)data;; tp++) { 460 error = copyin(tp, &ttp, sizeof(tone_t)); 461 if (error) 462 return(error); 463 if (ttp.duration == 0) 464 break; 465 playonetone(sc, &ttp); 466 } 467 return 0; 468 default: 469 return ENOTTY; 470 } 471 } 472 473 #ifdef _MODULE 474 extern struct cfdriver spkr_cd; 475 #include "ioconf.c" 476 #endif 477 478 int 479 spkr__modcmd(modcmd_t cmd, void *arg) 480 { 481 #ifdef _MODULE 482 devmajor_t bmajor, cmajor; 483 int error = 0; 484 485 switch(cmd) { 486 case MODULE_CMD_INIT: 487 bmajor = cmajor = -1; 488 error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor, 489 &spkr_cdevsw, &cmajor); 490 if (error) 491 break; 492 493 error = config_init_component(cfdriver_ioconf_spkr, 494 cfattach_ioconf_spkr, cfdata_ioconf_spkr); 495 if (error) { 496 devsw_detach(NULL, &spkr_cdevsw); 497 } 498 break; 499 500 case MODULE_CMD_FINI: 501 return EBUSY; 502 #ifdef notyet 503 error = config_fini_component(cfdriver_ioconf_spkr, 504 cfattach_ioconf_spkr, cfdata_ioconf_spkr); 505 devsw_detach(NULL, &spkr_cdevsw); 506 break; 507 #endif 508 default: 509 error = ENOTTY; 510 break; 511 } 512 513 return error; 514 #else 515 return 0; 516 #endif 517 } 518