spkr.c revision 32726
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.30 1997/12/02 21:06:28 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> 29void *devfs_token; 30#endif 31 32static d_open_t spkropen; 33static d_close_t spkrclose; 34static d_write_t spkrwrite; 35static d_ioctl_t spkrioctl; 36 37#define CDEV_MAJOR 26 38static 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 63static char endtone, endrest; 64 65static void tone __P((unsigned int thz, unsigned int ticks)); 66static void rest __P((int ticks)); 67static void playinit __P((void)); 68static void playtone __P((int pitch, int value, int sustain)); 69static int abs __P((int n)); 70static void playstring __P((char *cp, size_t slen)); 71 72/* emit tone of frequency thz for given number of ticks */ 73static void 74tone(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 */ 120static void 121rest(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 145typedef 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 153static int octave; /* currently selected octave */ 154static int whole; /* whole-note time at current tempo, in ticks */ 155static int value; /* whole divisor for note time, quarter note = 1 */ 156static int fill; /* controls spacing of notes */ 157static bool octtrack; /* octave-tracking on? */ 158static 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 */ 179static 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 */ 187static 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 199static void 200playinit() 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 */ 211static void 212playtone(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 247static int 248abs(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 */ 258static void 259playstring(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 455static int spkr_active = FALSE; /* exclusion flag */ 456static struct buf *spkr_inbuf; /* incoming buf */ 457 458int 459spkropen(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 485int 486spkrwrite(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 517int 518spkrclose(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 540int 541spkrioctl(dev, cmd, cmdarg, flags, p) 542 dev_t dev; 543 int cmd; 544 caddr_t cmdarg; 545 int flags; 546 struct proc *p; 547{ 548#ifdef DEBUG 549 (void) printf("spkrioctl: entering with dev = %x, cmd = %x\n"); 550#endif /* DEBUG */ 551 552 if (minor(dev) != 0) 553 return(ENXIO); 554 else if (cmd == SPKRTONE) 555 { 556 tone_t *tp = (tone_t *)cmdarg; 557 558 if (tp->frequency == 0) 559 rest(tp->duration); 560 else 561 tone(tp->frequency, tp->duration); 562 return 0; 563 } 564 else if (cmd == SPKRTUNE) 565 { 566 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); 567 tone_t ttp; 568 int error; 569 570 for (; ; tp++) { 571 error = copyin(tp, &ttp, sizeof(tone_t)); 572 if (error) 573 return(error); 574 if (ttp.duration == 0) 575 break; 576 if (ttp.frequency == 0) 577 rest(ttp.duration); 578 else 579 tone(ttp.frequency, ttp.duration); 580 } 581 return(0); 582 } 583 return(EINVAL); 584} 585 586 587static spkr_devsw_installed = 0; 588 589static void 590spkr_drvinit(void *unused) 591{ 592 dev_t dev; 593 594 if( ! spkr_devsw_installed ) { 595 dev = makedev(CDEV_MAJOR, 0); 596 cdevsw_add(&dev,&spkr_cdevsw, NULL); 597 spkr_devsw_installed = 1; 598#ifdef DEVFS 599 devfs_token = devfs_add_devswf(&spkr_cdevsw, 0, DV_CHR, 600 UID_ROOT, GID_WHEEL, 0600, 601 "speaker"); 602#endif 603 } 604} 605 606SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL) 607 608 609#endif /* NSPEAKER > 0 */ 610/* spkr.c ends here */ 611