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