On Thu, Feb 13, 2020 at 04:52:12AM +0000, Raf Czlonka wrote:
> 
> Hi Alexandre,
> 
> I have to say that I also find the two ranges mildly confusing,
> i.e. 0-255 in one place, and 0-127 in another. In terms of units,
> personally, I'm used to, and quite like, the granularity of 0-255.
> 
> Again, not my place so others will certainly be more help here.
> 
> One more point regarding the interface, though.
> 
> This is the way mixerctl(1) currently behaves:
> 
>       $ mixerctl outputs.master     
>       outputs.master=255,255
>       $ mixerctl outputs.master=100 
>       outputs.master: 255,255 -> 100,100
>       $ mixerctl outputs.master=300 
>       outputs.master: 100,100 -> 255,255
> 
> Should sndioctl(1) behave the same way?

Many thanks for the feedback.

After some thinking and experimenting, floats in the [0:1] seem the
simplest option. It avoids discussions about preferences and allows
arbitrary precision (if needed, later).

Furthermore, as all controls are in the [0:1] range, it makes sense to
request the user to provide numbers between 0 and 1. Providing numbers
outside this range indicates he is misunderstanding how the program
works.

Below are all 3 base diffs combined (libsndio, sndiod, sndioctl,
libossaudio), to ease testing.

Index: include/sndio.h
===================================================================
RCS file: /cvs/src/include/sndio.h,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 sndio.h
--- include/sndio.h     20 Dec 2015 11:29:29 -0000      1.9
+++ include/sndio.h     24 Feb 2020 06:03:28 -0000
@@ -26,10 +26,16 @@
 #define MIO_PORTANY    "default"
 
 /*
+ * limits
+ */
+#define SIOCTL_NAMEMAX         12      /* max name length */
+
+/*
  * private ``handle'' structure
  */
 struct sio_hdl;
 struct mio_hdl;
+struct sioctl_hdl;
 
 /*
  * parameters of a full-duplex stream
@@ -85,12 +91,41 @@ struct sio_cap {
 #define SIO_XSTRINGS { "ignore", "sync", "error" }
 
 /*
+ * controlled component of the device
+ */
+struct sioctl_node {
+       char name[SIOCTL_NAMEMAX];      /* ex. "spkr" */
+       int unit;                       /* optional number or -1 */
+};
+
+/*
+ * description of a control (index, value) pair
+ */
+struct sioctl_desc {
+       unsigned int addr;              /* control address */
+#define SIOCTL_NONE            0       /* deleted */
+#define SIOCTL_NUM             2       /* integer in the 0..127 range */
+#define SIOCTL_SW              3       /* on/off switch (0 or 1) */
+#define SIOCTL_VEC             4       /* number, element of vector */
+#define SIOCTL_LIST            5       /* switch, element of a list */
+       unsigned int type;              /* one of above */
+       char func[SIOCTL_NAMEMAX];      /* function name, ex. "level" */
+       char group[SIOCTL_NAMEMAX];     /* group this control belongs to */
+       struct sioctl_node node0;       /* affected node */
+       struct sioctl_node node1;       /* dito for SIOCTL_{VEC,LIST} */
+       unsigned int maxval;            /* max value for SIOCTL_{NUM,VEC} */
+       int __pad[3];
+};
+
+/*
  * mode bitmap
  */
 #define SIO_PLAY       1
 #define SIO_REC                2
 #define MIO_OUT                4
 #define MIO_IN         8
+#define SIOCTL_READ    0x100
+#define SIOCTL_WRITE   0x200
 
 /*
  * default bytes per sample for the given bits per sample
@@ -144,10 +179,24 @@ int mio_pollfd(struct mio_hdl *, struct 
 int mio_revents(struct mio_hdl *, struct pollfd *);
 int mio_eof(struct mio_hdl *);
 
+struct sioctl_hdl *sioctl_open(const char *, unsigned int, int);
+void sioctl_close(struct sioctl_hdl *);
+int sioctl_ondesc(struct sioctl_hdl *,
+    void (*)(void *, struct sioctl_desc *, int), void *);
+int sioctl_onval(struct sioctl_hdl *,
+    void (*)(void *, unsigned int, unsigned int), void *);
+int sioctl_setval(struct sioctl_hdl *, unsigned int, unsigned int);
+int sioctl_nfds(struct sioctl_hdl *);
+int sioctl_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+int sioctl_revents(struct sioctl_hdl *, struct pollfd *);
+int sioctl_eof(struct sioctl_hdl *);
+
 int mio_rmidi_getfd(const char *, unsigned int, int);
 struct mio_hdl *mio_rmidi_fdopen(int, unsigned int, int);
 int sio_sun_getfd(const char *, unsigned int, int);
 struct sio_hdl *sio_sun_fdopen(int, unsigned int, int);
+int sioctl_sun_getfd(const char *, unsigned int, int);
+struct sioctl_hdl *sioctl_sun_fdopen(int, unsigned int, int);
 
 #ifdef __cplusplus
 }
Index: lib/libsndio/Makefile
===================================================================
RCS file: /cvs/src/lib/libsndio/Makefile,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 Makefile
--- lib/libsndio/Makefile       26 Dec 2017 15:23:33 -0000      1.13
+++ lib/libsndio/Makefile       24 Feb 2020 06:03:28 -0000
@@ -1,9 +1,10 @@
 #      $OpenBSD: Makefile,v 1.13 2017/12/26 15:23:33 jca Exp $
 
 LIB=   sndio
-MAN=   sio_open.3 mio_open.3 sndio.7
+MAN=   sio_open.3 mio_open.3 sioctl_open.3 sndio.7
 SRCS=  debug.c aucat.c sio_aucat.c sio_sun.c sio.c \
-       mio_rmidi.c mio_aucat.c mio.c
+       mio_rmidi.c mio_aucat.c mio.c \
+       sioctl_aucat.c sioctl_sun.c sioctl.c
 CFLAGS+=-DDEBUG
 COPTS+=        -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith 
-Wundef
 
Index: lib/libsndio/Symbols.map
===================================================================
RCS file: /cvs/src/lib/libsndio/Symbols.map,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 Symbols.map
--- lib/libsndio/Symbols.map    26 Dec 2017 19:12:22 -0000      1.1
+++ lib/libsndio/Symbols.map    24 Feb 2020 06:03:28 -0000
@@ -27,10 +27,22 @@
                mio_revents;
                mio_eof;
 
+               sioctl_open;
+               sioctl_close;
+               sioctl_ondesc;
+               sioctl_onval;
+               sioctl_setval;
+               sioctl_nfds;
+               sioctl_pollfd;
+               sioctl_revents;
+               sioctl_eof;
+
                mio_rmidi_getfd;
                mio_rmidi_fdopen;
                sio_sun_getfd;
                sio_sun_fdopen;
+               sioctl_sun_getfd;
+               sioctl_sun_fdopen;
        local:
                *;
 };
Index: lib/libsndio/amsg.h
===================================================================
RCS file: /cvs/src/lib/libsndio/amsg.h,v
retrieving revision 1.12
diff -u -p -u -p -r1.12 amsg.h
--- lib/libsndio/amsg.h 12 Jul 2019 06:30:55 -0000      1.12
+++ lib/libsndio/amsg.h 24 Feb 2020 06:03:28 -0000
@@ -43,6 +43,11 @@
 #define AUCAT_PORT             11025
 
 /*
+ * limits
+ */
+#define AMSG_CTL_NAMEMAX       16      /* max name length */
+
+/*
  * WARNING: since the protocol may be simultaneously used by static
  * binaries or by different versions of a shared library, we are not
  * allowed to change the packet binary representation in a backward
@@ -64,6 +69,9 @@ struct amsg {
 #define AMSG_HELLO     10      /* say hello, check versions and so ... */
 #define AMSG_BYE       11      /* ask server to drop connection */
 #define AMSG_AUTH      12      /* send authentication cookie */
+#define AMSG_CTLSUB    13      /* ondesc/onctl subscription */
+#define AMSG_CTLSET    14      /* set control value */
+#define AMSG_CTLSYNC   15      /* end of controls descriptions */
        uint32_t cmd;
        uint32_t __pad;
        union {
@@ -108,7 +116,38 @@ struct amsg {
 #define AMSG_COOKIELEN 16
                        uint8_t cookie[AMSG_COOKIELEN];
                } auth;
+               struct amsg_ctlsub {
+                       uint8_t desc, val;
+               } ctlsub;
+               struct amsg_ctlset {
+                       uint16_t addr, val;
+               } ctlset;
        } u;
