speaker(4): enable concurrent opens from different threads

Prior to this patch, a thread would get EBUSY on open(2) if another
thread had the speaker open.

With this patch, two or more threads/processes can use the speaker
device at the same time. When two or more threads write to the speaker
concurrently, individual melodies--single strings, as written by
write(2) or ioctl(2) with command SPKRTONE/SPKRTUNE--are played
atomically.

Reviewed by: imp
Pull Request: https://github.com/freebsd/freebsd-src/pull/1922
This commit is contained in:
Raphael 'kena' Poss
2026-01-11 22:47:43 +01:00
committed by Warner Losh
parent 690ef95b33
commit d76523a318
+116 -91
View File
@@ -15,7 +15,6 @@
#include <sys/conf.h> #include <sys/conf.h>
#include <sys/ctype.h> #include <sys/ctype.h>
#include <sys/malloc.h> #include <sys/malloc.h>
#include <machine/atomic.h>
#include <machine/clock.h> #include <machine/clock.h>
#include <dev/speaker/speaker.h> #include <dev/speaker/speaker.h>
@@ -51,11 +50,12 @@ static MALLOC_DEFINE(M_SPKR, "spkr", "Speaker buffer");
#define SPKRPRI PSOCK #define SPKRPRI PSOCK
static char endtone, endrest; static char endtone, endrest;
struct spkr_state;
static void tone(unsigned int thz, unsigned int centisecs); static void tone(unsigned int thz, unsigned int centisecs);
static void rest(int centisecs); static void rest(int centisecs);
static void playinit(void); static void playinit(struct spkr_state *state);
static void playtone(int pitch, int value, int sustain); static void playtone(struct spkr_state *state, int pitch, int value, int sustain);
static void playstring(char *cp, size_t slen); static void playstring(struct spkr_state *state, char *cp, size_t slen);
/* /*
* Emit tone of frequency thz for given number of centisecs * Emit tone of frequency thz for given number of centisecs
@@ -128,12 +128,16 @@ rest(int centisecs)
#define dtoi(c) ((c) - '0') #define dtoi(c) ((c) - '0')
static int octave; /* currently selected octave */ struct spkr_state {
static int whole; /* whole-note time at current tempo, in ticks */ char *inbuf; /* incoming melody buffer */
static int value; /* whole divisor for note time, quarter note = 1 */
static int fill; /* controls spacing of notes */ int octave; /* currently selected octave */
static bool octtrack; /* octave-tracking on? */ int whole; /* whole-note time at current tempo, in ticks */
static bool octprefix; /* override current octave-tracking state? */ int value; /* whole divisor for note time, quarter note = 1 */
int fill; /* controls spacing of notes */
bool octtrack; /* octave-tracking on? */
bool octprefix; /* override current octave-tracking state? */
};
/* /*
* Magic number avoidance... * Magic number avoidance...
@@ -175,23 +179,25 @@ static const int pitchtab[] =
}; };
static void static void
playinit(void) playinit(struct spkr_state *state)
{ {
octave = DFLT_OCTAVE; state->octave = DFLT_OCTAVE;
whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; state->whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
fill = NORMAL; state->fill = NORMAL;
value = DFLT_VALUE; state->value = DFLT_VALUE;
octtrack = false; state->octtrack = false;
octprefix = true; /* act as though there was an initial O(n) */ state->octprefix = true; /* act as though there was an initial O(n) */
} }
/* /*
* Play tone of proper duration for current rhythm signature * Play tone of proper duration for current rhythm signature
*/ */
static void static void
playtone(int pitch, int value, int sustain) playtone(struct spkr_state *state, int pitch, int value, int sustain)
{ {
int sound, silence, snum = 1, sdenom = 1; int sound, silence, snum = 1, sdenom = 1;
int whole = state->whole;
int fill = state->fill;
/* this weirdness avoids floating-point arithmetic */ /* this weirdness avoids floating-point arithmetic */
for (; sustain; sustain--) { for (; sustain; sustain--) {
@@ -225,7 +231,7 @@ playtone(int pitch, int value, int sustain)
* Interpret and play an item from a notation string * Interpret and play an item from a notation string
*/ */
static void static void
playstring(char *cp, size_t slen) playstring(struct spkr_state *state, char *cp, size_t slen)
{ {
int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
@@ -248,7 +254,7 @@ playstring(char *cp, size_t slen)
case 'F': case 'F':
case 'G': case 'G':
/* compute pitch */ /* compute pitch */
pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; pitch = notetab[c - 'A'] + state->octave * OCTAVE_NOTES;
/* this may be followed by an accidental sign */ /* this may be followed by an accidental sign */
if (cp[1] == '#' || cp[1] == '+') { if (cp[1] == '#' || cp[1] == '+') {
@@ -266,26 +272,26 @@ playstring(char *cp, size_t slen)
* setting prefix, find the version of the current letter note * setting prefix, find the version of the current letter note
* closest to the last regardless of octave. * closest to the last regardless of octave.
*/ */
if (octtrack && !octprefix) { if (state->octtrack && !state->octprefix) {
if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES - if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES -
lastpitch)) { lastpitch)) {
++octave; ++state->octave;
pitch += OCTAVE_NOTES; pitch += OCTAVE_NOTES;
} }
if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES) - if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES) -
lastpitch)) { lastpitch)) {
--octave; --state->octave;
pitch -= OCTAVE_NOTES; pitch -= OCTAVE_NOTES;
} }
} }
octprefix = false; state->octprefix = false;
lastpitch = pitch; lastpitch = pitch;
/* ...which may in turn be followed by an override time value */ /* ...which may in turn be followed by an override time value */
GETNUM(cp, timeval); GETNUM(cp, timeval);
if (timeval <= 0 || timeval > MIN_VALUE) if (timeval <= 0 || timeval > MIN_VALUE)
timeval = value; timeval = state->value;
/* ...and/or sustain dots */ /* ...and/or sustain dots */
for (sustain = 0; cp[1] == '.'; cp++) { for (sustain = 0; cp[1] == '.'; cp++) {
@@ -294,43 +300,43 @@ playstring(char *cp, size_t slen)
} }
/* ...and/or a slur mark */ /* ...and/or a slur mark */
oldfill = fill; oldfill = state->fill;
if (cp[1] == '_') { if (cp[1] == '_') {
fill = LEGATO; state->fill = LEGATO;
++cp; ++cp;
slen--; slen--;
} }
/* time to emit the actual tone */ /* time to emit the actual tone */
playtone(pitch, timeval, sustain); playtone(state, pitch, timeval, sustain);
fill = oldfill; state->fill = oldfill;
break; break;
case 'O': case 'O':
if (cp[1] == 'N' || cp[1] == 'n') { if (cp[1] == 'N' || cp[1] == 'n') {
octprefix = octtrack = false; state->octprefix = state->octtrack = false;
++cp; ++cp;
slen--; slen--;
} else if (cp[1] == 'L' || cp[1] == 'l') { } else if (cp[1] == 'L' || cp[1] == 'l') {
octtrack = true; state->octtrack = true;
++cp; ++cp;
slen--; slen--;
} else { } else {
GETNUM(cp, octave); GETNUM(cp, state->octave);
if (octave >= nitems(pitchtab) / OCTAVE_NOTES) if (state->octave >= nitems(pitchtab) / OCTAVE_NOTES)
octave = DFLT_OCTAVE; state->octave = DFLT_OCTAVE;
octprefix = true; state->octprefix = true;
} }
break; break;
case '>': case '>':
if (octave < nitems(pitchtab) / OCTAVE_NOTES - 1) if (state->octave < nitems(pitchtab) / OCTAVE_NOTES - 1)
octave++; state->octave++;
octprefix = true; state->octprefix = true;
break; break;
case '<': case '<':
if (octave > 0) if (state->octave > 0)
octave--; state->octave--;
octprefix = true; state->octprefix = true;
break; break;
case 'N': case 'N':
GETNUM(cp, pitch); GETNUM(cp, pitch);
@@ -338,49 +344,49 @@ playstring(char *cp, size_t slen)
slen--; slen--;
sustain++; sustain++;
} }
oldfill = fill; oldfill = state->fill;
if (cp[1] == '_') { if (cp[1] == '_') {
fill = LEGATO; state->fill = LEGATO;
++cp; ++cp;
slen--; slen--;
} }
playtone(pitch - 1, value, sustain); playtone(state, pitch - 1, state->value, sustain);
fill = oldfill; state->fill = oldfill;
break; break;
case 'L': case 'L':
GETNUM(cp, value); GETNUM(cp, state->value);
if (value <= 0 || value > MIN_VALUE) if (state->value <= 0 || state->value > MIN_VALUE)
value = DFLT_VALUE; state->value = DFLT_VALUE;
break; break;
case 'P': case 'P':
case '~': case '~':
/* this may be followed by an override time value */ /* this may be followed by an override time value */
GETNUM(cp, timeval); GETNUM(cp, timeval);
if (timeval <= 0 || timeval > MIN_VALUE) if (timeval <= 0 || timeval > MIN_VALUE)
timeval = value; timeval = state->value;
for (sustain = 0; cp[1] == '.'; cp++) { for (sustain = 0; cp[1] == '.'; cp++) {
slen--; slen--;
sustain++; sustain++;
} }
playtone(-1, timeval, sustain); playtone(state, -1, timeval, sustain);
break; break;
case 'T': case 'T':
GETNUM(cp, tempo); GETNUM(cp, tempo);
if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
tempo = DFLT_TEMPO; tempo = DFLT_TEMPO;
whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / tempo; state->whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / tempo;
break; break;
case 'M': case 'M':
if (cp[1] == 'N' || cp[1] == 'n') { if (cp[1] == 'N' || cp[1] == 'n') {
fill = NORMAL; state->fill = NORMAL;
++cp; ++cp;
slen--; slen--;
} else if (cp[1] == 'L' || cp[1] == 'l') { } else if (cp[1] == 'L' || cp[1] == 'l') {
fill = LEGATO; state->fill = LEGATO;
++cp; ++cp;
slen--; slen--;
} else if (cp[1] == 'S' || cp[1] == 's') { } else if (cp[1] == 'S' || cp[1] == 's') {
fill = STACCATO; state->fill = STACCATO;
++cp; ++cp;
slen--; slen--;
} }
@@ -395,62 +401,82 @@ playstring(char *cp, size_t slen)
* endtone(), and rest() functions defined above. * endtone(), and rest() functions defined above.
*/ */
static int spkr_dev_busy = 0; /* one open at a time */
static char *spkr_inbuf; /* incoming buf */
/* /*
* we use a lock to serialize access to spkr_inbuf but also to prevent * we use a lock to prevent interleaving of melodies written
* interleaving of melodies written concurrently from two different * concurrently from two different threads.
* threads.
*/ */
static struct sx spkr_op_locked; static struct sx spkr_op_locked;
static void
spkr_dtor(void *data)
{
struct spkr_state *state = data;
free(state->inbuf, M_SPKR);
free(state, M_SPKR);
}
static int static int
spkropen(struct cdev *dev, int flags, int fmt, struct thread *td) spkropen(struct cdev *dev, int flags, int fmt, struct thread *td)
{ {
struct spkr_state *state;
int error;
#ifdef DEBUG #ifdef DEBUG
(void) printf("spkropen: entering with dev = %s\n", devtoname(dev)); (void) printf("spkropen: entering with dev = %s\n", devtoname(dev));
#endif /* DEBUG */ #endif /* DEBUG */
if (!atomic_cmpset_int(&spkr_dev_busy, 0, 1)) /* allocate per-fd state */
return(EBUSY); state = malloc(sizeof(*state), M_SPKR, M_WAITOK | M_ZERO);
else { state->inbuf = malloc(DEV_BSIZE, M_SPKR, M_WAITOK);
#ifdef DEBUG
(void) printf("spkropen: about to perform play initialization\n"); /* initialize playstring state */
#endif /* DEBUG */ playinit(state);
playinit();
spkr_inbuf = malloc(DEV_BSIZE, M_SPKR, M_WAITOK); /* store in the current fd private data */
return(0); error = devfs_set_cdevpriv(state, spkr_dtor);
if (error) {
free(state->inbuf, M_SPKR);
free(state, M_SPKR);
return (error);
} }
return (0);
} }
static int static int
spkrwrite(struct cdev *dev, struct uio *uio, int ioflag) spkrwrite(struct cdev *dev, struct uio *uio, int ioflag)
{ {
struct spkr_state *state;
int error;
unsigned n;
#ifdef DEBUG #ifdef DEBUG
printf("spkrwrite: entering with dev = %s, count = %zd\n", printf("spkrwrite: entering with dev = %s, count = %zd\n",
devtoname(dev), uio->uio_resid); devtoname(dev), uio->uio_resid);
#endif /* DEBUG */ #endif /* DEBUG */
if (uio->uio_resid > (DEV_BSIZE - 1)) /* prevent system crashes */ /* is the melody too long? */
return(E2BIG); if (uio->uio_resid > (DEV_BSIZE - 1))
else { return (E2BIG);
unsigned n;
char *cp;
int error;
sx_xlock(&spkr_op_locked); /* get this fd's state */
error = devfs_get_cdevpriv((void **)&state);
if (error)
return (error);
/* copy melody from userspace */
n = uio->uio_resid; n = uio->uio_resid;
cp = spkr_inbuf; error = uiomove(state->inbuf, n, uio);
error = uiomove(cp, n, uio); if (error)
if (!error) { return (error);
cp[n] = '\0'; state->inbuf[n] = '\0';
playstring(cp, n);
} /* play the melody. */
sx_xlock(&spkr_op_locked);
playstring(state, state->inbuf, n);
sx_xunlock(&spkr_op_locked); sx_xunlock(&spkr_op_locked);
return(error);
} return (0);
} }
static int static int
@@ -462,9 +488,8 @@ spkrclose(struct cdev *dev, int flags, int fmt, struct thread *td)
wakeup(&endtone); wakeup(&endtone);
wakeup(&endrest); wakeup(&endrest);
free(spkr_inbuf, M_SPKR); /* devfs_clear_cdevpriv() calls spkr_dtor automatically */
(void) atomic_swap_int(&spkr_dev_busy, 0); return (0);
return(0);
} }
static int static int
@@ -485,7 +510,7 @@ spkrioctl(struct cdev *dev, unsigned long cmd, caddr_t cmdarg, int flags,
else else
tone(tp->frequency, tp->duration); tone(tp->frequency, tp->duration);
sx_xunlock(&spkr_op_locked); sx_xunlock(&spkr_op_locked);
return 0; return (0);
} else if (cmd == SPKRTUNE) { } else if (cmd == SPKRTUNE) {
tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg);
tone_t ttp; tone_t ttp;
@@ -496,7 +521,7 @@ spkrioctl(struct cdev *dev, unsigned long cmd, caddr_t cmdarg, int flags,
error = copyin(tp, &ttp, sizeof(tone_t)); error = copyin(tp, &ttp, sizeof(tone_t));
if (error) { if (error) {
sx_xunlock(&spkr_op_locked); sx_xunlock(&spkr_op_locked);
return(error); return (error);
} }
if (ttp.duration == 0) if (ttp.duration == 0)
@@ -508,9 +533,9 @@ spkrioctl(struct cdev *dev, unsigned long cmd, caddr_t cmdarg, int flags,
tone(ttp.frequency, ttp.duration); tone(ttp.frequency, ttp.duration);
} }
sx_xunlock(&spkr_op_locked); sx_xunlock(&spkr_op_locked);
return(0); return (0);
} }
return(EINVAL); return (EINVAL);
} }
static struct cdev *speaker_dev; static struct cdev *speaker_dev;