1 /* $NetBSD: spkr.c,v 1.24 2022/09/24 23:16:37 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.24 2022/09/24 23:16:37 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/kmem.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__, 420 device_unit(self)); 421 #endif /* SPKRDEBUG */ 422 sc->sc_dev = self; 423 sc->sc_tone = tone; 424 sc->sc_inbuf = NULL; 425 sc->sc_wsbelldev = NULL; 426 427 spkr_rescan(self, NULL, NULL); 428 } 429 430 int 431 spkr_detach(device_t self, int flags) 432 { 433 struct spkr_softc *sc = device_private(self); 434 int rc; 435 436 #ifdef SPKRDEBUG 437 aprint_debug("%s: entering for unit %d\n", __func__, 438 device_unit(self)); 439 #endif /* SPKRDEBUG */ 440 if (sc == NULL) 441 return ENXIO; 442 443 /* XXXNS If speaker never closes, we cannot complete the detach. */ 444 while ((flags & DETACH_FORCE) != 0 && sc->sc_inbuf != NULL) 445 kpause("spkrwait", TRUE, 1, NULL); 446 if (sc->sc_inbuf != NULL) 447 return EBUSY; 448 449 rc = config_detach_children(self, flags); 450 451 return rc; 452 } 453 454 /* ARGSUSED */ 455 int 456 spkr_rescan(device_t self, const char *iattr, const int *locators) 457 { 458 #if NWSMUX > 0 459 struct spkr_softc *sc = device_private(self); 460 struct wsbelldev_attach_args a; 461 462 if (sc->sc_wsbelldev == NULL) { 463 a.accesscookie = sc; 464 sc->sc_wsbelldev = config_found(self, &a, wsbelldevprint, 465 CFARGS_NONE); 466 } 467 #endif 468 return 0; 469 } 470 471 int 472 spkr_childdet(device_t self, device_t child) 473 { 474 struct spkr_softc *sc = device_private(self); 475 476 if (sc->sc_wsbelldev == child) 477 sc->sc_wsbelldev = NULL; 478 479 return 0; 480 } 481 482 int 483 spkropen(dev_t dev, int flags, int mode, struct lwp *l) 484 { 485 struct spkr_softc *sc = spkrenter(minor(dev)); 486 487 #ifdef SPKRDEBUG 488 device_printf(sc->sc_dev, "%s: entering\n", __func__); 489 #endif /* SPKRDEBUG */ 490 if (sc == NULL) 491 return ENXIO; 492 if (sc->sc_inbuf != NULL) 493 return EBUSY; 494 495 sc->sc_inbuf = kmem_alloc(DEV_BSIZE, KM_SLEEP); 496 playinit(sc); 497 return 0; 498 } 499 500 int 501 spkrwrite(dev_t dev, struct uio *uio, int flags) 502 { 503 struct spkr_softc *sc = spkrenter(minor(dev)); 504 505 #ifdef SPKRDEBUG 506 device_printf(sc->sc_dev, "%s: entering with length = %zu\n", 507 __func__, uio->uio_resid); 508 #endif /* SPKRDEBUG */ 509 if (sc == NULL) 510 return ENXIO; 511 if (sc->sc_inbuf == NULL) 512 return EINVAL; 513 514 size_t n = uimin(DEV_BSIZE, uio->uio_resid); 515 int error = uiomove(sc->sc_inbuf, n, uio); 516 if (error) 517 return error; 518 playstring(sc, sc->sc_inbuf, n); 519 return 0; 520 } 521 522 int 523 spkrclose(dev_t dev, int flags, int mode, struct lwp *l) 524 { 525 struct spkr_softc *sc = spkrenter(minor(dev)); 526 527 #ifdef SPKRDEBUG 528 device_printf(sc->sc_dev, "%s: entering\n", __func__); 529 #endif /* SPKRDEBUG */ 530 if (sc == NULL) 531 return ENXIO; 532 if (sc->sc_inbuf == NULL) 533 return EINVAL; 534 535 sc->sc_tone(sc->sc_dev, 0, 0); 536 kmem_free(sc->sc_inbuf, DEV_BSIZE); 537 sc->sc_inbuf = NULL; 538 539 return 0; 540 } 541 542 /* 543 * Play tone specified by tp. 544 * tp->frequency is the frequency (0 means a rest). 545 * tp->duration is the length in tick (returns immediately if 0). 546 */ 547 static void 548 playonetone(struct spkr_softc *sc, tone_t *tp) 549 { 550 if (tp->duration <= 0) 551 return; 552 553 if (tp->frequency == 0) 554 rest(sc, tp->duration); 555 else 556 (*sc->sc_tone)(sc->sc_dev, tp->frequency, tp->duration); 557 } 558 559 int 560 spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 561 { 562 struct spkr_softc *sc = spkrenter(minor(dev)); 563 tone_t *tp; 564 tone_t ttp; 565 int error; 566 567 #ifdef SPKRDEBUG 568 device_printf(sc->sc_dev, "%s: entering with cmd = %lx\n", 569 __func__, cmd); 570 #endif /* SPKRDEBUG */ 571 if (sc == NULL) 572 return ENXIO; 573 if (sc->sc_inbuf == NULL) 574 return EINVAL; 575 576 switch (cmd) { 577 case SPKRTONE: 578 playonetone(sc, data); 579 return 0; 580 case SPKRTUNE: 581 for (tp = *(void **)data;; tp++) { 582 error = copyin(tp, &ttp, sizeof(tone_t)); 583 if (error) 584 return(error); 585 if (ttp.duration == 0) 586 break; 587 playonetone(sc, &ttp); 588 } 589 return 0; 590 case SPKRGETVOL: 591 if (data != NULL) 592 *(u_int *)data = sc->sc_vol; 593 return 0; 594 case SPKRSETVOL: 595 if (data != NULL && *(u_int *)data <= 100) 596 sc->sc_vol = *(u_int *)data; 597 return 0; 598 default: 599 return ENOTTY; 600 } 601 } 602 603 #ifdef _MODULE 604 #include "ioconf.c" 605 #endif 606 607 MODULE(MODULE_CLASS_DRIVER, spkr, "audio" /* and/or pcppi */ ); 608 609 int 610 spkr_modcmd(modcmd_t cmd, void *arg) 611 { 612 int error = 0; 613 #ifdef _MODULE 614 devmajor_t bmajor, cmajor; 615 #endif 616 617 switch(cmd) { 618 case MODULE_CMD_INIT: 619 #ifdef _MODULE 620 bmajor = cmajor = -1; 621 error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor, 622 &spkr_cdevsw, &cmajor); 623 if (error) 624 break; 625 626 error = config_init_component(cfdriver_ioconf_spkr, 627 cfattach_ioconf_spkr, cfdata_ioconf_spkr); 628 if (error) { 629 devsw_detach(NULL, &spkr_cdevsw); 630 } 631 #endif 632 break; 633 634 case MODULE_CMD_FINI: 635 #ifdef _MODULE 636 error = config_fini_component(cfdriver_ioconf_spkr, 637 cfattach_ioconf_spkr, cfdata_ioconf_spkr); 638 if (error == 0) 639 devsw_detach(NULL, &spkr_cdevsw); 640 #endif 641 break; 642 643 default: 644 error = ENOTTY; 645 break; 646 } 647 648 return error; 649 } 650