1 /* $NetBSD: spkr.c,v 1.37 2016/07/14 10:19:06 msaitoh Exp $ */
2
3 /*
4 * Copyright (c) 1990 Eric S. Raymond (esr@snark.thyrsus.com)
5 * Copyright (c) 1990 Andrew A. Chernov (ache@astral.msk.su)
6 * Copyright (c) 1990 Lennart Augustsson (lennart@augustsson.net)
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed by Eric S. Raymond
20 * 4. The name of the author may not be used to endorse or promote products
21 * derived from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 /*
37 * spkr.c -- device driver for console speaker on 80386
38 *
39 * v1.1 by Eric S. Raymond (esr@snark.thyrsus.com) Feb 1990
40 * modified for 386bsd by Andrew A. Chernov <ache@astral.msk.su>
41 * 386bsd only clean version, all SYSV stuff removed
42 * use hz value from param.c
43 */
44
45 #include <sys/cdefs.h>
46 __KERNEL_RCSID(0, "$NetBSD: spkr.c,v 1.37 2016/07/14 10:19:06 msaitoh Exp $");
47
48 #include <sys/param.h>
49 #include <sys/systm.h>
50 #include <sys/kernel.h>
51 #include <sys/errno.h>
52 #include <sys/device.h>
53 #include <sys/malloc.h>
54 #include <sys/module.h>
55 #include <sys/uio.h>
56 #include <sys/proc.h>
57 #include <sys/ioctl.h>
58 #include <sys/conf.h>
59
60 #include <sys/bus.h>
61
62 #include <dev/isa/pcppivar.h>
63
64 #include <dev/isa/spkrio.h>
65
66 int spkrprobe(device_t, cfdata_t, void *);
67 void spkrattach(device_t, device_t, void *);
68 int spkrdetach(device_t, int);
69
70 #include "ioconf.h"
71
72 MODULE(MODULE_CLASS_DRIVER, spkr, NULL /* "pcppi" */);
73
74 #ifdef _MODULE
75 #include "ioconf.c"
76 #endif
77
78
79 CFATTACH_DECL_NEW(spkr, 0,
80 spkrprobe, spkrattach, spkrdetach, NULL);
81
82 dev_type_open(spkropen);
83 dev_type_close(spkrclose);
84 dev_type_write(spkrwrite);
85 dev_type_ioctl(spkrioctl);
86
87 const struct cdevsw spkr_cdevsw = {
88 .d_open = spkropen,
89 .d_close = spkrclose,
90 .d_read = noread,
91 .d_write = spkrwrite,
92 .d_ioctl = spkrioctl,
93 .d_stop = nostop,
94 .d_tty = notty,
95 .d_poll = nopoll,
96 .d_mmap = nommap,
97 .d_kqfilter = nokqfilter,
98 .d_discard = nodiscard,
99 .d_flag = D_OTHER
100 };
101
102 static pcppi_tag_t ppicookie;
103
104 #define SPKRPRI (PZERO - 1)
105
106 static void tone(u_int, u_int);
107 static void rest(int);
108 static void playinit(void);
109 static void playtone(int, int, int);
110 static void playstring(char *, int);
111
112 static void
tone(u_int xhz,u_int ticks)113 tone(u_int xhz, u_int ticks)
114 /* emit tone of frequency hz for given number of ticks */
115 {
116 pcppi_bell(ppicookie, xhz, ticks, PCPPI_BELL_SLEEP);
117 }
118
119 static void
rest(int ticks)120 rest(int ticks)
121 /* rest for given number of ticks */
122 {
123 /*
124 * Set timeout to endrest function, then give up the timeslice.
125 * This is so other processes can execute while the rest is being
126 * waited out.
127 */
128 #ifdef SPKRDEBUG
129 printf("rest: %d\n", ticks);
130 #endif /* SPKRDEBUG */
131 if (ticks > 0)
132 tsleep(rest, SPKRPRI | PCATCH, "rest", ticks);
133 }
134
135 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
136 *
137 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
138 * M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
139 * Requires tone(), rest(), and endtone(). String play is not interruptible
140 * except possibly at physical block boundaries.
141 */
142
143 #define dtoi(c) ((c) - '0')
144
145 static int octave; /* currently selected octave */
146 static int whole; /* whole-note time at current tempo, in ticks */
147 static int value; /* whole divisor for note time, quarter note = 1 */
148 static int fill; /* controls spacing of notes */
149 static bool octtrack; /* octave-tracking on? */
150 static bool octprefix; /* override current octave-tracking state? */
151
152 /*
153 * Magic number avoidance...
154 */
155 #define SECS_PER_MIN 60 /* seconds per minute */
156 #define WHOLE_NOTE 4 /* quarter notes per whole note */
157 #define MIN_VALUE 64 /* the most we can divide a note by */
158 #define DFLT_VALUE 4 /* default value (quarter-note) */
159 #define FILLTIME 8 /* for articulation, break note in parts */
160 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */
161 #define NORMAL 7 /* 7/8ths of note interval is filled */
162 #define LEGATO 8 /* all of note interval is filled */
163 #define DFLT_OCTAVE 4 /* default octave */
164 #define MIN_TEMPO 32 /* minimum tempo */
165 #define DFLT_TEMPO 120 /* default tempo */
166 #define MAX_TEMPO 255 /* max tempo */
167 #define NUM_MULT 3 /* numerator of dot multiplier */
168 #define DENOM_MULT 2 /* denominator of dot multiplier */
169
170 /* letter to half-tone: A B C D E F G */
171 static const int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
172
173 /*
174 * This is the American Standard A440 Equal-Tempered scale with frequencies
175 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
176 * our octave 0 is standard octave 2.
177 */
178 #define OCTAVE_NOTES 12 /* semitones per octave */
179 static const int pitchtab[] =
180 {
181 /* C C# D D# E F F# G G# A A# B*/
182 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123,
183 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
184 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
185 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
186 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
187 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
188 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
189 };
190 #define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES)
191
192 static void
playinit(void)193 playinit(void)
194 {
195 octave = DFLT_OCTAVE;
196 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
197 fill = NORMAL;
198 value = DFLT_VALUE;
199 octtrack = false;
200 octprefix = true; /* act as though there was an initial O(n) */
201 }
202
203 static void
playtone(int pitch,int val,int sustain)204 playtone(int pitch, int val, int sustain)
205 /* play tone of proper duration for current rhythm signature */
206 {
207 int sound, silence, snum = 1, sdenom = 1;
208
209 /* this weirdness avoids floating-point arithmetic */
210 for (; sustain; sustain--)
211 {
212 snum *= NUM_MULT;
213 sdenom *= DENOM_MULT;
214 }
215
216 if (pitch == -1)
217 rest(whole * snum / (val * sdenom));
218 else
219 {
220 sound = (whole * snum) / (val * sdenom)
221 - (whole * (FILLTIME - fill)) / (val * FILLTIME);
222 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * val * sdenom);
223
224 #ifdef SPKRDEBUG
225 printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
226 pitch, sound, silence);
227 #endif /* SPKRDEBUG */
228
229 tone(pitchtab[pitch], sound);
230 if (fill != LEGATO)
231 rest(silence);
232 }
233 }
234
235 static void
playstring(char * cp,int slen)236 playstring(char *cp, int slen)
237 /* interpret and play an item from a notation string */
238 {
239 int pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
240
241 #define GETNUM(cp, v) for(v=0; slen > 0 && isdigit(cp[1]); ) \
242 {v = v * 10 + (*++cp - '0'); slen--;}
243 for (; slen--; cp++)
244 {
245 int sustain, timeval, tempo;
246 char c = toupper(*cp);
247
248 #ifdef SPKRDEBUG
249 printf("playstring: %c (%x)\n", c, c);
250 #endif /* SPKRDEBUG */
251
252 switch (c)
253 {
254 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
255
256 /* compute pitch */
257 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
258
259 /* this may be followed by an accidental sign */
260 if (slen > 0 && (cp[1] == '#' || cp[1] == '+'))
261 {
262 ++pitch;
263 ++cp;
264 slen--;
265 }
266 else if (slen > 0 && cp[1] == '-')
267 {
268 --pitch;
269 ++cp;
270 slen--;
271 }
272
273 /*
274 * If octave-tracking mode is on, and there has been no octave-
275 * setting prefix, find the version of the current letter note
276 * closest to the last regardless of octave.
277 */
278 if (octtrack && !octprefix)
279 {
280 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
281 {
282 if (octave < NOCTAVES - 1) {
283 ++octave;
284 pitch += OCTAVE_NOTES;
285 }
286 }
287
288 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
289 {
290 if (octave > 0) {
291 --octave;
292 pitch -= OCTAVE_NOTES;
293 }
294 }
295 }
296 octprefix = false;
297 lastpitch = pitch;
298
299 /* ...which may in turn be followed by an override time value */
300 GETNUM(cp, timeval);
301 if (timeval <= 0 || timeval > MIN_VALUE)
302 timeval = value;
303
304 /* ...and/or sustain dots */
305 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
306 {
307 slen--;
308 sustain++;
309 }
310
311 /* time to emit the actual tone */
312 playtone(pitch, timeval, sustain);
313 break;
314
315 case 'O':
316 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
317 {
318 octprefix = octtrack = false;
319 ++cp;
320 slen--;
321 }
322 else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
323 {
324 octtrack = true;
325 ++cp;
326 slen--;
327 }
328 else
329 {
330 GETNUM(cp, octave);
331 if (octave >= NOCTAVES)
332 octave = DFLT_OCTAVE;
333 octprefix = true;
334 }
335 break;
336
337 case '>':
338 if (octave < NOCTAVES - 1)
339 octave++;
340 octprefix = true;
341 break;
342
343 case '<':
344 if (octave > 0)
345 octave--;
346 octprefix = true;
347 break;
348
349 case 'N':
350 GETNUM(cp, pitch);
351 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
352 {
353 slen--;
354 sustain++;
355 }
356 playtone(pitch - 1, value, sustain);
357 break;
358
359 case 'L':
360 GETNUM(cp, value);
361 if (value <= 0 || value > MIN_VALUE)
362 value = DFLT_VALUE;
363 break;
364
365 case 'P':
366 case '~':
367 /* this may be followed by an override time value */
368 GETNUM(cp, timeval);
369 if (timeval <= 0 || timeval > MIN_VALUE)
370 timeval = value;
371 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
372 {
373 slen--;
374 sustain++;
375 }
376 playtone(-1, timeval, sustain);
377 break;
378
379 case 'T':
380 GETNUM(cp, tempo);
381 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
382 tempo = DFLT_TEMPO;
383 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
384 break;
385
386 case 'M':
387 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
388 {
389 fill = NORMAL;
390 ++cp;
391 slen--;
392 }
393 else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
394 {
395 fill = LEGATO;
396 ++cp;
397 slen--;
398 }
399 else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's'))
400 {
401 fill = STACCATO;
402 ++cp;
403 slen--;
404 }
405 break;
406 }
407 }
408 }
409
410 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
411 *
412 * This section implements driver hooks to run playstring() and the tone(),
413 * endtone(), and rest() functions defined above.
414 */
415
416 static int spkr_active; /* exclusion flag */
417 static void *spkr_inbuf;
418
419 static int spkr_attached = 0;
420
421 int
spkrprobe(device_t parent,cfdata_t match,void * aux)422 spkrprobe(device_t parent, cfdata_t match, void *aux)
423 {
424 return (!spkr_attached);
425 }
426
427 void
spkrattach(device_t parent,device_t self,void * aux)428 spkrattach(device_t parent, device_t self, void *aux)
429 {
430 aprint_naive("\n");
431 aprint_normal("\n");
432 ppicookie = ((struct pcppi_attach_args *)aux)->pa_cookie;
433 spkr_attached = 1;
434 if (!pmf_device_register(self, NULL, NULL))
435 aprint_error_dev(self, "couldn't establish power handler\n");
436 }
437
438 int
spkrdetach(device_t self,int flags)439 spkrdetach(device_t self, int flags)
440 {
441
442 pmf_device_deregister(self);
443 spkr_attached = 0;
444 ppicookie = NULL;
445
446 return 0;
447 }
448
449 int
spkropen(dev_t dev,int flags,int mode,struct lwp * l)450 spkropen(dev_t dev, int flags, int mode, struct lwp *l)
451 {
452 #ifdef SPKRDEBUG
453 printf("spkropen: entering with dev = %"PRIx64"\n", dev);
454 #endif /* SPKRDEBUG */
455
456 if (minor(dev) != 0 || !spkr_attached)
457 return(ENXIO);
458 else if (spkr_active)
459 return(EBUSY);
460 else
461 {
462 playinit();
463 spkr_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
464 spkr_active = 1;
465 }
466 return(0);
467 }
468
469 int
spkrwrite(dev_t dev,struct uio * uio,int flags)470 spkrwrite(dev_t dev, struct uio *uio, int flags)
471 {
472 int n;
473 int error;
474 #ifdef SPKRDEBUG
475 printf("spkrwrite: entering with dev = %"PRIx64", count = %zu\n",
476 dev, uio->uio_resid);
477 #endif /* SPKRDEBUG */
478
479 if (minor(dev) != 0)
480 return(ENXIO);
481 else
482 {
483 n = min(DEV_BSIZE, uio->uio_resid);
484 error = uiomove(spkr_inbuf, n, uio);
485 if (!error)
486 playstring((char *)spkr_inbuf, n);
487 return(error);
488 }
489 }
490
491 int
spkrclose(dev_t dev,int flags,int mode,struct lwp * l)492 spkrclose(dev_t dev, int flags, int mode, struct lwp *l)
493 {
494 #ifdef SPKRDEBUG
495 printf("spkrclose: entering with dev = %"PRIx64"\n", dev);
496 #endif /* SPKRDEBUG */
497
498 if (minor(dev) != 0)
499 return(ENXIO);
500 else
501 {
502 tone(0, 0);
503 free(spkr_inbuf, M_DEVBUF);
504 spkr_active = 0;
505 }
506 return(0);
507 }
508
509 int
spkrioctl(dev_t dev,u_long cmd,void * data,int flag,struct lwp * l)510 spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
511 {
512 #ifdef SPKRDEBUG
513 printf("spkrioctl: entering with dev = %"PRIx64", cmd = %lx\n", dev, cmd);
514 #endif /* SPKRDEBUG */
515
516 if (minor(dev) != 0)
517 return(ENXIO);
518 else if (cmd == SPKRTONE)
519 {
520 tone_t *tp = (tone_t *)data;
521
522 if (tp->frequency == 0)
523 rest(tp->duration);
524 else
525 tone(tp->frequency, tp->duration);
526 }
527 else if (cmd == SPKRTUNE)
528 {
529 tone_t *tp = (tone_t *)(*(void **)data);
530 tone_t ttp;
531 int error;
532
533 for (; ; tp++) {
534 error = copyin(tp, &ttp, sizeof(tone_t));
535 if (error)
536 return(error);
537 if (ttp.duration == 0)
538 break;
539 if (ttp.frequency == 0)
540 rest(ttp.duration);
541 else
542 tone(ttp.frequency, ttp.duration);
543 }
544 }
545 else
546 return(EINVAL);
547 return(0);
548 }
549
550 static int
spkr_modcmd(modcmd_t cmd,void * arg)551 spkr_modcmd(modcmd_t cmd, void *arg)
552 {
553 #ifdef _MODULE
554 devmajor_t bmajor, cmajor;
555 #endif
556 int error = 0;
557
558 #ifdef _MODULE
559 switch(cmd) {
560 case MODULE_CMD_INIT:
561 bmajor = cmajor = -1;
562 error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
563 &spkr_cdevsw, &cmajor);
564 if (error)
565 break;
566
567 error = config_init_component(cfdriver_ioconf_spkr,
568 cfattach_ioconf_spkr, cfdata_ioconf_spkr);
569 if (error) {
570 devsw_detach(NULL, &spkr_cdevsw);
571 }
572 break;
573
574 case MODULE_CMD_FINI:
575 if (spkr_active)
576 return EBUSY;
577 error = config_fini_component(cfdriver_ioconf_spkr,
578 cfattach_ioconf_spkr, cfdata_ioconf_spkr);
579 devsw_detach(NULL, &spkr_cdevsw);
580 break;
581 default:
582 error = ENOTTY;
583 break;
584 }
585 #endif
586
587 return error;
588 }
589 /* spkr.c ends here */
590