NetBSD/sys/dev/spkr.c
thorpej 2685996b0e Merge thorpej-cfargs branch:
Simplify and make extensible the config_search() / config_found() /
config_attach() interfaces: rather than having different variants for
which arguments you want pass along, just have a single call that
takes a variadic list of tag-value arguments.

Adjust all call sites:
- Simplify wherever possible; don't pass along arguments that aren't
  actually needed.
- Don't be explicit about what interface attribute is attaching if
  the device only has one.  (More simplification.)
- Add a config_probe() function to be used in indirect configuiration
  situations, making is visibly easier to see when indirect config is
  in play, and allowing for future change in semantics.  (As of now,
  this is just a wrapper around config_match(), but that is an
  implementation detail.)

Remove unnecessary or redundant interface attributes where they're not
needed.

There are currently 5 "cfargs" defined:
- CFARG_SUBMATCH (submatch function for direct config)
- CFARG_SEARCH (search function for indirect config)
- CFARG_IATTR (interface attribte)
- CFARG_LOCATORS (locators array)
- CFARG_DEVHANDLE (devhandle_t - wraps OFW, ACPI, etc. handles)

...and a sentinel value CFARG_EOL.

Add some extra sanity checking to ensure that interface attributes
aren't ambiguous.

Use CFARG_DEVHANDLE in MI FDT, OFW, and ACPI code, and macppc and shark
ports to associate those device handles with device_t instance.  This
will trickle trough to more places over time (need back-end for pre-OFW
Sun OBP; any others?).
2021-04-24 23:36:23 +00:00

650 lines
16 KiB
C

