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