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;
}