The diff bellow allows programs to get notifications about changes of
audio controls. Programs open the /dev/audioctlN device node and use
the read(4) syscall to get the index of each changed control.

There may be only one reader at a time. Supporting multiple readers
would be much more complicated and the purpose of this diff is to
allow sndiod(8) to expose the controls to programs. So, there will be
one reader only.

To test the diff, run:

        hexdump -ve '1/4 "%d\n"' </dev/audioctl0

then use mixerctl(1) or the laptop volume keys to change controls;
hexdump displays a single line (with the index) every time a control
is changed. As I have no volume keys on my laptop, test reports are
appreciated.

Note that this diff uses /dev/audioctlN (and not /dev/mixerN) and
enables /dev/audioctlN to be used as a "mixer" control device. This is
necessary because certain ports (like system tray apps) open
/dev/mixerN for reading and hold it open; restricting /dev/mixerN to a
single reader would prevent other programs from using it. Basically
this makes /dev/audioctlN and /dev/mixerN equivalent; once ports are
properly handled the plan is to delete one of them.

Thoughts? OKs?

Index: audio.c
===================================================================
RCS file: /cvs/src/sys/dev/audio.c,v
retrieving revision 1.185
diff -u -p -u -p -r1.185 audio.c
--- audio.c     24 Jan 2020 05:38:33 -0000      1.185
+++ audio.c     26 Jan 2020 06:09:50 -0000
@@ -97,6 +97,14 @@ struct wskbd_vol
 #endif
 
 /*
+ * event indicating that a control was changed
+ */
+struct mixer_ev {
+       struct mixer_ev *next;
+       int pending;
+};
+
+/*
  * device structure
  */
 struct audio_softc {
@@ -122,6 +130,11 @@ struct audio_softc {
        void (*conv_dec)(unsigned char *, int); /* decode to user */
        struct mixer_ctrl *mix_ents;    /* mixer state for suspend/resume */
        int mix_nent;                   /* size of mixer state */
+       int mix_isopen;                 /* mixer open for reading */
+       int mix_blocking;               /* read() blocking */
+       struct selinfo mix_sel;         /* wakeup poll(2) */
+       struct mixer_ev *mix_evbuf;     /* per mixer-control event */
+       struct mixer_ev *mix_pending;   /* list of changed controls */
 #if NWSKBD > 0
        struct wskbd_vol spkr, mic;
        struct task wskbd_task;
@@ -1192,6 +1205,8 @@ audio_attach(struct device *parent, stru
        sc->mix_nent = mi->index;
        sc->mix_ents = mallocarray(sc->mix_nent,
            sizeof(struct mixer_ctrl), M_DEVBUF, M_WAITOK);
+       sc->mix_evbuf = mallocarray(sc->mix_nent,
+           sizeof(struct mixer_ev), M_DEVBUF, M_WAITOK | M_ZERO);
 
        ent = sc->mix_ents;
        mi->index = 0;
@@ -1329,6 +1344,7 @@ audio_detach(struct device *self, int fl
        }
 
        /* free resources */
+       free(sc->mix_evbuf, M_DEVBUF, sc->mix_nent * sizeof(struct mixer_ev));
        free(sc->mix_ents, M_DEVBUF, sc->mix_nent * sizeof(struct mixer_ctrl));
        audio_buf_done(sc, &sc->play);
        audio_buf_done(sc, &sc->rec);
@@ -1718,6 +1734,28 @@ audio_ioctl(struct audio_softc *sc, unsi
        return error;
 }
 
+void
+audio_event(struct audio_softc *sc, int addr)
+{
+       struct mixer_ev *e;
+
+       mtx_enter(&audio_lock);
+       if (sc->mix_isopen) {
+               e = sc->mix_evbuf + addr;
+               if (!e->pending) {
+                       e->pending = 1;
+                       e->next = sc->mix_pending;
+                       sc->mix_pending = e;
+               }
+               if (sc->mix_blocking) {
+                       wakeup(&sc->mix_blocking);
+                       sc->mix_blocking = 0;
+               }
+               selwakeup(&sc->mix_sel);
+       }
+       mtx_leave(&audio_lock);
+}
+
 int
 audio_mixer_devinfo(struct audio_softc *sc, struct mixer_devinfo *devinfo)
 {
@@ -1784,6 +1822,7 @@ audio_mixer_set(struct audio_softc *sc, 
                        return error;
                if (sc->ops->commit_settings)
                        return sc->ops->commit_settings(sc->arg);
+               audio_event(sc, c->dev);
                return 0;
        }
 
@@ -1834,6 +1873,107 @@ audio_ioctl_mixer(struct audio_softc *sc
 }
 
 int
+audio_mixer_read(struct audio_softc *sc, struct uio *uio, int ioflag)
+{      
+       struct mixer_ev *e;
+       int data;
+       int error;
+
+       DPRINTF("%s: mixer read: resid = %zd\n", DEVNAME(sc), uio->uio_resid);
+
+       /* block if quiesced */
+       while (sc->quiesce)
+               tsleep_nsec(&sc->quiesce, 0, "mix_qrd", INFSLP);
+
+       mtx_enter(&audio_lock);
+
+       /* if there are no events then sleep */
+       while (!sc->mix_pending) {
+               if (ioflag & IO_NDELAY) {
+                       mtx_leave(&audio_lock);
+                       return EWOULDBLOCK;
+               }
+               DPRINTF("%s: mixer read sleep\n", DEVNAME(sc));
+               sc->mix_blocking = 1;
+               error = msleep_nsec(&sc->mix_blocking,
+                   &audio_lock, PWAIT | PCATCH, "mix_rd", INFSLP);
+               if (!(sc->dev.dv_flags & DVF_ACTIVE))
+                       error = EIO;
+               if (error) {
+                       DPRINTF("%s: mixer read woke up error = %d\n",
+                           DEVNAME(sc), error);
+                       mtx_leave(&audio_lock);
+                       return error;
+               }
+       }
+
+       /* at this stage, there is an event to transfer */
+       while (uio->uio_resid >= sizeof(int) && sc->mix_pending) {
+               e = sc->mix_pending;
+               sc->mix_pending = e->next;
+               e->pending = 0;
+               data = e - sc->mix_evbuf;
+               mtx_leave(&audio_lock);
+               DPRINTF("%s: mixer read: %u\n", DEVNAME(sc), data);
+               error = uiomove(&data, sizeof(int), uio);
+               if (error)
+                       return error;
+               mtx_enter(&audio_lock);
+       }
+
+       mtx_leave(&audio_lock);
+       return 0;
+}
+
+int
+audio_mixer_poll(struct audio_softc *sc, int events, struct proc *p)
+{
+       int revents = 0;
+
+       mtx_enter(&audio_lock);
+       if (sc->mix_isopen && sc->mix_pending)
+               revents |= events & (POLLIN | POLLRDNORM);
+       if (revents == 0) {
+               if (events & (POLLIN | POLLRDNORM))
+                       selrecord(p, &sc->mix_sel);
+       }
+       mtx_leave(&audio_lock);
+       return revents;
+}
+
+int
+audio_mixer_open(struct audio_softc *sc, int flags)
+{
+       DPRINTF("%s: flags = 0x%x\n", __func__, flags);
+
+       if (flags & FREAD) {
+               if (sc->mix_isopen)
+                       return EBUSY;
+               sc->mix_isopen = 1;
+       }
+       return 0;
+}
+
+int
+audio_mixer_close(struct audio_softc *sc, int flags)
+{
+       int i;
+
+       DPRINTF("%s: flags = 0x%x\n", __func__, flags);
+
+       if (flags & FREAD) {
+               sc->mix_isopen = 0;
+
+               mtx_enter(&audio_lock);
+               sc->mix_pending = NULL;
+               for (i = 0; i < sc->mix_nent; i++)
+                       sc->mix_evbuf[i].pending = 0;
+               mtx_leave(&audio_lock);
+       }
+       return 0;
+}
+
+int
 audio_poll(struct audio_softc *sc, int events, struct proc *p)
 {
        int revents = 0;
@@ -1870,6 +2010,8 @@ audioopen(dev_t dev, int flags, int mode
                        error = audio_open(sc, flags);
                        break;
                case AUDIO_DEV_AUDIOCTL:
+                       error = audio_mixer_open(sc, flags);
+                       break;
                case AUDIO_DEV_MIXER:
                        error = 0;
                        break;
@@ -1894,8 +2036,10 @@ audioclose(dev_t dev, int flags, int ifm
        case AUDIO_DEV_AUDIO:
                error = audio_close(sc);
                break;
-       case AUDIO_DEV_MIXER:
        case AUDIO_DEV_AUDIOCTL:
+               error = audio_mixer_close(sc, flags);
+               break;
+       case AUDIO_DEV_MIXER:
                error = 0;
                break;
        default:
@@ -1919,6 +2063,8 @@ audioread(dev_t dev, struct uio *uio, in
                error = audio_read(sc, uio, ioflag);
                break;
        case AUDIO_DEV_AUDIOCTL:
+               error = audio_mixer_read(sc, uio, ioflag);
+               break;
        case AUDIO_DEV_MIXER:
                error = ENODEV;
                break;
@@ -1975,7 +2121,12 @@ audioioctl(dev_t dev, u_long cmd, caddr_
                        error = ENXIO;
                        break;
                }
-               error = audio_ioctl(sc, cmd, addr);
+               if (cmd == AUDIO_MIXER_DEVINFO ||
+                   cmd == AUDIO_MIXER_READ ||
+                   cmd == AUDIO_MIXER_WRITE)
+                       error = audio_ioctl_mixer(sc, cmd, addr, p);
+               else
+                       error = audio_ioctl(sc, cmd, addr);
                break;
        case AUDIO_DEV_MIXER:
                error = audio_ioctl_mixer(sc, cmd, addr, p);
@@ -2001,6 +2152,8 @@ audiopoll(dev_t dev, int events, struct 
                revents = audio_poll(sc, events, p);
                break;
        case AUDIO_DEV_AUDIOCTL:
+               revents = audio_mixer_poll(sc, events, p);
+               break;
        case AUDIO_DEV_MIXER:
        default:
                revents = 0;
@@ -2144,6 +2297,7 @@ wskbd_mixer_update(struct audio_softc *s
                        DPRINTF("%s: set mute err = %d\n", DEVNAME(sc), error);
                        return;
                }
+               audio_event(sc, vol->mute);
        }
        if (vol->val >= 0 && val_pending) {
                ctrl.dev = vol->val;
@@ -2169,6 +2323,7 @@ wskbd_mixer_update(struct audio_softc *s
                        DPRINTF("%s: set vol err = %d\n", DEVNAME(sc), error);
                        return;
                }
+               audio_event(sc, vol->val);
        }
 }
 

Reply via email to