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