Audio control API, part 1: libsndio, sndiod bits

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
This diff adds an API to control knobs of audio interfaces. It aims to
address these points:

- allow the controls of modern audio hardware and sndiod software
  volume knobs in a uniform way. Hardware knobs are exposed by sndiod,
  which will be also important from security standpoint: by default
  programs using this API won't need to mess with /dev nodes.

- allow multiple programs to use the controls at the same time without
  the need to continuously scan the controls.

- the API is multi-channel and allows programs to "clump" controls and
  to represent many knobs as a single one. For instance the volume
  knob of a "modern" 10-channel azalia(4) could be represented as a
  single knob. This requires driver changes, not yet there.

- the API allows controls to be dynamically added or removed. This is
  useful to avoid restarting programs when devices are swapped,
  ex. when usb head-sets are used.

For now sndiod exposes only its own controls and the master output and
input volume knobs of the underlying hardware (if any). Other controls
don't seem necessary to expose to regular programs; we can keep things
simple and leave the other controls as configuration parameters set
only once, at system startup.

To ease discussions and review, the diff is split in 4 parts. This
diff is the first of 3 base diffs and 1 ports diff (see next
mails). To test the new code, apply all diffs, for instance:

cd /usr/src
patch -p0 <1.diff
patch -p0 <2.diff
patch -p0 <3.diff
cd /usr/src/include && doas make includes
cd /usr/src/lib/libsndio && make obj && make && doas make install
cd /usr/src/lib/libossaudio && make obj && make && doas make install
cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
doas rcctl restart sndiod
cd /usr/ports
patch -p0 <4.diff
cd /usr/ports/audio/gqmpeg && make && make install
cd /usr/ports/sysutils/tray-app && make && make install

I'm very interested in any regression. Feedback about the new features
is of course welcome as well.

Enjoy,

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 8 Feb 2020 14:49:37 -0000
@@ -24,12 +24,20 @@
  */
 #define SIO_DEVANY "default"
 #define MIO_PORTANY "default"
+#define SIOCTL_DEVANY "default"
+
+/*
+ * limits
+ */
+#define SIOCTL_NAMEMAX 12 /* max name length */
+#define SIOCTL_VALMAX 127 /* max control value */
 
 /*
  * private ``handle'' structure
  */
 struct sio_hdl;
 struct mio_hdl;
