diff --git a/include/sndio.h b/include/sndio.h index dcb9ddfbb19..4fed3d5c2d1 100644 --- a/include/sndio.h +++ b/include/sndio.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sndio.h,v 1.9 2015/12/20 11:29:29 ratchov Exp $ */ +/* $OpenBSD: sndio.h,v 1.10 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -25,11 +25,17 @@ #define SIO_DEVANY "default" #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 @@ -84,6 +90,33 @@ 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 */ @@ -91,6 +124,8 @@ struct sio_cap { #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 pollfd *, int); 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 } diff --git a/lib/libsndio/Makefile b/lib/libsndio/Makefile index 05e69a363e7..c5134fa4b42 100644 --- a/lib/libsndio/Makefile +++ b/lib/libsndio/Makefile @@ -1,9 +1,10 @@ -# $OpenBSD: Makefile,v 1.13 2017/12/26 15:23:33 jca Exp $ +# $OpenBSD: Makefile,v 1.14 2020/02/26 13:53:58 ratchov 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 diff --git a/lib/libsndio/Symbols.map b/lib/libsndio/Symbols.map index 171d696643f..a83e63d6a97 100644 --- a/lib/libsndio/Symbols.map +++ b/lib/libsndio/Symbols.map @@ -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: *; }; diff --git a/lib/libsndio/amsg.h b/lib/libsndio/amsg.h index a22ea0e1078..abea9088879 100644 --- a/lib/libsndio/amsg.h +++ b/lib/libsndio/amsg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: amsg.h,v 1.12 2019/07/12 06:30:55 ratchov Exp $ */ +/* $OpenBSD: amsg.h,v 1.13 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -42,6 +42,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 @@ -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,9 +116,40 @@ 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]; +}; + /* * Initialize an amsg structure: fill all fields with 0xff, so the read * can test which fields were set. diff --git a/lib/libsndio/shlib_version b/lib/libsndio/shlib_version index 5b844bbf422..b39addfcc64 100644 --- a/lib/libsndio/shlib_version +++ b/lib/libsndio/shlib_version @@ -1,2 +1,2 @@ major=7 -minor=0 +minor=1 diff --git a/lib/libsndio/sioctl.c b/lib/libsndio/sioctl.c new file mode 100644 index 00000000000..73bc5b5a213 --- /dev/null +++ b/lib/libsndio/sioctl.c @@ -0,0 +1,177 @@ +/* $OpenBSD: sioctl.c,v 1.1 2020/02/26 13:53:58 ratchov Exp $ */ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include + +#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); +} diff --git a/lib/libsndio/sioctl_aucat.c b/lib/libsndio/sioctl_aucat.c new file mode 100644 index 00000000000..b6de2744f96 --- /dev/null +++ b/lib/libsndio/sioctl_aucat.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/lib/libsndio/sioctl_open.3 b/lib/libsndio/sioctl_open.3 new file mode 100644 index 00000000000..64933fd7591 --- /dev/null +++ b/lib/libsndio/sioctl_open.3 @@ -0,0 +1,252 @@ +.\" $OpenBSD: sioctl_open.3,v 1.1 2020/02/26 13:53:58 ratchov Exp $ +.\" +.\" Copyright (c) 2011 Alexandre Ratchov +.\" +.\" 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: February 26 2020 $ +.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 +.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 diff --git a/lib/libsndio/sioctl_priv.h b/lib/libsndio/sioctl_priv.h new file mode 100644 index 00000000000..bfe806c0757 --- /dev/null +++ b/lib/libsndio/sioctl_priv.h @@ -0,0 +1,62 @@ +/* $OpenBSD: sioctl_priv.h,v 1.1 2020/02/26 13:53:58 ratchov Exp $ */ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 + +#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) */ diff --git a/lib/libsndio/sioctl_sun.c b/lib/libsndio/sioctl_sun.c new file mode 100644 index 00000000000..c1d3ac33dc7 --- /dev/null +++ b/lib/libsndio/sioctl_sun.c @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/usr.bin/sndiod/Makefile b/usr.bin/sndiod/Makefile index 2bfe3936c24..dbcc672bef4 100644 --- a/usr.bin/sndiod/Makefile +++ b/usr.bin/sndiod/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.5 2016/01/07 07:41:01 ratchov Exp $ +# $OpenBSD: Makefile,v 1.6 2020/02/26 13:53:58 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 diff --git a/usr.bin/sndiod/defs.h b/usr.bin/sndiod/defs.h index b0e09fa84eb..0ae96472aa3 100644 --- a/usr.bin/sndiod/defs.h +++ b/usr.bin/sndiod/defs.h @@ -1,4 +1,4 @@ -/* $OpenBSD: defs.h,v 1.4 2019/07/28 09:44:10 ratchov Exp $ */ +/* $OpenBSD: defs.h,v 1.5 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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 diff --git a/usr.bin/sndiod/dev.c b/usr.bin/sndiod/dev.c index 87352b19ac7..6a5ed2c6165 100644 --- a/usr.bin/sndiod/dev.c +++ b/usr.bin/sndiod/dev.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dev.c,v 1.63 2020/01/10 19:01:55 ratchov Exp $ */ +/* $OpenBSD: dev.c,v 1.64 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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, @@ -128,16 +132,23 @@ dev_log(struct dev *d) #endif } +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 slot *s) 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 *msg, int len) 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 *msg, int len) 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); @@ -2097,3 +2167,278 @@ 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; +} diff --git a/usr.bin/sndiod/dev.h b/usr.bin/sndiod/dev.h index 69a51297f72..d095fb9f632 100644 --- a/usr.bin/sndiod/dev.h +++ b/usr.bin/sndiod/dev.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dev.h,v 1.22 2019/09/21 04:42:46 ratchov Exp $ */ +/* $OpenBSD: dev.h,v 1.23 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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 */ @@ -104,6 +114,44 @@ struct opt { int mode; /* bitmap of MODE_XXX */ }; +/* + * 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 */ @@ -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; @@ -242,4 +299,19 @@ 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) */ diff --git a/usr.bin/sndiod/dev_sioctl.c b/usr.bin/sndiod/dev_sioctl.c new file mode 100644 index 00000000000..4270135efd0 --- /dev/null +++ b/usr.bin/sndiod/dev_sioctl.c @@ -0,0 +1,200 @@ +/* $OpenBSD: dev_sioctl.c,v 1.1 2020/02/26 13:53:58 ratchov Exp $ */ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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 +#include + +#include +#include +#include +#include +#include +#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); +} diff --git a/usr.bin/sndiod/dev_sioctl.h b/usr.bin/sndiod/dev_sioctl.h new file mode 100644 index 00000000000..8fd4edda2d1 --- /dev/null +++ b/usr.bin/sndiod/dev_sioctl.h @@ -0,0 +1,32 @@ +/* $OpenBSD: dev_sioctl.h,v 1.1 2020/02/26 13:53:58 ratchov Exp $ */ +/* + * Copyright (c) 2014-2020 Alexandre Ratchov + * + * 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) */ diff --git a/usr.bin/sndiod/fdpass.c b/usr.bin/sndiod/fdpass.c index 86849110880..1ab493fbe35 100644 --- a/usr.bin/sndiod/fdpass.c +++ b/usr.bin/sndiod/fdpass.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fdpass.c,v 1.8 2020/01/23 05:40:09 ratchov Exp $ */ +/* $OpenBSD: fdpass.c,v 1.9 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2015 Alexandre Ratchov * @@ -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, unsigned int mode) 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) { @@ -346,6 +363,23 @@ fdpass_in_helper(void *arg) } 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); return; diff --git a/usr.bin/sndiod/fdpass.h b/usr.bin/sndiod/fdpass.h index 29819b5b71e..e11926e26b0 100644 --- a/usr.bin/sndiod/fdpass.h +++ b/usr.bin/sndiod/fdpass.h @@ -1,4 +1,4 @@ -/* $OpenBSD: fdpass.h,v 1.2 2020/01/23 05:40:09 ratchov Exp $ */ +/* $OpenBSD: fdpass.h,v 1.3 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2015 Alexandre Ratchov * @@ -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) */ diff --git a/usr.bin/sndiod/siofile.c b/usr.bin/sndiod/siofile.c index 2579e7d0ce1..c66941ed3cb 100644 --- a/usr.bin/sndiod/siofile.c +++ b/usr.bin/sndiod/siofile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: siofile.c,v 1.17 2020/01/23 05:40:09 ratchov Exp $ */ +/* $OpenBSD: siofile.c,v 1.18 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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 int mode) 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 diff --git a/usr.bin/sndiod/sndiod.c b/usr.bin/sndiod/sndiod.c index 1d4cb60d2b6..7e48affb1f3 100644 --- a/usr.bin/sndiod/sndiod.c +++ b/usr.bin/sndiod/sndiod.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sndiod.c,v 1.37 2019/09/21 04:52:07 ratchov Exp $ */ +/* $OpenBSD: sndiod.c,v 1.38 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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) diff --git a/usr.bin/sndiod/sock.c b/usr.bin/sndiod/sock.c index 6bc800c4fa5..be192acb830 100644 --- a/usr.bin/sndiod/sock.c +++ b/usr.bin/sndiod/sock.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sock.c,v 1.31 2019/07/12 06:30:55 ratchov Exp $ */ +/* $OpenBSD: sock.c,v 1.32 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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. @@ -1380,6 +1508,106 @@ sock_buildmsg(struct sock *f) 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 if (log_level >= 4) { sock_log(f); diff --git a/usr.bin/sndiod/sock.h b/usr.bin/sndiod/sock.h index cb49dea1f2b..5c50412a101 100644 --- a/usr.bin/sndiod/sock.h +++ b/usr.bin/sndiod/sock.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sock.h,v 1.5 2018/06/26 07:13:54 ratchov Exp $ */ +/* $OpenBSD: sock.h,v 1.6 2020/02/26 13:53:58 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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);