xref: /openbsd-src/lib/libsndio/sioctl_sun.c (revision 544c3c0015fc10b18fc094d79fd9c48ed783e420)
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 
43 struct 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 
54 struct sioctl_sun_hdl {
55 	struct sioctl_hdl sioctl;
56 	char display[SIOCTL_DISPLAYMAX];
57 	int display_addr;
58 	struct volume output, input;
59 	int fd, events;
60 };
61 
62 static void sioctl_sun_close(struct sioctl_hdl *);
63 static int sioctl_sun_nfds(struct sioctl_hdl *);
64 static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int);
65 static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *);
66 static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
67 static int sioctl_sun_onval(struct sioctl_hdl *);
68 static int sioctl_sun_ondesc(struct sioctl_hdl *);
69 
70 /*
71  * operations every device should support
72  */
73 struct 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 
83 static int
84 initmute(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 
108 static int
109 initvol(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 
135 static void
136 init(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 
180 static int
181 setvol(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 
232 static int
233 scanvol(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 
286 static int
287 updatevol(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 
326 int
327 sioctl_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 
373 struct sioctl_hdl *
374 sioctl_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 
390 struct 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 
407 static void
408 sioctl_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 
416 static int
417 sioctl_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 
445 static int
446 sioctl_sun_onval(struct sioctl_hdl *addr)
447 {
448 	return 1;
449 }
450 
451 static int
452 sioctl_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 
464 static int
465 sioctl_sun_nfds(struct sioctl_hdl *addr)
466 {
467 	return 1;
468 }
469 
470 static int
471 sioctl_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 	hdl->events = events;
476 
477 	/*
478 	 * The audio(4) driver doesn't support POLLOUT, so if it is
479 	 * requested, don't set the struct pollfd. The AUDIO_MIXER_WRITE
480 	 * ioctl never blocks, so just return POLLOUT in sioctl_sun_revents().
481 	 */
482 	if (events & POLLOUT)
483 		return 0;
484 
485 	pfd->fd = hdl->fd;
486 	pfd->events = POLLIN;
487 	return 1;
488 }
489 
490 static int
491 sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd)
492 {
493 	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
494 	struct volume *vol;
495 	int idx, n;
496 
497 	if (hdl->events & POLLOUT)
498 		return POLLOUT;
499 
500 	if (pfd->revents & POLLIN) {
501 		while (1) {
502 			n = read(hdl->fd, &idx, sizeof(int));
503 			if (n == -1) {
504 				if (errno == EINTR || errno == EAGAIN)
505 					break;
506 				DPERROR("read");
507 				hdl->sioctl.eof = 1;
508 				return POLLHUP;
509 			}
510 			if (n < sizeof(int)) {
511 				DPRINTF("sioctl_sun_revents: short read\n");
512 				hdl->sioctl.eof = 1;
513 				return POLLHUP;
514 			}
515 
516 			if (idx == hdl->output.level_idx ||
517 			    idx == hdl->output.mute_idx) {
518 				vol = &hdl->output;
519 			} else if (idx == hdl->input.level_idx ||
520 			    idx == hdl->input.mute_idx) {
521 				vol = &hdl->input;
522 			} else
523 				continue;
524 
525 			if (!updatevol(hdl, vol, idx))
526 				return POLLHUP;
527 		}
528 	}
529 	return 0;
530 }
531