1 /* $NetBSD: spkr.c,v 1.20 2021/04/24 23:36:52 thorpej 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.20 2021/04/24 23:36:52 thorpej Exp $"); 47 48 #if defined(_KERNEL_OPT) 49 #include "wsmux.h" 50 #endif 51 52 #include <sys/param.h> 53 #include <sys/systm.h> 54 #include <sys/kernel.h> 55 #include <sys/errno.h> 56 #include <sys/device.h> 57 #include <sys/malloc.h> 58 #include <sys/module.h> 59 #include <sys/uio.h> 60 #include <sys/proc.h> 61 #include <sys/ioctl.h> 62 #include <sys/conf.h> 63 64 #include <sys/bus.h> 65 66 #include <dev/spkrio.h> 67 #include <dev/spkrvar.h> 68 #include <dev/wscons/wsconsio.h> 69 #include <dev/wscons/wsbellvar.h> 70 #include <dev/wscons/wsbellmuxvar.h> 71 72 #include "ioconf.h" 73 74 dev_type_open(spkropen); 75 dev_type_close(spkrclose); 76 dev_type_write(spkrwrite); 77 dev_type_ioctl(spkrioctl); 78 79 const struct cdevsw spkr_cdevsw = { 80 .d_open = spkropen, 81 .d_close = spkrclose, 82 .d_read = noread, 83 .d_write = spkrwrite, 84 .d_ioctl = spkrioctl, 85 .d_stop = nostop, 86 .d_tty = notty, 87 .d_poll = nopoll, 88 .d_mmap = nommap, 89 .d_kqfilter = nokqfilter, 90 .d_discard = nodiscard, 91 .d_flag = D_OTHER 92 }; 93 94 static void playinit(struct spkr_softc *); 95 static void playtone(struct spkr_softc *, int, int, int); 96 static void playstring(struct spkr_softc *, const char *, size_t); 97 98 /**************** PLAY STRING INTERPRETER BEGINS HERE ********************** 99 * 100 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 101 * M[LNS] are missing and the ~ synonym and octave-tracking facility is added. 102 * String play is not interruptible except possibly at physical block 103 * boundaries. 104 */ 105 106 /* 107 * Magic number avoidance... 108 */ 109 #define SECS_PER_MIN 60 /* seconds per minute */ 110 #define WHOLE_NOTE 4 /* quarter notes per whole note */ 111 #define MIN_VALUE 64 /* the most we can divide a note by */ 112 #define DFLT_VALUE 4 /* default value (quarter-note) */ 113 #define FILLTIME 8 /* for articulation, break note in parts */ 114 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */ 115 #define NORMAL 7 /* 7/8ths of note interval is filled */ 116 #define LEGATO 8 /* all of note interval is filled */ 117 #define DFLT_OCTAVE 4 /* default octave */ 118 #define MIN_TEMPO 32 /* minimum tempo */ 119 #define DFLT_TEMPO 120 /* default tempo */ 120 #define MAX_TEMPO 255 /* max tempo */ 121 #define NUM_MULT 3 /* numerator of dot multiplier */ 122 #define DENOM_MULT 2 /* denominator of dot multiplier */ 123 124 /* letter to half-tone: A B C D E F G */ 125 static const int notetab[8] = { 9, 11, 0, 2, 4, 5, 7 }; 126 127 /* 128 * This is the American Standard A440 Equal-Tempered scale with frequencies 129 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... 130 * our octave 0 is standard octave 2. 131 */ 132 #define OCTAVE_NOTES 12 /* semitones per octave */ 133 static const int pitchtab[] = 134 { 135 /* C C# D D# E F F# G G# A A# B*/ 136 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, 137 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 138 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 139 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 140 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, 141 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 142 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, 143 }; 144 #define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES) 145 146 static void 147 playinit(struct spkr_softc *sc) 148 { 149 sc->sc_octave = DFLT_OCTAVE; 150 sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 151 sc->sc_fill = NORMAL; 152 sc->sc_value = DFLT_VALUE; 153 sc->sc_octtrack = false; 154 sc->sc_octprefix = true;/* act as though there was an initial O(n) */ 155 } 156 157 #define SPKRPRI (PZERO - 1) 158 159 /* Rest for given number of ticks */ 160 static void 161 rest(struct spkr_softc *sc, int ticks) 162 { 163 164 #ifdef SPKRDEBUG 165 device_printf(sc->sc_dev, "%s: rest for %d ticks\n", __func__, ticks); 166 #endif /* SPKRDEBUG */ 167 KASSERT(ticks > 0); 168 169 tsleep(sc->sc_dev, SPKRPRI | PCATCH, device_xname(sc->sc_dev), ticks); 170 } 171 172 /* 173 * Play tone of proper duration for current rhythm signature. 174 * note indicates "O0C" = 0, "O0C#" = 1, "O0D" = 2, ... , and 175 * -1 indiacates a rest. 176 * val indicates the length, "L4" = 4, "L8" = 8. 177 * sustain indicates the number of subsequent dots that extend the sound 178 * by one a half. 179 */ 180 static void 181 playtone(struct spkr_softc *sc, int note, int val, int sustain) 182 { 183 int whole; 184 int total; 185 int sound; 186 int silence; 187 188 /* this weirdness avoids floating-point arithmetic */ 189 whole = sc->sc_whole; 190 for (; sustain; sustain--) { 191 whole *= NUM_MULT; 192 val *= DENOM_MULT; 193 } 194 195 /* Total length in tick */ 196 total = whole / val; 197 198 if (note == -1) { 199 #ifdef SPKRDEBUG 200 device_printf(sc->sc_dev, "%s: rest for %d ticks\n", 201 __func__, total); 202 #endif /* SPKRDEBUG */ 203 if (total != 0) 204 rest(sc, total); 205 return; 206 } 207 208 /* 209 * Rest 1/8 (if NORMAL) or 3/8 (if STACCATO) in tick. 210 * silence should be rounded down. 211 */ 212 silence = total * (FILLTIME - sc->sc_fill) / FILLTIME; 213 sound = total - silence; 214 215 #ifdef SPKRDEBUG 216 device_printf(sc->sc_dev, 217 "%s: note %d for %d ticks, rest for %d ticks\n", __func__, 218 note, sound, silence); 219 #endif /* SPKRDEBUG */ 220 221 if (sound != 0) 222 (*sc->sc_tone)(sc->sc_dev, pitchtab[note], sound); 223 if (silence != 0) 224 rest(sc, silence); 225 } 226 227 /* interpret and play an item from a notation string */ 228 static void 229 playstring(struct spkr_softc *sc, const char *cp, size_t slen) 230 { 231 int pitch; 232 int lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 233 234 #define GETNUM(cp, v) \ 235 for (v = 0; slen > 0 && isdigit((unsigned char)cp[1]); ) { \ 236 v = v * 10 + (*++cp - '0'); \ 237 slen--; \ 238 } 239 240 for (; slen--; cp++) { 241 int sustain, timeval, tempo; 242 char c = toupper((unsigned char)*cp); 243 244 #ifdef SPKRDEBUG 245 if (0x20 <= c && c < 0x7f) { 246 device_printf(sc->sc_dev, "%s: '%c'\n", __func__, c); 247 } else { 248 device_printf(sc->sc_dev, "%s: (0x%x)\n", __func__, c); 249 } 250 #endif /* SPKRDEBUG */ 251 252 switch (c) { 253 case 'A': case 'B': case 'C': case 'D': 254 case 'E': case 'F': case 'G': 255 /* compute pitch */ 256 pitch = notetab[c - 'A'] + sc->sc_octave * OCTAVE_NOTES; 257 258 /* this may be followed by an accidental sign */ 259 if (slen > 0 && (cp[1] == '#' || cp[1] == '+')) { 260 ++pitch; 261 ++cp; 262 slen--; 263 } else if (slen > 0 && cp[1] == '-') { 264 --pitch; 265 ++cp; 266 slen--; 267 } 268 269 /* 270 * If octave-tracking mode is on, and there has been no 271 * octave- setting prefix, find the version of the 272 * current letter note * closest to the last 273 * regardless of octave. 274 */ 275 if (sc->sc_octtrack && !sc->sc_octprefix) { 276 int d = abs(pitch - lastpitch); 277 if (d > abs(pitch + OCTAVE_NOTES - lastpitch)) { 278 if (sc->sc_octave < NOCTAVES - 1) { 279 ++sc->sc_octave; 280 pitch += OCTAVE_NOTES; 281 } 282 } 283 284 if (d > abs(pitch - OCTAVE_NOTES - lastpitch)) { 285 if (sc->sc_octave > 0) { 286 --sc->sc_octave; 287 pitch -= OCTAVE_NOTES; 288 } 289 } 290 } 291 sc->sc_octprefix = false; 292 lastpitch = pitch; 293 294 /* 295 * ...which may in turn be followed by an override 296 * time value 297 */ 298 GETNUM(cp, timeval); 299 if (timeval <= 0 || timeval > MIN_VALUE) 300 timeval = sc->sc_value; 301 302 /* ...and/or sustain dots */ 303 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) { 304 slen--; 305 sustain++; 306 } 307 308 /* time to emit the actual tone */ 309 playtone(sc, pitch, timeval, sustain); 310 break; 311 312 case 'O': 313 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) { 314 sc->sc_octprefix = sc->sc_octtrack = false; 315 ++cp; 316 slen--; 317 } else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) { 318 sc->sc_octtrack = true; 319 ++cp; 320 slen--; 321 } else { 322 GETNUM(cp, sc->sc_octave); 323 if (sc->sc_octave >= NOCTAVES) 324 sc->sc_octave = DFLT_OCTAVE; 325 sc->sc_octprefix = true; 326 } 327 break; 328 329 case '>': 330 if (sc->sc_octave < NOCTAVES - 1) 331 sc->sc_octave++; 332 sc->sc_octprefix = true; 333 break; 334 335 case '<': 336 if (sc->sc_octave > 0) 337 sc->sc_octave--; 338 sc->sc_octprefix = true; 339 break; 340 341 case 'N': 342 GETNUM(cp, pitch); 343 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) { 344 slen--; 345 sustain++; 346 } 347 playtone(sc, pitch - 1, sc->sc_value, sustain); 348 break; 349 350 case 'L': 351 GETNUM(cp, sc->sc_value); 352 if (sc->sc_value <= 0 || sc->sc_value > MIN_VALUE) 353 sc->sc_value = DFLT_VALUE; 354 break; 355 356 case 'P': 357 case '~': 358 /* this may be followed by an override time value */ 359 GETNUM(cp, timeval); 360 if (timeval <= 0 || timeval > MIN_VALUE) 361 timeval = sc->sc_value; 362 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) { 363 slen--; 364 sustain++; 365 } 366 playtone(sc, -1, timeval, sustain); 367 break; 368 369 case 'T': 370 GETNUM(cp, tempo); 371 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) 372 tempo = DFLT_TEMPO; 373 sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; 374 break; 375 376 case 'M': 377 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) { 378 sc->sc_fill = NORMAL; 379 ++cp; 380 slen--; 381 } else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) { 382 sc->sc_fill = LEGATO; 383 ++cp; 384 slen--; 385 } else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's')) { 386 sc->sc_fill = STACCATO; 387 ++cp; 388 slen--; 389 } 390 break; 391 } 392 } 393 } 394 395 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************/ 396 #define spkrenter(d) device_lookup_private(&spkr_cd, d) 397 398 /* 399 * Attaches spkr. Specify tone function with the following specification: 400 * 401 * void 402 * tone(device_t self, u_int pitch, u_int tick) 403 * plays a beep with specified parameters. 404 * The argument 'pitch' specifies the pitch of a beep in Hz. The argument 405 * 'tick' specifies the period of a beep in tick(9). This function waits 406 * to finish playing the beep and then halts it. 407 * If the pitch is zero, it halts all sound if any (for compatibility 408 * with the past confused specifications, but there should be no sound at 409 * this point). And it returns immediately, without waiting ticks. So 410 * you cannot use this as a rest. 411 * If the tick is zero, it returns immediately. 412 */ 413 void 414 spkr_attach(device_t self, void (*tone)(device_t, u_int, u_int)) 415 { 416 struct spkr_softc *sc = device_private(self); 417 418 #ifdef SPKRDEBUG 419 aprint_debug("%s: entering for unit %d\n", __func__, self->dv_unit); 420 #endif /* SPKRDEBUG */ 421 sc->sc_dev = self; 422 sc->sc_tone = tone; 423 sc->sc_inbuf = NULL; 424 sc->sc_wsbelldev = NULL; 425 426 spkr_rescan(self, NULL, NULL); 427 } 428 429 int 430 spkr_detach(device_t self, int flags) 431 { 432 struct spkr_softc *sc = device_private(self); 433 int rc; 434 435 #ifdef SPKRDEBUG 436 aprint_debug("%s: entering for unit %d\n", __func__, self->dv_unit); 437 #endif /* SPKRDEBUG */ 438 if (sc == NULL) 439 return ENXIO; 440 441 /* XXXNS If speaker never closes, we cannot complete the detach. */ 442 while ((flags & DETACH_FORCE) != 0 && sc->sc_inbuf != NULL) 443 kpause("spkrwait", TRUE, 1, NULL); 444 if (sc->sc_inbuf != NULL) 445 return EBUSY; 446 447 rc = config_detach_children(self, flags); 448 449 return rc; 450 } 451 452 /* ARGSUSED */ 453 int 454 spkr_rescan(device_t self, const char *iattr, const int *locators) 455 { 456 #if NWSMUX > 0 457 struct spkr_softc *sc = device_private(self); 458 struct wsbelldev_attach_args a; 459 460 if (sc->sc_wsbelldev == NULL) { 461 a.accesscookie = sc; 462 sc->sc_wsbelldev = config_found(self, &a, wsbelldevprint, 463 CFARG_EOL); 464 } 465 #endif 466 return 0; 467 } 468 469 int 470 spkr_childdet(device_t self, device_t child) 471 { 472 struct spkr_softc *sc = device_private(self); 473 474 if (sc->sc_wsbelldev == child) 475 sc->sc_wsbelldev = NULL; 476 477 return 0; 478 } 479 480 int 481 spkropen(dev_t dev, int flags, int mode, struct lwp *l) 482 { 483 struct spkr_softc *sc = spkrenter(minor(dev)); 484 485 #ifdef SPKRDEBUG 486 device_printf(sc->sc_dev, "%s: entering\n", __func__); 487 #endif /* SPKRDEBUG */ 488 if (sc == NULL) 489 return ENXIO; 490 if (sc->sc_inbuf != NULL) 491 return EBUSY; 492 493 sc->sc_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK); 494 playinit(sc); 495 return 0; 496 } 497 498 int 499 spkrwrite(dev_t dev, struct uio *uio, int flags) 500 { 501 struct spkr_softc *sc = spkrenter(minor(dev)); 502 503 #ifdef SPKRDEBUG 504 device_printf(sc->sc_dev, "%s: entering with length = %zu\n", 505 __func__, uio->uio_resid); 506 #endif /* SPKRDEBUG */ 507 if (sc == NULL) 508 return ENXIO; 509 if (sc->sc_inbuf == NULL) 510 return EINVAL; 511 512 size_t n = uimin(DEV_BSIZE, uio->uio_resid); 513 int error = uiomove(sc->sc_inbuf, n, uio); 514 if (error) 515 return error; 516 playstring(sc, sc->sc_inbuf, n); 517 return 0; 518 } 519 520 int 521 spkrclose(dev_t dev, int flags, int mode, struct lwp *l) 522 { 523 struct spkr_softc *sc = spkrenter(minor(dev)); 524 525 #ifdef SPKRDEBUG 526 device_printf(sc->sc_dev, "%s: entering\n", __func__); 527 #endif /* SPKRDEBUG */ 528 if (sc == NULL) 529 return ENXIO; 530 if (sc->sc_inbuf == NULL) 531 return EINVAL; 532 533 sc->sc_tone(sc->sc_dev, 0, 0); 534 free(sc->sc_inbuf, M_DEVBUF); 535 sc->sc_inbuf = NULL; 536 537 return 0; 538 } 539 540 /* 541 * Play tone specified by tp. 542 * tp->frequency is the frequency (0 means a rest). 543 * tp->duration is the length in tick (returns immediately if 0). 544 */ 545 static void 546 playonetone(struct spkr_softc *sc, tone_t *tp) 547 { 548 if (tp->duration <= 0) 549 return; 550 551 if (tp->frequency == 0) 552 rest(sc, tp->duration); 553 else 554 (*sc->sc_tone)(sc->sc_dev, tp->frequency, tp->duration); 555 } 556 557 int 558 spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 559 { 560 struct spkr_softc *sc = spkrenter(minor(dev)); 561 tone_t *tp; 562 tone_t ttp; 563 int error; 564 565 #ifdef SPKRDEBUG 566 device_printf(sc->sc_dev, "%s: entering with cmd = %lx\n", 567 __func__, cmd); 568 #endif /* SPKRDEBUG */ 569 if (sc == NULL) 570 return ENXIO; 571 if (sc->sc_inbuf == NULL) 572 return EINVAL; 573 574 switch (cmd) { 575 case SPKRTONE: 576 playonetone(sc, data); 577 return 0; 578 case SPKRTUNE: 579 for (tp = *(void **)data;; tp++) { 580 error = copyin(tp, &ttp, sizeof(tone_t)); 581 if (error) 582 return(error); 583 if (ttp.duration == 0) 584 break; 585 playonetone(sc, &ttp); 586 } 587 return 0; 588 case SPKRGETVOL: 589 if (data != NULL) 590 *(u_int *)data = sc->sc_vol; 591 return 0; 592 case SPKRSETVOL: 593 if (data != NULL && *(u_int *)data <= 100) 594 sc->sc_vol = *(u_int *)data; 595 return 0; 596 default: 597 return ENOTTY; 598 } 599 } 600 601 #ifdef _MODULE 602 #include "ioconf.c" 603 #endif 604 605 MODULE(MODULE_CLASS_DRIVER, spkr, "audio" /* and/or pcppi */ ); 606 607 int 608 spkr_modcmd(modcmd_t cmd, void *arg) 609 { 610 int error = 0; 611 #ifdef _MODULE 612 devmajor_t bmajor, cmajor; 613 #endif 614 615 switch(cmd) { 616 case MODULE_CMD_INIT: 617 #ifdef _MODULE 618 bmajor = cmajor = -1; 619 error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor, 620 &spkr_cdevsw, &cmajor); 621 if (error) 622 break; 623 624 error = config_init_component(cfdriver_ioconf_spkr, 625 cfattach_ioconf_spkr, cfdata_ioconf_spkr); 626 if (error) { 627 devsw_detach(NULL, &spkr_cdevsw); 628 } 629 #endif 630 break; 631 632 case MODULE_CMD_FINI: 633 #ifdef _MODULE 634 devsw_detach(NULL, &spkr_cdevsw); 635 error = config_fini_component(cfdriver_ioconf_spkr, 636 cfattach_ioconf_spkr, cfdata_ioconf_spkr); 637 if (error) 638 devsw_attach(spkr_cd.cd_name, NULL, &bmajor, 639 &spkr_cdevsw, &cmajor); 640 #endif 641 break; 642 643 default: 644 error = ENOTTY; 645 break; 646 } 647 648 return error; 649 } 650