+};
+
+/*
+ * network representation of sioctl_node structure
+ */
+struct amsg_ctl_node {
+       char name[AMSG_CTL_NAMEMAX];
+       int16_t unit;
+       uint8_t __pad[2];
+};
+
+/*
+ * network representation of sioctl_desc structure
+ */
+struct amsg_ctl_desc {
+       struct amsg_ctl_node node0;     /* affected channels */
+       struct amsg_ctl_node node1;     /* dito for AMSG_CTL_{SEL,VEC,LIST} */
+       char func[AMSG_CTL_NAMEMAX];    /* parameter function name */
+       char group[AMSG_CTL_NAMEMAX];   /* group of the control */
+       uint8_t type;                   /* see sioctl_desc structure */
+       uint8_t __pad1[1];
+       uint16_t addr;                  /* control address */
+       uint16_t maxval;
+       uint16_t curval;
+       uint32_t __pad2[3];
 };
 
 /*
Index: lib/libsndio/shlib_version
===================================================================
RCS file: /cvs/src/lib/libsndio/shlib_version,v
retrieving revision 1.11
diff -u -p -u -p -r1.11 shlib_version
--- lib/libsndio/shlib_version  26 Dec 2017 15:23:33 -0000      1.11
+++ lib/libsndio/shlib_version  24 Feb 2020 06:03:28 -0000
@@ -1,2 +1,2 @@
 major=7
-minor=0
+minor=1
Index: lib/libsndio/sioctl.c
===================================================================
RCS file: lib/libsndio/sioctl.c
diff -N lib/libsndio/sioctl.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl.c       24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,177 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "sioctl_priv.h"
+
+struct sioctl_hdl *
+sioctl_open(const char *str, unsigned int mode, int nbio)
+{
+       static char devany[] = SIO_DEVANY;
+       struct sioctl_hdl *hdl;
+
+#ifdef DEBUG
+       _sndio_debug_init();
+#endif
+       if (str == NULL) /* backward compat */
+               str = devany;
+       if (strcmp(str, devany) == 0 && !issetugid()) {
+               str = getenv("AUDIODEVICE");
+               if (str == NULL)
+                       str = devany;
+       }
+       if (strcmp(str, devany) == 0) {
+               hdl = _sioctl_aucat_open("snd/0", mode, nbio);
+               if (hdl != NULL)
+                       return hdl;
+               return _sioctl_sun_open("rsnd/0", mode, nbio);
+       }
+       if (_sndio_parsetype(str, "snd"))
+               return _sioctl_aucat_open(str, mode, nbio);
+       if (_sndio_parsetype(str, "rsnd"))
+               return _sioctl_sun_open(str, mode, nbio);
+       DPRINTF("sioctl_open: %s: unknown device type\n", str);
+       return NULL;
+}
+
+void
+_sioctl_create(struct sioctl_hdl *hdl, struct sioctl_ops *ops,
+    unsigned int mode, int nbio)
+{
+       hdl->ops = ops;
+       hdl->mode = mode;
+       hdl->nbio = nbio;
+       hdl->eof = 0;
+       hdl->ctl_cb = NULL;
+}
+
+int
+_sioctl_psleep(struct sioctl_hdl *hdl, int event)
+{
+       struct pollfd pfds[SIOCTL_MAXNFDS];
+       int revents, nfds;
+
+       for (;;) {
+               nfds = sioctl_pollfd(hdl, pfds, event);
+               if (nfds == 0)
+                       return 0;
+               while (poll(pfds, nfds, -1) < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       DPERROR("sioctl_psleep: poll");
+                       hdl->eof = 1;
+                       return 0;
+               }
+               revents = sioctl_revents(hdl, pfds);
+               if (revents & POLLHUP) {
+                       DPRINTF("sioctl_psleep: hang-up\n");
+                       return 0;
+               }
+               if (event == 0 || (revents & event))
+                       break;
+       }
+       return 1;
+}
+
+void
+sioctl_close(struct sioctl_hdl *hdl)
+{
+       hdl->ops->close(hdl);
+}
+
+int
+sioctl_nfds(struct sioctl_hdl *hdl)
+{
+       return hdl->ops->nfds(hdl);
+}
+
+int
+sioctl_pollfd(struct sioctl_hdl *hdl, struct pollfd *pfd, int events)
+{
+       if (hdl->eof)
+               return 0;
+       return hdl->ops->pollfd(hdl, pfd, events);
+}
+
+int
+sioctl_revents(struct sioctl_hdl *hdl, struct pollfd *pfd)
+{
+       if (hdl->eof)
+               return POLLHUP;
+       return hdl->ops->revents(hdl, pfd);
+}
+
+int
+sioctl_eof(struct sioctl_hdl *hdl)
+{
+       return hdl->eof;
+}
+
+int
+sioctl_ondesc(struct sioctl_hdl *hdl,
+    void (*cb)(void *, struct sioctl_desc *, int), void *arg)
+{
+       hdl->desc_cb = cb;
+       hdl->desc_arg = arg;
+       return hdl->ops->ondesc(hdl);
+}
+
+int
+sioctl_onval(struct sioctl_hdl *hdl,
+    void (*cb)(void *, unsigned int, unsigned int), void *arg)
+{
+       hdl->ctl_cb = cb;
+       hdl->ctl_arg = arg;
+       return hdl->ops->onctl(hdl);
+}
+
+void
+_sioctl_ondesc_cb(struct sioctl_hdl *hdl,
+    struct sioctl_desc *desc, unsigned int val)
+{
+       if (desc) {
+               DPRINTF("_sioctl_ondesc_cb: %u -> %s[%d].%s=%s[%d]:%d\n",
+                   desc->addr,
+                   desc->node0.name, desc->node0.unit,
+                   desc->func,
+                   desc->node1.name, desc->node1.unit,
+                   val);
+       }
+       if (hdl->desc_cb)
+               hdl->desc_cb(hdl->desc_arg, desc, val);
+}
+
+void
+_sioctl_onval_cb(struct sioctl_hdl *hdl, unsigned int addr, unsigned int val)
+{
+       DPRINTF("_sioctl_onval_cb: %u -> %u\n", addr, val);
+       if (hdl->ctl_cb)
+               hdl->ctl_cb(hdl->ctl_arg, addr, val);
+}
+
+int
+sioctl_setval(struct sioctl_hdl *hdl, unsigned int addr, unsigned int val)
+{
+       if (!(hdl->mode & SIOCTL_WRITE))
+               return 0;
+       return hdl->ops->setctl(hdl, addr, val);
+}
Index: lib/libsndio/sioctl_aucat.c
===================================================================
RCS file: lib/libsndio/sioctl_aucat.c
diff -N lib/libsndio/sioctl_aucat.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_aucat.c 24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <errno.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sndio.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include "debug.h"
+#include "aucat.h"
+#include "sioctl_priv.h"
+
+struct sioctl_aucat_hdl {
+       struct sioctl_hdl sioctl;
+       struct aucat aucat;
+       struct sioctl_desc desc;
+       struct amsg_ctl_desc buf[16];
+       size_t buf_wpos;
+       int dump_wait;
+};
+
+static void sioctl_aucat_close(struct sioctl_hdl *);
+static int sioctl_aucat_nfds(struct sioctl_hdl *);
+static int sioctl_aucat_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+static int sioctl_aucat_revents(struct sioctl_hdl *, struct pollfd *);
+static int sioctl_aucat_setctl(struct sioctl_hdl *, unsigned int, unsigned 
int);
+static int sioctl_aucat_onval(struct sioctl_hdl *);
+static int sioctl_aucat_ondesc(struct sioctl_hdl *);
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops sioctl_aucat_ops = {
+       sioctl_aucat_close,
+       sioctl_aucat_nfds,
+       sioctl_aucat_pollfd,
+       sioctl_aucat_revents,
+       sioctl_aucat_setctl,
+       sioctl_aucat_onval,
+       sioctl_aucat_ondesc
+};
+
+static int
+sioctl_aucat_rdata(struct sioctl_aucat_hdl *hdl)
+{
+       struct sioctl_desc desc;
+       struct amsg_ctl_desc *c;
+       size_t rpos;
+       int n;
+
+       while (hdl->aucat.rstate == RSTATE_DATA) {
+
+               /* read entries */
+               while (hdl->buf_wpos < sizeof(hdl->buf) &&
+                   hdl->aucat.rstate == RSTATE_DATA) {
+                       n = _aucat_rdata(&hdl->aucat,
+                           (unsigned char *)hdl->buf + hdl->buf_wpos,
+                           sizeof(hdl->buf) - hdl->buf_wpos,
+                           &hdl->sioctl.eof);
+                       if (n == 0 || hdl->sioctl.eof)
+                               return 0;
+                       hdl->buf_wpos += n;
+               }
+
+               /* parse entries */
+               c = hdl->buf;
+               rpos = 0;
+               while (rpos < hdl->buf_wpos) {
+                       strlcpy(desc.group, c->group, SIOCTL_NAMEMAX);
+                       strlcpy(desc.node0.name, c->node0.name, SIOCTL_NAMEMAX);
+                       desc.node0.unit = (int16_t)ntohs(c->node0.unit);
+                       strlcpy(desc.node1.name, c->node1.name, SIOCTL_NAMEMAX);
+                       desc.node1.unit = (int16_t)ntohs(c->node1.unit);
+                       strlcpy(desc.func, c->func, SIOCTL_NAMEMAX);
+                       desc.type = c->type;
+                       desc.addr = ntohs(c->addr);
+                       desc.maxval = ntohs(c->maxval);
+                       _sioctl_ondesc_cb(&hdl->sioctl,
+                           &desc, ntohs(c->curval));
+                       rpos += sizeof(struct amsg_ctl_desc);
+                       c++;
+               }
+               hdl->buf_wpos = 0;
+       }
+       return 1;
+}
+
+/*
+ * execute the next message, return 0 if blocked
+ */
+static int
+sioctl_aucat_runmsg(struct sioctl_aucat_hdl *hdl)
+{
+       if (!_aucat_rmsg(&hdl->aucat, &hdl->sioctl.eof))
+               return 0;
+       switch (ntohl(hdl->aucat.rmsg.cmd)) {
+       case AMSG_DATA:
+               hdl->buf_wpos = 0;
+               if (!sioctl_aucat_rdata(hdl))
+                       return 0;
+               break;
+       case AMSG_CTLSET:
+               DPRINTF("sioctl_aucat_runmsg: got CTLSET\n");
+               _sioctl_onval_cb(&hdl->sioctl,
+                   ntohs(hdl->aucat.rmsg.u.ctlset.addr),
+                   ntohs(hdl->aucat.rmsg.u.ctlset.val));
+               break;
+       case AMSG_CTLSYNC:
+               DPRINTF("sioctl_aucat_runmsg: got CTLSYNC\n");
+               hdl->dump_wait = 0;
+               _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
+               break;
+       default:
+               DPRINTF("sio_aucat_runmsg: unhandled message %u\n",
+                   hdl->aucat.rmsg.cmd);
+               hdl->sioctl.eof = 1;
+               return 0;
+       }
+       hdl->aucat.rstate = RSTATE_MSG;
+       hdl->aucat.rtodo = sizeof(struct amsg);
+       return 1;
+}
+
+struct sioctl_hdl *
+_sioctl_aucat_open(const char *str, unsigned int mode, int nbio)
+{
+       struct sioctl_aucat_hdl *hdl;
+
+       hdl = malloc(sizeof(struct sioctl_aucat_hdl));
+       if (hdl == NULL)
+               return NULL;
+       if (!_aucat_open(&hdl->aucat, str, mode))
+               goto bad;
+       _sioctl_create(&hdl->sioctl, &sioctl_aucat_ops, mode, nbio);
+       if (!_aucat_setfl(&hdl->aucat, 1, &hdl->sioctl.eof))
+               goto bad;
+       hdl->dump_wait = 0;
+       return (struct sioctl_hdl *)hdl;
+bad:
+       free(hdl);
+       return NULL;
+}
+
+static void
+sioctl_aucat_close(struct sioctl_hdl *addr)
+{
+       struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+       if (!hdl->sioctl.eof)
+               _aucat_setfl(&hdl->aucat, 0, &hdl->sioctl.eof);
+       _aucat_close(&hdl->aucat, hdl->sioctl.eof);
+       free(hdl);
+}
+
+static int
+sioctl_aucat_ondesc(struct sioctl_hdl *addr)
+{
+       struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+       while (hdl->aucat.wstate != WSTATE_IDLE) {
+               if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
+                       return 0;
+       }
+       AMSG_INIT(&hdl->aucat.wmsg);
+       hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
+       hdl->aucat.wmsg.u.ctlsub.desc = 1;
+       hdl->aucat.wmsg.u.ctlsub.val = 0;
+       hdl->aucat.wtodo = sizeof(struct amsg);
+       if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+               return 0;
+       hdl->dump_wait = 1;
+       while (hdl->dump_wait) {
+               DPRINTF("psleeping...\n");
+               if (!_sioctl_psleep(&hdl->sioctl, 0))
+                       return 0;
+               DPRINTF("psleeping done\n");
+       }
+       DPRINTF("done\n");
+       return 1;
+}
+
+static int
+sioctl_aucat_onval(struct sioctl_hdl *addr)
+{
+       struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+       while (hdl->aucat.wstate != WSTATE_IDLE) {
+               if (!_sioctl_psleep(&hdl->sioctl, POLLOUT))
+                       return 0;
+       }
+       AMSG_INIT(&hdl->aucat.wmsg);
+       hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSUB);
+       hdl->aucat.wmsg.u.ctlsub.desc = 1;
+       hdl->aucat.wmsg.u.ctlsub.val = 1;
+       hdl->aucat.wtodo = sizeof(struct amsg);
+       if (!_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+               return 0;
+       return 1;
+}
+
+static int
+sioctl_aucat_setctl(struct sioctl_hdl *addr, unsigned int a, unsigned int v)
+{
+       struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+       hdl->aucat.wstate = WSTATE_MSG;
+       hdl->aucat.wtodo = sizeof(struct amsg);
+       hdl->aucat.wmsg.cmd = htonl(AMSG_CTLSET);
+       hdl->aucat.wmsg.u.ctlset.addr = htons(a);
+       hdl->aucat.wmsg.u.ctlset.val = htons(v);
+       while (hdl->aucat.wstate != WSTATE_IDLE) {
+               if (_aucat_wmsg(&hdl->aucat, &hdl->sioctl.eof))
+                       break;
+               if (hdl->sioctl.nbio || !_sioctl_psleep(&hdl->sioctl, POLLOUT))
+                       return 0;
+       }
+       return 1;
+}
+
+static int
+sioctl_aucat_nfds(struct sioctl_hdl *addr)
+{
+       return 1;
+}
+
+static int
+sioctl_aucat_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
+{
+       struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+
+       return _aucat_pollfd(&hdl->aucat, pfd, events | POLLIN);
+}
+
+static int
+sioctl_aucat_revents(struct sioctl_hdl *addr, struct pollfd *pfd)
+{
+       struct sioctl_aucat_hdl *hdl = (struct sioctl_aucat_hdl *)addr;
+       int revents;
+
+       revents = _aucat_revents(&hdl->aucat, pfd);
+       if (revents & POLLIN) {
+               while (1) {
+                       if (hdl->aucat.rstate == RSTATE_MSG) {
+                               if (!sioctl_aucat_runmsg(hdl))
+                                       break;
+                       }
+                       if (hdl->aucat.rstate == RSTATE_DATA) {
+                               if (!sioctl_aucat_rdata(hdl))
+                                       break;
+                       }
+               }
+               revents &= ~POLLIN;
+       }
+       if (hdl->sioctl.eof)
+               return POLLHUP;
+       DPRINTFN(3, "sioctl_aucat_revents: revents = 0x%x\n", revents);
+       return revents;
+}
Index: lib/libsndio/sioctl_open.3
===================================================================
RCS file: lib/libsndio/sioctl_open.3
diff -N lib/libsndio/sioctl_open.3
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_open.3  24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,252 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2011 Alexandre Ratchov <[email protected]>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: September 29 2012 $
+.Dt SIO_OPEN 3
+.Os
+.Sh NAME
+.Nm sioctl_open ,
+.Nm sioctl_close ,
+.Nm sioctl_ondesc ,
+.Nm sioctl_onval ,
+.Nm sioctl_setval ,
+.Nm sioctl_nfds ,
+.Nm sioctl_pollfd ,
+.Nm sioctl_eof
+.Nd interface to audio parameters
+.Sh SYNOPSIS
+.Fd #include <sndio.h>
+.Ft "struct sioctl_hdl *"
+.Fn "sioctl_open" "const char *name" "unsigned int mode" "int nbio_flag"
+.Ft "void"
+.Fn "sioctl_close" "struct sioctl_hdl *hdl"
+.Ft "int"
+.Fn "sioctl_ondesc" "struct sioctl_hdl *hdl" "void (*cb)(void *arg, struct 
sioctl_desc *desc, int val)" "void *arg"
+.Ft "void"
+.Fn "sioctl_onval" "struct sioctl_hdl *hdl" "void (*cb)(void *arg, unsigned 
int addr, unsigned int val)" "void *arg"
+.Ft "int"
+.Fn "sioctl_setval" "struct sioctl_hdl *hdl" "unsigned int addr" "unsigned int 
val"
+.Ft "int"
+.Fn "sioctl_nfds" "struct sioctl_hdl *hdl"
+.Ft "int"
+.Fn "sioctl_pollfd" "struct sioctl_hdl *hdl" "struct pollfd *pfd" "int events"
+.Ft "int"
+.Fn "sioctl_revents" "struct sioctl_hdl *hdl" "struct pollfd *pfd"
+.Ft "int"
+.Fn "sioctl_eof" "struct sioctl_hdl *hdl"
+.Sh DESCRIPTION
+Audio devices may expose a number of controls, like the playback volume 
control.
+Each control has an integer
+.Em address
+and an integer
+.Em value .
+Depending on the control type, its integer value represents either a
+continuous quantity or a boolean.
+Any control may be changed by submitting
+a new value to its address.
+When values change, asynchronous notifications are sent.
+.Pp
+Controls descriptions are available, allowing them to be grouped and
+represented in a human usable form.
+.Sh Opening and closing the control device
+First the application must call the
+.Fn sioctl_open
+function to obtain a handle
+that will be passed as the
+.Ar hdl
+argument to other functions.
+.Pp
+The
+.Ar name
+parameter gives the device string discussed in
+.Xr sndio 7 .
+In most cases it should be set to SIOCTL_DEVANY to allow
+the user to select it using the
+.Ev AUDIODEVICE
+environment variable.
+The
+.Ar mode
+parameter is a bitmap of the SIOCTL_READ and SIOCTL_WRITE constants
+indicating whether control values can be read and
+modified respectively.
+.Pp
+If the
+.Ar nbio_flag
+argument is 1, then the
+.Fn sioctl_setval
+function (see below) may fail instead of blocking and
+the
+.Fn sioctl_ondesc
+function doesn't block.
+.Pp
+The
+.Fn sioctl_close
+function closes the control device and frees any allocated resources
+associated with the handle.
+.Sh Controls descriptions
+The
+.Fn sioctl_ondesc
+function can be used to obtain the description of all available controls
+and their initial values.
+It registers a call-back that is immediately invoked for all
+controls.
+It's called once with a NULL argument to indicate that the full
+description was sent and that the caller has a consistent
+representation of the controls set.
+.Pp
+Then, whenever a control description changes, the call-back is
+invoked with the updated information followed by a call with a NULL
+argument.
+.Pp
+Controls are described by the
+.Va sioctl_ondesc
+stucture as follows:
+.Bd -literal
+struct sioctl_node {
+       char name[SIOCTL_NAMEMAX];      /* ex. "spkr" */
+       int unit;                       /* optional number or -1 */
+};
+
+struct sioctl_desc {
+       unsigned int addr;              /* control address */
+#define SIOCTL_NONE            0       /* deleted */
+#define SIOCTL_NUM             2       /* integer in the 0..127 range */
+#define SIOCTL_SW              3       /* on/off switch (0 or 1) */
+#define SIOCTL_VEC             4       /* number, element of vector */
+#define SIOCTL_LIST            5       /* switch, element of a list */
+       unsigned int type;              /* one of above */
+       char func[SIOCTL_NAMEMAX];      /* function name, ex. "level" */
+       char group[SIOCTL_NAMEMAX];     /* group this control belongs to */
+       struct sioctl_node node0;       /* affected node */
+       struct sioctl_node node1;       /* dito for SIOCTL_{VEC,LIST} */
+};
+.Ed
+.Pp
+The
+.Va addr
+attribute is the control address, usable with
+.Fn sioctl_setval
+to set its value.
+.Pp
+The
+.Va type
+attribute indicates what the structure describes.
+Possible types are:
+.Bl -tag -width "SIOCTL_LIST"
+.It SIOCTL_NONE
+A previously valid control was deleted.
+.It SIOCTL_NUM
+A continuous control in the 0..SIOCTL_VALMAX range.
+For instance the volume of the speaker.
+.It SIOCTL_SW
+A on/off switch control.
+For instance the switch to mute the speaker.
+.It SIOCTL_VEC
+Element of an array of continuous controls.
+For instance the knob to control the amount of signal flowing
+from the line input to the speaker.
+.It SIOCTL_LIST
+An element of an array of on/off switches.
+For instance the line-in position of the
+speaker source selector.
+.El
+.Pp
+The
+.Va func
+attribute is the name of the parameter being controlled.
+There may be no parameters of different types with the same name.
+.Pp
+The
+.Va node0
+and
+.Va node1
+attributes indicate the names of the controlled nodes, typically
+channels of audio streams.
+.Va node1
+is meaningful for
+.Va SIOCTL_VEC
+and
+.Va SIOCTL_LIST
+only.
+.Pp
+Names in the
+.Va node0
+and
+.Va node1
+attributes and
+.Va func
+are strings usable as unique identifiers within the the given
+.Va group .
+.Sh Changing and reading control values
+Controls are changed with the
+.Fn sioctl_setval
+function, by giving the index of the control and the new value.
+The
+.Fn sioctl_onval
+function can be used to register a call-back which will be invoked whenever
+a control changes.
+Continuous values are in the 0..127 range.
+.Sh "Interface to" Xr poll 2
+The
+.Fn sioctl_pollfd
+function fills the array
+.Ar pfd
+of
+.Va pollfd
+structures, used by
+.Xr poll 2 ,
+with
+.Ar events ;
+the latter is a bit-mask of
+.Va POLLIN
+and
+.Va POLLOUT
+constants.
+.Fn sioctl_pollfd
+returns the number of
+.Va pollfd
+structures filled.
+The
+.Fn sioctl_revents
+function returns the bit-mask set by
+.Xr poll 2
+in the
+.Va pfd
+array of
+.Va pollfd
+structures.
+If
+.Va POLLOUT
+is set,
+.Fn sioctl_setval
+can be called without blocking.
+POLLHUP may be set if an error occurs, even if
+it is not selected with
+.Fn sioctl_pollfd .
+POLLIN is not used yet.
+.Pp
+The
+.Fn sioctl_nfds
+function returns the number of
+.Va pollfd
+structures the caller must preallocate in order to be sure
+that
+.Fn sioctl_pollfd
+will never overrun.
+.Sh SEE ALSO
+.Xr sndioctl 1 ,
+.Xr poll 2 ,
+.Xr sndio 7
Index: lib/libsndio/sioctl_priv.h
===================================================================
RCS file: lib/libsndio/sioctl_priv.h
diff -N lib/libsndio/sioctl_priv.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_priv.h  24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,62 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef SIOCTL_PRIV_H
+#define SIOCTL_PRIV_H
+
+#include <sndio.h>
+
+#define SIOCTL_MAXNFDS 4
+
+/*
+ * private ``handle'' structure
+ */
+struct sioctl_hdl {
+       struct sioctl_ops *ops;
+       void (*desc_cb)(void *, struct sioctl_desc *, int);
+       void *desc_arg;
+       void (*ctl_cb)(void *, unsigned int, unsigned int);
+       void *ctl_arg;
+       unsigned int mode;              /* SIOCTL_READ | SIOCTL_WRITE */
+       int nbio;                       /* true if non-blocking io */
+       int eof;                        /* true if error occured */
+};
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops {
+       void (*close)(struct sioctl_hdl *);
+       int (*nfds)(struct sioctl_hdl *);
+       int (*pollfd)(struct sioctl_hdl *, struct pollfd *, int);
+       int (*revents)(struct sioctl_hdl *, struct pollfd *);
+       int (*setctl)(struct sioctl_hdl *, unsigned int, unsigned int);
+       int (*onctl)(struct sioctl_hdl *);
+       int (*ondesc)(struct sioctl_hdl *);
+};
+
+struct sioctl_hdl *_sioctl_aucat_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_obsd_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_fake_open(const char *, unsigned int, int);
+struct sioctl_hdl *_sioctl_sun_open(const char *, unsigned int, int);
+void _sioctl_create(struct sioctl_hdl *,
+    struct sioctl_ops *, unsigned int, int);
+void _sioctl_ondesc_cb(struct sioctl_hdl *,
+    struct sioctl_desc *, unsigned int);
+void _sioctl_onval_cb(struct sioctl_hdl *, unsigned int, unsigned int);
+int _sioctl_psleep(struct sioctl_hdl *, int);
+
+#endif /* !defined(SIOCTL_PRIV_H) */
Index: lib/libsndio/sioctl_sun.c
===================================================================
RCS file: lib/libsndio/sioctl_sun.c
diff -N lib/libsndio/sioctl_sun.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/libsndio/sioctl_sun.c   24 Feb 2020 06:03:30 -0000
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+ * the way the sun mixer is designed doesn't let us representing
+ * it easily with the sioctl api. For now expose only few
+ * white-listed controls the same way as we do in kernel
+ * for the wskbd volume keys.
+ */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "sioctl_priv.h"
+
+#define DEVPATH_PREFIX "/dev/audioctl"
+#define DEVPATH_MAX    (1 +            \
+       sizeof(DEVPATH_PREFIX) - 1 +    \
+       sizeof(int) * 3)
+
+struct volume
+{
+       int nch;                        /* channels in the level control */
+       int level_idx;                  /* index of the level control */
+       int level_val[8];               /* current value */
+       int mute_idx;                   /* index of the mute control */
+       int mute_val;                   /* per channel state of mute control */
+       int base_addr;
+       char *name;
+};
+
+struct sioctl_sun_hdl {
+       struct sioctl_hdl sioctl;
+       struct volume output, input;
+       int fd, events;
+};
+
+static void sioctl_sun_close(struct sioctl_hdl *);
+static int sioctl_sun_nfds(struct sioctl_hdl *);
+static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int);
+static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *);
+static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
+static int sioctl_sun_onval(struct sioctl_hdl *);
+static int sioctl_sun_ondesc(struct sioctl_hdl *);
+
+/*
+ * operations every device should support
+ */
+struct sioctl_ops sioctl_sun_ops = {
+       sioctl_sun_close,
+       sioctl_sun_nfds,
+       sioctl_sun_pollfd,
+       sioctl_sun_revents,
+       sioctl_sun_setctl,
+       sioctl_sun_onval,
+       sioctl_sun_ondesc
+};
+
+static int
+initmute(struct sioctl_sun_hdl *hdl, struct mixer_devinfo *info)
+{
+       struct mixer_devinfo mi;
+
+       mi.index = info->next;
+       for (mi.index = info->next; mi.index != -1; mi.index = mi.next) {
+               if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
+                       break;
+               if (strcmp(mi.label.name, AudioNmute) == 0)
+                       return mi.index;
+       }
+       return -1;
+}
+
+static int
+initvol(struct sioctl_sun_hdl *hdl, struct volume *vol, char *cn, char *dn)
+{
+       struct mixer_devinfo dev, cls;
+
+       for (dev.index = 0; ; dev.index++) {
+               if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &dev) < 0)
+                       break;
+               if (dev.type != AUDIO_MIXER_VALUE)
+                       continue;
+               cls.index = dev.mixer_class;
+               if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &cls) < 0)
+                       break;
+               if (strcmp(cls.label.name, cn) == 0 &&
+                   strcmp(dev.label.name, dn) == 0) {
+                       vol->nch = dev.un.v.num_channels;
+                       vol->level_idx = dev.index;
+                       vol->mute_idx = initmute(hdl, &dev);
+                       DPRINTF("using %s.%s, %d channels, %s\n", cn, dn,
+                           vol->nch, vol->mute_idx >= 0 ? "mute" : "no mute");
+                       return 1;
+               }
+       }
+       vol->level_idx = vol->mute_idx = -1;
+       return 0;
+}
+
+static void
+init(struct sioctl_sun_hdl *hdl)
+{
+       static struct {
+               char *cn, *dn;
+       } output_names[] = {
+               {AudioCoutputs, AudioNmaster},
+               {AudioCinputs,  AudioNdac},
+               {AudioCoutputs, AudioNdac},
+               {AudioCoutputs, AudioNoutput}
+       }, input_names[] = {
+               {AudioCrecord, AudioNrecord},
+               {AudioCrecord, AudioNvolume},
+               {AudioCinputs, AudioNrecord},
+               {AudioCinputs, AudioNvolume},
+               {AudioCinputs, AudioNinput}
+       };
+       int i;
+
+       for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) {
+               if (initvol(hdl, &hdl->output,
+                       output_names[i].cn, output_names[i].dn)) {
+                       hdl->output.name = "output";
+                       hdl->output.base_addr = 0;
+                       break;
+               }
+       }
+       for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) {
+               if (initvol(hdl, &hdl->input,
+                       input_names[i].cn, input_names[i].dn)) {
+                       hdl->input.name = "input";
+                       hdl->input.base_addr = 64;
+                       break;
+               }
+       }
+}
+
+static int
+setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val)
+{
+       struct mixer_ctrl ctrl;
+       int i;
+
+       addr -= vol->base_addr;
+       if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) {
+               if (vol->level_val[addr] == val) {
+                       DPRINTF("level %d, no change\n", val);
+                       return 1;
+               }
+               vol->level_val[addr] = val;
+               ctrl.dev = vol->level_idx;
+               ctrl.type = AUDIO_MIXER_VALUE;
+               ctrl.un.value.num_channels = vol->nch;
+               for (i = 0; i < vol->nch; i++)
+                       ctrl.un.value.level[i] = vol->level_val[i];
+               DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]);
+               if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
+                       DPRINTF("level write failed\n");
+                       return 0;
+               }
+               _sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val);
+               return 1;
+       }
+
+       addr -= 32;
+       if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) {
+               val = val ? 1 : 0;
+               if (vol->mute_val == val) {
+                       DPRINTF("mute %d, no change\n", val);
+                       return 1;
+               }
+               vol->mute_val = val;
+               ctrl.dev = vol->mute_idx;
+               ctrl.type = AUDIO_MIXER_ENUM;
+               ctrl.un.ord = val;
+               DPRINTF("mute setting to %d\n", val);
+               if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
+                       DPERROR("mute write\n");
+                       return 0;
+               }
+               for (i = 0; i < vol->nch; i++) {
+                       _sioctl_onval_cb(&hdl->sioctl,
+                           vol->base_addr + 32 + i, val);
+               }
+               return 1;
+       }
+       return 1;
+}
+
+static int
+scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol)
+{
+       struct sioctl_desc desc;
+       struct mixer_ctrl ctrl;
+       int i, val;
+
+       memset(&desc, 0, sizeof(struct sioctl_desc));
+       if (vol->level_idx >= 0) {
+               ctrl.dev = vol->level_idx;
+               ctrl.type = AUDIO_MIXER_VALUE;
+               ctrl.un.value.num_channels = vol->nch;
+               if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
+                       DPRINTF("level read failed\n");
+                       return 0;
+               }
+               desc.type = SIOCTL_NUM;
+               desc.maxval = AUDIO_MAX_GAIN;
+               desc.node1.name[0] = 0;
+               desc.node1.unit = -1;
+               strlcpy(desc.func, "level", SIOCTL_NAMEMAX);
+               strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
+               for (i = 0; i < vol->nch; i++) {
+                       desc.node0.unit = i;
+                       desc.addr = vol->base_addr + i;
+                       val = ctrl.un.value.level[i];
+                       vol->level_val[i] = val;
+                       _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
+               }
+       }
+       if (vol->mute_idx >= 0) {
+               ctrl.dev = vol->mute_idx;
+               ctrl.type = AUDIO_MIXER_ENUM;
+               if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
+                       DPRINTF("mute read failed\n");
+                       return 0;
+               }
+               desc.type = SIOCTL_SW;
+               desc.maxval = 1;
+               desc.node1.name[0] = 0;
+               desc.node1.unit = -1;
+               strlcpy(desc.func, "mute", SIOCTL_NAMEMAX);
+               strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
+               val = ctrl.un.ord ? 1 : 0;
+               vol->mute_val = val;
+               for (i = 0; i < vol->nch; i++) {
+                       desc.node0.unit = i;
+                       desc.addr = vol->base_addr + 32 + i;
+                       _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
+               }
+       }
+       return 1;
+}
+
+static int
+updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx)
+{
+       struct mixer_ctrl ctrl;
+       int val, i;
+
+       if (idx == vol->mute_idx)
+               ctrl.type = AUDIO_MIXER_ENUM;
+       else {
+               ctrl.type = AUDIO_MIXER_VALUE;
+               ctrl.un.value.num_channels = vol->nch;
+       }
+       ctrl.dev = idx;
+       if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) {
+               DPERROR("sioctl_sun_revents: ioctl\n");
+               hdl->sioctl.eof = 1;
+               return 0;
+       }
+       if (idx == vol->mute_idx) {
+               val = ctrl.un.ord ? 1 : 0;
+               if (vol->mute_val == val)
+                       return 1;
+               vol->mute_val = val;
+               for (i = 0; i < vol->nch; i++) {
+                       _sioctl_onval_cb(&hdl->sioctl,
+                           vol->base_addr + 32 + i, val);
+               }
+       } else {
+               for (i = 0; i < vol->nch; i++) {
+                       val = ctrl.un.value.level[i];
+                       if (vol->level_val[i] == val)
+                               continue;
+                       vol->level_val[i] = val;
+                       _sioctl_onval_cb(&hdl->sioctl,
+                           vol->base_addr + i, val);
+               }
+       }
+       return 1;
+}
+
+int
+sioctl_sun_getfd(const char *str, unsigned int mode, int nbio)
+{
+       const char *p;
+       char path[DEVPATH_MAX];
+       unsigned int devnum;
+       int fd, flags;
+
+#ifdef DEBUG
+       _sndio_debug_init();
+#endif
+       p = _sndio_parsetype(str, "rsnd");
+       if (p == NULL) {
+               DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str);
+               return -1;
+       }
+       switch (*p) {
+       case '/':
+               p++;
+               break;
+       default:
+               DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str);
+               return -1;
+       }
+       if (strcmp(p, "default") == 0) {
+               devnum = 0;
+       } else {
+               p = _sndio_parsenum(p, &devnum, 255);
+               if (p == NULL || *p != '\0') {
+                       DPRINTF("sioctl_sun_getfd: %s: number expected after 
'/'\n", str);
+                       return -1;
+               }
+       }
+       snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
+       if (mode == (SIOCTL_READ | SIOCTL_WRITE))
+               flags = O_RDWR;
+       else
+               flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY;
+       while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) {
+               if (errno == EINTR)
+                       continue;
+               DPERROR(path);
+               return -1;
+       }
+       return fd;
+}
+
+struct sioctl_hdl *
+sioctl_sun_fdopen(int fd, unsigned int mode, int nbio)
+{
+       struct sioctl_sun_hdl *hdl;
+
+#ifdef DEBUG
+       _sndio_debug_init();
+#endif
+       hdl = malloc(sizeof(struct sioctl_sun_hdl));
+       if (hdl == NULL)
+               return NULL;
+       _sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio);
+       hdl->fd = fd;
+       init(hdl);
+       return (struct sioctl_hdl *)hdl;
+}
+
+struct sioctl_hdl *
+_sioctl_sun_open(const char *str, unsigned int mode, int nbio)
+{
+       struct sioctl_hdl *hdl;
+       int fd;
+
+       fd = sioctl_sun_getfd(str, mode, nbio);
+       if (fd < 0)
+               return NULL;
+       hdl = sioctl_sun_fdopen(fd, mode, nbio);
+       if (hdl != NULL)
+               return hdl;
+       while (close(fd) < 0 && errno == EINTR)
+               ; /* retry */
+       return NULL;
+}
+
+static void
+sioctl_sun_close(struct sioctl_hdl *addr)
+{
+       struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+       close(hdl->fd);
+       free(hdl);
+}
+
+static int
+sioctl_sun_ondesc(struct sioctl_hdl *addr)
+{
+       struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+       if (!scanvol(hdl, &hdl->output) ||
+           !scanvol(hdl, &hdl->input)) {
+               hdl->sioctl.eof = 1;
+               return 0;
+       }
+       _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
+       return 1;
+}
+
+static int
+sioctl_sun_onval(struct sioctl_hdl *addr)
+{
+       return 1;
+}
+
+static int
+sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val)
+{
+       struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
+
+       if (!setvol(hdl, &hdl->output, addr, val) ||
+           !setvol(hdl, &hdl->input, addr, val)) {
+               hdl->sioctl.eof = 1;
+               return 0;
+       }
+       return 1;
+}
+
+static int
+sioctl_sun_nfds(struct sioctl_hdl *addr)
+{
+       return 1;
+}
+
+static int
+sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
+{
+       struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
+
+       pfd->fd = hdl->fd;
+       pfd->events = POLLIN;
+       hdl->events = events;
+       return 1;
+}
+
+static int
+sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd)
+{
+       struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
+       struct volume *vol;
+       int idx, n;
+
+       if (pfd->revents & POLLIN) {
+               while (1) {
+                       n = read(hdl->fd, &idx, sizeof(int));
+                       if (n == -1) {
+                               if (errno == EINTR || errno == EAGAIN)
+                                       break;
+                               DPERROR("read");
+                               hdl->sioctl.eof = 1;
+                               return POLLHUP;
+                       }
+                       if (n < sizeof(int)) {
+                               DPRINTF("sioctl_sun_revents: short read\n");
+                               hdl->sioctl.eof = 1;
+                               return POLLHUP;
+                       }
+
+                       if (idx == hdl->output.level_idx ||
+                           idx == hdl->output.mute_idx) {
+                               vol = &hdl->output;
+                       } else if (idx == hdl->input.level_idx ||
+                           idx == hdl->input.mute_idx) {
+                               vol = &hdl->input;
+                       } else
+                               continue;
+
+                       if (!updatevol(hdl, vol, idx))
+                               return POLLHUP;
+               }
+       }
+       return hdl->events & POLLOUT;
+}
Index: usr.bin/sndiod/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/Makefile,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 Makefile
--- usr.bin/sndiod/Makefile     7 Jan 2016 07:41:01 -0000       1.5
+++ usr.bin/sndiod/Makefile     24 Feb 2020 06:03:30 -0000
@@ -1,8 +1,8 @@
 #      $OpenBSD: Makefile,v 1.5 2016/01/07 07:41:01 ratchov Exp $
 
 PROG=  sndiod
