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