xref: /openbsd/lib/libsndio/sioctl_sun.c (revision 09467b48)
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 	struct volume output, input;
57 	int fd, events;
58 };
59 
60 static void sioctl_sun_close(struct sioctl_hdl *);
61 static int sioctl_sun_nfds(struct sioctl_hdl *);
62 static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int);
63 static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *);
64 static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int);
65 static int sioctl_sun_onval(struct sioctl_hdl *);
66 static int sioctl_sun_ondesc(struct sioctl_hdl *);
67 
68 /*
69  * operations every device should support
70  */
71 struct 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 
81 static int
82 initmute(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 
106 static int
107 initvol(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 
133 static void
134 init(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 
170 static int
171 setvol(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 
222 static int
223 scanvol(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 
276 static int
277 updatevol(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 
316 int
317 sioctl_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 
363 struct sioctl_hdl *
364 sioctl_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 
380 struct 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 
397 static void
398 sioctl_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 
406 static int
407 sioctl_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 
420 static int
421 sioctl_sun_onval(struct sioctl_hdl *addr)
422 {
423 	return 1;
424 }
425 
426 static int
427 sioctl_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 
439 static int
440 sioctl_sun_nfds(struct sioctl_hdl *addr)
441 {
442 	return 1;
443 }
444 
445 static int
446 sioctl_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 
456 static int
457 sioctl_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