1/*
2 * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16/*
17 * the way the sun mixer is designed doesn't let us representing
18 * it easily with the sioctl api. For now expose only few
19 * white-listed controls the same way as we do in kernel
20 * for the wskbd volume keys.
21 */
22#include <sys/types.h>
23#include <sys/ioctl.h>
24#include <sys/audioio.h>
25#include <errno.h>
26#include <fcntl.h>
27#include <limits.h>
28#include <poll.h>
29#include <sndio.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34
35#include "debug.h"
36#include "sioctl_priv.h"
37
38#define DEVPATH_PREFIX	"/dev/audioctl"
39#define DEVPATH_MAX 	(1 +		\
40	sizeof(DEVPATH_PREFIX) - 1 +	\
41	sizeof(int) * 3)
42
43struct volume
44{
45	int nch;			/* channels in the level control */
46	int level_idx;			/* index of the level control */
47	int level_val[8];		/* current value */
48	int mute_idx;			/* index of the mute control */
49	int mute_val;			/* per channel state of mute control */
50	int base_addr;
51	char *name;
52};
53
54struct sioctl_sun_hdl {
55	struct sioctl_hdl sioctl;
56	struct volume output, input;
57	int fd, events;
58};
59
60static void sioctl_sun_close(struct sioctl_hdl *);
61static int sioctl_sun_nfds(struct sioctl_hdl *);
62static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int);
63static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *);
64static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
65static int sioctl_sun_onval(struct sioctl_hdl *);
66static int sioctl_sun_ondesc(struct sioctl_hdl *);
67
68/*
69 * operations every device should support
70 */
71struct sioctl_ops sioctl_sun_ops = {
72	sioctl_sun_close,
73	sioctl_sun_nfds,
74	sioctl_sun_pollfd,
75	sioctl_sun_revents,
76	sioctl_sun_setctl,
77	sioctl_sun_onval,
78	sioctl_sun_ondesc
79};
80
81static int
82initmute(struct sioctl_sun_hdl *hdl, struct mixer_devinfo *info)
83{
84	struct mixer_devinfo mi;
85	char name[MAX_AUDIO_DEV_LEN];
86
87	for (mi.index = info->next; mi.index != -1; mi.index = mi.next) {
88		if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
89			break;
90		if (strcmp(mi.label.name, AudioNmute) == 0)
91			return mi.index;
92	}
93
94	/* try "_mute" suffix */
95	snprintf(name, sizeof(name), "%s_mute", info->label.name);
96	for (mi.index = 0; ; mi.index++) {
97		if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
98			break;
99		if (info->mixer_class == mi.mixer_class &&
100		    strcmp(mi.label.name, name) == 0)
101			return mi.index;
102	}
103	return -1;
104}
105
106static int
107initvol(struct sioctl_sun_hdl *hdl, struct volume *vol, char *cn, char *dn)
108{
109	struct mixer_devinfo dev, cls;
110
111	for (dev.index = 0; ; dev.index++) {
112		if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &dev) < 0)
113			break;
114		if (dev.type != AUDIO_MIXER_VALUE)
115			continue;
116		cls.index = dev.mixer_class;
117		if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &cls) < 0)
118			break;
119		if (strcmp(cls.label.name, cn) == 0 &&
120		    strcmp(dev.label.name, dn) == 0) {
121			vol->nch = dev.un.v.num_channels;
122			vol->level_idx = dev.index;
123			vol->mute_idx = initmute(hdl, &dev);
124			DPRINTF("using %s.%s, %d channels, %s\n", cn, dn,
125			    vol->nch, vol->mute_idx >= 0 ? "mute" : "no mute");
126			return 1;
127		}
128	}
129	vol->level_idx = vol->mute_idx = -1;
130	return 0;
131}
132
133static void
134init(struct sioctl_sun_hdl *hdl)
135{
136	static struct {
137		char *cn, *dn;
138	} output_names[] = {
139		{AudioCoutputs, AudioNmaster},
140		{AudioCinputs,  AudioNdac},
141		{AudioCoutputs, AudioNdac},
142		{AudioCoutputs, AudioNoutput}
143	}, input_names[] = {
144		{AudioCrecord, AudioNrecord},
145		{AudioCrecord, AudioNvolume},
146		{AudioCinputs, AudioNrecord},
147		{AudioCinputs, AudioNvolume},
148		{AudioCinputs, AudioNinput}
149	};
150	int i;
151
152	for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) {
153		if (initvol(hdl, &hdl->output,
154			output_names[i].cn, output_names[i].dn)) {
155			hdl->output.name = "output";
156			hdl->output.base_addr = 0;
157			break;
158		}
159	}
160	for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) {
161		if (initvol(hdl, &hdl->input,
162			input_names[i].cn, input_names[i].dn)) {
163			hdl->input.name = "input";
164			hdl->input.base_addr = 64;
165			break;
166		}
167	}
168}
169
170static int
171setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val)
172{
173	struct mixer_ctrl ctrl;
174	int i;
175
176	addr -= vol->base_addr;
177	if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) {
178		if (vol->level_val[addr] == val) {
179			DPRINTF("level %d, no change\n", val);
180			return 1;
181		}
182		vol->level_val[addr] = val;
183		ctrl.dev = vol->level_idx;
184		ctrl.type = AUDIO_MIXER_VALUE;
185		ctrl.un.value.num_channels = vol->nch;
186		for (i = 0; i < vol->nch; i++)
187			ctrl.un.value.level[i] = vol->level_val[i];
188		DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]);
189		if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
190			DPRINTF("level write failed\n");
191			return 0;
192		}
193		_sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val);
194		return 1;
195	}
196
197	addr -= 32;
198	if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) {
199		val = val ? 1 : 0;
200		if (vol->mute_val == val) {
201			DPRINTF("mute %d, no change\n", val);
202			return 1;
203		}
204		vol->mute_val = val;
205		ctrl.dev = vol->mute_idx;
206		ctrl.type = AUDIO_MIXER_ENUM;
207		ctrl.un.ord = val;
208		DPRINTF("mute setting to %d\n", val);
209		if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
210			DPERROR("mute write\n");
211			return 0;
212		}
213		for (i = 0; i < vol->nch; i++) {
214			_sioctl_onval_cb(&hdl->sioctl,
215			    vol->base_addr + 32 + i, val);
216		}
217		return 1;
218	}
219	return 1;
220}
221
222static int
223scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol)
224{
225	struct sioctl_desc desc;
226	struct mixer_ctrl ctrl;
227	int i, val;
228
229	memset(&desc, 0, sizeof(struct sioctl_desc));
230	if (vol->level_idx >= 0) {
231		ctrl.dev = vol->level_idx;
232		ctrl.type = AUDIO_MIXER_VALUE;
233		ctrl.un.value.num_channels = vol->nch;
234		if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
235			DPRINTF("level read failed\n");
236			return 0;
237		}
238		desc.type = SIOCTL_NUM;
239		desc.maxval = AUDIO_MAX_GAIN;
240		desc.node1.name[0] = 0;
241		desc.node1.unit = -1;
242		strlcpy(desc.func, "level", SIOCTL_NAMEMAX);
243		strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
244		for (i = 0; i < vol->nch; i++) {
245			desc.node0.unit = i;
246			desc.addr = vol->base_addr + i;
247			val = ctrl.un.value.level[i];
248			vol->level_val[i] = val;
249			_sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
250		}
251	}
252	if (vol->mute_idx >= 0) {
253		ctrl.dev = vol->mute_idx;
254		ctrl.type = AUDIO_MIXER_ENUM;
255		if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
256			DPRINTF("mute read failed\n");
257			return 0;
258		}
259		desc.type = SIOCTL_SW;
260		desc.maxval = 1;
261		desc.node1.name[0] = 0;
262		desc.node1.unit = -1;
263		strlcpy(desc.func, "mute", SIOCTL_NAMEMAX);
264		strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
265		val = ctrl.un.ord ? 1 : 0;
266		vol->mute_val = val;
267		for (i = 0; i < vol->nch; i++) {
268			desc.node0.unit = i;
269			desc.addr = vol->base_addr + 32 + i;
270			_sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
271		}
272	}
273	return 1;
274}
275
276static int
277updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx)
278{
279	struct mixer_ctrl ctrl;
280	int val, i;
281
282	if (idx == vol->mute_idx)
283		ctrl.type = AUDIO_MIXER_ENUM;
284	else {
285		ctrl.type = AUDIO_MIXER_VALUE;
286		ctrl.un.value.num_channels = vol->nch;
287	}
288	ctrl.dev = idx;
289	if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) {
290		DPERROR("sioctl_sun_revents: ioctl\n");
291		hdl->sioctl.eof = 1;
292		return 0;
293	}
294	if (idx == vol->mute_idx) {
295		val = ctrl.un.ord ? 1 : 0;
296		if (vol->mute_val == val)
297			return 1;
298		vol->mute_val = val;
299		for (i = 0; i < vol->nch; i++) {
300			_sioctl_onval_cb(&hdl->sioctl,
301			    vol->base_addr + 32 + i, val);
302		}
303	} else {
304		for (i = 0; i < vol->nch; i++) {
305			val = ctrl.un.value.level[i];
306			if (vol->level_val[i] == val)
307				continue;
308			vol->level_val[i] = val;
309			_sioctl_onval_cb(&hdl->sioctl,
310			    vol->base_addr + i, val);
311		}
312	}
313	return 1;
314}
315
316int
317sioctl_sun_getfd(const char *str, unsigned int mode, int nbio)
318{
319	const char *p;
320	char path[DEVPATH_MAX];
321	unsigned int devnum;
322	int fd, flags;
323
324#ifdef DEBUG
325	_sndio_debug_init();
326#endif
327	p = _sndio_parsetype(str, "rsnd");
328	if (p == NULL) {
329		DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str);
330		return -1;
331	}
332	switch (*p) {
333	case '/':
334		p++;
335		break;
336	default:
337		DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str);
338		return -1;
339	}
340	if (strcmp(p, "default") == 0) {
341		devnum = 0;
342	} else {
343		p = _sndio_parsenum(p, &devnum, 255);
344		if (p == NULL || *p != '\0') {
345			DPRINTF("sioctl_sun_getfd: %s: number expected after '/'\n", str);
346			return -1;
347		}
348	}
349	snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
350	if (mode == (SIOCTL_READ | SIOCTL_WRITE))
351		flags = O_RDWR;
352	else
353		flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY;
354	while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) {
355		if (errno == EINTR)
356			continue;
357		DPERROR(path);
358		return -1;
359	}
360	return fd;
361}
362
363struct sioctl_hdl *
364sioctl_sun_fdopen(int fd, unsigned int mode, int nbio)
365{
366	struct sioctl_sun_hdl *hdl;
367
368#ifdef DEBUG
369	_sndio_debug_init();
370#endif
371	hdl = malloc(sizeof(struct sioctl_sun_hdl));
372	if (hdl == NULL)
373		return NULL;
374	_sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio);
375	hdl->fd = fd;
376	init(hdl);
377	return (struct sioctl_hdl *)hdl;
378}
379
380struct sioctl_hdl *
381_sioctl_sun_open(const char *str, unsigned int mode, int nbio)
382{
383	struct sioctl_hdl *hdl;
384	int fd;
385
386	fd = sioctl_sun_getfd(str, mode, nbio);
387	if (fd < 0)
388		return NULL;
389	hdl = sioctl_sun_fdopen(fd, mode, nbio);
390	if (hdl != NULL)
391		return hdl;
392	while (close(fd) < 0 && errno == EINTR)
393		; /* retry */
394	return NULL;
395}
396
397static void
398sioctl_sun_close(struct sioctl_hdl *addr)
399{
400	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
401
402	close(hdl->fd);
403	free(hdl);
404}
405
406static int
407sioctl_sun_ondesc(struct sioctl_hdl *addr)
408{
409	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
410
411	if (!scanvol(hdl, &hdl->output) ||
412	    !scanvol(hdl, &hdl->input)) {
413		hdl->sioctl.eof = 1;
414		return 0;
415	}
416	_sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
417	return 1;
418}
419
420static int
421sioctl_sun_onval(struct sioctl_hdl *addr)
422{
423	return 1;
424}
425
426static int
427sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val)
428{
429	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
430
431	if (!setvol(hdl, &hdl->output, addr, val) ||
432	    !setvol(hdl, &hdl->input, addr, val)) {
433		hdl->sioctl.eof = 1;
434		return 0;
435	}
436	return 1;
437}
438
439static int
440sioctl_sun_nfds(struct sioctl_hdl *addr)
441{
442	return 1;
443}
444
445static int
446sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
447{
448	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
449
450	pfd->fd = hdl->fd;
451	pfd->events = POLLIN;
452	hdl->events = events;
453	return 1;
454}
455
456static int
457sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd)
458{
459	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
460	struct volume *vol;
461	int idx, n;
462
463	if (pfd->revents & POLLIN) {
464		while (1) {
465			n = read(hdl->fd, &idx, sizeof(int));
466			if (n == -1) {
467				if (errno == EINTR || errno == EAGAIN)
468					break;
469				DPERROR("read");
470				hdl->sioctl.eof = 1;
471				return POLLHUP;
472			}
473			if (n < sizeof(int)) {
474				DPRINTF("sioctl_sun_revents: short read\n");
475				hdl->sioctl.eof = 1;
476				return POLLHUP;
477			}
478
479			if (idx == hdl->output.level_idx ||
480			    idx == hdl->output.mute_idx) {
481				vol = &hdl->output;
482			} else if (idx == hdl->input.level_idx ||
483			    idx == hdl->input.mute_idx) {
484				vol = &hdl->input;
485			} else
486				continue;
487
488			if (!updatevol(hdl, vol, idx))
489				return POLLHUP;
490		}
491	}
492	return hdl->events & POLLOUT;
493}
494