1*fb2547fdSratchov /* $OpenBSD: ossaudio.c,v 1.21 2020/04/02 19:57:10 ratchov Exp $ */
242752947Sbrad /* $NetBSD: ossaudio.c,v 1.14 2001/05/10 01:53:48 augustss Exp $ */
3f39d4eeeSprovos
442752947Sbrad /*-
5f39d4eeeSprovos * Copyright (c) 1997 The NetBSD Foundation, Inc.
6f39d4eeeSprovos * All rights reserved.
7f39d4eeeSprovos *
8f39d4eeeSprovos * Redistribution and use in source and binary forms, with or without
9f39d4eeeSprovos * modification, are permitted provided that the following conditions
10f39d4eeeSprovos * are met:
11f39d4eeeSprovos * 1. Redistributions of source code must retain the above copyright
12f39d4eeeSprovos * notice, this list of conditions and the following disclaimer.
13f39d4eeeSprovos * 2. Redistributions in binary form must reproduce the above copyright
14f39d4eeeSprovos * notice, this list of conditions and the following disclaimer in the
15f39d4eeeSprovos * documentation and/or other materials provided with the distribution.
16f39d4eeeSprovos *
17f39d4eeeSprovos * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18f39d4eeeSprovos * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19f39d4eeeSprovos * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20f39d4eeeSprovos * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21f39d4eeeSprovos * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22f39d4eeeSprovos * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23f39d4eeeSprovos * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24f39d4eeeSprovos * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25f39d4eeeSprovos * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26f39d4eeeSprovos * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27f39d4eeeSprovos * POSSIBILITY OF SUCH DAMAGE.
28f39d4eeeSprovos */
29f39d4eeeSprovos
30f39d4eeeSprovos /*
31f39d4eeeSprovos * This is an OSS (Linux) sound API emulator.
32f39d4eeeSprovos * It provides the essentials of the API.
33f39d4eeeSprovos */
34f39d4eeeSprovos
359e43436fSespie #include <stdarg.h>
36f39d4eeeSprovos #include <string.h>
37f39d4eeeSprovos #include <sys/types.h>
38f39d4eeeSprovos #include <sys/ioctl.h>
39f39d4eeeSprovos #include <errno.h>
40*fb2547fdSratchov #include <poll.h>
41*fb2547fdSratchov #include <sndio.h>
42*fb2547fdSratchov #include <stdlib.h>
43*fb2547fdSratchov #include <stdio.h>
44f39d4eeeSprovos #include "soundcard.h"
45*fb2547fdSratchov
46*fb2547fdSratchov #ifdef DEBUG
47*fb2547fdSratchov #define DPRINTF(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
48*fb2547fdSratchov #else
49*fb2547fdSratchov #define DPRINTF(...) do {} while (0)
50*fb2547fdSratchov #endif
51f39d4eeeSprovos
52f39d4eeeSprovos #define GET_DEV(com) ((com) & 0xff)
53*fb2547fdSratchov #define INTARG (*(int*)argp)
54f39d4eeeSprovos
55*fb2547fdSratchov struct control {
56*fb2547fdSratchov struct control *next;
57*fb2547fdSratchov int type; /* one of SOUND_MIXER_xxx */
58*fb2547fdSratchov int chan; /* 0 -> left, 1 -> right, -1 -> mono */
59*fb2547fdSratchov int addr; /* sioctl control id */
60*fb2547fdSratchov int value; /* current value */
61*fb2547fdSratchov int max;
62*fb2547fdSratchov };
63f39d4eeeSprovos
64f39d4eeeSprovos static int mixer_ioctl(int, unsigned long, void *);
65f39d4eeeSprovos
66*fb2547fdSratchov static int initialized;
67*fb2547fdSratchov static struct control *controls;
68*fb2547fdSratchov static struct sioctl_hdl *hdl;
69*fb2547fdSratchov static char *dev_name = SIO_DEVANY;
70*fb2547fdSratchov static struct pollfd *pfds;
71f39d4eeeSprovos
72f39d4eeeSprovos int
_oss_ioctl(int fd,unsigned long com,...)739e43436fSespie _oss_ioctl(int fd, unsigned long com, ...)
74f39d4eeeSprovos {
759e43436fSespie va_list ap;
769e43436fSespie void *argp;
779e43436fSespie
789e43436fSespie va_start(ap, com);
799e43436fSespie argp = va_arg(ap, void *);
809e43436fSespie va_end(ap);
81f39d4eeeSprovos if (IOCGROUP(com) == 'P')
82d7a81f84Sratchov return ENOTTY;
83f39d4eeeSprovos else if (IOCGROUP(com) == 'M')
84f39d4eeeSprovos return mixer_ioctl(fd, com, argp);
85f39d4eeeSprovos else
86*fb2547fdSratchov return (ioctl)(fd, com, argp);
8742752947Sbrad }
8842752947Sbrad
89f39d4eeeSprovos /*
90*fb2547fdSratchov * new control
91f39d4eeeSprovos */
92*fb2547fdSratchov static void
mixer_ondesc(void * unused,struct sioctl_desc * d,int val)93*fb2547fdSratchov mixer_ondesc(void *unused, struct sioctl_desc *d, int val)
94f39d4eeeSprovos {
95*fb2547fdSratchov struct control *i, **pi;
96*fb2547fdSratchov int type;
97f39d4eeeSprovos
98*fb2547fdSratchov if (d == NULL)
99*fb2547fdSratchov return;
100*fb2547fdSratchov
101*fb2547fdSratchov /*
102*fb2547fdSratchov * delete existing control with the same address
103f39d4eeeSprovos */
104*fb2547fdSratchov for (pi = &controls; (i = *pi) != NULL; pi = &i->next) {
105*fb2547fdSratchov if (d->addr == i->addr) {
106*fb2547fdSratchov *pi = i->next;
107*fb2547fdSratchov free(i);
108*fb2547fdSratchov break;
109*fb2547fdSratchov }
110*fb2547fdSratchov }
111*fb2547fdSratchov
112*fb2547fdSratchov /*
113*fb2547fdSratchov * we support only numeric "level" controls, first 2 channels
114*fb2547fdSratchov */
115*fb2547fdSratchov if (d->type != SIOCTL_NUM || d->node0.unit >= 2 ||
116*fb2547fdSratchov strcmp(d->func, "level") != 0)
117*fb2547fdSratchov return;
118*fb2547fdSratchov
119*fb2547fdSratchov /*
120*fb2547fdSratchov * We expose top-level input.level and output.level as OSS
121*fb2547fdSratchov * volume and microphone knobs. By default sndiod exposes
122*fb2547fdSratchov * the underlying hardware knobs as hw/input.level and
123*fb2547fdSratchov * hw/output.level that we map to OSS gain controls. This
124*fb2547fdSratchov * ensures useful knobs are exposed no matter if sndiod
125*fb2547fdSratchov * is running or not.
126*fb2547fdSratchov */
127*fb2547fdSratchov if (d->group[0] == 0) {
128*fb2547fdSratchov if (strcmp(d->node0.name, "output") == 0)
129*fb2547fdSratchov type = SOUND_MIXER_VOLUME;
130*fb2547fdSratchov else if (strcmp(d->node0.name, "input") == 0)
131*fb2547fdSratchov type = SOUND_MIXER_MIC;
132*fb2547fdSratchov else
133*fb2547fdSratchov return;
134*fb2547fdSratchov } else if (strcmp(d->group, "hw") == 0) {
135*fb2547fdSratchov if (strcmp(d->node0.name, "output") == 0)
136*fb2547fdSratchov type = SOUND_MIXER_OGAIN;
137*fb2547fdSratchov else if (strcmp(d->node0.name, "input") == 0)
138*fb2547fdSratchov type = SOUND_MIXER_IGAIN;
139*fb2547fdSratchov else
140*fb2547fdSratchov return;
141*fb2547fdSratchov } else
142*fb2547fdSratchov return;
143*fb2547fdSratchov
144*fb2547fdSratchov i = malloc(sizeof(struct control));
145*fb2547fdSratchov if (i == NULL) {
146*fb2547fdSratchov DPRINTF("%s: cannot allocate control\n", __func__);
147*fb2547fdSratchov return;
148*fb2547fdSratchov }
149*fb2547fdSratchov
150*fb2547fdSratchov i->addr = d->addr;
151*fb2547fdSratchov i->chan = d->node0.unit;
152*fb2547fdSratchov i->max = d->maxval;
153*fb2547fdSratchov i->value = val;
154*fb2547fdSratchov i->type = type;
155*fb2547fdSratchov i->next = controls;
156*fb2547fdSratchov controls = i;
157*fb2547fdSratchov DPRINTF("%s: %d: used as %d, chan = %d, value = %d\n", __func__,
158*fb2547fdSratchov i->addr, i->type, i->chan, i->value);
159*fb2547fdSratchov }
160*fb2547fdSratchov
161*fb2547fdSratchov /*
162*fb2547fdSratchov * control value changed
163*fb2547fdSratchov */
164*fb2547fdSratchov static void
mixer_onval(void * unused,unsigned int addr,unsigned int value)165*fb2547fdSratchov mixer_onval(void *unused, unsigned int addr, unsigned int value)
166*fb2547fdSratchov {
167*fb2547fdSratchov struct control *c;
168*fb2547fdSratchov
169*fb2547fdSratchov for (c = controls; ; c = c->next) {
170*fb2547fdSratchov if (c == NULL) {
171*fb2547fdSratchov DPRINTF("%s: %d: change ignored\n", __func__, addr);
172*fb2547fdSratchov return;
173*fb2547fdSratchov }
174*fb2547fdSratchov if (c->addr == addr)
175*fb2547fdSratchov break;
176*fb2547fdSratchov }
177*fb2547fdSratchov
178*fb2547fdSratchov DPRINTF("%s: %d: changed to %d\n", __func__, addr, value);
179*fb2547fdSratchov c->value = value;
180*fb2547fdSratchov }
181*fb2547fdSratchov
182*fb2547fdSratchov static int
mixer_init(void)183*fb2547fdSratchov mixer_init(void)
184*fb2547fdSratchov {
185*fb2547fdSratchov if (initialized)
186*fb2547fdSratchov return hdl != NULL;
187*fb2547fdSratchov
188*fb2547fdSratchov initialized = 1;
189*fb2547fdSratchov
190*fb2547fdSratchov hdl = sioctl_open(dev_name, SIOCTL_READ | SIOCTL_WRITE, 0);
191*fb2547fdSratchov if (hdl == NULL) {
192*fb2547fdSratchov DPRINTF("%s: cannot open audio control device\n", __func__);
193f39d4eeeSprovos return 0;
194f39d4eeeSprovos }
195f39d4eeeSprovos
196*fb2547fdSratchov pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
197*fb2547fdSratchov if (pfds == NULL) {
198*fb2547fdSratchov DPRINTF("%s: cannot allocate pfds\n", __func__);
199*fb2547fdSratchov goto bad_close;
200*fb2547fdSratchov }
201*fb2547fdSratchov
202*fb2547fdSratchov if (!sioctl_ondesc(hdl, mixer_ondesc, NULL)) {
203*fb2547fdSratchov DPRINTF("%s: cannot get controls descriptions\n", __func__);
204*fb2547fdSratchov goto bad_free;
205*fb2547fdSratchov }
206*fb2547fdSratchov
207*fb2547fdSratchov if (!sioctl_onval(hdl, mixer_onval, NULL)) {
208*fb2547fdSratchov DPRINTF("%s: cannot get controls values\n", __func__);
209*fb2547fdSratchov goto bad_free;
210*fb2547fdSratchov }
211*fb2547fdSratchov
212*fb2547fdSratchov return 1;
213*fb2547fdSratchov
214*fb2547fdSratchov bad_free:
215*fb2547fdSratchov free(pfds);
216*fb2547fdSratchov bad_close:
217*fb2547fdSratchov sioctl_close(hdl);
218*fb2547fdSratchov return 0;
219*fb2547fdSratchov }
220*fb2547fdSratchov
221*fb2547fdSratchov static int
mixer_ioctl(int fd,unsigned long com,void * argp)222f39d4eeeSprovos mixer_ioctl(int fd, unsigned long com, void *argp)
223f39d4eeeSprovos {
224*fb2547fdSratchov struct control *c;
22542752947Sbrad struct mixer_info *omi;
2266ce51518Savsm int idat = 0;
227*fb2547fdSratchov int v, n;
228f39d4eeeSprovos
229*fb2547fdSratchov if (!mixer_init()) {
230*fb2547fdSratchov DPRINTF("%s: not initialized\n", __func__);
231*fb2547fdSratchov errno = EIO;
232f39d4eeeSprovos return -1;
233*fb2547fdSratchov }
234*fb2547fdSratchov
235*fb2547fdSratchov n = sioctl_pollfd(hdl, pfds, POLLIN);
236*fb2547fdSratchov if (n > 0) {
237*fb2547fdSratchov n = poll(pfds, n, 0);
238*fb2547fdSratchov if (n == -1)
239*fb2547fdSratchov return -1;
240*fb2547fdSratchov if (n > 0)
241*fb2547fdSratchov sioctl_revents(hdl, pfds);
242*fb2547fdSratchov }
243f39d4eeeSprovos
244f39d4eeeSprovos switch (com) {
24542752947Sbrad case OSS_GETVERSION:
24642752947Sbrad idat = SOUND_VERSION;
24742752947Sbrad break;
24842752947Sbrad case SOUND_MIXER_INFO:
24942752947Sbrad case SOUND_OLD_MIXER_INFO:
25042752947Sbrad omi = argp;
25142752947Sbrad if (com == SOUND_MIXER_INFO)
25242752947Sbrad omi->modify_counter = 1;
253*fb2547fdSratchov strlcpy(omi->id, dev_name, sizeof omi->id);
254*fb2547fdSratchov strlcpy(omi->name, dev_name, sizeof omi->name);
25542752947Sbrad return 0;
256f39d4eeeSprovos case SOUND_MIXER_READ_RECSRC:
257*fb2547fdSratchov case SOUND_MIXER_READ_RECMASK:
258*fb2547fdSratchov idat = 0;
259*fb2547fdSratchov for (c = controls; c != NULL; c = c->next)
260*fb2547fdSratchov idat |= 1 << c->type;
261*fb2547fdSratchov idat &= (1 << SOUND_MIXER_MIC) | (1 << SOUND_MIXER_IGAIN);
262*fb2547fdSratchov DPRINTF("%s: SOUND_MIXER_READ_RECSRC: %d\n", __func__, idat);
263f39d4eeeSprovos break;
264f39d4eeeSprovos case SOUND_MIXER_READ_DEVMASK:
265*fb2547fdSratchov idat = 0;
266*fb2547fdSratchov for (c = controls; c != NULL; c = c->next)
267*fb2547fdSratchov idat |= 1 << c->type;
268*fb2547fdSratchov DPRINTF("%s: SOUND_MIXER_READ_DEVMASK: %d\n", __func__, idat);
269f39d4eeeSprovos break;
270f39d4eeeSprovos case SOUND_MIXER_READ_STEREODEVS:
271*fb2547fdSratchov idat = 0;
272*fb2547fdSratchov for (c = controls; c != NULL; c = c->next) {
273*fb2547fdSratchov if (c->chan == 1)
274*fb2547fdSratchov idat |= 1 << c->type;
275*fb2547fdSratchov }
276*fb2547fdSratchov DPRINTF("%s: SOUND_MIXER_STEREODEVS: %d\n", __func__, idat);
277f39d4eeeSprovos break;
278f39d4eeeSprovos case SOUND_MIXER_READ_CAPS:
279*fb2547fdSratchov idat = 0;
280*fb2547fdSratchov DPRINTF("%s: SOUND_MIXER_READ_CAPS: %d\n", __func__, idat);
281f39d4eeeSprovos break;
282f39d4eeeSprovos case SOUND_MIXER_WRITE_RECSRC:
283f39d4eeeSprovos case SOUND_MIXER_WRITE_R_RECSRC:
284*fb2547fdSratchov DPRINTF("%s: SOUND_MIXER_WRITE_RECSRC\n", __func__);
285*fb2547fdSratchov errno = EINVAL;
286*fb2547fdSratchov return -1;
287f39d4eeeSprovos default:
288f39d4eeeSprovos if (MIXER_READ(SOUND_MIXER_FIRST) <= com &&
289f39d4eeeSprovos com < MIXER_READ(SOUND_MIXER_NRDEVICES)) {
290f39d4eeeSprovos doread:
291*fb2547fdSratchov idat = 0;
292*fb2547fdSratchov n = GET_DEV(com);
293*fb2547fdSratchov for (c = controls; c != NULL; c = c->next) {
294*fb2547fdSratchov if (c->type != n)
295*fb2547fdSratchov continue;
296*fb2547fdSratchov v = (c->value * 100 + c->max / 2) / c->max;
297*fb2547fdSratchov if (c->chan == 1)
298*fb2547fdSratchov v <<= 8;
299*fb2547fdSratchov idat |= v;
300f39d4eeeSprovos }
301*fb2547fdSratchov DPRINTF("%s: MIXER_READ: %d: 0x%04x\n",
302*fb2547fdSratchov __func__, n, idat);
303f39d4eeeSprovos break;
304f39d4eeeSprovos } else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com &&
305f39d4eeeSprovos com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) ||
306f39d4eeeSprovos (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
307f39d4eeeSprovos com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) {
308f39d4eeeSprovos idat = INTARG;
309*fb2547fdSratchov n = GET_DEV(com);
310*fb2547fdSratchov for (c = controls; c != NULL; c = c->next) {
311*fb2547fdSratchov if (c->type != n)
312*fb2547fdSratchov continue;
313*fb2547fdSratchov v = idat;
314*fb2547fdSratchov if (c->chan == 1)
315*fb2547fdSratchov v >>= 8;
316*fb2547fdSratchov v &= 0xff;
317*fb2547fdSratchov if (v > 100)
318*fb2547fdSratchov v = 100;
319*fb2547fdSratchov v = (v * c->max + 50) / 100;
320*fb2547fdSratchov sioctl_setval(hdl, c->addr, v);
321*fb2547fdSratchov DPRINTF("%s: MIXER_WRITE: %d: %d\n",
322*fb2547fdSratchov __func__, n, v);
323f39d4eeeSprovos }
324f39d4eeeSprovos if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
325f39d4eeeSprovos com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))
326f39d4eeeSprovos return 0;
327f39d4eeeSprovos goto doread;
328f39d4eeeSprovos } else {
329f39d4eeeSprovos errno = EINVAL;
330f39d4eeeSprovos return -1;
331f39d4eeeSprovos }
332f39d4eeeSprovos }
333*fb2547fdSratchov
334f39d4eeeSprovos INTARG = idat;
335f39d4eeeSprovos return 0;
336f39d4eeeSprovos }
337