-SRCS=  abuf.c dev.c dsp.c fdpass.c file.c listen.c midi.c miofile.c \
-       opt.c siofile.c sndiod.c sock.c utils.c
+SRCS=  abuf.c dev.c dev_sioctl.c dsp.c fdpass.c file.c listen.c \
+       midi.c miofile.c opt.c siofile.c sndiod.c sock.c utils.c
 MAN=   sndiod.8
 CFLAGS+=-DDEBUG -I${.CURDIR}/../../lib/libsndio
 COPTS+=        -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith 
-Wundef
Index: usr.bin/sndiod/defs.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/defs.h,v
retrieving revision 1.4
diff -u -p -u -p -r1.4 defs.h
--- usr.bin/sndiod/defs.h       28 Jul 2019 09:44:10 -0000      1.4
+++ usr.bin/sndiod/defs.h       24 Feb 2020 06:03:30 -0000
@@ -37,9 +37,12 @@
 #define MODE_MIDIOUT   0x04    /* allowed to read midi */
 #define MODE_MIDIIN    0x08    /* allowed to write midi */
 #define MODE_MON       0x10    /* allowed to monitor */
+#define MODE_CTLREAD   0x100   /* allowed to read controls */
+#define MODE_CTLWRITE  0x200   /* allowed to change controls */
 #define MODE_RECMASK   (MODE_REC | MODE_MON)
 #define MODE_AUDIOMASK (MODE_PLAY | MODE_REC | MODE_MON)
 #define MODE_MIDIMASK  (MODE_MIDIIN | MODE_MIDIOUT)
+#define MODE_CTLMASK   (MODE_CTLREAD | MODE_CTLWRITE)
 
 /*
  * underrun/overrun policies, must be the same as SIO_ constants
Index: usr.bin/sndiod/dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
retrieving revision 1.63
diff -u -p -u -p -r1.63 dev.c
--- usr.bin/sndiod/dev.c        10 Jan 2020 19:01:55 -0000      1.63
+++ usr.bin/sndiod/dev.c        24 Feb 2020 06:03:31 -0000
@@ -75,6 +75,7 @@ void dev_mmcstart(struct dev *);
 void dev_mmcstop(struct dev *);
 void dev_mmcloc(struct dev *, unsigned int);
 
+void slot_ctlname(struct slot *, char *, size_t);
 void slot_log(struct slot *);
 void slot_del(struct slot *);
 void slot_setvol(struct slot *, unsigned int);
@@ -91,6 +92,9 @@ void slot_write(struct slot *);
 void slot_read(struct slot *);
 int slot_skip(struct slot *);
 
+void ctl_node_log(struct ctl_node *);
+void ctl_log(struct ctl *);
+
 struct midiops dev_midiops = {
        dev_midi_imsg,
        dev_midi_omsg,
@@ -129,15 +133,22 @@ dev_log(struct dev *d)
 }
 
 void
+slot_ctlname(struct slot *s, char *name, size_t size)
+{
+       snprintf(name, size, "%s%u", s->name, s->unit);
+}
+
+void
 slot_log(struct slot *s)
 {
+       char name[CTL_NAMEMAX];
 #ifdef DEBUG
        static char *pstates[] = {
                "ini", "sta", "rdy", "run", "stp", "mid"
        };
 #endif
-       log_puts(s->name);
-       log_putu(s->unit);
+       slot_ctlname(s, name, CTL_NAMEMAX);
+       log_puts(name);
 #ifdef DEBUG
        if (log_level >= 3) {
                log_puts(" vol=");
@@ -365,10 +376,8 @@ dev_midi_slotdesc(struct dev *d, struct 
        x.dev = SYSEX_DEV_ANY;
        x.id0 = SYSEX_AUCAT;
        x.id1 = SYSEX_AUCAT_SLOTDESC;
-       if (*s->name != '\0') {
-               snprintf((char *)x.u.slotdesc.name, SYSEX_NAMELEN,
-                   "%s%u", s->name, s->unit);
-       }
+       if (*s->name != '\0')
+               slot_ctlname(s, (char *)x.u.slotdesc.name, SYSEX_NAMELEN);
        x.u.slotdesc.chan = s - d->slot;
        x.u.slotdesc.end = SYSEX_END;
        midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(slotdesc));
@@ -419,6 +428,7 @@ dev_midi_omsg(void *arg, unsigned char *
                if (chan >= DEV_NSLOT)
                        return;
                slot_setvol(d->slot + chan, msg[2]);
+               dev_onval(d, CTLADDR_SLOT_LEVEL(chan), msg[2]);
                return;
        }
        x = (struct sysex *)msg;
@@ -429,8 +439,11 @@ dev_midi_omsg(void *arg, unsigned char *
        switch (x->type) {
        case SYSEX_TYPE_RT:
                if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) {
-                       if (len == SYSEX_SIZE(master))
+                       if (len == SYSEX_SIZE(master)) {
                                dev_master(d, x->u.master.coarse);
+                               dev_onval(d, CTLADDR_MASTER,
+                                   x->u.master.coarse);
+                       }
                        return;
                }
                if (x->id0 != SYSEX_MMC)
@@ -1001,10 +1014,17 @@ dev_new(char *path, struct aparams *par,
                d->slot[i].serial = d->serial++;
                strlcpy(d->slot[i].name, "prog", SLOT_NAMEMAX);
        }
+       for (i = 0; i < DEV_NCTLSLOT; i++) {
+               d->ctlslot[i].ops = NULL;
+               d->ctlslot[i].dev = d;
+               d->ctlslot[i].mask = 0;
+               d->ctlslot[i].mode = 0;
+       }
        d->slot_list = NULL;
        d->master = MIDI_MAXCTL;
        d->mtc.origin = 0;
        d->tstate = MMC_STOP;
+       d->ctl_list = NULL;
        d->next = dev_list;
        dev_list = d;
        return d;
@@ -1097,6 +1117,9 @@ dev_allocbufs(struct dev *d)
 int
 dev_open(struct dev *d)
 {
+       int i;
+       char name[CTL_NAMEMAX];
+
        d->mode = d->reqmode;
        d->round = d->reqround;
        d->bufsz = d->reqbufsz;
@@ -1117,6 +1140,17 @@ dev_open(struct dev *d)
        }
        if (!dev_allocbufs(d))
                return 0;
+
+       for (i = 0; i < DEV_NSLOT; i++) {
+               slot_ctlname(&d->slot[i], name, CTL_NAMEMAX);
+               dev_addctl(d, "app", CTL_NUM,
+                   CTLADDR_SLOT_LEVEL(i),
+                   name, -1, "level",
+                   NULL, -1, 127, d->slot[i].vol);
+       }
+       dev_addctl(d, "", CTL_NUM,
+           CTLADDR_MASTER, "output", -1, "level", NULL, -1, 127, d->master);
+
        d->pstate = DEV_INIT;
        return 1;
 }
@@ -1129,6 +1163,7 @@ dev_exitall(struct dev *d)
 {
        int i;
        struct slot *s;
+       struct ctlslot *c;
 
        for (s = d->slot, i = DEV_NSLOT; i > 0; i--, s++) {
                if (s->ops)
@@ -1136,6 +1171,12 @@ dev_exitall(struct dev *d)
                s->ops = NULL;
        }
        d->slot_list = NULL;
+
+       for (c = d->ctlslot, i = DEV_NCTLSLOT; i > 0; i--, c++) {
+               if (c->ops)
+                       c->ops->exit(c->arg);
+               c->ops = NULL;
+       }
 }
 
 /*
@@ -1169,10 +1210,18 @@ dev_freebufs(struct dev *d)
 void
 dev_close(struct dev *d)
 {
+       struct ctl *c;
+
        dev_exitall(d);
        d->pstate = DEV_CFG;
        dev_sio_close(d);
        dev_freebufs(d);
+
+       /* there are no clients, just free remaining local controls */
+       while ((c = d->ctl_list) != NULL) {
+               d->ctl_list = c->next;
+               xfree(c);
+       }
 }
 
 /*
@@ -1183,6 +1232,7 @@ int
 dev_reopen(struct dev *d)
 {
        struct slot *s;
+       struct ctl *c, **pc;
        long long pos;
        unsigned int pstate;
        int delta;
@@ -1236,6 +1286,25 @@ dev_reopen(struct dev *d)
                }
        }
 
+       /* remove controls of old device */
+       pc = &d->ctl_list;
+       while ((c = *pc) != NULL) {
+               if (c->addr >= CTLADDR_END) {
+                       c->refs_mask &= ~CTL_DEVMASK;
+                       if (c->refs_mask == 0) {
+                               *pc = c->next;
+                               xfree(c);
+                               continue;
+                       }
+                       c->type = CTL_NONE;
+                       c->desc_mask = ~0;
+               }
+               pc = &c->next;
+       }
+
+       /* add new device controls */
+       dev_sioctl_open(d);
+
        /* start the device if needed */
        if (pstate == DEV_RUN)
                dev_wakeup(d);
