1
0
mirror of https://github.com/openbsd/src.git synced 2026-06-18 07:13:36 +02:00

Add API to control audio device parameters exposed by sndiod.

The API exposes controls of modern audio hardware and sndiod software
volume knobs in a uniform way.  Hardware knobs are exposed through
sndiod.  Multiple programs may use the controls at the same time
without the need to continuously scan the controls.

For now sndiod exposes only its own controls and the master output and
input volumes of the underlying hardware (if any), i.e. those
typically exposed by acpi volume keys.

ok deraadt
This commit is contained in:
ratchov
2020-02-26 13:53:58 +00:00
parent c9831b39c7
commit d07fece68e
22 changed files with 2336 additions and 34 deletions
+50 -1
View File
@@ -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 <alex@caoua.org>
*
@@ -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
}
+4 -3
View File
@@ -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
+12
View File
@@ -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:
*;
};
+40 -1
View File
@@ -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 <alex@caoua.org>
*
@@ -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.
+1 -1
View File
@@ -1,2 +1,2 @@
major=7
minor=0
minor=1
+177
View File
@@ -0,0 +1,177 @@
/* $OpenBSD: sioctl.c,v 1.1 2020/02/26 13:53:58 ratchov Exp $ */
/*
* Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
*
* 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);
}
+273
View File
@@ -0,0 +1,273 @@
/*
* Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
*
* 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;
}
+252
View File
@@ -0,0 +1,252 @@
.\" $OpenBSD: sioctl_open.3,v 1.1 2020/02/26 13:53:58 ratchov Exp $
.\"
.\" Copyright (c) 2011 Alexandre Ratchov <alex@caoua.org>
.\"
.\" 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 <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
+62
View File
@@ -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 <alex@caoua.org>
*
* 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) */
+483
View File
@@ -0,0 +1,483 @@
/*
* Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
*
* 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;
}
+3 -3
View File
@@ -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
+4 -1
View File
@@ -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 <alex@caoua.org>
*
@@ -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
+353 -8
View File
@@ -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 <alex@caoua.org>
*
@@ -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;
}
+74 -2
View File
@@ -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 <alex@caoua.org>
*
@@ -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) */
+200
View File
@@ -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 <alex@caoua.org>
*
* 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);
}
+32
View File
@@ -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 <alex@caoua.org>
*
* 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) */
+35 -1
View File
@@ -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 <alex@caoua.org>
*
@@ -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;
+2 -1
View File
@@ -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 <alex@caoua.org>
*
@@ -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) */
+37 -6
View File
@@ -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 <alex@caoua.org>
*
@@ -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
+4 -2
View File
@@ -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 <alex@caoua.org>
*
@@ -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)
+231 -3
View File
@@ -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 <alex@caoua.org>
*
@@ -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);
+7 -1
View File
@@ -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 <alex@caoua.org>
*
@@ -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);