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