@@ -1760,6 +1829,7 @@ found:
        }
        if (!dev_ref(d))
                return NULL;
+       dev_label(d, s - d->slot);
        if ((mode & d->mode) != mode) {
                if (log_level >= 1) {
                        slot_log(s);
@@ -2096,4 +2166,279 @@ void
 slot_read(struct slot *s)
 {
        slot_skip_update(s);
+}
+
+/*
+ * allocate at control slot
+ */
+struct ctlslot *
+ctlslot_new(struct dev *d, struct ctlops *ops, void *arg)
+{
+       struct ctlslot *s;
+       struct ctl *c;
+       int i;
+
+       i = 0;
+       for (;;) {
+               if (i == DEV_NCTLSLOT)
+                       return NULL;
+               s = d->ctlslot + i;
+               if (s->ops == NULL)
+                       break;
+               i++;
+       }
+       s->dev = d;
+       s->mask = 1 << i;
+       if (!dev_ref(d))
+               return NULL;
+       s->ops = ops;
+       s->arg = arg;
+       for (c = d->ctl_list; c != NULL; c = c->next)
+               c->refs_mask |= s->mask;
+       return s;
+}
+
+/*
+ * free control slot
+ */
+void
+ctlslot_del(struct ctlslot *s)
+{
+       struct ctl *c, **pc;
+
+       pc = &s->dev->ctl_list;
+       while ((c = *pc) != NULL) {
+               c->refs_mask &= ~s->mask;
+               if (c->refs_mask == 0) {
+                       *pc = c->next;
+                       xfree(c);
+               } else
+                       pc = &c->next;
+       }
+       s->ops = NULL;
+       dev_unref(s->dev);
+}
+
+void
+ctl_node_log(struct ctl_node *c)
+{
+       log_puts(c->name);
+       if (c->unit >= 0)
+               log_putu(c->unit);
+}
+
+void
+ctl_log(struct ctl *c)
+{
+       if (c->group[0] != 0) {
+               log_puts(c->group);
+               log_puts("/");
+       }
+       ctl_node_log(&c->node0);
+       log_puts(".");
+       log_puts(c->func);
+       log_puts("=");
+       switch (c->type) {
+       case CTL_NUM:
+       case CTL_SW:
+               log_putu(c->curval);
+               break;
+       case CTL_VEC:
+       case CTL_LIST:
+               ctl_node_log(&c->node1);
+               log_puts(":");
+               log_putu(c->curval);
+       }
+       log_puts(" at ");
+       log_putu(c->addr);
+}
+
+/*
+ * add a ctl
+ */
+struct ctl *
+dev_addctl(struct dev *d, char *gstr, int type, int addr,
+    char *str0, int unit0, char *func, char *str1, int unit1, int maxval, int 
val)
+{
+       struct ctl *c, **pc;
+       int i;
+
+       c = xmalloc(sizeof(struct ctl));
+       c->type = type;
+       strlcpy(c->func, func, CTL_NAMEMAX);
+       strlcpy(c->group, gstr, CTL_NAMEMAX);
+       strlcpy(c->node0.name, str0, CTL_NAMEMAX);
+       c->node0.unit = unit0;
+       if (c->type == CTL_VEC || c->type == CTL_LIST) {
+               strlcpy(c->node1.name, str1, CTL_NAMEMAX);
+               c->node1.unit = unit1;
+       } else
+               memset(&c->node1, 0, sizeof(struct ctl_node));
+       c->addr = addr;
+       c->maxval = maxval;
+       c->val_mask = ~0;
+       c->desc_mask = ~0;
+       c->curval = val;
+       c->dirty = 0;
+       c->refs_mask = 0;
+       for (i = 0; i < DEV_NCTLSLOT; i++) {
+               c->refs_mask |= CTL_DEVMASK;
+               if (d->ctlslot[i].ops != NULL)
+                       c->refs_mask |= 1 << i;
+       }
+       for (pc = &d->ctl_list; *pc != NULL; pc = &(*pc)->next)
+               ; /* nothing */
+       c->next = NULL;
+       *pc = c;
+#ifdef DEBUG
+       if (log_level >= 3) {
+               dev_log(d);
+               log_puts(": adding ");
+               ctl_log(c);
+               log_puts("\n");
+       }
+#endif
+       return c;
+}
+
+void
+dev_rmctl(struct dev *d, int addr)
+{
+       struct ctl *c, **pc;
+
+       pc = &d->ctl_list;
+       for (;;) {
+               c = *pc;
+               if (c == NULL)
+                       return;
+               if (c->type != CTL_NONE && c->addr == addr)
+                       break;
+               pc = &c->next;
+       }
+       c->type = CTL_NONE;
+#ifdef DEBUG
+       if (log_level >= 3) {
+               dev_log(d);
+               log_puts(": removing ");
+               ctl_log(c);
+               log_puts(", refs_mask = 0x");
+               log_putx(c->refs_mask);
+               log_puts("\n");
+       }
+#endif
+       c->refs_mask &= ~CTL_DEVMASK;
+       if (c->refs_mask != 0)
+               return;
+       *pc = c->next;
+       xfree(c);
+}
+
+int
+dev_setctl(struct dev *d, int addr, int val)
+{
+       struct ctl *c;
+       int num;
+
+       c = d->ctl_list;
+       for (;;) {
+               if (c == NULL) {
+                       if (log_level >= 3) {
+                               dev_log(d);
+                               log_puts(": ");
+                               log_putu(addr);
+                               log_puts(": no such ctl address\n");
+                       }
+                       return 0;
+               }
+               if (c->type != CTL_NONE && c->addr == addr)
+                       break;
+               c = c->next;
+       }
+       if (c->curval == val) {
+               if (log_level >= 3) {
+                       ctl_log(c);
+                       log_puts(": already set\n");
+               }
+               return 1;
+       }
+       if (val < 0 || val > c->maxval) {
+               if (log_level >= 3) {
+                       dev_log(d);
+                       log_puts(": ");
+                       log_putu(val);
+                       log_puts(": ctl val out of bounds\n");
+               }
+               return 0;
+       }
+       if (addr >= CTLADDR_END) {
+               if (log_level >= 3) {
+                       ctl_log(c);
+                       log_puts(": marked as dirty\n");
+               }
+               c->dirty = 1;
+               dev_ref(d);
+       } else {
+               if (addr == CTLADDR_MASTER) {
+                       dev_master(d, val);
+                       dev_midi_master(d);
+               } else {
+                       num = addr - CTLADDR_SLOT_LEVEL(0);
+                       slot_setvol(d->slot + num, val);
+                       dev_midi_vol(d, d->slot + num);
+               }
+       }
+       c->curval = val;
+       c->val_mask = ~0U;
+       return 1;
+}
+
+int
+dev_onval(struct dev *d, int addr, int val)
+{
+       struct ctl *c;
+
+       c = d->ctl_list;
+       for (;;) {
+               if (c == NULL)
+                       return 0;
+               if (c->type != CTL_NONE && c->addr == addr)
+                       break;
+               c = c->next;
+       }
+       c->curval = val;
+       c->val_mask = ~0U;
+       return 1;
+}
+
+void
+dev_label(struct dev *d, int i)
+{
+       struct ctl *c;
+       char name[CTL_NAMEMAX];
+
+       c = d->ctl_list;
+       for (;;) {
+               if (c == NULL)
+                       return;
+               if (c->addr == CTLADDR_SLOT_LEVEL(i))
+                       break;
+               c = c->next;
+       }
+       slot_ctlname(&d->slot[i], name, CTL_NAMEMAX);
+       if (strcmp(c->node0.name, name) == 0)
+               return;
+       strlcpy(c->node0.name, name, CTL_NAMEMAX);
+       c->desc_mask = ~0;
+}
+
+int
+dev_nctl(struct dev *d)
+{
+       struct ctl *c;
+       int n;
+
+       n = 0;
+       for (c = d->ctl_list; c != NULL; c = c->next)
+               n++;
+       return n;
 }
Index: usr.bin/sndiod/dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
retrieving revision 1.22
diff -u -p -u -p -r1.22 dev.h
--- usr.bin/sndiod/dev.h        21 Sep 2019 04:42:46 -0000      1.22
+++ usr.bin/sndiod/dev.h        24 Feb 2020 06:03:31 -0000
@@ -20,6 +20,11 @@
 #include "abuf.h"
 #include "dsp.h"
 #include "siofile.h"
+#include "dev_sioctl.h"
+
+#define CTLADDR_SLOT_LEVEL(n)  (n)
+#define CTLADDR_MASTER         (DEV_NSLOT)
+#define CTLADDR_END            (DEV_NSLOT + 1)
 
 /*
  * audio stream state structure
@@ -28,13 +33,18 @@
 struct slotops
 {
        void (*onmove)(void *);                 /* clock tick */
-       void (*onvol)(void *);          /* tell client vol changed */
+       void (*onvol)(void *);                  /* tell client vol changed */
        void (*fill)(void *);                   /* request to fill a play block 
*/
        void (*flush)(void *);                  /* request to flush a rec block 
*/
        void (*eof)(void *);                    /* notify that play drained */
        void (*exit)(void *);                   /* delete client */
 };
 
+struct ctlops
+{
+       void (*exit)(void *);                   /* delete client */
+};
+
 struct slot {
        struct slotops *ops;                    /* client callbacks */
        struct slot *next;                      /* next on the play list */
@@ -105,6 +115,44 @@ struct opt {
 };
 
 /*
+ * subset of channels of a stream
+ */
+
+struct ctl {
+       struct ctl *next;
+#define CTL_NONE       0               /* deleted */
+#define CTL_NUM                2               /* number (aka integer value) */
+#define CTL_SW         3               /* on/off switch, only bit 7 counts */
+#define CTL_VEC                4               /* number, element of vector */
+#define CTL_LIST       5               /* switch, element of a list */
+       unsigned int type;              /* one of above */
+       unsigned int addr;              /* control address */
+#define CTL_NAMEMAX    16              /* max name lenght */
+       char func[CTL_NAMEMAX];         /* parameter function name */
+       char group[CTL_NAMEMAX];        /* group aka namespace */
+       struct ctl_node {
+               char name[CTL_NAMEMAX]; /* stream name */
+               int unit;
+       } node0, node1;                 /* affected channels */
+#define CTL_DEVMASK            (1 << 31)
+#define CTL_SLOTMASK(i)                (1 << (i))
+       unsigned int val_mask;
+       unsigned int desc_mask;
+       unsigned int refs_mask;
+       unsigned int maxval;
+       unsigned int curval;
+       int dirty;
+};
+
+struct ctlslot {
+       struct ctlops *ops;
+       void *arg;
+       struct dev *dev;
+       unsigned int mask;
+       unsigned int mode;
+};
+
+/*
  * audio device with plenty of slots
  */
 struct dev {
@@ -117,6 +165,7 @@ struct dev {
         * audio device (while opened)
         */
        struct dev_sio sio;
+       struct dev_sioctl sioctl;
        struct aparams par;                     /* encoding */
        int pchan, rchan;                       /* play & rec channels */
        adata_t *rbuf;                          /* rec buffer */
@@ -195,6 +244,14 @@ struct dev {
 #define MMC_RUN                3                       /* started */
        unsigned int tstate;                    /* one of above */
        unsigned int master;                    /* master volume controller */
+
+       /*
+        * control
+        */
+
+       struct ctl *ctl_list;
+#define DEV_NCTLSLOT 8
+       struct ctlslot ctlslot[DEV_NCTLSLOT];
 };
 
 extern struct dev *dev_list;
@@ -241,5 +298,20 @@ void slot_start(struct slot *);
 void slot_stop(struct slot *);
 void slot_read(struct slot *);
 void slot_write(struct slot *);
+
+/*
+ * control related functions
+ */
+void ctl_log(struct ctl *);
+struct ctlslot *ctlslot_new(struct dev *, struct ctlops *, void *);
+void ctlslot_del(struct ctlslot *);
+int dev_setctl(struct dev *, int, int);
+int dev_onval(struct dev *, int, int);
+int dev_nctl(struct dev *);
+void dev_label(struct dev *, int);
+struct ctl *dev_addctl(struct dev *, char *, int, int,
+    char *, int, char *, char *, int, int, int);
+void dev_rmctl(struct dev *, int);
+int dev_makeunit(struct dev *, char *);
 
 #endif /* !defined(DEV_H) */
Index: usr.bin/sndiod/dev_sioctl.c
===================================================================
RCS file: usr.bin/sndiod/dev_sioctl.c
diff -N usr.bin/sndiod/dev_sioctl.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndiod/dev_sioctl.c 24 Feb 2020 06:03:31 -0000
@@ -0,0 +1,200 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <poll.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "abuf.h"
+#include "defs.h"
+#include "dev.h"
+#include "dsp.h"
+#include "file.h"
+#include "dev_sioctl.h"
+#include "utils.h"
+
+void dev_sioctl_ondesc(void *, struct sioctl_desc *, int);
+void dev_sioctl_onval(void *, unsigned int, unsigned int);
+int dev_sioctl_pollfd(void *, struct pollfd *);
+int dev_sioctl_revents(void *, struct pollfd *);
+void dev_sioctl_in(void *);
+void dev_sioctl_out(void *);
+void dev_sioctl_hup(void *);
+
+struct fileops dev_sioctl_ops = {
+       "sioctl",
+       dev_sioctl_pollfd,
+       dev_sioctl_revents,
+       dev_sioctl_in,
+       dev_sioctl_out,
+       dev_sioctl_hup
+};
+
+void
+dev_sioctl_ondesc(void *arg, struct sioctl_desc *desc, int val)
+{
+#define GROUP_PREFIX           "hw"
+       char group_buf[CTL_NAMEMAX], *group;
+       struct dev *d = arg;
+       int addr;
+
+       if (desc == NULL)
+               return;
+       addr = CTLADDR_END + desc->addr;
+       dev_rmctl(d, addr);
+
+       /*
+        * prefix group names we use (top-level and "app") with "hw."
+        * to ensure that all controls have unique names when multiple
+        * sndiod's are chained
+        */
+       if (desc->group[0] == 0)
+               group = GROUP_PREFIX;
+       else {
+               group = group_buf;
+               if (snprintf(group_buf, CTL_NAMEMAX, GROUP_PREFIX "/%s",
+                   desc->group) >= CTL_NAMEMAX)
+                       return;
+       }
+
+       dev_addctl(d, group, desc->type, addr,
+           desc->node0.name, desc->node0.unit, desc->func,
+           desc->node1.name, desc->node1.unit, desc->maxval, val);
+}
+
+void
+dev_sioctl_onval(void *arg, unsigned int addr, unsigned int val)
+{
+       struct dev *d = arg;
+       struct ctl *c;
+
+       addr += CTLADDR_END;
+
+       dev_log(d);
+       log_puts(": onctl: addr = ");
+       log_putu(addr);
+       log_puts(", val = ");
+       log_putu(val);
+       log_puts("\n");
+
+       for (c = d->ctl_list; c != NULL; c = c->next) {
+               if (c->addr != addr)
+                       continue;
+               ctl_log(c);
+               log_puts(": new value -> ");
+               log_putu(val);
+               log_puts("\n");
+               c->val_mask = ~0U;
+               c->curval = val;
+       }
+}
+
+/*
+ * open the control device.
+ */
+void
+dev_sioctl_open(struct dev *d)
+{
+       if (d->sioctl.hdl == NULL)
+               return;
+       sioctl_ondesc(d->sioctl.hdl, dev_sioctl_ondesc, d);
+       sioctl_onval(d->sioctl.hdl, dev_sioctl_onval, d);
+       d->sioctl.file = file_new(&dev_sioctl_ops, d, "mix",
+           sioctl_nfds(d->sioctl.hdl));
+}
+
+/*
+ * close the control device.
+ */
+void
+dev_sioctl_close(struct dev *d)
+{
+       if (d->sioctl.hdl == NULL)
+               return;
+       file_del(d->sioctl.file);
+}
+
+int
+dev_sioctl_pollfd(void *arg, struct pollfd *pfd)
+{
+       struct dev *d = arg;
+       struct ctl *c;
+       int events = 0;
+
+       for (c = d->ctl_list; c != NULL; c = c->next) {
+               if (c->dirty)
+                       events |= POLLOUT;
+       }
+       return sioctl_pollfd(d->sioctl.hdl, pfd, events);
+}
+
+int
+dev_sioctl_revents(void *arg, struct pollfd *pfd)
+{
+       struct dev *d = arg;
+
+       return sioctl_revents(d->sioctl.hdl, pfd);
+}
+
+void
+dev_sioctl_in(void *arg)
+{
+}
+
+void
+dev_sioctl_out(void *arg)
+{
+       struct dev *d = arg;
+       struct ctl *c;
+       int cnt;
+
+       /*
+        * for each dirty ctl, call sioctl_setval() and dev_unref(). As
+        * dev_unref() may destroy the ctl_list, we must call it after
+        * we've finished iterating on it.
+        */
+       cnt = 0;
+       for (c = d->ctl_list; c != NULL; c = c->next) {
+               if (!c->dirty)
+                       continue;
+               if (!sioctl_setval(d->sioctl.hdl,
+                       c->addr - CTLADDR_END, c->curval)) {
+                       ctl_log(c);
+                       log_puts(": set failed\n");
+                       break;
+               }
+               if (log_level >= 2) {
+                       ctl_log(c);
+                       log_puts(": changed\n");
+               }
+               c->dirty = 0;
+               cnt++;
+       }
+       while (cnt-- > 0)
+               dev_unref(d);
+}
+
+void
+dev_sioctl_hup(void *arg)
+{
+       struct dev *d = arg;
+
+       dev_sioctl_close(d);
+}
Index: usr.bin/sndiod/dev_sioctl.h
===================================================================
RCS file: usr.bin/sndiod/dev_sioctl.h
diff -N usr.bin/sndiod/dev_sioctl.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndiod/dev_sioctl.h 24 Feb 2020 06:03:31 -0000
@@ -0,0 +1,32 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef DEV_SIOCTL_H
+#define DEV_SIOCTL_H
+
+#include "file.h"
+
+struct dev;
+
+struct dev_sioctl {
+       struct sioctl_hdl *hdl;
+       struct file *file;
+};
+
+void dev_sioctl_open(struct dev *);
+void dev_sioctl_close(struct dev *);
+
+#endif /* !defined(DEV_SIOCTL_H) */
Index: usr.bin/sndiod/fdpass.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.c,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 fdpass.c
--- usr.bin/sndiod/fdpass.c     23 Jan 2020 05:40:09 -0000      1.8
+++ usr.bin/sndiod/fdpass.c     24 Feb 2020 06:03:32 -0000
@@ -32,6 +32,7 @@
 struct fdpass_msg {
 #define FDPASS_OPEN_SND                0       /* open an audio device */
 #define FDPASS_OPEN_MIDI       1       /* open a midi port */
+#define FDPASS_OPEN_CTL                2       /* open an audio control device 
*/
 #define FDPASS_RETURN          3       /* return after above commands */
        unsigned int cmd;               /* one of above */
        unsigned int num;               /* audio device or midi port number */
@@ -287,6 +288,22 @@ fdpass_mio_open(int num, int idx, unsign
        return mio_rmidi_fdopen(fd, mode, 1);
 }
 
+struct sioctl_hdl *
+fdpass_sioctl_open(int num, int idx, unsigned int mode)
+{
+       int fd;
+
+       if (fdpass_peer == NULL)
+               return NULL;
+       if (!fdpass_send(fdpass_peer, FDPASS_OPEN_CTL, num, idx, mode, -1))
+               return NULL;
+       if (!fdpass_waitret(fdpass_peer, &fd))
+               return NULL;
+       if (fd < 0)
+               return NULL;
+       return sioctl_sun_fdopen(fd, mode, 1);
+}
+
 void
 fdpass_in_worker(void *arg)
 {
@@ -345,6 +362,23 @@ fdpass_in_helper(void *arg)
                        return;
                }
                fd = mio_rmidi_getfd(path, mode, 1);
+               break;
+       case FDPASS_OPEN_CTL:
+               d = dev_bynum(num);
+               if (d == NULL || !(mode & (SIOCTL_READ | SIOCTL_WRITE))) {
+                       if (log_level >= 1) {
+                               fdpass_log(f);
+                               log_puts(": bad audio control device\n");
+                       }
+                       fdpass_close(f);
+                       return;
+               }
+               path = namelist_byindex(&d->path_list, idx);
+               if (path == NULL) {
+                       fdpass_close(f);
+                       return;
+               }
+               fd = sioctl_sun_getfd(path, mode, 1);
                break;
        default:
                fdpass_close(f);
Index: usr.bin/sndiod/fdpass.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.h,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 fdpass.h
--- usr.bin/sndiod/fdpass.h     23 Jan 2020 05:40:09 -0000      1.2
+++ usr.bin/sndiod/fdpass.h     24 Feb 2020 06:03:32 -0000
@@ -27,5 +27,6 @@ extern struct fdpass *fdpass_peer;
 
 struct sio_hdl *fdpass_sio_open(int, int, unsigned int);
 struct mio_hdl *fdpass_mio_open(int, int, unsigned int);
+struct sioctl_hdl *fdpass_sioctl_open(int, int, unsigned int);
 
 #endif /* !defined(FDPASS_H) */
Index: usr.bin/sndiod/siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
retrieving revision 1.17
diff -u -p -u -p -r1.17 siofile.c
--- usr.bin/sndiod/siofile.c    23 Jan 2020 05:40:09 -0000      1.17
+++ usr.bin/sndiod/siofile.c    24 Feb 2020 06:03:32 -0000
@@ -26,6 +26,7 @@
 #include "abuf.h"
 #include "defs.h"
 #include "dev.h"
+#include "dev_sioctl.h"
 #include "dsp.h"
 #include "fdpass.h"
 #include "file.h"
@@ -88,10 +89,11 @@ dev_sio_timeout(void *arg)
  * open the device using one of the provided paths
  */
 static struct sio_hdl *
-dev_sio_openlist(struct dev *d, unsigned int mode)
+dev_sio_openlist(struct dev *d, unsigned int mode, struct sioctl_hdl **rctlhdl)
 {
        struct name *n;
        struct sio_hdl *hdl;
+       struct sioctl_hdl *ctlhdl;
        int idx;
 
        idx = 0;
@@ -107,6 +109,15 @@ dev_sio_openlist(struct dev *d, unsigned
                                log_puts(n->str);
                                log_puts("\n");
                        }
+                       ctlhdl = fdpass_sioctl_open(d->num, idx,
+                           SIOCTL_READ | SIOCTL_WRITE);
+                       if (ctlhdl == NULL) {
+                               if (log_level >= 1) {
+                                       dev_log(d);
+                                       log_puts(": no control device\n");
+                               }
+                       }
+                       *rctlhdl = ctlhdl;
                        return hdl;
                }
                n = n->next;
@@ -124,15 +135,16 @@ dev_sio_open(struct dev *d)
        struct sio_par par;
        unsigned int mode = d->mode & (MODE_PLAY | MODE_REC);
 
-       d->sio.hdl = dev_sio_openlist(d, mode);
+       d->sio.hdl = dev_sio_openlist(d, mode, &d->sioctl.hdl);
        if (d->sio.hdl == NULL) {
                if (mode != (SIO_PLAY | SIO_REC))
                        return 0;
-               d->sio.hdl = dev_sio_openlist(d, SIO_PLAY);
+               d->sio.hdl = dev_sio_openlist(d, SIO_PLAY, &d->sioctl.hdl);
                if (d->sio.hdl != NULL)
                        mode = SIO_PLAY;
                else {
-                       d->sio.hdl = dev_sio_openlist(d, SIO_REC);
+                       d->sio.hdl = dev_sio_openlist(d,
+                           SIO_REC, &d->sioctl.hdl);
                        if (d->sio.hdl != NULL)
                                mode = SIO_REC;
                        else
@@ -245,9 +257,14 @@ dev_sio_open(struct dev *d)
        sio_onmove(d->sio.hdl, dev_sio_onmove, d);
        d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(d->sio.hdl));
        timo_set(&d->sio.watchdog, dev_sio_timeout, d);
+       dev_sioctl_open(d);
        return 1;
  bad_close:
        sio_close(d->sio.hdl);
+       if (d->sioctl.hdl) {
+               sioctl_close(d->sioctl.hdl);
+               d->sioctl.hdl = NULL;
+       }
        return 0;
 }
 
@@ -259,10 +276,11 @@ dev_sio_open(struct dev *d)
 int
 dev_sio_reopen(struct dev *d)
 {
+       struct sioctl_hdl *ctlhdl;
        struct sio_par par;
        struct sio_hdl *hdl;
 
-       hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC));
+       hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC), &ctlhdl);
        if (hdl == NULL) {
                if (log_level >= 1) {
                        dev_log(d);
@@ -303,6 +321,11 @@ dev_sio_reopen(struct dev *d)
        timo_del(&d->sio.watchdog);
        file_del(d->sio.file);
        sio_close(d->sio.hdl);
+       dev_sioctl_close(d);
+       if (d->sioctl.hdl) {
+               sioctl_close(d->sioctl.hdl);
+               d->sioctl.hdl = NULL;
+       }
 
        /* update parameters */
        d->par.bits = par.bits;
@@ -316,17 +339,21 @@ dev_sio_reopen(struct dev *d)
                d->rchan = par.rchan;
 
        d->sio.hdl = hdl;
+       d->sioctl.hdl = ctlhdl;
        d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(hdl));
        sio_onmove(hdl, dev_sio_onmove, d);
        return 1;
 bad_close:
        sio_close(hdl);
+       if (ctlhdl)
+               sioctl_close(ctlhdl);
        return 0;
 }
 
 void
 dev_sio_close(struct dev *d)
 {
+       dev_sioctl_close(d);
 #ifdef DEBUG
        if (log_level >= 3) {
                dev_log(d);
@@ -336,6 +363,10 @@ dev_sio_close(struct dev *d)
        timo_del(&d->sio.watchdog);
        file_del(d->sio.file);
        sio_close(d->sio.hdl);
+       if (d->sioctl.hdl) {
+               sioctl_close(d->sioctl.hdl);
+               d->sioctl.hdl = NULL;
+       }
 }
 
 void
Index: usr.bin/sndiod/sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
retrieving revision 1.37
diff -u -p -u -p -r1.37 sndiod.c
--- usr.bin/sndiod/sndiod.c     21 Sep 2019 04:52:07 -0000      1.37
+++ usr.bin/sndiod/sndiod.c     24 Feb 2020 06:03:32 -0000
@@ -413,8 +413,10 @@ start_helper(int background)
                                err(1, "cannot drop privileges");
                }
                for (d = dev_list; d != NULL; d = d->next) {
-                       for (n = d->path_list; n != NULL; n = n->next)
+                       for (n = d->path_list; n != NULL; n = n->next) {
                                dounveil(n->str, "rsnd/", "/dev/audio");
+                               dounveil(n->str, "rsnd/", "/dev/audioctl");
+                       }
                }
                for (p = port_list; p != NULL; p = p->next) {
                        for (n = p->path_list; n != NULL; n = n->next)
Index: usr.bin/sndiod/sock.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.c,v
retrieving revision 1.31
diff -u -p -u -p -r1.31 sock.c
--- usr.bin/sndiod/sock.c       12 Jul 2019 06:30:55 -0000      1.31
+++ usr.bin/sndiod/sock.c       24 Feb 2020 06:03:32 -0000
@@ -32,6 +32,8 @@
 #include "sock.h"
 #include "utils.h"
 
+#define SOCK_CTLDESC_SIZE      16      /* number of entries in s->ctldesc */
+
 void sock_log(struct sock *);
 void sock_close(struct sock *);
 void sock_slot_fill(void *);
@@ -88,6 +90,10 @@ struct midiops sock_midiops = {
        sock_exit
 };
 
+struct ctlops sock_ctlops = {
+       sock_exit
+};
+
 struct sock *sock_list = NULL;
 unsigned int sock_sesrefs = 0;         /* connections to the session */
 uint8_t sock_sescookie[AMSG_COOKIELEN];        /* owner of the session */
@@ -103,7 +109,10 @@ sock_log(struct sock *f)
                slot_log(f->slot);
        else if (f->midi)
                midi_log(f->midi);
-       else
+       else if (f->ctlslot) {
+               log_puts("ctlslot");
+               log_putu(f->ctlslot - f->ctlslot->dev->ctlslot);
+       } else
                log_puts("sock");
 #ifdef DEBUG
        if (log_level >= 3) {
@@ -150,6 +159,11 @@ sock_close(struct sock *f)
                port_unref(f->port);
                f->port = NULL;
        }
+       if (f->ctlslot) {
+               ctlslot_del(f->ctlslot);
+               f->ctlslot = NULL;
+               xfree(f->ctldesc);
+       }
        file_del(f->file);
        close(f->fd);
        file_slowaccept = 0;
@@ -277,6 +291,7 @@ sock_new(int fd)
        f->slot = NULL;
        f->port = NULL;
        f->midi = NULL;
+       f->ctlslot = NULL;
        f->tickpending = 0;
        f->fillpending = 0;
        f->stoppending = 0;
@@ -286,6 +301,8 @@ sock_new(int fd)
        f->rtodo = sizeof(struct amsg);
        f->wmax = f->rmax = 0;
        f->lastvol = -1;
+       f->ctlops = 0;
+       f->ctlsyncpending = 0;
        f->file = file_new(&sock_fileops, f, "sock", 1);
        f->fd = fd;
        if (f->file == NULL) {
@@ -547,6 +564,11 @@ sock_wdata(struct sock *f)
                        data = abuf_rgetblk(&f->slot->sub.buf, &count);
                else if (f->midi)
                        data = abuf_rgetblk(&f->midi->obuf, &count);
+               else {
+                       data = (unsigned char *)f->ctldesc +
+                           (f->wsize - f->wtodo);
+                       count = f->wtodo;
+               }
                if (count > f->wtodo)
                        count = f->wtodo;
                n = sock_fdwrite(f, data, count);
@@ -800,6 +822,9 @@ sock_hello(struct sock *f)
        case MODE_REC:
        case MODE_PLAY:
        case MODE_PLAY | MODE_REC:
+       case MODE_CTLREAD:
+       case MODE_CTLWRITE:
+       case MODE_CTLREAD | MODE_CTLWRITE:
                break;
        default:
 #ifdef DEBUG
@@ -837,6 +862,31 @@ sock_hello(struct sock *f)
                        return 0;
                return 1;
        }
+       if (mode & MODE_CTLMASK) {
+               d = dev_bynum(p->devnum);
+               if (d == NULL) {
+                       if (log_level >= 2) {
+                               sock_log(f);
+                               log_puts(": ");
+                               log_putu(p->devnum);
+                               log_puts(": no such device\n");
+                       }
+                       return 0;
+               }
+               f->ctlslot = ctlslot_new(d, &sock_ctlops, f);
+               if (f->ctlslot == NULL) {
+                       if (log_level >= 2) {
+                               sock_log(f);
+                               log_puts(": couldn't get slot\n");
+                       }
+                       return 0;
+               }
+               f->ctldesc = xmalloc(SOCK_CTLDESC_SIZE *
+                   sizeof(struct amsg_ctl_desc));
+               f->ctlops = 0;
+               f->ctlsyncpending = 0;
+               return 1;
+       }
        d = dev_bynum(p->devnum);
        if (d == NULL)
                return 0;
@@ -856,6 +906,7 @@ sock_hello(struct sock *f)
 int
 sock_execmsg(struct sock *f)
 {
+       struct ctl *c;
        struct slot *s = f->slot;
        struct amsg *m = &f->rmsg;
        unsigned char *data;
@@ -1153,6 +1204,81 @@ sock_execmsg(struct sock *f)
                f->lastvol = ctl; /* dont trigger feedback message */
                slot_setvol(s, ctl);
                dev_midi_vol(s->dev, s);
+               dev_onval(s->dev,
+                   CTLADDR_SLOT_LEVEL(f->slot - s->dev->slot), ctl);
+               break;
+       case AMSG_CTLSUB:
+#ifdef DEBUG
+               if (log_level >= 3) {
+                       sock_log(f);
+                       log_puts(": CTLSUB message, desc = ");
+                       log_putx(m->u.ctlsub.desc);
+                       log_puts(", val = ");
+                       log_putx(m->u.ctlsub.val);
+                       log_puts("\n");
+               }
+#endif
+               if (f->pstate != SOCK_INIT || f->ctlslot == NULL) {
+#ifdef DEBUG
+                       if (log_level >= 1) {
+                               sock_log(f);
+                               log_puts(": CTLSUB, wrong state\n");
+                       }
+#endif
+                       sock_close(f);
+                       return 0;
+               }
+               if (m->u.ctlsub.desc) {
+                       if (!(f->ctlops & SOCK_CTLDESC)) {
+                               ctl = f->ctlslot->mask;
+                               c = f->ctlslot->dev->ctl_list;
+                               while (c != NULL) {
+                                       c->desc_mask |= ctl;
+                                       c = c->next;
+                               }
+                       }
+                       f->ctlops |= SOCK_CTLDESC;
+                       f->ctlsyncpending = 1;
+               } else
+                       f->ctlops &= ~SOCK_CTLDESC;
+               if (m->u.ctlsub.val) {
+                       f->ctlops |= SOCK_CTLVAL;
+               } else
+                       f->ctlops &= ~SOCK_CTLVAL;
+               f->rstate = SOCK_RMSG;
+               f->rtodo = sizeof(struct amsg);
+               break;
+       case AMSG_CTLSET:
+#ifdef DEBUG
+               if (log_level >= 3) {
+                       sock_log(f);
+                       log_puts(": CTLSET message\n");
+               }
+#endif
+               if (f->pstate < SOCK_INIT || f->ctlslot == NULL) {
+#ifdef DEBUG
+                       if (log_level >= 1) {
+                               sock_log(f);
+                               log_puts(": CTLSET, wrong state\n");
+                       }
+#endif
+                       sock_close(f);
+                       return 0;
+               }
+               if (!dev_setctl(f->ctlslot->dev,
+                       ntohs(m->u.ctlset.addr),
+                       ntohs(m->u.ctlset.val))) {
+#ifdef DEBUG
+                       if (log_level >= 1) {
+                               sock_log(f);
+                               log_puts(": CTLSET, wrong addr/val\n");
+                       }
+#endif
+                       sock_close(f);
+                       return 0;
+               }
+               f->rtodo = sizeof(struct amsg);
+               f->rstate = SOCK_RMSG;
                break;
        case AMSG_AUTH:
 #ifdef DEBUG
@@ -1241,7 +1367,9 @@ sock_execmsg(struct sock *f)
 int
 sock_buildmsg(struct sock *f)
 {
-       unsigned int size;
+       unsigned int size, mask;
+       struct amsg_ctl_desc *desc;
+       struct ctl *c, **pc;
 
        /*
         * If pos changed (or initial tick), build a MOVE message.
@@ -1378,6 +1506,106 @@ sock_buildmsg(struct sock *f)
                f->wmsg.cmd = htonl(AMSG_STOP);
                f->wtodo = sizeof(struct amsg);
                f->wstate = SOCK_WMSG;
+               return 1;
+       }
+
+       /*
+        * XXX: add a flag indicating if there are changes
+        * in controls not seen by this client, rather
+        * than walking through the full list of control
+        * searching for the {desc,val}_mask bits
+        */
+       if (f->ctlslot && (f->ctlops & SOCK_CTLDESC)) {
+               desc = f->ctldesc;
+               mask = f->ctlslot->mask;
+               size = 0;
+               pc = &f->ctlslot->dev->ctl_list;
+               while ((c = *pc) != NULL) {
+                       if ((c->desc_mask & mask) == 0 ||
+                           (c->refs_mask & mask) == 0) {
+                               pc = &c->next;
+                               continue;
+                       }
+                       if (size == SOCK_CTLDESC_SIZE *
+                               sizeof(struct amsg_ctl_desc))
+                               break;
+                       c->desc_mask &= ~mask;
+                       c->val_mask &= ~mask;
+                       strlcpy(desc->group, c->group,
+                           AMSG_CTL_NAMEMAX);
+                       strlcpy(desc->node0.name, c->node0.name,
+                           AMSG_CTL_NAMEMAX);
+                       desc->node0.unit = ntohs(c->node0.unit);
+                       strlcpy(desc->node1.name, c->node1.name,
+                           AMSG_CTL_NAMEMAX);
+                       desc->node1.unit = ntohs(c->node1.unit);
+                       desc->type = c->type;
+                       strlcpy(desc->func, c->func, AMSG_CTL_NAMEMAX);
+                       desc->addr = htons(c->addr);
+                       desc->maxval = htons(c->maxval);
+                       desc->curval = htons(c->curval);
+                       size += sizeof(struct amsg_ctl_desc);
+                       desc++;
+
+                       /* if this is a deleted entry unref it */
+                       if (c->type == CTL_NONE) {
+                               c->refs_mask &= ~mask;
+                               if (c->refs_mask == 0) {
+                                       *pc = c->next;
+                                       xfree(c);
+                                       continue;
+                               }
+                       }
+
+                       pc = &c->next;
+               }
+               if (size > 0) {
+                       AMSG_INIT(&f->wmsg);
+                       f->wmsg.cmd = htonl(AMSG_DATA);
+                       f->wmsg.u.data.size = htonl(size);
+                       f->wtodo = sizeof(struct amsg);
+                       f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+                       if (log_level >= 3) {
+                               sock_log(f);
+                               log_puts(": building control DATA message\n");
+                       }
+#endif
+                       return 1;
+               }
+       }
+       if (f->ctlslot && (f->ctlops & SOCK_CTLVAL)) {
+               mask = f->ctlslot->mask;
+               for (c = f->ctlslot->dev->ctl_list; c != NULL; c = c->next) {
+                       if ((c->val_mask & mask) == 0)
+                               continue;
+                       c->val_mask &= ~mask;
+                       AMSG_INIT(&f->wmsg);
+                       f->wmsg.cmd = htonl(AMSG_CTLSET);
+                       f->wmsg.u.ctlset.addr = htons(c->addr);
+                       f->wmsg.u.ctlset.val = htons(c->curval);
+                       f->wtodo = sizeof(struct amsg);
+                       f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+                       if (log_level >= 3) {
+                               sock_log(f);
+                               log_puts(": building CTLSET message\n");
+                       }
+#endif
+                       return 1;
+               }
+       }
+       if (f->ctlslot && f->ctlsyncpending) {
+               f->ctlsyncpending = 0;
+               f->wmsg.cmd = htonl(AMSG_CTLSYNC);
+               f->wtodo = sizeof(struct amsg);
+               f->wstate = SOCK_WMSG;
+#ifdef DEBUG
+               if (log_level >= 3) {
+                       sock_log(f);
+                       log_puts(": building CTLSYNC message\n");
+               }
+#endif
                return 1;
        }
 #ifdef DEBUG
Index: usr.bin/sndiod/sock.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.h,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 sock.h
--- usr.bin/sndiod/sock.h       26 Jun 2018 07:13:54 -0000      1.5
+++ usr.bin/sndiod/sock.h       24 Feb 2020 06:03:32 -0000
@@ -58,6 +58,12 @@ struct sock {
        struct slot *slot;              /* audio device slot number */
        struct midi *midi;              /* midi endpoint */
        struct port *port;              /* midi port */
+       struct ctlslot *ctlslot;
+       struct amsg_ctl_desc *ctldesc;  /* temporary buffer */
+#define SOCK_CTLDESC   1               /* dump desc and send changes */
+#define SOCK_CTLVAL    2               /* send value changes */
+       unsigned int ctlops;            /* bitmap of above */
+       int ctlsyncpending;             /* CTLSYNC waiting to be transmitted */
 };
 
 struct sock *sock_new(int fd);
Index: usr.bin/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/Makefile,v
retrieving revision 1.162
diff -u -p -u -p -r1.162 Makefile
--- usr.bin/Makefile    7 Feb 2020 10:19:50 -0000       1.162
+++ usr.bin/Makefile    25 Feb 2020 06:47:34 -0000
@@ -22,7 +22,7 @@ SUBDIR= apply arch at aucat audioctl awk
        pr printenv printf quota radioctl rcs rdist rdistd \
        readlink renice rev rpcgen rpcinfo rs rsync rup rusers rwall \
        sdiff script sed sendbug shar showmount signify skey \
-       skeyaudit skeyinfo skeyinit sndiod snmp \
+       skeyaudit skeyinfo skeyinit sndioctl sndiod snmp \
        sort spell split ssh stat su systat \
        tail talk tcpbench tee telnet tftp tic time \
        tmux top touch tput tr true tset tsort tty usbhidaction usbhidctl \
Index: usr.bin/sndioctl/Makefile
===================================================================
RCS file: usr.bin/sndioctl/Makefile
diff -N usr.bin/sndioctl/Makefile
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndioctl/Makefile   25 Feb 2020 06:47:34 -0000
@@ -0,0 +1,5 @@
+#      $OpenBSD$
+
+PROG=  sndioctl
+LDADD+=        -lsndio
+.include <bsd.prog.mk>
Index: usr.bin/sndioctl/sndioctl.1
===================================================================
RCS file: usr.bin/sndioctl/sndioctl.1
diff -N usr.bin/sndioctl/sndioctl.1
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndioctl/sndioctl.1 25 Feb 2020 06:47:34 -0000
@@ -0,0 +1,146 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: April 8 2011 $
+.Dt SNDIOCTL 1
+.Os
+.Sh NAME
+.Nm sndioctl
+.Nd control audio parameters
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl iv
+.Op Fl f Ar device
+.Op Ar command ...
+.Ek
+.Nm
+.Fl d
+.Sh DESCRIPTION
+The
+.Nm
+utility can display or change parameters of
+.Xr sndio 7
+audio devices.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Dump the raw list of available parameters and exit.
+Useful as a debugging tool.
+.It Fl f Ar device
+Use this
+.Xr sndio 7
+audio device.
+.It Fl i
+Display characteristics of requested parameters
+instead of their values.
+.It Fl m
+Monitor and display audio parameter changes.
+.It Fl v
+Enable verbose mode, a.k.a. multi-channel mode.
+By default parameters affecting different channels
+of the same stream are disguised as a single mono
+parameter to hide details that are not essential.
+.El
+.Pp
+If no commands are specified all valid parameters are displayed on
+.Em stdout .
+Unless
+.Fl d ,
+.Fl m ,
+or
+.Fl i
+are used, displayed parameters are valid commands.
+The set of available controls depends on the control device.
+.Pp
+Commands use the following two formats to display and set
+parameters respectively:
+.Pp
+.Dl group/stream[channel].function
+.Dl group/stream[channel].function=value
+.Pp
+On the left-hand side are specified the optional parameter group,
+the affected stream name, and the optional channel number.
+Examples of left-hand side terms:
+.Pp
+.Dl output.level
+.Dl hw/spkr[6].mute
+.Pp
+There are 4 parameter types: switches, numbers, selectors, and vectors.
+.Pp
+Numbers are specified in decimal and follow the same semantics
+as MIDI controllers.
+Values are in the 0..127 range and 64 is the neutral state (if applicable).
+Two-state controls (switches) take either 0 or 1 as value,
+typically corresponding to the
+.Em off
+and
+.Em on
+states respectively.
+.Pp
+If a decimal is prefixed by the plus (minus) sign then
+the given value is added to (subtracted from) the
+current value of the control.
+If
+.Qq \&!
+is used instead of a number, then the switch is toggled.
+Examples:
+.Pp
+.Dl hw/spkr.level=85
+.Dl hw/spkr.level=+10
+.Dl hw/spkr.mute=0
+.Dl hw/spkr.mute=!
+.Pp
+Selector values are substreams; they are specified
+as the stream name followed by an optional channel
+number.
+If no channel number is specified, the same
+number as the stream specified on the left-hand side is used.
+For instance the following are equivalent:
+.Pp
+.Dl hw/record[1].source=mic
+.Dl hw/record[1].source=mic1
+.Pp
+Vectors are arrays of numbers.
+Values are specified as comma-separated components.
+Each component is a substream, followed by
+a colon, followed by a number.
+If the colon and the number are omitted, then 127 is
+assumed.
+If a component is missing, then 0 is assumed.
+Example:
+.Pp
+.Dl hw/monitor.mix=play:120,linein:85
+.Dl hw/record.source=mic,linein
+.Pp
+Numbers are specified as discussed above.
+Note that a vector of switches is equivalent to
+a list.
+.Sh EXAMPLES
+The following will set all
+.Ar level
+parameters that control the
+.Ar spkr
+stream to zero.
+.Pp
+.Dl $ sndioctl hw/spkr.level=0
+.Pp
+The following commands are equivalent:
+.Pp
+.Dl $ sndioctl hw/record[0].source=mic0 hw/record[1].source=mic1
+.Dl $ sndioctl hw/record.source=mic
+.Sh SEE ALSO
+.Xr sioctl_open 3
Index: usr.bin/sndioctl/sndioctl.c
===================================================================
RCS file: usr.bin/sndioctl/sndioctl.c
diff -N usr.bin/sndioctl/sndioctl.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndioctl/sndioctl.c 25 Feb 2020 06:47:35 -0000
@@ -0,0 +1,939 @@
+/*     $OpenBSD$       */
+/*
+ * Copyright (c) 2014-2020 Alexandre Ratchov <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <errno.h>
+#include <poll.h>
+#include <sndio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+struct info {
+       struct info *next;
+       struct sioctl_desc desc;
+       unsigned ctladdr;
+#define MODE_IGNORE    0       /* ignore this value */
+#define MODE_PRINT     1       /* print-only, don't change value */
+#define MODE_SET       2       /* set to newval value */
+#define MODE_ADD       3       /* increase current value by newval */
+#define MODE_SUB       4       /* decrease current value by newval */
+#define MODE_TOGGLE    5       /* toggle current value */
+       unsigned mode;
+       int curval, newval;
+};
+
+int cmpdesc(struct sioctl_desc *, struct sioctl_desc *);
+int isdiag(struct info *);
+struct info *vecent(struct info *, char *, int);
+struct info *nextfunc(struct info *);
+struct info *nextpar(struct info *);
+struct info *firstent(struct info *, char *);
+struct info *nextent(struct info *, int);
+int matchpar(struct info *, char *, int);
+int matchent(struct info *, char *, int);
+int ismono(struct info *);
+void print_node(struct sioctl_node *, int);
+void print_desc(struct info *, int);
+void print_val(struct info *, int);
+void print_par(struct info *, int, char *);
+int parse_name(char **, char *);
+int parse_unit(char **, unsigned int *);
+int parse_val(char **, float *);
+int parse_node(char **, char *, int *);
+int parse_modeval(char **, int *, float *);
+void dump(void);
+int cmd(char *);
+void commit(void);
+void list(void);
+void ondesc(void *, struct sioctl_desc *, int);
+void onctl(void *, unsigned, unsigned);
+
+struct sioctl_hdl *hdl;
+struct info *infolist;
+int i_flag = 0, v_flag = 0, m_flag = 0;
+
+static inline int
+isname_first(int c)
+{
+       return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static inline int
+isname_next(int c)
+{
+       return isname_first(c) || (c >= '0' && c <= '9') || (c == '_');
+}
+
+static int
+ftoi(float f)
+{
+       return f + 0.5;
+}
+
+/*
+ * compare two sioctl_desc structures, used to sort infolist
+ */
+int
+cmpdesc(struct sioctl_desc *d1, struct sioctl_desc *d2)
+{
+       int res;
+
+       res = strcmp(d1->group, d2->group);
+       if (res != 0)
+               return res;
+       res = strcmp(d1->node0.name, d2->node0.name);
+       if (res != 0)
+               return res;
+       res = d1->type - d2->type;
+       if (res != 0)
+               return res;
+       res = strcmp(d1->func, d2->func);
+       if (res != 0)
+               return res;
+       res = d1->node0.unit - d2->node0.unit;
+       if (d1->type == SIOCTL_VEC ||
+           d1->type == SIOCTL_LIST) {
+               if (res != 0)
+                       return res;
+               res = strcmp(d1->node1.name, d2->node1.name);
+               if (res != 0)
+                       return res;
+               res = d1->node1.unit - d2->node1.unit;
+       }
+       return res;
+}
+
+/*
+ * return true of the vector entry is diagonal
+ */
+int
+isdiag(struct info *e)
+{
+       if (e->desc.node0.unit < 0 || e->desc.node1.unit < 0)
+               return 1;
+       return e->desc.node1.unit == e->desc.node0.unit;
+}
+
+/*
+ * find the selector or vector entry with the given name and channels
+ */
+struct info *
+vecent(struct info *i, char *vstr, int vunit)
+{
+       while (i != NULL) {
+               if ((strcmp(i->desc.node1.name, vstr) == 0) &&
+                   (vunit < 0 || i->desc.node1.unit == vunit))
+                       break;
+               i = i->next;
+       }
+       return i;
+}
+
+/*
+ * skip all parameters with the same group, name, and func
+ */
+struct info *
+nextfunc(struct info *i)
+{
+       char *str, *group, *func;
+
+       group = i->desc.group;
+       func = i->desc.func;
+       str = i->desc.node0.name;
+       for (i = i->next; i != NULL; i = i->next) {
+               if (strcmp(i->desc.group, group) != 0 ||
+                   strcmp(i->desc.node0.name, str) != 0 ||
+                   strcmp(i->desc.func, func) != 0)
+                       return i;
+       }
+       return NULL;
+}
+
+/*
+ * find the next parameter with the same group, name, func
+ */
+struct info *
+nextpar(struct info *i)
+{
+       char *str, *group, *func;
+       int unit;
+
+       group = i->desc.group;
+       func = i->desc.func;
+       str = i->desc.node0.name;
+       unit = i->desc.node0.unit;
+       for (i = i->next; i != NULL; i = i->next) {
+               if (strcmp(i->desc.group, group) != 0 ||
+                   strcmp(i->desc.node0.name, str) != 0 ||
+                   strcmp(i->desc.func, func) != 0)
+                       break;
+               /* XXX: need to check for -1 ? */
+               if (i->desc.node0.unit != unit)
+                       return i;
+       }
+       return NULL;
+}
+
+/*
+ * return the first vector entry with the given name
+ */
+struct info *
+firstent(struct info *g, char *vstr)
+{
+       char *astr, *group, *func;
+       struct info *i;
+
+       group = g->desc.group;
+       astr = g->desc.node0.name;
+       func = g->desc.func;
+       for (i = g; i != NULL; i = i->next) {
+               if (strcmp(i->desc.group, group) != 0 ||
+                   strcmp(i->desc.node0.name, astr) != 0 ||
+                   strcmp(i->desc.func, func) != 0)
+                       break;
+               if (!isdiag(i))
+                       continue;
+               if (strcmp(i->desc.node1.name, vstr) == 0)
+                       return i;
+       }
+       return NULL;
+}
+
+/*
+ * find the next entry of the given vector, if the mono flag
+ * is set then the whole group is searched and off-diagonal entries are
+ * skipped
+ */
+struct info *
+nextent(struct info *i, int mono)
+{
+       char *str, *group, *func;
+       int unit;
+
+       group = i->desc.group;
+       func = i->desc.func;
+       str = i->desc.node0.name;
+       unit = i->desc.node0.unit;
+       for (i = i->next; i != NULL; i = i->next) {
+               if (strcmp(i->desc.group, group) != 0 ||
+                   strcmp(i->desc.node0.name, str) != 0 ||
+                   strcmp(i->desc.func, func) != 0)
+                       return NULL;
+               if (mono)
+                       return i;
+               if (i->desc.node0.unit == unit)
+                       return i;
+       }
+       return NULL;
+}
+
+/*
+ * return true if parameter matches the given name and channel
+ */
+int
+matchpar(struct info *i, char *astr, int aunit)
+{
+       if (strcmp(i->desc.node0.name, astr) != 0)
+               return 0;
+       if (aunit < 0)
+               return 1;
+       else if (i->desc.node0.unit < 0) {
+               fprintf(stderr, "unit used for parameter with no unit\n");
+               exit(1);
+       }
+       return i->desc.node0.unit == aunit;
+}
+
+/*
+ * return true if selector or vector entry matches the given name and
+ * channel range
+ */
+int
+matchent(struct info *i, char *vstr, int vunit)
+{
+       if (strcmp(i->desc.node1.name, vstr) != 0)
+               return 0;
+       if (vunit < 0)
+               return 1;
+       else if (i->desc.node1.unit < 0) {
+               fprintf(stderr, "unit used for parameter with no unit\n");
+               exit(1);
+       }
+       return i->desc.node1.unit == vunit;
+}
+
+/*
+ * return true if the given group can be represented as a signle mono
+ * parameter
+ */
+int
+ismono(struct info *g)
+{
+       struct info *p1, *p2;
+       struct info *e1, *e2;
+
+       p1 = g;
+       switch (g->desc.type) {
+       case SIOCTL_NUM:
+       case SIOCTL_SW:
+               for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+                       if (p2->curval != p1->curval)
+                               return 0;
+               }
+               break;
+       case SIOCTL_VEC:
+       case SIOCTL_LIST:
+               for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+                       for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
+                               if (!isdiag(e2)) {
+                                       if (e2->curval != 0)
+                                               return 0;
+                               } else {
+                                       e1 = vecent(p1,
+                                           e2->desc.node1.name,
+                                           p1->desc.node0.unit);
+                                       if (e1 == NULL)
+                                               continue;
+                                       if (e1->curval != e2->curval)
+                                               return 0;
+                               }
+                       }
+               }
+               break;
+       }
+       return 1;
+}
+
+/*
+ * print a sub-stream, eg. "spkr[4]"
+ */
+void
+print_node(struct sioctl_node *c, int mono)
+{
+       printf("%s", c->name);
+       if (!mono && c->unit >= 0)
+               printf("[%d]", c->unit);
+}
+
+/*
+ * print info about the parameter
+ */
+void
+print_desc(struct info *p, int mono)
+{
+       struct info *e;
+       int more;
+
+       switch (p->desc.type) {
+       case SIOCTL_NUM:
+       case SIOCTL_SW:
+               printf("*");
+               break;
+       case SIOCTL_VEC:
+       case SIOCTL_LIST:
+               more = 0;
+               for (e = p; e != NULL; e = nextent(e, mono)) {
+                       if (mono) {
+                               if (!isdiag(e))
+                                       continue;
+                               if (e != firstent(p, e->desc.node1.name))
+                                       continue;
+                       }
+                       if (more)
+                               printf(",");
+                       print_node(&e->desc.node1, mono);
+                       printf(":*");
+                       more = 1;
+               }
+       }
+}
+
+/*
+ * print parameter value
+ */
+void
+print_val(struct info *p, int mono)
+{
+       struct info *e;
+       int more;
+
+       switch (p->desc.type) {
+       case SIOCTL_NUM:
+       case SIOCTL_SW:
+               printf("%.2g", p->curval / (float)p->desc.maxval);
+               break;
+       case SIOCTL_VEC:
+       case SIOCTL_LIST:
+               more = 0;
+               for (e = p; e != NULL; e = nextent(e, mono)) {
+                       if (mono) {
+                               if (!isdiag(e))
+                                       continue;
+                               if (e != firstent(p, e->desc.node1.name))
+                                       continue;
+                       }
+                       if (more)
+                               printf(",");
+                       print_node(&e->desc.node1, mono);
+                       printf(":%.2g", e->curval / (float)e->desc.maxval);
+                       more = 1;
+               }
+       }
+}
+
+/*
+ * print ``<parameter>=<value>'' string (including '\n')
+ */
+void
+print_par(struct info *p, int mono, char *comment)
+{
+       if (p->desc.group[0] != 0) {
+               printf("%s", p->desc.group);
+               printf("/");
+       }
+       print_node(&p->desc.node0, mono);
+       printf(".%s=", p->desc.func);
+       if (i_flag)
+               print_desc(p, mono);
+       else
+               print_val(p, mono);
+       if (comment)
+               printf(" # %s", comment);
+       printf("\n");
+}
+
+/*
+ * parse a stream name or parameter name
+ */
+int
+parse_name(char **line, char *name)
+{
+       char *p = *line;
+       unsigned len = 0;
+
+       if (!isname_first(*p)) {
+               fprintf(stderr, "letter expected near '%s'\n", p);
+               return 0;
+       }
+       while (isname_next(*p)) {
+               if (len >= SIOCTL_NAMEMAX - 1) {
+                       name[SIOCTL_NAMEMAX - 1] = '\0';
+                       fprintf(stderr, "%s...: too long\n", name);
+                       return 0;
+               }
+               name[len++] = *p;
+               p++;
+       }
+       name[len] = '\0';
+       *line = p;
+       return 1;
+}
+
+/*
+ * parse a decimal integer
+ */
+int
+parse_unit(char **line, unsigned int *num)
+{
+       char *p = *line;
+       unsigned int val;
+       int n;
+
+       if (sscanf(p, "%u%n", &val, &n) != 1) {
+               fprintf(stderr, "number expected near '%s'\n", p);
+               return 0;
+       }
+       if (val >= 255) {
+               fprintf(stderr, "%d: too large\n", val);
+               return 0;
+       }
+       *num = val;
+       *line = p + n;
+       return 1;
+}
+
+int
+parse_val(char **line, float *num)
+{
+       char *p = *line;
+       float val;
+       int n;
+
+       if (sscanf(p, "%g%n", &val, &n) != 1) {
+               fprintf(stderr, "number expected near '%s'\n", p);
+               return 0;
+       }
+       if (val < 0 || val > 1) {
+               fprintf(stderr, "%g: expected number between 0 and 1\n", val);
+               return 0;
+       }
+       *num = val;
+       *line = p + n;
+       return 1;
+}
+
+/*
+ * parse a sub-stream, eg. "spkr[7]"
+ */
+int
+parse_node(char **line, char *str, int *unit)
+{
+       char *p = *line;
+
+       if (!parse_name(&p, str))
+               return 0;
+       if (*p != '[') {
+               *unit = -1;
+               *line = p;
+               return 1;
+       }
+       p++;
+       if (!parse_unit(&p, unit))
+               return 0;
+       if (*p != ']') {
+               fprintf(stderr, "']' expected near '%s'\n", p);
+               return 0;
+       }
+       p++;
+       *line = p;
+       return 1;
+}
+
+/*
+ * parse a decimal prefixed by the optional mode
+ */
+int
+parse_modeval(char **line, int *rmode, float *rval)
+{
+       char *p = *line;
+       unsigned mode;
+
+       switch (*p) {
+       case '+':
+               mode = MODE_ADD;
+               p++;
+               break;
+       case '-':
+               mode = MODE_SUB;
+               p++;
+               break;
+       case '!':
+               mode = MODE_TOGGLE;
+               p++;
+               break;
+       default:
+               mode = MODE_SET;
+       }
+       if (mode != MODE_TOGGLE) {
+               if (!parse_val(&p, rval))
+                       return 0;
+       }
+       *line = p;
+       *rmode = mode;
+       return 1;
+}
+
+/*
+ * dump the whole controls list, useful for debugging
+ */
+void
+dump(void)
+{
+       struct info *i;
+
+       for (i = infolist; i != NULL; i = i->next) {
+               printf("%03u:", i->ctladdr);
+               print_node(&i->desc.node0, 0);
+               printf(".%s", i->desc.func);
+               printf("=");
+               switch (i->desc.type) {
+               case SIOCTL_NUM:
+               case SIOCTL_SW:
+                       printf("0..%d (%u)", i->desc.maxval, i->curval);
+                       break;
+               case SIOCTL_VEC:
+               case SIOCTL_LIST:
+                       print_node(&i->desc.node1, 0);
+                       printf(":0..%d (%u)", i->desc.maxval, i->curval);
+               }
+               printf("\n");
+       }
+}
+
+/*
+ * parse and execute a command ``<parameter>[=<value>]''
+ */
+int
+cmd(char *line)
+{
+       char *pos, *group;
+       struct info *i, *e, *g;
+       char func[SIOCTL_NAMEMAX];
+       char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
+       int aunit, vunit;
+       unsigned npar = 0, nent = 0;
+       int comma, mode;
+       float val;
+
+       pos = strrchr(line, '/');
+       if (pos != NULL) {
+               group = line;
+               pos[0] = 0;
+               pos++;
+       } else {
+               group = "";
+               pos = line;
+       }
+       if (!parse_node(&pos, astr, &aunit))
+               return 0;
+       if (*pos != '.') {
+               fprintf(stderr, "'.' expected near '%s'\n", pos);
+               return 0;
+       }
+       pos++;
+       if (!parse_name(&pos, func))
+               return 0;
+       for (g = infolist;; g = g->next) {
+               if (g == NULL) {
+                       fprintf(stderr, "%s.%s: no such control\n", astr, func);
+                       return 0;
+               }
+               if (strcmp(g->desc.group, group) == 0 &&
+                   strcmp(g->desc.func, func) == 0 &&
+                   strcmp(g->desc.node0.name, astr) == 0)
+                       break;
+       }
+       g->mode = MODE_PRINT;
+       if (*pos != '=') {
+               if (*pos != '\0') {
+                       fprintf(stderr, "junk at end of command\n");
+                       return 0;
+               }
+               return 1;
+       }
+       pos++;
+       if (i_flag) {
+               printf("can't set values in info mode\n");
+               return 0;
+       }
+       npar = 0;
+       switch (g->desc.type) {
+       case SIOCTL_NUM:
+       case SIOCTL_SW:
+               if (!parse_modeval(&pos, &mode, &val))
+                       return 0;
+               for (i = g; i != NULL; i = nextpar(i)) {
+                       if (!matchpar(i, astr, aunit))
+                               continue;
+                       i->mode = mode;
+                       i->newval = ftoi(val * i->desc.maxval);
+                       npar++;
+               }
+               break;
+       case SIOCTL_VEC:
+       case SIOCTL_LIST:
+               for (i = g; i != NULL; i = nextpar(i)) {
+                       if (!matchpar(i, astr, aunit))
+                               continue;
+                       for (e = i; e != NULL; e = nextent(e, 0)) {
+                               e->newval = 0;
+                               e->mode = MODE_SET;
+                       }
+                       npar++;
+               }
+               comma = 0;
+               for (;;) {
+                       if (*pos == '\0')
+                               break;
+                       if (comma) {
+                               if (*pos != ',')
+                                       break;
+                               pos++;
+                       }
+                       if (!parse_node(&pos, vstr, &vunit))
+                               return 0;
+                       if (*pos == ':') {
+                               pos++;
+                               if (!parse_modeval(&pos, &mode, &val))
+                                       return 0;
+                       } else {
+                               val = 1.;
+                               mode = MODE_SET;
+                       }
+                       nent = 0;
+                       for (i = g; i != NULL; i = nextpar(i)) {
+                               if (!matchpar(i, astr, aunit))
+                                       continue;
+                               for (e = i; e != NULL; e = nextent(e, 0)) {
+                                       if (matchent(e, vstr, vunit)) {
+                                               e->newval = ftoi(val * 
e->desc.maxval);
+                                               e->mode = mode;
+                                               nent++;
+                                       }
+                               }
+                       }
+                       if (nent == 0) {
+                               /* XXX: use print_node()-like routine */
+                               fprintf(stderr, "%s[%d]: invalid value\n", 
vstr, vunit);
+                               print_par(g, 0, NULL);
+                               exit(1);
+                       }
+                       comma = 1;
+               }
+       }
+       if (npar == 0) {
+               fprintf(stderr, "%s: invalid parameter\n", line);
+               exit(1);
+       }
+       if (*pos != '\0') {
+               printf("%s: junk at end of command\n", pos);
+               exit(1);
+       }
+       return 1;
+}
+
+/*
+ * write the controls with the ``set'' flag on the device
+ */
+void
+commit(void)
+{
+       struct info *i;
+       int val;
+
+       for (i = infolist; i != NULL; i = i->next) {
+               val = 0xdeadbeef;
+               switch (i->mode) {
+               case MODE_IGNORE:
+               case MODE_PRINT:
+                       continue;
+               case MODE_SET:
+                       val = i->newval;
+                       break;
+               case MODE_ADD:
+                       val = i->curval + i->newval;
+                       if (val > i->desc.maxval)
+                               val = i->desc.maxval;
+                       break;
+               case MODE_SUB:
+                       val = i->curval - i->newval;
+                       if (val < 0)
+                               val = 0;
+                       break;
+               case MODE_TOGGLE:
+                       val = i->curval ? 0 : i->desc.maxval;
+               }
+               sioctl_setval(hdl, i->ctladdr, val);
+               i->curval = val;
+       }
+}
+
+/*
+ * print all parameters
+ */
+void
+list(void)
+{
+       struct info *p, *g;
+
+       for (g = infolist; g != NULL; g = nextfunc(g)) {
+               if (g->mode == MODE_IGNORE)
+                       continue;
+               if (i_flag) {
+                       if (v_flag) {
+                               for (p = g; p != NULL; p = nextpar(p))
+                                       print_par(p, 0, NULL);
+                       } else
+                               print_par(g, 1, NULL);
+               } else {
+                       if (v_flag || !ismono(g)) {
+                               for (p = g; p != NULL; p = nextpar(p))
+                                       print_par(p, 0, NULL);
+                       } else
+                               print_par(g, 1, NULL);
+               }
+       }
+}
+
+/*
+ * register a new knob/button, called from the poll() loop.  this may be
+ * called when label string changes, in which case we update the
+ * existing label widged rather than inserting a new one.
+ */
+void
+ondesc(void *arg, struct sioctl_desc *d, int curval)
+{
+       struct info *i, **pi;
+       int cmp;
+
+       if (d == NULL)
+               return;
+
+       /*
+        * delete control
+        */
+       for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+               if (d->addr == i->desc.addr) {
+                       if (m_flag)
+                               print_par(i, 0, "deleted");
+                       *pi = i->next;
+                       free(i);
+                       break;
+               }
+       }
+
+       if (d->type == SIOCTL_NONE)
+               return;
+
+       /*
+        * find the right position to insert the new widget
+        */
+       for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+               cmp = cmpdesc(d, &i->desc);
+               if (cmp == 0) {
+                       fprintf(stderr, "fatal: duplicate control:\n");
+                       print_par(i, 0, "duplicate");
+                       exit(1);
+               }
+               if (cmp < 0)
+                       break;
+       }
+       i = malloc(sizeof(struct info));
+       if (i == NULL) {
+               perror("malloc");
+               exit(1);
+       }
+       i->desc = *d;
+       i->ctladdr = d->addr;
+       i->curval = i->newval = curval;
+       i->mode = MODE_IGNORE;
+       i->next = *pi;
+       *pi = i;
+       if (m_flag)
+               print_par(i, 0, "added");
+}
+
+/*
+ * update a knob/button state, called from the poll() loop
+ */
+void
+onctl(void *arg, unsigned addr, unsigned val)
+{
+       struct info *i;
+
+       for (i = infolist; i != NULL; i = i->next) {
+               if (i->ctladdr != addr)
+                       continue;
+               i->curval = val;
+               if (m_flag)
+                       print_par(i, 0, "changed");
+       }
+}
+
+int
+main(int argc, char **argv)
+{
+       char *devname = SIO_DEVANY;
+       int i, c, d_flag = 0;
+       struct info *g;
+       struct pollfd *pfds;
+       int nfds, revents;
+
+       while ((c = getopt(argc, argv, "df:imv")) != -1) {
+               switch (c) {
+               case 'd':
+                       d_flag = 1;
+                       break;
+               case 'f':
+                       devname = optarg;
+                       break;
+               case 'i':
+                       i_flag = 1;
+                       break;
+               case 'm':
+                       m_flag = 1;
+                       break;
+               case 'v':
+                       v_flag++;
+                       break;
+               default:
+                       fprintf(stderr, "usage: sndioctl "
+                           "[-dimnv] [-f device] [command ...]\n");
+                       exit(1);
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       hdl = sioctl_open(devname, SIOCTL_READ | SIOCTL_WRITE, 0);
+       if (hdl == NULL) {
+               fprintf(stderr, "%s: can't open control device\n", devname);
+               exit(1);
+       }
+       if (!sioctl_ondesc(hdl, ondesc, NULL)) {
+               fprintf(stderr, "%s: can't get device description\n", devname);
+               exit(1);
+       }
+       sioctl_onval(hdl, onctl, NULL);
+
+       if (d_flag) {
+               if (argc > 0) {
+                       fprintf(stderr,
+                           "commands are not allowed with -d option\n");
+                       exit(1);
+               }
+               dump();
+       } else {
+               if (argc == 0) {
+                       for (g = infolist; g != NULL; g = nextfunc(g))
+                               g->mode = MODE_PRINT;
+               } else {
+                       for (i = 0; i < argc; i++) {
+                               if (!cmd(argv[i]))
+                                       return 1;
+                       }
+               }
+               commit();
+               list();
+       }
+       if (m_flag) {
+               pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
+               if (pfds == NULL) {
+                       perror("malloc");
+                       exit(1);
+               }
+               for (;;) {
+                       nfds = sioctl_pollfd(hdl, pfds, POLLIN);
+                       if (nfds == 0)
+                               break;
+                       while (poll(pfds, nfds, -1) < 0) {
+                               if (errno != EINTR) {
+                                       perror("poll");
+                                       exit(1);
+                               }
+                       }
+                       revents = sioctl_revents(hdl, pfds);
+                       if (revents & POLLHUP) {
+                               fprintf(stderr, "disconnected\n");
+                               break;
+                       }
+               }
+               free(pfds);
+       }
+       sioctl_close(hdl);
+       return 0;
+}
Index: lib/libossaudio/Makefile
===================================================================
RCS file: /cvs/src/lib/libossaudio/Makefile,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 Makefile
--- lib/libossaudio/Makefile    16 Jul 2014 20:02:17 -0000      1.5
+++ lib/libossaudio/Makefile    22 Feb 2020 16:07:05 -0000
@@ -4,9 +4,11 @@
 LIB=   ossaudio
 MAN=   ossaudio.3
 
-SRCS=  ossaudio.c
+SRCS=  ossaudio.c aucat.c debug.c sioctl.c sioctl_aucat.c sioctl_sun.c
 
 CPPFLAGS+= -I${.CURDIR}
+
+.PATH: ${.CURDIR}/../libsndio
 
 includes:
        @cd ${.CURDIR}; cmp -s soundcard.h ${DESTDIR}/usr/include/soundcard.h 
|| \
Index: lib/libossaudio/ossaudio.c
===================================================================
RCS file: /cvs/src/lib/libossaudio/ossaudio.c,v
retrieving revision 1.20
diff -u -p -u -p -r1.20 ossaudio.c
--- lib/libossaudio/ossaudio.c  28 Jun 2019 13:32:42 -0000      1.20
+++ lib/libossaudio/ossaudio.c  22 Feb 2020 16:07:05 -0000
@@ -36,26 +36,38 @@
 #include <string.h>
 #include <sys/types.h>
 #include <sys/ioctl.h>
-#include <sys/audioio.h>
-#include <sys/stat.h>
 #include <errno.h>
-
+#include <poll.h>
+#include <sndio.h>
+#include <stdlib.h>
+#include <stdio.h>
 #include "soundcard.h"
-#undef ioctl
 
-#define GET_DEV(com) ((com) & 0xff)
+#ifdef DEBUG
+#define DPRINTF(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(...) do {} while (0)
+#endif
 
-#define TO_OSSVOL(x)   (((x) * 100 + 127) / 255)
-#define FROM_OSSVOL(x) ((((x) > 100 ? 100 : (x)) * 255 + 50) / 100)
+#define GET_DEV(com) ((com) & 0xff)
+#define INTARG (*(int*)argp)
 
-static struct audiodevinfo *getdevinfo(int);
+struct control {
+       struct control *next;
+       int type;               /* one of SOUND_MIXER_xxx */
+       int chan;               /* 0 -> left, 1 -> right, -1 -> mono */
+       int addr;               /* sioctl control id */
+       int value;              /* current value */
+       int max;
+};
 
 static int mixer_ioctl(int, unsigned long, void *);
-static int opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, 
int opq);
-static int enum_to_ord(struct audiodevinfo *di, int enm);
-static int enum_to_mask(struct audiodevinfo *di, int enm);
 
-#define INTARG (*(int*)argp)
+static int initialized;
+static struct control *controls;
+static struct sioctl_hdl *hdl;
+static char *dev_name = SIO_DEVANY;
+static struct pollfd *pfds;
 
 int
 _oss_ioctl(int fd, unsigned long com, ...)
@@ -71,201 +83,163 @@ _oss_ioctl(int fd, unsigned long com, ..
        else if (IOCGROUP(com) == 'M')
                return mixer_ioctl(fd, com, argp);
        else
-               return ioctl(fd, com, argp);
+               return (ioctl)(fd, com, argp);
 }
 
-/* If the mixer device should have more than MAX_MIXER_DEVS devices
- * some will not be available to Linux */
-#define MAX_MIXER_DEVS 64
-struct audiodevinfo {
-       int done;
-       dev_t dev;
-       ino_t ino;
-       int16_t devmap[SOUND_MIXER_NRDEVICES],
-               rdevmap[MAX_MIXER_DEVS];
-       char names[MAX_MIXER_DEVS][MAX_AUDIO_DEV_LEN];
-       int enum2opaque[MAX_MIXER_DEVS];
-        u_long devmask, recmask, stereomask;
-       u_long caps, recsource;
-};
-
-static int
-opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq)
+/*
+ * new control
+ */
+static void
+mixer_ondesc(void *unused, struct sioctl_desc *d, int val)
 {
-       int i, o;
+       struct control *i, **pi;
+       int type;
 
-       for (i = 0; i < MAX_MIXER_DEVS; i++) {
-               o = di->enum2opaque[i];
-               if (o == opq)
-                       break;
-               if (o == -1 && label != NULL &&
-                   !strncmp(di->names[i], label->name, sizeof di->names[i])) {
-                       di->enum2opaque[i] = opq;
+       if (d == NULL)
+               return;
+
+       /*
+        * delete existing control with the same address
+        */
+       for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
+               if (d->addr == i->addr) {
+                       *pi = i->next;
+                       free(i);
                        break;
                }
        }
-       if (i >= MAX_MIXER_DEVS)
-               i = -1;
-       /*printf("opq_to_enum %s %d -> %d\n", label->name, opq, i);*/
-       return (i);
+
+       /*
+        * we support only numeric "level" controls, first 2 channels
+        */
+       if (d->type != SIOCTL_NUM || d->node0.unit >= 2 ||
+           strcmp(d->func, "level") != 0)
+               return;
+
+       /*
+        * We expose top-level input.level and output.level as OSS
+        * volume and microphone knobs. By default sndiod exposes
+        * the underlying hardware knobs as hw/input.level and
+        * hw/output.level that we map to OSS gain controls. This
+        * ensures useful knobs are exposed no matter if sndiod
+        * is running or not.
+        */ 
+       if (d->group[0] == 0) {
+               if (strcmp(d->node0.name, "output") == 0)
+                       type = SOUND_MIXER_VOLUME;
+               else if (strcmp(d->node0.name, "input") == 0)
+                       type = SOUND_MIXER_MIC;
+               else
+                       return;
+       } else if (strcmp(d->group, "hw") == 0) {
+               if (strcmp(d->node0.name, "output") == 0)
+                       type = SOUND_MIXER_OGAIN;
+               else if (strcmp(d->node0.name, "input") == 0)
+                       type = SOUND_MIXER_IGAIN;
+               else
+                       return;
+       } else
+               return;
+
+       i = malloc(sizeof(struct control));
+       if (i == NULL) {
+               DPRINTF("%s: cannot allocate control\n", __func__);             
+               return;
+       }
+
+       i->addr = d->addr;
+       i->chan = d->node0.unit;
+       i->max = d->maxval;
+       i->value = val;
+       i->type = type;
+       i->next = controls;
+       controls = i;
+       DPRINTF("%s: %d: used as %d, chan = %d, value = %d\n", __func__,
+           i->addr, i->type, i->chan, i->value);
 }
 
-static int
-enum_to_ord(struct audiodevinfo *di, int enm)
+/*
+ * control value changed
+ */
+static void
+mixer_onval(void *unused, unsigned int addr, unsigned int value)
 {
-       if (enm >= MAX_MIXER_DEVS)
-               return (-1);
+       struct control *c;
+
+       for (c = controls; ; c = c->next) {
+               if (c == NULL) {
+                       DPRINTF("%s: %d: change ignored\n", __func__, addr);
+                       return;
+               }
+               if (c->addr == addr)
+                       break;
+       }
 
-       /*printf("enum_to_ord %d -> %d\n", enm, di->enum2opaque[enm]);*/
-       return (di->enum2opaque[enm]);
+       DPRINTF("%s: %d: changed to %d\n", __func__, addr, value);
+       c->value = value;
 }
 
 static int
-enum_to_mask(struct audiodevinfo *di, int enm)
+mixer_init(void)
 {
-       int m;
-       if (enm >= MAX_MIXER_DEVS)
-               return (0);
-
-       m = di->enum2opaque[enm];
-       if (m == -1)
-               m = 0;
-       /*printf("enum_to_mask %d -> %d\n", enm, di->enum2opaque[enm]);*/
-       return (m);
-}
+       if (initialized)
+               return hdl != NULL;
 
-/*
- * Collect the audio device information to allow faster
- * emulation of the Linux mixer ioctls.  Cache the information
- * to eliminate the overhead of repeating all the ioctls needed
- * to collect the information.
- */
-static struct audiodevinfo *
-getdevinfo(int fd)
-{
-       mixer_devinfo_t mi, cl;
-       int i, j, e;
-       static struct {
-               char *name;
-               int code;
-       } *dp, devs[] = {
-               { AudioNmicrophone,     SOUND_MIXER_MIC },
-               { AudioNline,           SOUND_MIXER_LINE },
-               { AudioNcd,             SOUND_MIXER_CD },
-               { AudioNdac,            SOUND_MIXER_PCM },
-               { AudioNaux,            SOUND_MIXER_LINE1 },
-               { AudioNrecord,         SOUND_MIXER_IMIX },
-               { AudioNmaster,         SOUND_MIXER_VOLUME },
-               { AudioNtreble,         SOUND_MIXER_TREBLE },
-               { AudioNbass,           SOUND_MIXER_BASS },
-               { AudioNspeaker,        SOUND_MIXER_SPEAKER },
-               { AudioNoutput,         SOUND_MIXER_OGAIN },
-               { AudioNinput,          SOUND_MIXER_IGAIN },
-               { AudioNfmsynth,        SOUND_MIXER_SYNTH },
-               { AudioNmidi,           SOUND_MIXER_SYNTH },
-               { 0, -1 }
-       };
-       static struct audiodevinfo devcache = { 0 };
-       struct audiodevinfo *di = &devcache;
-       struct stat sb;
+       initialized = 1;
 
-       /* Figure out what device it is so we can check if the
-        * cached data is valid.
-        */
-       if (fstat(fd, &sb) < 0)
+       hdl = sioctl_open(dev_name, SIOCTL_READ | SIOCTL_WRITE, 0);
+       if (hdl == NULL) {
+               DPRINTF("%s: cannot open audio control device\n", __func__);
                return 0;
-       if (di->done && (di->dev == sb.st_dev && di->ino == sb.st_ino))
-               return di;
+       }
 
-       di->done = 1;
-       di->dev = sb.st_dev;
-       di->ino = sb.st_ino;
-       di->devmask = 0;
-       di->recmask = 0;
-       di->stereomask = 0;
-       di->recsource = ~0;
-       di->caps = 0;
-       for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
-               di->devmap[i] = -1;
-       for(i = 0; i < MAX_MIXER_DEVS; i++) {
-               di->rdevmap[i] = -1;
-               di->names[i][0] = '\0';
-               di->enum2opaque[i] = -1;
+       pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
+       if (pfds == NULL) {
+               DPRINTF("%s: cannot allocate pfds\n", __func__);
+               goto bad_close;
        }
-       for(i = 0; i < MAX_MIXER_DEVS; i++) {
-               mi.index = i;
-               if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1)
-                       break;
-               switch(mi.type) {
-               case AUDIO_MIXER_VALUE:
-                       for(dp = devs; dp->name; dp++)
-                               if (strcmp(dp->name, mi.label.name) == 0)
-                                       break;
-                       if (dp->code >= 0) {
-                               di->devmap[dp->code] = i;
-                               di->rdevmap[i] = dp->code;
-                               di->devmask |= 1 << dp->code;
-                               if (mi.un.v.num_channels == 2)
-                                       di->stereomask |= 1 << dp->code;
-                               strncpy(di->names[i], mi.label.name,
-                                       sizeof di->names[i]);
-                       }
-                       break;
-               }
+
+       if (!sioctl_ondesc(hdl, mixer_ondesc, NULL)) {
+               DPRINTF("%s: cannot get controls descriptions\n", __func__);
+               goto bad_free;
        }
-       for(i = 0; i < MAX_MIXER_DEVS; i++) {
-               mi.index = i;
-               if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1)
-                       break;
-               if (strcmp(mi.label.name, AudioNsource) != 0)
-                       continue;
-               cl.index = mi.mixer_class;
-               if (ioctl(fd, AUDIO_MIXER_DEVINFO, &cl) == -1)
-                       break;
-               if ((cl.type != AUDIO_MIXER_CLASS) ||
-                   (strcmp(cl.label.name, AudioCrecord) != 0))
-                       continue;
-               di->recsource = i;
-               switch(mi.type) {
-               case AUDIO_MIXER_ENUM:
-                       for(j = 0; j < mi.un.e.num_mem; j++) {
-                               e = opaque_to_enum(di,
-                                                  &mi.un.e.member[j].label,
-                                                  mi.un.e.member[j].ord);
-                               if (e >= 0)
-                                       di->recmask |= 1 << di->rdevmap[e];
-                       }
-                       di->caps = SOUND_CAP_EXCL_INPUT;
-                       break;
-               case AUDIO_MIXER_SET:
-                       for(j = 0; j < mi.un.s.num_mem; j++) {
-                               e = opaque_to_enum(di,
-                                                  &mi.un.s.member[j].label,
-                                                  mi.un.s.member[j].mask);
-                               if (e >= 0)
-                                       di->recmask |= 1 << di->rdevmap[e];
-                       }
-                       break;
-               }
+
+       if (!sioctl_onval(hdl, mixer_onval, NULL)) {
+               DPRINTF("%s: cannot get controls values\n", __func__);
+               goto bad_free;
        }
-       return di;
+
+       return 1;
+
+bad_free:
+       free(pfds);
+bad_close:
+       sioctl_close(hdl);
+       return 0;
 }
 
-int
+static int
 mixer_ioctl(int fd, unsigned long com, void *argp)
 {
-       struct audiodevinfo *di;
+       struct control *c;
        struct mixer_info *omi;
-       struct audio_device adev;
-       mixer_ctrl_t mc;
        int idat = 0;
-       int i;
-       int retval;
-       int l, r, n, error, e;
+       int v, n;
 
-       di = getdevinfo(fd);
-       if (di == 0)
+       if (!mixer_init()) {
+               DPRINTF("%s: not initialized\n", __func__);
+               errno = EIO;
                return -1;
+       }
+
+       n = sioctl_pollfd(hdl, pfds, POLLIN);
+       if (n > 0) {
+               n = poll(pfds, n, 0);
+               if (n == -1)
+                       return -1;
+               if (n > 0)
+                       sioctl_revents(hdl, pfds);
+       }
 
        switch (com) {
        case OSS_GETVERSION:
@@ -273,122 +247,80 @@ mixer_ioctl(int fd, unsigned long com, v
                break;
        case SOUND_MIXER_INFO:
        case SOUND_OLD_MIXER_INFO:
-               error = ioctl(fd, AUDIO_GETDEV, &adev);
-               if (error == -1)
-                       return (error);
                omi = argp;
                if (com == SOUND_MIXER_INFO)
                        omi->modify_counter = 1;
-               strncpy(omi->id, adev.name, sizeof omi->id);
-               strncpy(omi->name, adev.name, sizeof omi->name);
+               strlcpy(omi->id, dev_name, sizeof omi->id);
+               strlcpy(omi->name, dev_name, sizeof omi->name);
                return 0;
        case SOUND_MIXER_READ_RECSRC:
-               if (di->recsource == -1)
-                       return EINVAL;
-               mc.dev = di->recsource;
-               if (di->caps & SOUND_CAP_EXCL_INPUT) {
-                       mc.type = AUDIO_MIXER_ENUM;
-                       retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
-                       if (retval == -1)
-                               return retval;
-                       e = opaque_to_enum(di, NULL, mc.un.ord);
-                       if (e >= 0)
-                               idat = 1 << di->rdevmap[e];
-               } else {
-                       mc.type = AUDIO_MIXER_SET;
-                       retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
-                       if (retval == -1)
-                               return retval;
-                       e = opaque_to_enum(di, NULL, mc.un.mask);
-                       if (e >= 0)
-                               idat = 1 << di->rdevmap[e];
-               }
+       case SOUND_MIXER_READ_RECMASK:
+               idat = 0;
+               for (c = controls; c != NULL; c = c->next)
+                       idat |= 1 << c->type;
+               idat &= (1 << SOUND_MIXER_MIC) | (1 << SOUND_MIXER_IGAIN);
+               DPRINTF("%s: SOUND_MIXER_READ_RECSRC: %d\n", __func__, idat);
                break;
        case SOUND_MIXER_READ_DEVMASK:
-               idat = di->devmask;
-               break;
-       case SOUND_MIXER_READ_RECMASK:
-               idat = di->recmask;
+               idat = 0;
+               for (c = controls; c != NULL; c = c->next)
+                       idat |= 1 << c->type;
+               DPRINTF("%s: SOUND_MIXER_READ_DEVMASK: %d\n", __func__, idat);
                break;
        case SOUND_MIXER_READ_STEREODEVS:
-               idat = di->stereomask;
+               idat = 0;
+               for (c = controls; c != NULL; c = c->next) {
+                       if (c->chan == 1)
+                               idat |= 1 << c->type;
+               }
+               DPRINTF("%s: SOUND_MIXER_STEREODEVS: %d\n", __func__, idat);
                break;
        case SOUND_MIXER_READ_CAPS:
-               idat = di->caps;
+               idat = 0;
+               DPRINTF("%s: SOUND_MIXER_READ_CAPS: %d\n", __func__, idat);
                break;
        case SOUND_MIXER_WRITE_RECSRC:
        case SOUND_MIXER_WRITE_R_RECSRC:
-               if (di->recsource == -1)
-                       return EINVAL;
-               mc.dev = di->recsource;
-               idat = INTARG;
-               if (di->caps & SOUND_CAP_EXCL_INPUT) {
-                       mc.type = AUDIO_MIXER_ENUM;
-                       for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
-                               if (idat & (1 << i))
-                                       break;
-                       if (i >= SOUND_MIXER_NRDEVICES ||
-                           di->devmap[i] == -1)
-                               return EINVAL;
-                       mc.un.ord = enum_to_ord(di, di->devmap[i]);
-               } else {
-                       mc.type = AUDIO_MIXER_SET;
-                       mc.un.mask = 0;
-                       for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
-                               if (idat & (1 << i)) {
-                                       if (di->devmap[i] == -1)
-                                               return EINVAL;
-                                       mc.un.mask |= enum_to_mask(di, 
di->devmap[i]);
-                               }
-                       }
-               }
-               return ioctl(fd, AUDIO_MIXER_WRITE, &mc);
+               DPRINTF("%s: SOUND_MIXER_WRITE_RECSRC\n", __func__);
+               errno = EINVAL;
+               return -1;
        default:
                if (MIXER_READ(SOUND_MIXER_FIRST) <= com &&
                    com < MIXER_READ(SOUND_MIXER_NRDEVICES)) {
+               doread:
+                       idat = 0;
                        n = GET_DEV(com);
-                       if (di->devmap[n] == -1)
-                               return EINVAL;
-                       mc.dev = di->devmap[n];
-                       mc.type = AUDIO_MIXER_VALUE;
-                   doread:
-                       mc.un.value.num_channels = di->stereomask & (1<<n) ? 2 
: 1;
-                       retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
-                       if (retval == -1)
-                               return retval;
-                       if (mc.type != AUDIO_MIXER_VALUE)
-                               return EINVAL;
-                       if (mc.un.value.num_channels != 2) {
-                               l = r = 
mc.un.value.level[AUDIO_MIXER_LEVEL_MONO];
-                       } else {
-                               l = mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
-                               r = mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
+                       for (c = controls; c != NULL; c = c->next) {
+                               if (c->type != n)
+                                       continue;
+                               v = (c->value * 100 + c->max / 2) / c->max;
+                               if (c->chan == 1)
+                                       v <<= 8;
+                               idat |= v;
                        }
-                       idat = TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
+                       DPRINTF("%s: MIXER_READ: %d: 0x%04x\n",
+                           __func__, n, idat);
                        break;
                } else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com &&
                           com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) ||
                           (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
                           com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) {
-                       n = GET_DEV(com);
-                       if (di->devmap[n] == -1)
-                               return EINVAL;
                        idat = INTARG;
-                       l = FROM_OSSVOL( idat       & 0xff);
-                       r = FROM_OSSVOL((idat >> 8) & 0xff);
-                       mc.dev = di->devmap[n];
-                       mc.type = AUDIO_MIXER_VALUE;
-                       if (di->stereomask & (1<<n)) {
-                               mc.un.value.num_channels = 2;
-                               mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
-                               mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
-                       } else {
-                               mc.un.value.num_channels = 1;
-                               mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] = 
(l+r)/2;
+                       n = GET_DEV(com);
+                       for (c = controls; c != NULL; c = c->next) {
+                               if (c->type != n)
+                                       continue;
+                               v = idat;
+                               if (c->chan == 1)
+                                       v >>= 8;
+                               v &= 0xff;
+                               if (v > 100)
+                                       v = 100;
+                               v = (v * c->max + 50) / 100;
+                               sioctl_setval(hdl, c->addr, v);
+                               DPRINTF("%s: MIXER_WRITE: %d: %d\n",
+                                   __func__, n, v);
                        }
-                       retval = ioctl(fd, AUDIO_MIXER_WRITE, &mc);
-                       if (retval == -1)
-                               return retval;
                        if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
                           com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))
                                return 0;
@@ -398,6 +330,7 @@ mixer_ioctl(int fd, unsigned long com, v
                        return -1;
                }
        }
+
        INTARG = idat;
        return 0;
 }

Reply via email to