summaryrefslogtreecommitdiffstats
path: root/lib/libsndio/sioctl_sun.c
diff options
context:
space:
mode:
authorratchov <ratchov@openbsd.org>2020-02-26 13:53:58 +0000
committerratchov <ratchov@openbsd.org>2020-02-26 13:53:58 +0000
commitd07fece68e8c883aebc7155fb3b4c23ca5dc4675 (patch)
tree0e6bfec81cd81ab69423bb4cf3e5d0f669eaeddd /lib/libsndio/sioctl_sun.c
parentchange explicit_bzero();free() to freezero() (diff)
downloadwireguard-openbsd-d07fece68e8c883aebc7155fb3b4c23ca5dc4675.tar.xz
wireguard-openbsd-d07fece68e8c883aebc7155fb3b4c23ca5dc4675.zip
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
Diffstat (limited to 'lib/libsndio/sioctl_sun.c')
-rw-r--r--lib/libsndio/sioctl_sun.c483
1 files changed, 483 insertions, 0 deletions
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 <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;
+}