Home | History | Annotate | Line # | Download | only in dev
spkr.c revision 1.3
      1 /*	$NetBSD: spkr.c,v 1.3 2016/12/09 05:17:03 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1990 Eric S. Raymond (esr (at) snark.thyrsus.com)
      5  * Copyright (c) 1990 Andrew A. Chernov (ache (at) astral.msk.su)
      6  * Copyright (c) 1990 Lennart Augustsson (lennart (at) 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 (at) snark.thyrsus.com) Feb 1990
     40  *      modified for 386bsd by Andrew A. Chernov <ache (at) 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.3 2016/12/09 05:17:03 christos 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/spkrio.h>
     63 #include <dev/spkrvar.h>
     64 
     65 dev_type_open(spkropen);
     66 dev_type_close(spkrclose);
     67 dev_type_write(spkrwrite);
     68 dev_type_ioctl(spkrioctl);
     69 
     70 const struct cdevsw spkr_cdevsw = {
     71 	.d_open = spkropen,
     72 	.d_close = spkrclose,
     73 	.d_read = noread,
     74 	.d_write = spkrwrite,
     75 	.d_ioctl = spkrioctl,
     76 	.d_stop = nostop,
     77 	.d_tty = notty,
     78 	.d_poll = nopoll,
     79 	.d_mmap = nommap,
     80 	.d_kqfilter = nokqfilter,
     81 	.d_discard = nodiscard,
     82 	.d_flag = D_OTHER
     83 };
     84 
     85 static void playinit(void);
     86 static void playtone(int, int, int);
     87 static void playstring(char *, int);
     88 
     89 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
     90  *
     91  * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
     92  * M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
     93  * Requires spkr_tone(), spkr_rest(). String play is not interruptible
     94  * except possibly at physical block boundaries.
     95  */
     96 
     97 #define dtoi(c)		((c) - '0')
     98 
     99 static int octave;	/* currently selected octave */
    100 static int whole;	/* whole-note time at current tempo, in ticks */
    101 static int value;	/* whole divisor for note time, quarter note = 1 */
    102 static int fill;	/* controls spacing of notes */
    103 static bool octtrack;	/* octave-tracking on? */
    104 static bool octprefix;	/* override current octave-tracking state? */
    105 
    106 /*
    107  * Magic number avoidance...
    108  */
    109 #define SECS_PER_MIN	60	/* seconds per minute */
    110 #define WHOLE_NOTE	4	/* quarter notes per whole note */
    111 #define MIN_VALUE	64	/* the most we can divide a note by */
    112 #define DFLT_VALUE	4	/* default value (quarter-note) */
    113 #define FILLTIME	8	/* for articulation, break note in parts */
    114 #define STACCATO	6	/* 6/8 = 3/4 of note is filled */
    115 #define NORMAL		7	/* 7/8ths of note interval is filled */
    116 #define LEGATO		8	/* all of note interval is filled */
    117 #define DFLT_OCTAVE	4	/* default octave */
    118 #define MIN_TEMPO	32	/* minimum tempo */
    119 #define DFLT_TEMPO	120	/* default tempo */
    120 #define MAX_TEMPO	255	/* max tempo */
    121 #define NUM_MULT	3	/* numerator of dot multiplier */
    122 #define DENOM_MULT	2	/* denominator of dot multiplier */
    123 
    124 /* letter to half-tone:  A   B  C  D  E  F  G */
    125 static const int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
    126 
    127 /*
    128  * This is the American Standard A440 Equal-Tempered scale with frequencies
    129  * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
    130  * our octave 0 is standard octave 2.
    131  */
    132 #define OCTAVE_NOTES	12	/* semitones per octave */
    133 static const int pitchtab[] =
    134 {
    135 /*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
    136 /* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
    137 /* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
    138 /* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
    139 /* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
    140 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
    141 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
    142 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
    143 };
    144 #define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES)
    145 
    146 static void
    147 playinit(void)
    148 {
    149     octave = DFLT_OCTAVE;
    150     whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
    151     fill = NORMAL;
    152     value = DFLT_VALUE;
    153     octtrack = false;
    154     octprefix = true;	/* act as though there was an initial O(n) */
    155 }
    156 
    157 static void
    158 playtone(int pitch, int val, int sustain)
    159 /* play tone of proper duration for current rhythm signature */
    160 {
    161     int	sound, silence, snum = 1, sdenom = 1;
    162 
    163     /* this weirdness avoids floating-point arithmetic */
    164     for (; sustain; sustain--)
    165     {
    166 	snum *= NUM_MULT;
    167 	sdenom *= DENOM_MULT;
    168     }
    169 
    170     if (pitch == -1)
    171 	spkr_rest(whole * snum / (val * sdenom));
    172     else
    173     {
    174 	sound = (whole * snum) / (val * sdenom)
    175 		- (whole * (FILLTIME - fill)) / (val * FILLTIME);
    176 	silence = whole * (FILLTIME-fill) * snum / (FILLTIME * val * sdenom);
    177 
    178 #ifdef SPKRDEBUG
    179 	printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
    180 	    pitch, sound, silence);
    181 #endif /* SPKRDEBUG */
    182 
    183 	spkr_tone(pitchtab[pitch], sound);
    184 	if (fill != LEGATO)
    185 	    spkr_rest(silence);
    186     }
    187 }
    188 
    189 static void
    190 playstring(char *cp, int slen)
    191 /* interpret and play an item from a notation string */
    192 {
    193     int		pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
    194 
    195 #define GETNUM(cp, v)	for(v=0; slen > 0 && isdigit(cp[1]); ) \
    196 				{v = v * 10 + (*++cp - '0'); slen--;}
    197     for (; slen--; cp++)
    198     {
    199 	int		sustain, timeval, tempo;
    200 	char	c = toupper(*cp);
    201 
    202 #ifdef SPKRDEBUG
    203 	printf("playstring: %c (%x)\n", c, c);
    204 #endif /* SPKRDEBUG */
    205 
    206 	switch (c)
    207 	{
    208 	case 'A':  case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
    209 
    210 	    /* compute pitch */
    211 	    pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
    212 
    213 	    /* this may be followed by an accidental sign */
    214 	    if (slen > 0 && (cp[1] == '#' || cp[1] == '+'))
    215 	    {
    216 		++pitch;
    217 		++cp;
    218 		slen--;
    219 	    }
    220 	    else if (slen > 0 && cp[1] == '-')
    221 	    {
    222 		--pitch;
    223 		++cp;
    224 		slen--;
    225 	    }
    226 
    227 	    /*
    228 	     * If octave-tracking mode is on, and there has been no octave-
    229 	     * setting prefix, find the version of the current letter note
    230 	     * closest to the last regardless of octave.
    231 	     */
    232 	    if (octtrack && !octprefix)
    233 	    {
    234 		if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
    235 		{
    236 		    if (octave < NOCTAVES - 1) {
    237 			++octave;
    238 			pitch += OCTAVE_NOTES;
    239 		    }
    240 		}
    241 
    242 		if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
    243 		{
    244 		    if (octave > 0) {
    245 			--octave;
    246 			pitch -= OCTAVE_NOTES;
    247 		    }
    248 		}
    249 	    }
    250 	    octprefix = false;
    251 	    lastpitch = pitch;
    252 
    253 	    /* ...which may in turn be followed by an override time value */
    254 	    GETNUM(cp, timeval);
    255 	    if (timeval <= 0 || timeval > MIN_VALUE)
    256 		timeval = value;
    257 
    258 	    /* ...and/or sustain dots */
    259 	    for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
    260 	    {
    261 		slen--;
    262 		sustain++;
    263 	    }
    264 
    265 	    /* time to emit the actual tone */
    266 	    playtone(pitch, timeval, sustain);
    267 	    break;
    268 
    269 	case 'O':
    270 	    if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
    271 	    {
    272 		octprefix = octtrack = false;
    273 		++cp;
    274 		slen--;
    275 	    }
    276 	    else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
    277 	    {
    278 		octtrack = true;
    279 		++cp;
    280 		slen--;
    281 	    }
    282 	    else
    283 	    {
    284 		GETNUM(cp, octave);
    285 		if (octave >= NOCTAVES)
    286 		    octave = DFLT_OCTAVE;
    287 		octprefix = true;
    288 	    }
    289 	    break;
    290 
    291 	case '>':
    292 	    if (octave < NOCTAVES - 1)
    293 		octave++;
    294 	    octprefix = true;
    295 	    break;
    296 
    297 	case '<':
    298 	    if (octave > 0)
    299 		octave--;
    300 	    octprefix = true;
    301 	    break;
    302 
    303 	case 'N':
    304 	    GETNUM(cp, pitch);
    305 	    for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
    306 	    {
    307 		slen--;
    308 		sustain++;
    309 	    }
    310 	    playtone(pitch - 1, value, sustain);
    311 	    break;
    312 
    313 	case 'L':
    314 	    GETNUM(cp, value);
    315 	    if (value <= 0 || value > MIN_VALUE)
    316 		value = DFLT_VALUE;
    317 	    break;
    318 
    319 	case 'P':
    320 	case '~':
    321 	    /* this may be followed by an override time value */
    322 	    GETNUM(cp, timeval);
    323 	    if (timeval <= 0 || timeval > MIN_VALUE)
    324 		timeval = value;
    325 	    for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
    326 	    {
    327 		slen--;
    328 		sustain++;
    329 	    }
    330 	    playtone(-1, timeval, sustain);
    331 	    break;
    332 
    333 	case 'T':
    334 	    GETNUM(cp, tempo);
    335 	    if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
    336 		tempo = DFLT_TEMPO;
    337 	    whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
    338 	    break;
    339 
    340 	case 'M':
    341 	    if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
    342 	    {
    343 		fill = NORMAL;
    344 		++cp;
    345 		slen--;
    346 	    }
    347 	    else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
    348 	    {
    349 		fill = LEGATO;
    350 		++cp;
    351 		slen--;
    352 	    }
    353 	    else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's'))
    354 	    {
    355 		fill = STACCATO;
    356 		++cp;
    357 		slen--;
    358 	    }
    359 	    break;
    360 	}
    361     }
    362 }
    363 
    364 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
    365  *
    366  * This section implements driver hooks to run playstring() and the spkr_tone()
    367  * and spkr_rest() functions defined above.
    368  */
    369 
    370 static int spkr_active;	/* exclusion flag */
    371 int spkr_attached;
    372 static void *spkr_inbuf;
    373 
    374 int
    375 spkr_probe(device_t parent, cfdata_t match, void *aux)
    376 {
    377 	return (!spkr_attached);
    378 }
    379 
    380 int
    381 spkropen(dev_t dev, int	flags, int mode, struct lwp *l)
    382 {
    383 #ifdef SPKRDEBUG
    384     printf("spkropen: entering with dev = %"PRIx64"\n", dev);
    385 #endif /* SPKRDEBUG */
    386 
    387     if (minor(dev) != 0 || !spkr_attached)
    388 	return(ENXIO);
    389     else if (spkr_active)
    390 	return(EBUSY);
    391     else
    392     {
    393 	playinit();
    394 	spkr_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
    395 	spkr_active = 1;
    396     }
    397     return(0);
    398 }
    399 
    400 int
    401 spkrwrite(dev_t dev, struct uio *uio, int flags)
    402 {
    403     int n;
    404     int error;
    405 #ifdef SPKRDEBUG
    406     printf("spkrwrite: entering with dev = %"PRIx64", count = %zu\n",
    407 		dev, uio->uio_resid);
    408 #endif /* SPKRDEBUG */
    409 
    410     if (minor(dev) != 0)
    411 	return(ENXIO);
    412     else
    413     {
    414 	n = min(DEV_BSIZE, uio->uio_resid);
    415 	error = uiomove(spkr_inbuf, n, uio);
    416 	if (!error)
    417 		playstring((char *)spkr_inbuf, n);
    418 	return(error);
    419     }
    420 }
    421 
    422 int
    423 spkrclose(dev_t dev, int flags, int mode, struct lwp *l)
    424 {
    425 #ifdef SPKRDEBUG
    426     printf("spkrclose: entering with dev = %"PRIx64"\n", dev);
    427 #endif /* SPKRDEBUG */
    428 
    429     if (minor(dev) != 0)
    430 	return(ENXIO);
    431     else
    432     {
    433 	spkr_tone(0, 0);
    434 	free(spkr_inbuf, M_DEVBUF);
    435 	spkr_active = 0;
    436     }
    437     return(0);
    438 }
    439 
    440 int
    441 spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
    442 {
    443 #ifdef SPKRDEBUG
    444     printf("spkrioctl: entering with dev = %"PRIx64", cmd = %lx\n", dev, cmd);
    445 #endif /* SPKRDEBUG */
    446 
    447     if (minor(dev) != 0)
    448 	return(ENXIO);
    449     else if (cmd == SPKRTONE)
    450     {
    451 	tone_t	*tp = (tone_t *)data;
    452 
    453 	if (tp->frequency == 0)
    454 	    spkr_rest(tp->duration);
    455 	else
    456 	    spkr_tone(tp->frequency, tp->duration);
    457     }
    458     else if (cmd == SPKRTUNE)
    459     {
    460 	tone_t  *tp = (tone_t *)(*(void **)data);
    461 	tone_t ttp;
    462 	int error;
    463 
    464 	for (; ; tp++) {
    465 	    error = copyin(tp, &ttp, sizeof(tone_t));
    466 	    if (error)
    467 		    return(error);
    468 	    if (ttp.duration == 0)
    469 		    break;
    470 	    if (ttp.frequency == 0)
    471 		spkr_rest(ttp.duration);
    472 	    else
    473 		spkr_tone(ttp.frequency, ttp.duration);
    474 	}
    475     }
    476     else
    477 	return(EINVAL);
    478     return(0);
    479 }
    480 
    481 #ifdef _MODULE
    482 extern struct cfdriver spkr_cd;
    483 #include "ioconf.c"
    484 #endif
    485 
    486 int
    487 spkr__modcmd(modcmd_t cmd, void *arg)
    488 {
    489 #ifdef _MODULE
    490 	devmajor_t bmajor, cmajor;
    491 	int error = 0;
    492 
    493 	switch(cmd) {
    494 	case MODULE_CMD_INIT:
    495 		bmajor = cmajor = -1;
    496 		error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
    497 		    &spkr_cdevsw, &cmajor);
    498 		if (error)
    499 			break;
    500 
    501 		error = config_init_component(cfdriver_ioconf_spkr,
    502 			cfattach_ioconf_spkr, cfdata_ioconf_spkr);
    503 		if (error) {
    504 			devsw_detach(NULL, &spkr_cdevsw);
    505 		}
    506 		break;
    507 
    508 	case MODULE_CMD_FINI:
    509 		if (spkr_active)
    510 			return EBUSY;
    511 		error = config_fini_component(cfdriver_ioconf_spkr,
    512 			cfattach_ioconf_spkr, cfdata_ioconf_spkr);
    513 		devsw_detach(NULL, &spkr_cdevsw);
    514 		break;
    515 	default:
    516 		error = ENOTTY;
    517 		break;
    518 	}
    519 
    520 	return error;
    521 #else
    522 	return 0;
    523 #endif
    524 }
    525