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