xref: /freebsd/sys/dev/speaker/spkr.c (revision f31e6b198faa98c4f8a7b681b4e01218f8468411)
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  * modified for PC98 by Kakefuda
7  */
8 
9 #include <sys/param.h>
10 #include <sys/systm.h>
11 #include <sys/kernel.h>
12 #include <sys/module.h>
13 #include <sys/sx.h>
14 #include <sys/uio.h>
15 #include <sys/conf.h>
16 #include <sys/ctype.h>
17 #include <sys/malloc.h>
18 #include <machine/clock.h>
19 #include <dev/speaker/speaker.h>
20 
21 static	d_open_t	spkropen;
22 static	d_close_t	spkrclose;
23 static	d_write_t	spkrwrite;
24 static	d_ioctl_t	spkrioctl;
25 
26 static struct cdevsw spkr_cdevsw = {
27 	.d_version =	D_VERSION,
28 	.d_open =	spkropen,
29 	.d_close =	spkrclose,
30 	.d_write =	spkrwrite,
31 	.d_ioctl =	spkrioctl,
32 	.d_name =	"spkr",
33 };
34 
35 static MALLOC_DEFINE(M_SPKR, "spkr", "Speaker buffer");
36 
37 /*
38  **************** MACHINE DEPENDENT PART STARTS HERE *************************
39  * This section defines a function tone() which causes a tone of given
40  * frequency and duration from the ISA console speaker.
41  * Another function endtone() is defined to force sound off, and there is
42  * also a rest() entry point to do pauses.
43  *
44  * Audible sound is generated using the Programmable Interval Timer (PIT) and
45  * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The
46  * PPI controls whether sound is passed through at all; the PIT's channel 2 is
47  * used to generate clicks (a square wave) of whatever frequency is desired.
48  */
49 
50 #define SPKRPRI PSOCK
51 static char endtone, endrest;
52 
53 struct spkr_state;
54 static void tone(unsigned int thz, unsigned int centisecs);
55 static void rest(int centisecs);
56 static void playinit(struct spkr_state *state);
57 static void playtone(struct spkr_state *state, int pitch, int value, int sustain);
58 static void playstring(struct spkr_state *state, char *cp, size_t slen);
59 
60 /*
61  * Emit tone of frequency thz for given number of centisecs
62  */
63 static void
tone(unsigned int thz,unsigned int centisecs)64 tone(unsigned int thz, unsigned int centisecs)
65 {
66 	int timo;
67 
68 	if (thz <= 0)
69 		return;
70 
71 #ifdef DEBUG
72 	(void) printf("tone: thz=%d centisecs=%d\n", thz, centisecs);
73 #endif /* DEBUG */
74 
75 	/*
76 	 * Acquire the i8254 clock, configure it to drive the speaker
77 	 * signal, and turn on the speaker.
78 	 */
79 	if (timer_spkr_acquire()) {
80 		return;
81 	}
82 	/* Configure the speaker with the tone frequency. */
83 	timer_spkr_setfreq(thz);
84 
85 	/*
86 	 * Make the current thread sleep while the tone is being
87 	 * emitted.
88 	 */
89 	timo = centisecs * hz / 100;
90 	if (timo > 0)
91 		tsleep(&endtone, SPKRPRI | PCATCH, "spkrtn", timo);
92 
93 	/*
94 	 * Turn off the speaker and release the i8254 clock.
95 	 */
96 	timer_spkr_release();
97 }
98 
99 /*
100  * Rest for given number of centisecs
101  */
102 static void
rest(int centisecs)103 rest(int centisecs)
104 {
105 	int timo;
106 
107 	/*
108 	 * Set timeout to endrest function, then give up the timeslice.
109 	 * This is so other processes can execute while the rest is being
110 	 * waited out.
111 	 */
112 #ifdef DEBUG
113 	(void) printf("rest: %d\n", centisecs);
114 #endif /* DEBUG */
115 	timo = centisecs * hz / 100;
116 	if (timo > 0)
117 		tsleep(&endrest, SPKRPRI | PCATCH, "spkrrs", timo);
118 }
119 
120 /*
121  **************** PLAY STRING INTERPRETER BEGINS HERE **********************
122  * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
123  * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave-
124  * tracking facility are added.
125  * Requires tone(), rest(), and endtone(). String play is not interruptible
126  * except possibly at physical block boundaries.
127  */
128 
129 #define dtoi(c)		((c) - '0')
130 
131 struct spkr_state {
132 	char *inbuf;    /* incoming melody buffer */
133 
134 	int octave;	/* currently selected octave */
135 	int whole;	/* whole-note time at current tempo, in ticks */
136 	int value;	/* whole divisor for note time, quarter note = 1 */
137 	int fill;	/* controls spacing of notes */
138 	bool octtrack;	/* octave-tracking on? */
139 	bool octprefix;	/* override current octave-tracking state? */
140 };
141 
142 /*
143  * Magic number avoidance...
144  */
145 #define SECS_PER_MIN	60	/* seconds per minute */
146 #define WHOLE_NOTE	4	/* quarter notes per whole note */
147 #define MIN_VALUE	64	/* the most we can divide a note by */
148 #define DFLT_VALUE	4	/* default value (quarter-note) */
149 #define FILLTIME	8	/* for articulation, break note in parts */
150 #define STACCATO	6	/* 6/8 = 3/4 of note is filled */
151 #define NORMAL		7	/* 7/8ths of note interval is filled */
152 #define LEGATO		8	/* all of note interval is filled */
153 #define DFLT_OCTAVE	4	/* default octave */
154 #define MIN_TEMPO	32	/* minimum tempo */
155 #define DFLT_TEMPO	120	/* default tempo */
156 #define MAX_TEMPO	255	/* max tempo */
157 #define NUM_MULT	3	/* numerator of dot multiplier */
158 #define DENOM_MULT	2	/* denominator of dot multiplier */
159 
160 /* letter to half-tone:  A   B  C  D  E  F  G */
161 static const int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
162 
163 /*
164  * This is the American Standard A440 Equal-Tempered scale with frequencies
165  * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
166  * our octave 0 is standard octave 2.
167  */
168 #define OCTAVE_NOTES	12	/* semitones per octave */
169 static const int pitchtab[] =
170 {
171 /*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
172 /* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
173 /* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
174 /* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
175 /* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
176 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
177 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
178 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
179 };
180 
181 static void
playinit(struct spkr_state * state)182 playinit(struct spkr_state *state)
183 {
184     state->octave = DFLT_OCTAVE;
185     state->whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
186     state->fill = NORMAL;
187     state->value = DFLT_VALUE;
188     state->octtrack = false;
189     state->octprefix = true;	/* act as though there was an initial O(n) */
190 }
191 
192 /*
193  * Play tone of proper duration for current rhythm signature
194  */
195 static void
playtone(struct spkr_state * state,int pitch,int value,int sustain)196 playtone(struct spkr_state *state, int pitch, int value, int sustain)
197 {
198 	int sound, silence, snum = 1, sdenom = 1;
199 	int whole = state->whole;
200 	int fill = state->fill;
201 
202 	/* this weirdness avoids floating-point arithmetic */
203 	for (; sustain; sustain--) {
204 		/* See the BUGS section in the man page for discussion */
205 		snum *= NUM_MULT;
206 		sdenom *= DENOM_MULT;
207 	}
208 
209 	if (value == 0 || sdenom == 0)
210 		return;
211 
212 	if (pitch == -1)
213 		rest(whole * snum / (value * sdenom));
214 	else {
215 		sound = (whole * snum) / (value * sdenom)
216 			- (whole * (FILLTIME - fill)) / (value * FILLTIME);
217 		silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom);
218 
219 #ifdef DEBUG
220 		(void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
221 			pitch, sound, silence);
222 #endif /* DEBUG */
223 
224 		tone(pitchtab[pitch], sound);
225 		if (fill != LEGATO)
226 			rest(silence);
227 	}
228 }
229 
230 /*
231  * Interpret and play an item from a notation string
232  */
233 static void
playstring(struct spkr_state * state,char * cp,size_t slen)234 playstring(struct spkr_state *state, char *cp, size_t slen)
235 {
236 	int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
237 
238 #define GETNUM(cp, v)	for(v=0; isdigit(cp[1]) && slen > 0; ) \
239 				{v = v * 10 + (*++cp - '0'); slen--;}
240 	for (; slen--; cp++) {
241 		int sustain, timeval, tempo;
242 		char c = toupper(*cp);
243 
244 #ifdef DEBUG
245 		(void) printf("playstring: %c (%x)\n", c, c);
246 #endif /* DEBUG */
247 
248 		switch (c) {
249 		case 'A':
250 		case 'B':
251 		case 'C':
252 		case 'D':
253 		case 'E':
254 		case 'F':
255 		case 'G':
256 			/* compute pitch */
257 			pitch = notetab[c - 'A'] + state->octave * OCTAVE_NOTES;
258 
259 			/* this may be followed by an accidental sign */
260 			if (cp[1] == '#' || cp[1] == '+') {
261 				++pitch;
262 				++cp;
263 				slen--;
264 			} else if (cp[1] == '-') {
265 				--pitch;
266 				++cp;
267 				slen--;
268 			}
269 
270 			/*
271 			 * If octave-tracking mode is on, and there has been no octave-
272 			 * setting prefix, find the version of the current letter note
273 			 * closest to the last regardless of octave.
274 			 */
275 			if (state->octtrack && !state->octprefix) {
276 				if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES -
277 					lastpitch)) {
278 					++state->octave;
279 					pitch += OCTAVE_NOTES;
280 				}
281 
282 				if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES) -
283 					lastpitch)) {
284 					--state->octave;
285 					pitch -= OCTAVE_NOTES;
286 				}
287 			}
288 			state->octprefix = false;
289 			lastpitch = pitch;
290 
291 			/* ...which may in turn be followed by an override time value */
292 			GETNUM(cp, timeval);
293 			if (timeval <= 0 || timeval > MIN_VALUE)
294 				timeval = state->value;
295 
296 			/* ...and/or sustain dots */
297 			for (sustain = 0; cp[1] == '.'; cp++) {
298 				slen--;
299 				sustain++;
300 			}
301 
302 			/* ...and/or a slur mark */
303 			oldfill = state->fill;
304 			if (cp[1] == '_') {
305 				state->fill = LEGATO;
306 				++cp;
307 				slen--;
308 			}
309 
310 			/* time to emit the actual tone */
311 			playtone(state, pitch, timeval, sustain);
312 
313 			state->fill = oldfill;
314 			break;
315 		case 'O':
316 			if (cp[1] == 'N' || cp[1] == 'n') {
317 				state->octprefix = state->octtrack = false;
318 				++cp;
319 				slen--;
320 			} else if (cp[1] == 'L' || cp[1] == 'l') {
321 				state->octtrack = true;
322 				++cp;
323 				slen--;
324 			} else {
325 				GETNUM(cp, state->octave);
326 				if (state->octave >= nitems(pitchtab) / OCTAVE_NOTES)
327 					state->octave = DFLT_OCTAVE;
328 				state->octprefix = true;
329 			}
330 			break;
331 		case '>':
332 			if (state->octave < nitems(pitchtab) / OCTAVE_NOTES - 1)
333 				state->octave++;
334 			state->octprefix = true;
335 			break;
336 		case '<':
337 			if (state->octave > 0)
338 				state->octave--;
339 			state->octprefix = true;
340 			break;
341 		case 'N':
342 			GETNUM(cp, pitch);
343 			for (sustain = 0; cp[1] == '.'; cp++) {
344 				slen--;
345 				sustain++;
346 			}
347 			oldfill = state->fill;
348 			if (cp[1] == '_') {
349 				state->fill = LEGATO;
350 				++cp;
351 				slen--;
352 			}
353 			playtone(state, pitch - 1, state->value, sustain);
354 			state->fill = oldfill;
355 			break;
356 		case 'L':
357 			GETNUM(cp, state->value);
358 			if (state->value <= 0 || state->value > MIN_VALUE)
359 				state->value = DFLT_VALUE;
360 			break;
361 		case 'P':
362 		case '~':
363 			/* this may be followed by an override time value */
364 			GETNUM(cp, timeval);
365 			if (timeval <= 0 || timeval > MIN_VALUE)
366 				timeval = state->value;
367 			for (sustain = 0; cp[1] == '.'; cp++) {
368 				slen--;
369 				sustain++;
370 			}
371 			playtone(state, -1, timeval, sustain);
372 			break;
373 		case 'T':
374 			GETNUM(cp, tempo);
375 			if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
376 				tempo = DFLT_TEMPO;
377 			state->whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / tempo;
378 			break;
379 		case 'M':
380 			if (cp[1] == 'N' || cp[1] == 'n') {
381 				state->fill = NORMAL;
382 				++cp;
383 				slen--;
384 			} else if (cp[1] == 'L' || cp[1] == 'l') {
385 				state->fill = LEGATO;
386 				++cp;
387 				slen--;
388 			} else if (cp[1] == 'S' || cp[1] == 's') {
389 				state->fill = STACCATO;
390 				++cp;
391 				slen--;
392 			}
393 			break;
394 		}
395 	}
396 }
397 
398 /*
399  * ****************** UNIX DRIVER HOOKS BEGIN HERE **************************
400  * This section implements driver hooks to run playstring() and the tone(),
401  * endtone(), and rest() functions defined above.
402  */
403 
404 /*
405  * we use a lock to prevent interleaving of melodies written
406  * concurrently from two different threads.
407  */
408 static struct sx spkr_op_locked;
409 
410 static void
spkr_dtor(void * data)411 spkr_dtor(void *data)
412 {
413 	struct spkr_state *state = data;
414 
415 	free(state->inbuf, M_SPKR);
416 	free(state, M_SPKR);
417 }
418 
419 static int
spkropen(struct cdev * dev,int flags,int fmt,struct thread * td)420 spkropen(struct cdev *dev, int flags, int fmt, struct thread *td)
421 {
422 	struct spkr_state *state;
423 	int error;
424 #ifdef DEBUG
425 	(void) printf("spkropen: entering with dev = %s\n", devtoname(dev));
426 #endif /* DEBUG */
427 
428 	/* allocate per-fd state */
429 	state = malloc(sizeof(*state), M_SPKR, M_WAITOK | M_ZERO);
430 	state->inbuf = malloc(DEV_BSIZE, M_SPKR, M_WAITOK);
431 
432 	/* initialize playstring state */
433 	playinit(state);
434 
435 	/* store in the current fd private data */
436 	error = devfs_set_cdevpriv(state, spkr_dtor);
437 	if (error) {
438 		free(state->inbuf, M_SPKR);
439 		free(state, M_SPKR);
440 		return (error);
441 	}
442 
443 	return (0);
444 }
445 
446 static int
spkrwrite(struct cdev * dev,struct uio * uio,int ioflag)447 spkrwrite(struct cdev *dev, struct uio *uio, int ioflag)
448 {
449 	struct spkr_state *state;
450 	int error;
451 	unsigned n;
452 
453 #ifdef DEBUG
454 	printf("spkrwrite: entering with dev = %s, count = %zd\n",
455 		devtoname(dev), uio->uio_resid);
456 #endif /* DEBUG */
457 
458 	/* is the melody too long? */
459 	if (uio->uio_resid > (DEV_BSIZE - 1))
460 		return (E2BIG);
461 
462 	/* get this fd's state */
463 	error = devfs_get_cdevpriv((void **)&state);
464 	if (error)
465 		return (error);
466 
467 	/* copy melody from userspace */
468 	n = uio->uio_resid;
469 	error = uiomove(state->inbuf, n, uio);
470 	if (error)
471 		return (error);
472 	state->inbuf[n] = '\0';
473 
474 	/* play the melody. */
475 	sx_xlock(&spkr_op_locked);
476 	playstring(state, state->inbuf, n);
477 	sx_xunlock(&spkr_op_locked);
478 
479 	return (0);
480 }
481 
482 static int
spkrclose(struct cdev * dev,int flags,int fmt,struct thread * td)483 spkrclose(struct cdev *dev, int flags, int fmt, struct thread *td)
484 {
485 #ifdef DEBUG
486 	(void) printf("spkrclose: entering with dev = %s\n", devtoname(dev));
487 #endif /* DEBUG */
488 
489 	wakeup(&endtone);
490 	wakeup(&endrest);
491 	/* devfs_clear_cdevpriv() calls spkr_dtor automatically */
492 	return (0);
493 }
494 
495 static int
spkrioctl(struct cdev * dev,unsigned long cmd,caddr_t cmdarg,int flags,struct thread * td)496 spkrioctl(struct cdev *dev, unsigned long cmd, caddr_t cmdarg, int flags,
497     struct thread *td)
498 {
499 #ifdef DEBUG
500 	(void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n",
501     		devtoname(dev), cmd);
502 #endif /* DEBUG */
503 
504 	if (cmd == SPKRTONE) {
505 		tone_t	*tp = (tone_t *)cmdarg;
506 
507 		sx_xlock(&spkr_op_locked);
508 		if (tp->frequency == 0)
509 			rest(tp->duration);
510 		else
511 			tone(tp->frequency, tp->duration);
512 		sx_xunlock(&spkr_op_locked);
513 		return (0);
514 	} else if (cmd == SPKRTUNE) {
515 		tone_t  *tp = (tone_t *)(*(caddr_t *)cmdarg);
516 		tone_t ttp;
517 		int error;
518 
519 		sx_xlock(&spkr_op_locked);
520 		for (; ; tp++) {
521 			error = copyin(tp, &ttp, sizeof(tone_t));
522 			if (error) {
523 				sx_xunlock(&spkr_op_locked);
524 				return (error);
525 			}
526 
527 			if (ttp.duration == 0)
528 				break;
529 
530 			if (ttp.frequency == 0)
531 				rest(ttp.duration);
532 			else
533 				tone(ttp.frequency, ttp.duration);
534 		}
535 		sx_xunlock(&spkr_op_locked);
536 		return (0);
537 	}
538 	return (EINVAL);
539 }
540 
541 static struct cdev *speaker_dev;
542 
543 /*
544  * Module handling
545  */
546 static int
speaker_modevent(module_t mod,int type,void * data)547 speaker_modevent(module_t mod, int type, void *data)
548 {
549 	int error = 0;
550 
551 	switch(type) {
552 	case MOD_LOAD:
553 		sx_init(&spkr_op_locked, "spkr");
554 		speaker_dev = make_dev(&spkr_cdevsw, 0,
555 		    UID_ROOT, GID_WHEEL, 0600, "speaker");
556 		break;
557 	case MOD_SHUTDOWN:
558 	case MOD_UNLOAD:
559 		destroy_dev(speaker_dev);
560 		sx_destroy(&spkr_op_locked);
561 		break;
562 	default:
563 		error = EOPNOTSUPP;
564 	}
565 	return (error);
566 }
567 
568 DEV_MODULE(speaker, speaker_modevent, NULL);
569