1 /* 2 * spkr.c -- device driver for console speaker 3 * 4 * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993 5 * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su> 6 * 7 * $FreeBSD$ 8 */ 9 10 #include "speaker.h" 11 12 #if NSPEAKER > 0 13 14 #include <sys/param.h> 15 #include <sys/systm.h> 16 #include <sys/kernel.h> 17 #include <sys/buf.h> 18 #include <sys/uio.h> 19 #include <sys/conf.h> 20 #include <sys/ctype.h> 21 #include <i386/isa/isa.h> 22 #include <i386/isa/timerreg.h> 23 #include <machine/clock.h> 24 #include <machine/speaker.h> 25 26 static d_open_t spkropen; 27 static d_close_t spkrclose; 28 static d_write_t spkrwrite; 29 static d_ioctl_t spkrioctl; 30 31 #define CDEV_MAJOR 26 32 static struct cdevsw spkr_cdevsw = { 33 /* open */ spkropen, 34 /* close */ spkrclose, 35 /* read */ noread, 36 /* write */ spkrwrite, 37 /* ioctl */ spkrioctl, 38 /* poll */ nopoll, 39 /* mmap */ nommap, 40 /* strategy */ nostrategy, 41 /* name */ "spkr", 42 /* maj */ CDEV_MAJOR, 43 /* dump */ nodump, 44 /* psize */ nopsize, 45 /* flags */ 0, 46 /* bmaj */ -1 47 }; 48 49 /**************** MACHINE DEPENDENT PART STARTS HERE ************************* 50 * 51 * This section defines a function tone() which causes a tone of given 52 * frequency and duration from the ISA console speaker. 53 * Another function endtone() is defined to force sound off, and there is 54 * also a rest() entry point to do pauses. 55 * 56 * Audible sound is generated using the Programmable Interval Timer (PIT) and 57 * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The 58 * PPI controls whether sound is passed through at all; the PIT's channel 2 is 59 * used to generate clicks (a square wave) of whatever frequency is desired. 60 */ 61 62 /* 63 * PPI control values. 64 * XXX should be in a header and used in clock.c. 65 */ 66 #define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */ 67 68 #define SPKRPRI PSOCK 69 static char endtone, endrest; 70 71 static void tone __P((unsigned int thz, unsigned int ticks)); 72 static void rest __P((int ticks)); 73 static void playinit __P((void)); 74 static void playtone __P((int pitch, int value, int sustain)); 75 static int abs __P((int n)); 76 static void playstring __P((char *cp, size_t slen)); 77 78 /* emit tone of frequency thz for given number of ticks */ 79 static void 80 tone(thz, ticks) 81 unsigned int thz, ticks; 82 { 83 unsigned int divisor; 84 int sps; 85 86 if (thz <= 0) 87 return; 88 89 divisor = timer_freq / thz; 90 91 #ifdef DEBUG 92 (void) printf("tone: thz=%d ticks=%d\n", thz, ticks); 93 #endif /* DEBUG */ 94 95 /* set timer to generate clicks at given frequency in Hertz */ 96 sps = splclock(); 97 98 if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) { 99 /* enter list of waiting procs ??? */ 100 splx(sps); 101 return; 102 } 103 splx(sps); 104 disable_intr(); 105 outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */ 106 outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */ 107 enable_intr(); 108 109 /* turn the speaker on */ 110 outb(IO_PPI, inb(IO_PPI) | PPI_SPKR); 111 112 /* 113 * Set timeout to endtone function, then give up the timeslice. 114 * This is so other processes can execute while the tone is being 115 * emitted. 116 */ 117 if (ticks > 0) 118 tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks); 119 outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR); 120 sps = splclock(); 121 release_timer2(); 122 splx(sps); 123 } 124 125 /* rest for given number of ticks */ 126 static void 127 rest(ticks) 128 int ticks; 129 { 130 /* 131 * Set timeout to endrest function, then give up the timeslice. 132 * This is so other processes can execute while the rest is being 133 * waited out. 134 */ 135 #ifdef DEBUG 136 (void) printf("rest: %d\n", ticks); 137 #endif /* DEBUG */ 138 if (ticks > 0) 139 tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks); 140 } 141 142 /**************** PLAY STRING INTERPRETER BEGINS HERE ********************** 143 * 144 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 145 * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- 146 * tracking facility are added. 147 * Requires tone(), rest(), and endtone(). String play is not interruptible 148 * except possibly at physical block boundaries. 149 */ 150 151 typedef int bool; 152 #define TRUE 1 153 #define FALSE 0 154 155 #define dtoi(c) ((c) - '0') 156 157 static int octave; /* currently selected octave */ 158 static int whole; /* whole-note time at current tempo, in ticks */ 159 static int value; /* whole divisor for note time, quarter note = 1 */ 160 static int fill; /* controls spacing of notes */ 161 static bool octtrack; /* octave-tracking on? */ 162 static bool octprefix; /* override current octave-tracking state? */ 163 164 /* 165 * Magic number avoidance... 166 */ 167 #define SECS_PER_MIN 60 /* seconds per minute */ 168 #define WHOLE_NOTE 4 /* quarter notes per whole note */ 169 #define MIN_VALUE 64 /* the most we can divide a note by */ 170 #define DFLT_VALUE 4 /* default value (quarter-note) */ 171 #define FILLTIME 8 /* for articulation, break note in parts */ 172 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */ 173 #define NORMAL 7 /* 7/8ths of note interval is filled */ 174 #define LEGATO 8 /* all of note interval is filled */ 175 #define DFLT_OCTAVE 4 /* default octave */ 176 #define MIN_TEMPO 32 /* minimum tempo */ 177 #define DFLT_TEMPO 120 /* default tempo */ 178 #define MAX_TEMPO 255 /* max tempo */ 179 #define NUM_MULT 3 /* numerator of dot multiplier */ 180 #define DENOM_MULT 2 /* denominator of dot multiplier */ 181 182 /* letter to half-tone: A B C D E F G */ 183 static int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; 184 185 /* 186 * This is the American Standard A440 Equal-Tempered scale with frequencies 187 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... 188 * our octave 0 is standard octave 2. 189 */ 190 #define OCTAVE_NOTES 12 /* semitones per octave */ 191 static int pitchtab[] = 192 { 193 /* C C# D D# E F F# G G# A A# B*/ 194 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, 195 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 196 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 197 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 198 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, 199 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 200 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, 201 }; 202 203 static void 204 playinit() 205 { 206 octave = DFLT_OCTAVE; 207 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 208 fill = NORMAL; 209 value = DFLT_VALUE; 210 octtrack = FALSE; 211 octprefix = TRUE; /* act as though there was an initial O(n) */ 212 } 213 214 /* play tone of proper duration for current rhythm signature */ 215 static void 216 playtone(pitch, value, sustain) 217 int pitch, value, sustain; 218 { 219 register int sound, silence, snum = 1, sdenom = 1; 220 221 /* this weirdness avoids floating-point arithmetic */ 222 for (; sustain; sustain--) 223 { 224 /* See the BUGS section in the man page for discussion */ 225 snum *= NUM_MULT; 226 sdenom *= DENOM_MULT; 227 } 228 229 if (value == 0 || sdenom == 0) 230 return; 231 232 if (pitch == -1) 233 rest(whole * snum / (value * sdenom)); 234 else 235 { 236 sound = (whole * snum) / (value * sdenom) 237 - (whole * (FILLTIME - fill)) / (value * FILLTIME); 238 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); 239 240 #ifdef DEBUG 241 (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", 242 pitch, sound, silence); 243 #endif /* DEBUG */ 244 245 tone(pitchtab[pitch], sound); 246 if (fill != LEGATO) 247 rest(silence); 248 } 249 } 250 251 static int 252 abs(n) 253 int n; 254 { 255 if (n < 0) 256 return(-n); 257 else 258 return(n); 259 } 260 261 /* interpret and play an item from a notation string */ 262 static void 263 playstring(cp, slen) 264 char *cp; 265 size_t slen; 266 { 267 int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 268 269 #define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ 270 {v = v * 10 + (*++cp - '0'); slen--;} 271 for (; slen--; cp++) 272 { 273 int sustain, timeval, tempo; 274 register char c = toupper(*cp); 275 276 #ifdef DEBUG 277 (void) printf("playstring: %c (%x)\n", c, c); 278 #endif /* DEBUG */ 279 280 switch (c) 281 { 282 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': 283 284 /* compute pitch */ 285 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; 286 287 /* this may be followed by an accidental sign */ 288 if (cp[1] == '#' || cp[1] == '+') 289 { 290 ++pitch; 291 ++cp; 292 slen--; 293 } 294 else if (cp[1] == '-') 295 { 296 --pitch; 297 ++cp; 298 slen--; 299 } 300 301 /* 302 * If octave-tracking mode is on, and there has been no octave- 303 * setting prefix, find the version of the current letter note 304 * closest to the last regardless of octave. 305 */ 306 if (octtrack && !octprefix) 307 { 308 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) 309 { 310 ++octave; 311 pitch += OCTAVE_NOTES; 312 } 313 314 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) 315 { 316 --octave; 317 pitch -= OCTAVE_NOTES; 318 } 319 } 320 octprefix = FALSE; 321 lastpitch = pitch; 322 323 /* ...which may in turn be followed by an override time value */ 324 GETNUM(cp, timeval); 325 if (timeval <= 0 || timeval > MIN_VALUE) 326 timeval = value; 327 328 /* ...and/or sustain dots */ 329 for (sustain = 0; cp[1] == '.'; cp++) 330 { 331 slen--; 332 sustain++; 333 } 334 335 /* ...and/or a slur mark */ 336 oldfill = fill; 337 if (cp[1] == '_') 338 { 339 fill = LEGATO; 340 ++cp; 341 slen--; 342 } 343 344 /* time to emit the actual tone */ 345 playtone(pitch, timeval, sustain); 346 347 fill = oldfill; 348 break; 349 350 case 'O': 351 if (cp[1] == 'N' || cp[1] == 'n') 352 { 353 octprefix = octtrack = FALSE; 354 ++cp; 355 slen--; 356 } 357 else if (cp[1] == 'L' || cp[1] == 'l') 358 { 359 octtrack = TRUE; 360 ++cp; 361 slen--; 362 } 363 else 364 { 365 GETNUM(cp, octave); 366 if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES) 367 octave = DFLT_OCTAVE; 368 octprefix = TRUE; 369 } 370 break; 371 372 case '>': 373 if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1) 374 octave++; 375 octprefix = TRUE; 376 break; 377 378 case '<': 379 if (octave > 0) 380 octave--; 381 octprefix = TRUE; 382 break; 383 384 case 'N': 385 GETNUM(cp, pitch); 386 for (sustain = 0; cp[1] == '.'; cp++) 387 { 388 slen--; 389 sustain++; 390 } 391 oldfill = fill; 392 if (cp[1] == '_') 393 { 394 fill = LEGATO; 395 ++cp; 396 slen--; 397 } 398 playtone(pitch - 1, value, sustain); 399 fill = oldfill; 400 break; 401 402 case 'L': 403 GETNUM(cp, value); 404 if (value <= 0 || value > MIN_VALUE) 405 value = DFLT_VALUE; 406 break; 407 408 case 'P': 409 case '~': 410 /* this may be followed by an override time value */ 411 GETNUM(cp, timeval); 412 if (timeval <= 0 || timeval > MIN_VALUE) 413 timeval = value; 414 for (sustain = 0; cp[1] == '.'; cp++) 415 { 416 slen--; 417 sustain++; 418 } 419 playtone(-1, timeval, sustain); 420 break; 421 422 case 'T': 423 GETNUM(cp, tempo); 424 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) 425 tempo = DFLT_TEMPO; 426 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; 427 break; 428 429 case 'M': 430 if (cp[1] == 'N' || cp[1] == 'n') 431 { 432 fill = NORMAL; 433 ++cp; 434 slen--; 435 } 436 else if (cp[1] == 'L' || cp[1] == 'l') 437 { 438 fill = LEGATO; 439 ++cp; 440 slen--; 441 } 442 else if (cp[1] == 'S' || cp[1] == 's') 443 { 444 fill = STACCATO; 445 ++cp; 446 slen--; 447 } 448 break; 449 } 450 } 451 } 452 453 /******************* UNIX DRIVER HOOKS BEGIN HERE ************************** 454 * 455 * This section implements driver hooks to run playstring() and the tone(), 456 * endtone(), and rest() functions defined above. 457 */ 458 459 static int spkr_active = FALSE; /* exclusion flag */ 460 static struct buf *spkr_inbuf; /* incoming buf */ 461 462 int 463 spkropen(dev, flags, fmt, p) 464 dev_t dev; 465 int flags; 466 int fmt; 467 struct proc *p; 468 { 469 #ifdef DEBUG 470 (void) printf("spkropen: entering with dev = %s\n", devtoname(dev)); 471 #endif /* DEBUG */ 472 473 if (minor(dev) != 0) 474 return(ENXIO); 475 else if (spkr_active) 476 return(EBUSY); 477 else 478 { 479 #ifdef DEBUG 480 (void) printf("spkropen: about to perform play initialization\n"); 481 #endif /* DEBUG */ 482 playinit(); 483 spkr_inbuf = geteblk(DEV_BSIZE); 484 spkr_active = TRUE; 485 return(0); 486 } 487 } 488 489 int 490 spkrwrite(dev, uio, ioflag) 491 dev_t dev; 492 struct uio *uio; 493 int ioflag; 494 { 495 #ifdef DEBUG 496 printf("spkrwrite: entering with dev = %s, count = %d\n", 497 devtoname(dev), uio->uio_resid); 498 #endif /* DEBUG */ 499 500 if (minor(dev) != 0) 501 return(ENXIO); 502 else if (uio->uio_resid > (DEV_BSIZE - 1)) /* prevent system crashes */ 503 return(E2BIG); 504 else 505 { 506 unsigned n; 507 char *cp; 508 int error; 509 510 n = uio->uio_resid; 511 cp = spkr_inbuf->b_data; 512 error = uiomove(cp, n, uio); 513 if (!error) { 514 cp[n] = '\0'; 515 playstring(cp, n); 516 } 517 return(error); 518 } 519 } 520 521 int 522 spkrclose(dev, flags, fmt, p) 523 dev_t dev; 524 int flags; 525 int fmt; 526 struct proc *p; 527 { 528 #ifdef DEBUG 529 (void) printf("spkrclose: entering with dev = %s\n", devtoname(dev)); 530 #endif /* DEBUG */ 531 532 if (minor(dev) != 0) 533 return(ENXIO); 534 else 535 { 536 wakeup((caddr_t)&endtone); 537 wakeup((caddr_t)&endrest); 538 brelse(spkr_inbuf); 539 spkr_active = FALSE; 540 return(0); 541 } 542 } 543 544 int 545 spkrioctl(dev, cmd, cmdarg, flags, p) 546 dev_t dev; 547 unsigned long cmd; 548 caddr_t cmdarg; 549 int flags; 550 struct proc *p; 551 { 552 #ifdef DEBUG 553 (void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n", 554 devtoname(dev), cmd); 555 #endif /* DEBUG */ 556 557 if (minor(dev) != 0) 558 return(ENXIO); 559 else if (cmd == SPKRTONE) 560 { 561 tone_t *tp = (tone_t *)cmdarg; 562 563 if (tp->frequency == 0) 564 rest(tp->duration); 565 else 566 tone(tp->frequency, tp->duration); 567 return 0; 568 } 569 else if (cmd == SPKRTUNE) 570 { 571 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); 572 tone_t ttp; 573 int error; 574 575 for (; ; tp++) { 576 error = copyin(tp, &ttp, sizeof(tone_t)); 577 if (error) 578 return(error); 579 if (ttp.duration == 0) 580 break; 581 if (ttp.frequency == 0) 582 rest(ttp.duration); 583 else 584 tone(ttp.frequency, ttp.duration); 585 } 586 return(0); 587 } 588 return(EINVAL); 589 } 590 591 static void 592 spkr_drvinit(void *unused) 593 { 594 make_dev(&spkr_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "speaker"); 595 } 596 597 SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL) 598 599 600 #endif /* NSPEAKER > 0 */ 601 /* spkr.c ends here */ 602