/* $NetBSD: spkr.c,v 1.20 2021/04/24 23:36:52 thorpej Exp $ */
/*
* Copyright (c) 1990 Eric S. Raymond (esr@snark.thyrsus.com)
* Copyright (c) 1990 Andrew A. Chernov (ache@astral.msk.su)
* Copyright (c) 1990 Lennart Augustsson (lennart@augustsson.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Eric S. Raymond
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* spkr.c -- device driver for console speaker on 80386
*
* v1.1 by Eric S. Raymond (esr@snark.thyrsus.com) Feb 1990
* modified for 386bsd by Andrew A. Chernov <ache@astral.msk.su>
* 386bsd only clean version, all SYSV stuff removed
* use hz value from param.c
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: spkr.c,v 1.20 2021/04/24 23:36:52 thorpej Exp $");
#if defined(_KERNEL_OPT)
#include "wsmux.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/uio.h>
#include <sys/proc.h>
#include <sys/ioctl.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <dev/spkrio.h>
#include <dev/spkrvar.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsbellvar.h>
#include <dev/wscons/wsbellmuxvar.h>
#include "ioconf.h"
dev_type_open(spkropen);
dev_type_close(spkrclose);
dev_type_write(spkrwrite);
dev_type_ioctl(spkrioctl);
const struct cdevsw spkr_cdevsw = {
.d_open = spkropen,
.d_close = spkrclose,
.d_read = noread,
.d_write = spkrwrite,
.d_ioctl = spkrioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_OTHER
};
static void playinit(struct spkr_softc *);
static void playtone(struct spkr_softc *, int, int, int);
static void playstring(struct spkr_softc *, const char *, size_t);
/**************** PLAY STRING INTERPRETER BEGINS HERE **********************
*
* Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
* M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
* String play is not interruptible except possibly at physical block
* boundaries.
*/
/*
* Magic number avoidance...
*/
#define SECS_PER_MIN 60 /* seconds per minute */
#define WHOLE_NOTE 4 /* quarter notes per whole note */
#define MIN_VALUE 64 /* the most we can divide a note by */
#define DFLT_VALUE 4 /* default value (quarter-note) */
#define FILLTIME 8 /* for articulation, break note in parts */
#define STACCATO 6 /* 6/8 = 3/4 of note is filled */
#define NORMAL 7 /* 7/8ths of note interval is filled */
#define LEGATO 8 /* all of note interval is filled */
#define DFLT_OCTAVE 4 /* default octave */
#define MIN_TEMPO 32 /* minimum tempo */
#define DFLT_TEMPO 120 /* default tempo */
#define MAX_TEMPO 255 /* max tempo */
#define NUM_MULT 3 /* numerator of dot multiplier */
#define DENOM_MULT 2 /* denominator of dot multiplier */
/* letter to half-tone: A B C D E F G */
static const int notetab[8] = { 9, 11, 0, 2, 4, 5, 7 };
/*
* This is the American Standard A440 Equal-Tempered scale with frequencies
* rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
* our octave 0 is standard octave 2.
*/
#define OCTAVE_NOTES 12 /* semitones per octave */
static const int pitchtab[] =
{
/* C C# D D# E F F# G G# A A# B*/
/* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123,
/* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
/* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
/* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
/* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
/* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
/* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
};
#define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES)
static void
playinit(struct spkr_softc *sc)
{
sc->sc_octave = DFLT_OCTAVE;
sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
sc->sc_fill = NORMAL;
sc->sc_value = DFLT_VALUE;
sc->sc_octtrack = false;
sc->sc_octprefix = true;/* act as though there was an initial O(n) */
}
#define SPKRPRI (PZERO - 1)
/* Rest for given number of ticks */
static void
rest(struct spkr_softc *sc, int ticks)
{
#ifdef SPKRDEBUG
device_printf(sc->sc_dev, "%s: rest for %d ticks\n", __func__, ticks);
#endif /* SPKRDEBUG */
KASSERT(ticks > 0);
tsleep(sc->sc_dev, SPKRPRI | PCATCH, device_xname(sc->sc_dev), ticks);
}
/*
* Play tone of proper duration for current rhythm signature.
* note indicates "O0C" = 0, "O0C#" = 1, "O0D" = 2, ... , and
* -1 indiacates a rest.
* val indicates the length, "L4" = 4, "L8" = 8.
* sustain indicates the number of subsequent dots that extend the sound
* by one a half.
*/
static void
playtone(struct spkr_softc *sc, int note, int val, int sustain)
{
int whole;
int total;
int sound;
int silence;
/* this weirdness avoids floating-point arithmetic */
whole = sc->sc_whole;
for (; sustain; sustain--) {
whole *= NUM_MULT;
val *= DENOM_MULT;
}
/* Total length in tick */
total = whole / val;
if (note == -1) {
#ifdef SPKRDEBUG
device_printf(sc->sc_dev, "%s: rest for %d ticks\n",
__func__, total);
#endif /* SPKRDEBUG */
if (total != 0)
rest(sc, total);
return;
}
/*
* Rest 1/8 (if NORMAL) or 3/8 (if STACCATO) in tick.
* silence should be rounded down.
*/
silence = total * (FILLTIME - sc->sc_fill) / FILLTIME;
sound = total - silence;
#ifdef SPKRDEBUG
device_printf(sc->sc_dev,
"%s: note %d for %d ticks, rest for %d ticks\n", __func__,
note, sound, silence);
#endif /* SPKRDEBUG */
if (sound != 0)
(*sc->sc_tone)(sc->sc_dev, pitchtab[note], sound);
if (silence != 0)
rest(sc, silence);
}
/* interpret and play an item from a notation string */
static void
playstring(struct spkr_softc *sc, const char *cp, size_t slen)
{
int pitch;
int lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
#define GETNUM(cp, v) \
for (v = 0; slen > 0 && isdigit((unsigned char)cp[1]); ) { \
v = v * 10 + (*++cp - '0'); \
slen--; \
}
for (; slen--; cp++) {
int sustain, timeval, tempo;
char c = toupper((unsigned char)*cp);
#ifdef SPKRDEBUG
if (0x20 <= c && c < 0x7f) {
device_printf(sc->sc_dev, "%s: '%c'\n", __func__, c);
} else {
device_printf(sc->sc_dev, "%s: (0x%x)\n", __func__, c);
}
#endif /* SPKRDEBUG */
switch (c) {
case 'A': case 'B': case 'C': case 'D':
case 'E': case 'F': case 'G':
/* compute pitch */
pitch = notetab[c - 'A'] + sc->sc_octave * OCTAVE_NOTES;
/* this may be followed by an accidental sign */
if (slen > 0 && (cp[1] == '#' || cp[1] == '+')) {
++pitch;
++cp;
slen--;
} else if (slen > 0 && cp[1] == '-') {
--pitch;
++cp;
slen--;
}
/*
* If octave-tracking mode is on, and there has been no
* octave- setting prefix, find the version of the
* current letter note * closest to the last
* regardless of octave.
*/
if (sc->sc_octtrack && !sc->sc_octprefix) {
int d = abs(pitch - lastpitch);
if (d > abs(pitch + OCTAVE_NOTES - lastpitch)) {
if (sc->sc_octave < NOCTAVES - 1) {
++sc->sc_octave;
pitch += OCTAVE_NOTES;
}
}
if (d > abs(pitch - OCTAVE_NOTES - lastpitch)) {
if (sc->sc_octave > 0) {
--sc->sc_octave;
pitch -= OCTAVE_NOTES;
}
}
}
sc->sc_octprefix = false;
lastpitch = pitch;
/*
* ...which may in turn be followed by an override
* time value
*/
GETNUM(cp, timeval);
if (timeval <= 0 || timeval > MIN_VALUE)
timeval = sc->sc_value;
/* ...and/or sustain dots */
for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
slen--;
sustain++;
}
/* time to emit the actual tone */
playtone(sc, pitch, timeval, sustain);
break;
case 'O':
if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
sc->sc_octprefix = sc->sc_octtrack = false;
++cp;
slen--;
} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
sc->sc_octtrack = true;
++cp;
slen--;
} else {
GETNUM(cp, sc->sc_octave);
if (sc->sc_octave >= NOCTAVES)
sc->sc_octave = DFLT_OCTAVE;
sc->sc_octprefix = true;
}
break;
case '>':
if (sc->sc_octave < NOCTAVES - 1)
sc->sc_octave++;
sc->sc_octprefix = true;
break;
case '<':
if (sc->sc_octave > 0)
sc->sc_octave--;
sc->sc_octprefix = true;
break;
case 'N':
GETNUM(cp, pitch);
for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
slen--;
sustain++;
}
playtone(sc, pitch - 1, sc->sc_value, sustain);
break;
case 'L':
GETNUM(cp, sc->sc_value);
if (sc->sc_value <= 0 || sc->sc_value > MIN_VALUE)
sc->sc_value = DFLT_VALUE;
break;
case 'P':
case '~':
/* this may be followed by an override time value */
GETNUM(cp, timeval);
if (timeval <= 0 || timeval > MIN_VALUE)
timeval = sc->sc_value;
for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
slen--;
sustain++;
}
playtone(sc, -1, timeval, sustain);
break;
case 'T':
GETNUM(cp, tempo);
if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
tempo = DFLT_TEMPO;
sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
break;
case 'M':
if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
sc->sc_fill = NORMAL;
++cp;
slen--;
} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
sc->sc_fill = LEGATO;
++cp;
slen--;
} else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's')) {
sc->sc_fill = STACCATO;
++cp;
slen--;
}
break;
}
}
}
/******************* UNIX DRIVER HOOKS BEGIN HERE **************************/
#define spkrenter(d) device_lookup_private(&spkr_cd, d)
/*
* Attaches spkr. Specify tone function with the following specification:
*
* void
* tone(device_t self, u_int pitch, u_int tick)
* plays a beep with specified parameters.
* The argument 'pitch' specifies the pitch of a beep in Hz. The argument
* 'tick' specifies the period of a beep in tick(9). This function waits
* to finish playing the beep and then halts it.
* If the pitch is zero, it halts all sound if any (for compatibility
* with the past confused specifications, but there should be no sound at
* this point). And it returns immediately, without waiting ticks. So
* you cannot use this as a rest.
* If the tick is zero, it returns immediately.
*/
void
spkr_attach(device_t self, void (*tone)(device_t, u_int, u_int))
{
struct spkr_softc *sc = device_private(self);
#ifdef SPKRDEBUG
aprint_debug("%s: entering for unit %d\n", __func__, self->dv_unit);
#endif /* SPKRDEBUG */
sc->sc_dev = self;
sc->sc_tone = tone;
sc->sc_inbuf = NULL;
sc->sc_wsbelldev = NULL;
spkr_rescan(self, NULL, NULL);
}
int
spkr_detach(device_t self, int flags)
{
struct spkr_softc *sc = device_private(self);
int rc;
#ifdef SPKRDEBUG
aprint_debug("%s: entering for unit %d\n", __func__, self->dv_unit);
#endif /* SPKRDEBUG */
if (sc == NULL)
return ENXIO;
/* XXXNS If speaker never closes, we cannot complete the detach. */
while ((flags & DETACH_FORCE) != 0 && sc->sc_inbuf != NULL)
kpause("spkrwait", TRUE, 1, NULL);
if (sc->sc_inbuf != NULL)
return EBUSY;
rc = config_detach_children(self, flags);
return rc;
}
/* ARGSUSED */
int
spkr_rescan(device_t self, const char *iattr, const int *locators)
{
#if NWSMUX > 0
struct spkr_softc *sc = device_private(self);
struct wsbelldev_attach_args a;
if (sc->sc_wsbelldev == NULL) {
a.accesscookie = sc;
sc->sc_wsbelldev = config_found(self, &a, wsbelldevprint,
CFARG_EOL);
}
#endif
return 0;
}
int
spkr_childdet(device_t self, device_t child)
{
struct spkr_softc *sc = device_private(self);
if (sc->sc_wsbelldev == child)
sc->sc_wsbelldev = NULL;
return 0;
}
int
spkropen(dev_t dev, int flags, int mode, struct lwp *l)
{
struct spkr_softc *sc = spkrenter(minor(dev));
#ifdef SPKRDEBUG
device_printf(sc->sc_dev, "%s: entering\n", __func__);
#endif /* SPKRDEBUG */
if (sc == NULL)
return ENXIO;
if (sc->sc_inbuf != NULL)
return EBUSY;
sc->sc_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
playinit(sc);
return 0;
}
int
spkrwrite(dev_t dev, struct uio *uio, int flags)
{
struct spkr_softc *sc = spkrenter(minor(dev));
#ifdef SPKRDEBUG
device_printf(sc->sc_dev, "%s: entering with length = %zu\n",
__func__, uio->uio_resid);
#endif /* SPKRDEBUG */
if (sc == NULL)
return ENXIO;
if (sc->sc_inbuf == NULL)
return EINVAL;
size_t n = uimin(DEV_BSIZE, uio->uio_resid);
int error = uiomove(sc->sc_inbuf, n, uio);
if (error)
return error;
playstring(sc, sc->sc_inbuf, n);
return 0;
}
int
spkrclose(dev_t dev, int flags, int mode, struct lwp *l)
{
struct spkr_softc *sc = spkrenter(minor(dev));
#ifdef SPKRDEBUG
device_printf(sc->sc_dev, "%s: entering\n", __func__);
#endif /* SPKRDEBUG */
if (sc == NULL)
return ENXIO;
if (sc->sc_inbuf == NULL)
return EINVAL;
sc->sc_tone(sc->sc_dev, 0, 0);
free(sc->sc_inbuf, M_DEVBUF);
sc->sc_inbuf = NULL;
return 0;
}
/*
* Play tone specified by tp.
* tp->frequency is the frequency (0 means a rest).
* tp->duration is the length in tick (returns immediately if 0).
*/
static void
playonetone(struct spkr_softc *sc, tone_t *tp)
{
if (tp->duration <= 0)
return;
if (tp->frequency == 0)
rest(sc, tp->duration);
else
(*sc->sc_tone)(sc->sc_dev, tp->frequency, tp->duration);
}
int
spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
struct spkr_softc *sc = spkrenter(minor(dev));
tone_t *tp;
tone_t ttp;
int error;
#ifdef SPKRDEBUG
device_printf(sc->sc_dev, "%s: entering with cmd = %lx\n",
__func__, cmd);
#endif /* SPKRDEBUG */
if (sc == NULL)
return ENXIO;
if (sc->sc_inbuf == NULL)
return EINVAL;
switch (cmd) {
case SPKRTONE:
playonetone(sc, data);
return 0;
case SPKRTUNE:
for (tp = *(void **)data;; tp++) {
error = copyin(tp, &ttp, sizeof(tone_t));
if (error)
return(error);
if (ttp.duration == 0)
break;
playonetone(sc, &ttp);
}
return 0;
case SPKRGETVOL:
if (data != NULL)
*(u_int *)data = sc->sc_vol;
return 0;
case SPKRSETVOL:
if (data != NULL && *(u_int *)data <= 100)
sc->sc_vol = *(u_int *)data;
return 0;
default:
return ENOTTY;
}
}
#ifdef _MODULE
#include "ioconf.c"
#endif
MODULE(MODULE_CLASS_DRIVER, spkr, "audio" /* and/or pcppi */ );
int
spkr_modcmd(modcmd_t cmd, void *arg)
{
int error = 0;
#ifdef _MODULE
devmajor_t bmajor, cmajor;
#endif
switch(cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
bmajor = cmajor = -1;
error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
&spkr_cdevsw, &cmajor);
if (error)
break;
error = config_init_component(cfdriver_ioconf_spkr,
cfattach_ioconf_spkr, cfdata_ioconf_spkr);
if (error) {
devsw_detach(NULL, &spkr_cdevsw);
}
#endif
break;
case MODULE_CMD_FINI:
#ifdef _MODULE
devsw_detach(NULL, &spkr_cdevsw);
error = config_fini_component(cfdriver_ioconf_spkr,
cfattach_ioconf_spkr, cfdata_ioconf_spkr);
if (error)
devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
&spkr_cdevsw, &cmajor);
#endif
break;
default:
error = ENOTTY;
break;
}
return error;
}