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