+struct sioctl_hdl;
 
 /*
  * parameters of a full-duplex stream
@@ -85,12 +93,39 @@ 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} */
+};
+
+/*
  * 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 8 Feb 2020 14:49:37 -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 8 Feb 2020 14:49:37 -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 8 Feb 2020 14:49:37 -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,37 @@ 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 __pad2[1];
+ uint16_t curval;
 };
 
 /*
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 8 Feb 2020 14:49:37 -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 8 Feb 2020 14:49:37 -0000
@@ -0,0 +1,177 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <[hidden email]>
+ *
+ * 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[] = SIOCTL_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 8 Feb 2020 14:49:37 -0000
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2010-2011 Alexandre Ratchov <[hidden email]>
+ *
+ * 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);
+ _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 8 Feb 2020 14:49:37 -0000
@@ -0,0 +1,252 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2011 Alexandre Ratchov <[hidden email]>
+.\"
+.\" 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 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,62 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2010-2011 Alexandre Ratchov <[hidden email]>
+ *
+ * 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)
+
+#define SUN_TO_SIOCTL(v) (((v) * 127 + 127) / 255)
+#define SIOCTL_TO_SUN(v) (((v) * 255 + 63) / 127)
+
+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] =
+    SIOCTL_TO_SUN(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.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 = SUN_TO_SIOCTL(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.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 = SUN_TO_SIOCTL(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 8 Feb 2020 14:49:38 -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 8 Feb 2020 14:49:38 -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 8 Feb 2020 14:49:38 -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, d->slot[i].vol);
+ }
+ dev_addctl(d, "", CTL_NUM,
+    CTLADDR_MASTER, "output", -1, "level", NULL, -1, 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,278 @@ 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 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->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;
+
+ if (val < 0 || val > MIDI_MAXCTL) {
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": ");
+ log_putu(val);
+ log_puts(": ctl val out of bounds\n");
+ }
+ return 0;
+ }
+ 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 (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 8 Feb 2020 14:49:38 -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,43 @@ 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 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 +164,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 +243,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 +297,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);
+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 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,204 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014 Alexandre Ratchov <[hidden email]>
+ *
+ * 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;
+ size_t len;
+ 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 if (strcmp(desc->group, GROUP_PREFIX) == 0 ||
+    strcmp(desc->group, "app") == 0) {
+ group = group_buf;
+ len = snprintf(group_buf, CTL_NAMEMAX,
+    GROUP_PREFIX ".%s", desc->group);
+ if (len >= CTL_NAMEMAX)
+ return;
+ } else
+ group = desc->group;
+
+ dev_addctl(d, group, desc->type, addr,
+    desc->node0.name, desc->node0.unit, desc->func,
+    desc->node1.name, desc->node1.unit, 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 8 Feb 2020 14:49:38 -0000
@@ -0,0 +1,32 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2014 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 8 Feb 2020 14:49:38 -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 8 Feb 2020 14:49:38 -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 8 Feb 2020 14:49:39 -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 8 Feb 2020 14:49:39 -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 8 Feb 2020 14:49:39 -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,105 @@ 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->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 8 Feb 2020 14:49:39 -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);

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Florian Obser-2
I've been running the base diffs since you posted them. Firefox,
chrome and mpv still make noise :)

I'm puzzled by this:

$ cat /etc/mixerctl.conf                                                      
outputs.master=255,255
record.enable=off

$ mixerctl outputs.master                                                    
outputs.master=255,255

$ sndioctl                
output.level=127

I don't understand how they relate and why one goes to 255 and the
other to 127.
The error reporting is confusing, too:

$ sndioctl output.level=128                                                  
integer overflow

But no regressions to report :)

--
I'm not entirely sure you are real.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
On Tue, Feb 11, 2020 at 07:01:28PM +0100, Florian Obser wrote:

> I've been running the base diffs since you posted them. Firefox,
> chrome and mpv still make noise :)
>
> I'm puzzled by this:
>
> $ cat /etc/mixerctl.conf                                                      
> outputs.master=255,255
> record.enable=off
>
> $ mixerctl outputs.master                                                    
> outputs.master=255,255
>
> $ sndioctl                
> output.level=127
>
> I don't understand how they relate and why one goes to 255 and the
> other to 127.
> The error reporting is confusing, too:
>
> $ sndioctl output.level=128                                                  
> integer overflow
>
> But no regressions to report :)
>

Thanks, the code is base on MIDI bits, which uses the 0..127 range;
sndiod, aucat and many codecs also use the 0..127 range. Anyway,
replaced the error message by:

$ sndioctl output.level=128
128: expected integer in the 0..127 range

[...]

I'm wondering if persents or floating points in the [0:1] range would
be less confusing and solve most "units" problems.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Jan Stary
In reply to this post by Alexandre Ratchov-2
Hi,

On Feb 09 13:13:02, [hidden email] wrote:

> cd /usr/src
> patch -p0 <1.diff
> patch -p0 <2.diff
> patch -p0 <3.diff
> cd /usr/src/include && doas make includes
> cd /usr/src/lib/libsndio && make obj && make && doas make install
> cd /usr/src/lib/libossaudio && make obj && make && doas make install
> cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> doas rcctl restart sndiod
>
> I'm very interested in any regression.

After restarting sndiod (with empty flags),
starting sndioctl -m makes sndiod crash with

  Feb 12 21:02:31 box /bsd: sndiod[95433]: pledge "tty", syscall 54

That's ioctl(2). I don't see that in any of the three pledge(2) calls
revealed by a grep in (the patched) .../sndiod/, but apparently
the new code does call ioctl().


Strangely, this happens on

OpenBSD 6.6-current (GENERIC.MP) #0: Tue Feb  4 17:33:19 CET 2020
    [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP

but not on

OpenBSD 6.6-current (GENERIC.MP) #0: Sun Feb  9 17:30:47 CET 2020
    [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP

(Did I miss somethong pledge() related in that window?)

        Jan

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Jan Stary
In reply to this post by Alexandre Ratchov-2
Hi,

On Feb 09 13:13:02, [hidden email] wrote:

> cd /usr/src
> patch -p0 <1.diff
> patch -p0 <2.diff
> patch -p0 <3.diff
> cd /usr/src/include && doas make includes
> cd /usr/src/lib/libsndio && make obj && make && doas make install
> cd /usr/src/lib/libossaudio && make obj && make && doas make install
> cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> doas rcctl restart sndiod
> cd /usr/ports
> patch -p0 <4.diff
> cd /usr/ports/audio/gqmpeg && make && make install
> cd /usr/ports/sysutils/tray-app && make && make install
>
> Feedback about the new features
> is of course welcome as well.

sndioctl -m works fine with e.g. mplayer,
i.e. sndioctl notices the "added" app,
and reports the volume changes.

With ffplay and firefox:youtube, volume changes are not reported.
Is that expected? Do they tweak their "internal" volume knob
but not the volume they register with sndio? As opposed to mplayer?

At any rate, thanks for the new tool!

        Jan

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
In reply to this post by Jan Stary
On Wed, Feb 12, 2020 at 09:22:20PM +0100, Jan Stary wrote:

> Hi,
>
> On Feb 09 13:13:02, [hidden email] wrote:
> > cd /usr/src
> > patch -p0 <1.diff
> > patch -p0 <2.diff
> > patch -p0 <3.diff
> > cd /usr/src/include && doas make includes
> > cd /usr/src/lib/libsndio && make obj && make && doas make install
> > cd /usr/src/lib/libossaudio && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> > doas rcctl restart sndiod
> >
> > I'm very interested in any regression.
>
> After restarting sndiod (with empty flags),
> starting sndioctl -m makes sndiod crash with
>
>   Feb 12 21:02:31 box /bsd: sndiod[95433]: pledge "tty", syscall 54
>
> That's ioctl(2). I don't see that in any of the three pledge(2) calls
> revealed by a grep in (the patched) .../sndiod/, but apparently
> the new code does call ioctl().

sndiod does unauthorized ioctls on this kernel, see below

>
> Strangely, this happens on
>
> OpenBSD 6.6-current (GENERIC.MP) #0: Tue Feb  4 17:33:19 CET 2020
>     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
>
> but not on
>
> OpenBSD 6.6-current (GENERIC.MP) #0: Sun Feb  9 17:30:47 CET 2020
>     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
>
> (Did I miss somethong pledge() related in that window?)
>

Yes, there was a kernel commit to allow programs with the "audio"
promise (like sndiod) to use the mixer ioctl()s.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Alexandre Ratchov-2
In reply to this post by Jan Stary
On Wed, Feb 12, 2020 at 09:28:38PM +0100, Jan Stary wrote:

> Hi,
>
> On Feb 09 13:13:02, [hidden email] wrote:
> > cd /usr/src
> > patch -p0 <1.diff
> > patch -p0 <2.diff
> > patch -p0 <3.diff
> > cd /usr/src/include && doas make includes
> > cd /usr/src/lib/libsndio && make obj && make && doas make install
> > cd /usr/src/lib/libossaudio && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> > cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> > doas rcctl restart sndiod
> > cd /usr/ports
> > patch -p0 <4.diff
> > cd /usr/ports/audio/gqmpeg && make && make install
> > cd /usr/ports/sysutils/tray-app && make && make install
> >
> > Feedback about the new features
> > is of course welcome as well.
>
> sndioctl -m works fine with e.g. mplayer,
> i.e. sndioctl notices the "added" app,
> and reports the volume changes.

great :)

> With ffplay and firefox:youtube, volume changes are not reported.
> Is that expected? Do they tweak their "internal" volume knob
> but not the volume they register with sndio? As opposed to mplayer?

Yes exactly. I haven't checked ffplay, but firefox uses an internal
volume control because its internal API has no "getter", this is
explained here:

https://marc.info/?l=openbsd-ports&m=152641946326955

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Jan Stary
In reply to this post by Alexandre Ratchov-2
On Feb 12 21:38:56, [hidden email] wrote:

> On Wed, Feb 12, 2020 at 09:22:20PM +0100, Jan Stary wrote:
> > Hi,
> >
> > On Feb 09 13:13:02, [hidden email] wrote:
> > > cd /usr/src
> > > patch -p0 <1.diff
> > > patch -p0 <2.diff
> > > patch -p0 <3.diff
> > > cd /usr/src/include && doas make includes
> > > cd /usr/src/lib/libsndio && make obj && make && doas make install
> > > cd /usr/src/lib/libossaudio && make obj && make && doas make install
> > > cd /usr/src/usr.bin/sndiod && make obj && make && doas make install
> > > cd /usr/src/usr.bin/sndioctl && make obj && make && doas make install
> > > doas rcctl restart sndiod
> > >
> > > I'm very interested in any regression.
> >
> > After restarting sndiod (with empty flags),
> > starting sndioctl -m makes sndiod crash with
> >
> >   Feb 12 21:02:31 box /bsd: sndiod[95433]: pledge "tty", syscall 54
> >
> > That's ioctl(2). I don't see that in any of the three pledge(2) calls
> > revealed by a grep in (the patched) .../sndiod/, but apparently
> > the new code does call ioctl().
>
> sndiod does unauthorized ioctls on this kernel, see below
>
> >
> > Strangely, this happens on
> >
> > OpenBSD 6.6-current (GENERIC.MP) #0: Tue Feb  4 17:33:19 CET 2020
> >     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> >
> > but not on
> >
> > OpenBSD 6.6-current (GENERIC.MP) #0: Sun Feb  9 17:30:47 CET 2020
> >     [hidden email]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> >
> > (Did I miss somethong pledge() related in that window?)
> >
>
> Yes, there was a kernel commit to allow programs with the "audio"
> promise (like sndiod) to use the mixer ioctl()s.

Indeed, the problem disappears with the current curent.
Sorry for the noise.

        Jan

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Raf Czlonka-2
In reply to this post by Alexandre Ratchov-2
On Tue, Feb 11, 2020 at 07:15:00PM GMT, Alexandre Ratchov wrote:

> On Tue, Feb 11, 2020 at 07:01:28PM +0100, Florian Obser wrote:
> > I've been running the base diffs since you posted them. Firefox,
> > chrome and mpv still make noise :)
> >
> > I'm puzzled by this:
> >
> > $ cat /etc/mixerctl.conf                                                      
> > outputs.master=255,255
> > record.enable=off
> >
> > $ mixerctl outputs.master                                                    
> > outputs.master=255,255
> >
> > $ sndioctl                
> > output.level=127
> >
> > I don't understand how they relate and why one goes to 255 and the
> > other to 127.
> > The error reporting is confusing, too:
> >
> > $ sndioctl output.level=128                                                  
> > integer overflow
> >
> > But no regressions to report :)
> >
>
> Thanks, the code is base on MIDI bits, which uses the 0..127 range;
> sndiod, aucat and many codecs also use the 0..127 range. Anyway,
> replaced the error message by:
>
> $ sndioctl output.level=128
> 128: expected integer in the 0..127 range
>
> [...]
>
> I'm wondering if persents or floating points in the [0:1] range would
> be less confusing and solve most "units" problems.
>

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?

Cheers,

Raf