xref: /openbsd-src/lib/libsndio/sioctl_sun.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
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 
86 	mi.index = info->next;
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 	return -1;
94 }
95 
96 static int
97 initvol(struct sioctl_sun_hdl *hdl, struct volume *vol, char *cn, char *dn)
98 {
99 	struct mixer_devinfo dev, cls;
100 
101 	for (dev.index = 0; ; dev.index++) {
102 		if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &dev) < 0)
103 			break;
104 		if (dev.type != AUDIO_MIXER_VALUE)
105 			continue;
106 		cls.index = dev.mixer_class;
107 		if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &cls) < 0)
108 			break;
109 		if (strcmp(cls.label.name, cn) == 0 &&
110 		    strcmp(dev.label.name, dn) == 0) {
111 			vol->nch = dev.un.v.num_channels;
112 			vol->level_idx = dev.index;
113 			vol->mute_idx = initmute(hdl, &dev);
114 			DPRINTF("using %s.%s, %d channels, %s\n", cn, dn,
115 			    vol->nch, vol->mute_idx >= 0 ? "mute" : "no mute");
116 			return 1;
117 		}
118 	}
119 	vol->level_idx = vol->mute_idx = -1;
120 	return 0;
121 }
122 
123 static void
124 init(struct sioctl_sun_hdl *hdl)
125 {
126 	static struct {
127 		char *cn, *dn;
128 	} output_names[] = {
129 		{AudioCoutputs, AudioNmaster},
130 		{AudioCinputs,  AudioNdac},
131 		{AudioCoutputs, AudioNdac},
132 		{AudioCoutputs, AudioNoutput}
133 	}, input_names[] = {
134 		{AudioCrecord, AudioNrecord},
135 		{AudioCrecord, AudioNvolume},
136 		{AudioCinputs, AudioNrecord},
137 		{AudioCinputs, AudioNvolume},
138 		{AudioCinputs, AudioNinput}
139 	};
140 	int i;
141 
142 	for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) {
143 		if (initvol(hdl, &hdl->output,
144 			output_names[i].cn, output_names[i].dn)) {
145 			hdl->output.name = "output";
146 			hdl->output.base_addr = 0;
147 			break;
148 		}
149 	}
150 	for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) {
151 		if (initvol(hdl, &hdl->input,
152 			input_names[i].cn, input_names[i].dn)) {
153 			hdl->input.name = "input";
154 			hdl->input.base_addr = 64;
155 			break;
156 		}
157 	}
158 }
159 
160 static int
161 setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val)
162 {
163 	struct mixer_ctrl ctrl;
164 	int i;
165 
166 	addr -= vol->base_addr;
167 	if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) {
168 		if (vol->level_val[addr] == val) {
169 			DPRINTF("level %d, no change\n", val);
170 			return 1;
171 		}
172 		vol->level_val[addr] = val;
173 		ctrl.dev = vol->level_idx;
174 		ctrl.type = AUDIO_MIXER_VALUE;
175 		ctrl.un.value.num_channels = vol->nch;
176 		for (i = 0; i < vol->nch; i++)
177 			ctrl.un.value.level[i] = vol->level_val[i];
178 		DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]);
179 		if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
180 			DPRINTF("level write failed\n");
181 			return 0;
182 		}
183 		_sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val);
184 		return 1;
185 	}
186 
187 	addr -= 32;
188 	if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) {
189 		val = val ? 1 : 0;
190 		if (vol->mute_val == val) {
191 			DPRINTF("mute %d, no change\n", val);
192 			return 1;
193 		}
194 		vol->mute_val = val;
195 		ctrl.dev = vol->mute_idx;
196 		ctrl.type = AUDIO_MIXER_ENUM;
197 		ctrl.un.ord = val;
198 		DPRINTF("mute setting to %d\n", val);
199 		if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
200 			DPERROR("mute write\n");
201 			return 0;
202 		}
203 		for (i = 0; i < vol->nch; i++) {
204 			_sioctl_onval_cb(&hdl->sioctl,
205 			    vol->base_addr + 32 + i, val);
206 		}
207 		return 1;
208 	}
209 	return 1;
210 }
211 
212 static int
213 scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol)
214 {
215 	struct sioctl_desc desc;
216 	struct mixer_ctrl ctrl;
217 	int i, val;
218 
219 	memset(&desc, 0, sizeof(struct sioctl_desc));
220 	if (vol->level_idx >= 0) {
221 		ctrl.dev = vol->level_idx;
222 		ctrl.type = AUDIO_MIXER_VALUE;
223 		ctrl.un.value.num_channels = vol->nch;
224 		if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
225 			DPRINTF("level read failed\n");
226 			return 0;
227 		}
228 		desc.type = SIOCTL_NUM;
229 		desc.maxval = AUDIO_MAX_GAIN;
230 		desc.node1.name[0] = 0;
231 		desc.node1.unit = -1;
232 		strlcpy(desc.func, "level", SIOCTL_NAMEMAX);
233 		strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
234 		for (i = 0; i < vol->nch; i++) {
235 			desc.node0.unit = i;
236 			desc.addr = vol->base_addr + i;
237 			val = ctrl.un.value.level[i];
238 			vol->level_val[i] = val;
239 			_sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
240 		}
241 	}
242 	if (vol->mute_idx >= 0) {
243 		ctrl.dev = vol->mute_idx;
244 		ctrl.type = AUDIO_MIXER_ENUM;
245 		if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
246 			DPRINTF("mute read failed\n");
247 			return 0;
248 		}
249 		desc.type = SIOCTL_SW;
250 		desc.maxval = 1;
251 		desc.node1.name[0] = 0;
252 		desc.node1.unit = -1;
253 		strlcpy(desc.func, "mute", SIOCTL_NAMEMAX);
254 		strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
255 		val = ctrl.un.ord ? 1 : 0;
256 		vol->mute_val = val;
257 		for (i = 0; i < vol->nch; i++) {
258 			desc.node0.unit = i;
259 			desc.addr = vol->base_addr + 32 + i;
260 			_sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
261 		}
262 	}
263 	return 1;
264 }
265 
266 static int
267 updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx)
268 {
269 	struct mixer_ctrl ctrl;
270 	int val, i;
271 
272 	if (idx == vol->mute_idx)
273 		ctrl.type = AUDIO_MIXER_ENUM;
274 	else {
275 		ctrl.type = AUDIO_MIXER_VALUE;
276 		ctrl.un.value.num_channels = vol->nch;
277 	}
278 	ctrl.dev = idx;
279 	if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) {
280 		DPERROR("sioctl_sun_revents: ioctl\n");
281 		hdl->sioctl.eof = 1;
282 		return 0;
283 	}
284 	if (idx == vol->mute_idx) {
285 		val = ctrl.un.ord ? 1 : 0;
286 		if (vol->mute_val == val)
287 			return 1;
288 		vol->mute_val = val;
289 		for (i = 0; i < vol->nch; i++) {
290 			_sioctl_onval_cb(&hdl->sioctl,
291 			    vol->base_addr + 32 + i, val);
292 		}
293 	} else {
294 		for (i = 0; i < vol->nch; i++) {
295 			val = ctrl.un.value.level[i];
296 			if (vol->level_val[i] == val)
297 				continue;
298 			vol->level_val[i] = val;
299 			_sioctl_onval_cb(&hdl->sioctl,
300 			    vol->base_addr + i, val);
301 		}
302 	}
303 	return 1;
304 }
305 
306 int
307 sioctl_sun_getfd(const char *str, unsigned int mode, int nbio)
308 {
309 	const char *p;
310 	char path[DEVPATH_MAX];
311 	unsigned int devnum;
312 	int fd, flags;
313 
314 #ifdef DEBUG
315 	_sndio_debug_init();
316 #endif
317 	p = _sndio_parsetype(str, "rsnd");
318 	if (p == NULL) {
319 		DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str);
320 		return -1;
321 	}
322 	switch (*p) {
323 	case '/':
324 		p++;
325 		break;
326 	default:
327 		DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str);
328 		return -1;
329 	}
330 	if (strcmp(p, "default") == 0) {
331 		devnum = 0;
332 	} else {
333 		p = _sndio_parsenum(p, &devnum, 255);
334 		if (p == NULL || *p != '\0') {
335 			DPRINTF("sioctl_sun_getfd: %s: number expected after '/'\n", str);
336 			return -1;
337 		}
338 	}
339 	snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
340 	if (mode == (SIOCTL_READ | SIOCTL_WRITE))
341 		flags = O_RDWR;
342 	else
343 		flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY;
344 	while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) {
345 		if (errno == EINTR)
346 			continue;
347 		DPERROR(path);
348 		return -1;
349 	}
350 	return fd;
351 }
352 
353 struct sioctl_hdl *
354 sioctl_sun_fdopen(int fd, unsigned int mode, int nbio)
355 {
356 	struct sioctl_sun_hdl *hdl;
357 
358 #ifdef DEBUG
359 	_sndio_debug_init();
360 #endif
361 	hdl = malloc(sizeof(struct sioctl_sun_hdl));
362 	if (hdl == NULL)
363 		return NULL;
364 	_sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio);
365 	hdl->fd = fd;
366 	init(hdl);
367 	return (struct sioctl_hdl *)hdl;
368 }
369 
370 struct sioctl_hdl *
371 _sioctl_sun_open(const char *str, unsigned int mode, int nbio)
372 {
373 	struct sioctl_hdl *hdl;
374 	int fd;
375 
376 	fd = sioctl_sun_getfd(str, mode, nbio);
377 	if (fd < 0)
378 		return NULL;
379 	hdl = sioctl_sun_fdopen(fd, mode, nbio);
380 	if (hdl != NULL)
381 		return hdl;
382 	while (close(fd) < 0 && errno == EINTR)
383 		; /* retry */
384 	return NULL;
385 }
386 
387 static void
388 sioctl_sun_close(struct sioctl_hdl *addr)
389 {
390 	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
391 
392 	close(hdl->fd);
393 	free(hdl);
394 }
395 
396 static int
397 sioctl_sun_ondesc(struct sioctl_hdl *addr)
398 {
399 	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
400 
401 	if (!scanvol(hdl, &hdl->output) ||
402 	    !scanvol(hdl, &hdl->input)) {
403 		hdl->sioctl.eof = 1;
404 		return 0;
405 	}
406 	_sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
407 	return 1;
408 }
409 
410 static int
411 sioctl_sun_onval(struct sioctl_hdl *addr)
412 {
413 	return 1;
414 }
415 
416 static int
417 sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val)
418 {
419 	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
420 
421 	if (!setvol(hdl, &hdl->output, addr, val) ||
422 	    !setvol(hdl, &hdl->input, addr, val)) {
423 		hdl->sioctl.eof = 1;
424 		return 0;
425 	}
426 	return 1;
427 }
428 
429 static int
430 sioctl_sun_nfds(struct sioctl_hdl *addr)
431 {
432 	return 1;
433 }
434 
435 static int
436 sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
437 {
438 	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
439 
440 	pfd->fd = hdl->fd;
441 	pfd->events = POLLIN;
442 	hdl->events = events;
443 	return 1;
444 }
445 
446 static int
447 sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd)
448 {
449 	struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
450 	struct volume *vol;
451 	int idx, n;
452 
453 	if (pfd->revents & POLLIN) {
454 		while (1) {
455 			n = read(hdl->fd, &idx, sizeof(int));
456 			if (n == -1) {
457 				if (errno == EINTR || errno == EAGAIN)
458 					break;
459 				DPERROR("read");
460 				hdl->sioctl.eof = 1;
461 				return POLLHUP;
462 			}
463 			if (n < sizeof(int)) {
464 				DPRINTF("sioctl_sun_revents: short read\n");
465 				hdl->sioctl.eof = 1;
466 				return POLLHUP;
467 			}
468 
469 			if (idx == hdl->output.level_idx ||
470 			    idx == hdl->output.mute_idx) {
471 				vol = &hdl->output;
472 			} else if (idx == hdl->input.level_idx ||
473 			    idx == hdl->input.mute_idx) {
474 				vol = &hdl->input;
475 			} else
476 				continue;
477 
478 			if (!updatevol(hdl, vol, idx))
479 				return POLLHUP;
480 		}
481 	}
482 	return hdl->events & POLLOUT;
483 }
484