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 #ifdef USE_SUN_MIXER
23 #include <sys/types.h>
24 #include <sys/ioctl.h>
25 #include <sys/audioio.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <limits.h>
29 #include <poll.h>
30 #include <sndio.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include "debug.h"
37 #include "sioctl_priv.h"
38 #include "bsd-compat.h"
39
40 #define DEVPATH_PREFIX "/dev/audioctl"
41 #define DEVPATH_MAX (1 + \
42 sizeof(DEVPATH_PREFIX) - 1 + \
43 sizeof(int) * 3)
44
45 struct volume
46 {
47 int nch; /* channels in the level control */
48 int level_idx; /* index of the level control */
49 int level_val[8]; /* current value */
50 int mute_idx; /* index of the mute control */
51 int mute_val; /* per channel state of mute control */
52 int base_addr;
53 char *name;
54 };
55
56 struct sioctl_sun_hdl {
57 struct sioctl_hdl sioctl;
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
initmute(struct sioctl_sun_hdl * hdl,struct mixer_devinfo * info)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
initvol(struct sioctl_sun_hdl * hdl,struct volume * vol,char * cn,char * dn)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
init(struct sioctl_sun_hdl * hdl)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 int i;
153
154 for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) {
155 if (initvol(hdl, &hdl->output,
156 output_names[i].cn, output_names[i].dn)) {
157 hdl->output.name = "output";
158 hdl->output.base_addr = 0;
159 break;
160 }
161 }
162 for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) {
163 if (initvol(hdl, &hdl->input,
164 input_names[i].cn, input_names[i].dn)) {
165 hdl->input.name = "input";
166 hdl->input.base_addr = 64;
167 break;
168 }
169 }
170 }
171
172 static int
setvol(struct sioctl_sun_hdl * hdl,struct volume * vol,int addr,int val)173 setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val)
174 {
175 struct mixer_ctrl ctrl;
176 int i;
177
178 addr -= vol->base_addr;
179 if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) {
180 if (vol->level_val[addr] == val) {
181 DPRINTF("level %d, no change\n", val);
182 return 1;
183 }
184 vol->level_val[addr] = val;
185 ctrl.dev = vol->level_idx;
186 ctrl.type = AUDIO_MIXER_VALUE;
187 ctrl.un.value.num_channels = vol->nch;
188 for (i = 0; i < vol->nch; i++)
189 ctrl.un.value.level[i] = vol->level_val[i];
190 DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]);
191 if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
192 DPRINTF("level write failed\n");
193 return 0;
194 }
195 _sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val);
196 return 1;
197 }
198
199 addr -= 32;
200 if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) {
201 val = val ? 1 : 0;
202 if (vol->mute_val == val) {
203 DPRINTF("mute %d, no change\n", val);
204 return 1;
205 }
206 vol->mute_val = val;
207 ctrl.dev = vol->mute_idx;
208 ctrl.type = AUDIO_MIXER_ENUM;
209 ctrl.un.ord = val;
210 DPRINTF("mute setting to %d\n", val);
211 if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) {
212 DPERROR("mute write\n");
213 return 0;
214 }
215 for (i = 0; i < vol->nch; i++) {
216 _sioctl_onval_cb(&hdl->sioctl,
217 vol->base_addr + 32 + i, val);
218 }
219 return 1;
220 }
221 return 1;
222 }
223
224 static int
scanvol(struct sioctl_sun_hdl * hdl,struct volume * vol)225 scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol)
226 {
227 struct sioctl_desc desc;
228 struct mixer_ctrl ctrl;
229 int i, val;
230
231 memset(&desc, 0, sizeof(struct sioctl_desc));
232 if (vol->level_idx >= 0) {
233 ctrl.dev = vol->level_idx;
234 ctrl.type = AUDIO_MIXER_VALUE;
235 ctrl.un.value.num_channels = vol->nch;
236 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
237 DPRINTF("level read failed\n");
238 return 0;
239 }
240 desc.type = SIOCTL_NUM;
241 desc.maxval = AUDIO_MAX_GAIN;
242 desc.node1.name[0] = 0;
243 desc.node1.unit = -1;
244 strlcpy(desc.func, "level", SIOCTL_NAMEMAX);
245 strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
246 for (i = 0; i < vol->nch; i++) {
247 desc.node0.unit = i;
248 desc.addr = vol->base_addr + i;
249 val = ctrl.un.value.level[i];
250 vol->level_val[i] = val;
251 _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
252 }
253 }
254 if (vol->mute_idx >= 0) {
255 ctrl.dev = vol->mute_idx;
256 ctrl.type = AUDIO_MIXER_ENUM;
257 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) {
258 DPRINTF("mute read failed\n");
259 return 0;
260 }
261 desc.type = SIOCTL_SW;
262 desc.maxval = 1;
263 desc.node1.name[0] = 0;
264 desc.node1.unit = -1;
265 strlcpy(desc.func, "mute", SIOCTL_NAMEMAX);
266 strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX);
267 val = ctrl.un.ord ? 1 : 0;
268 vol->mute_val = val;
269 for (i = 0; i < vol->nch; i++) {
270 desc.node0.unit = i;
271 desc.addr = vol->base_addr + 32 + i;
272 _sioctl_ondesc_cb(&hdl->sioctl, &desc, val);
273 }
274 }
275 return 1;
276 }
277
278 static int
updatevol(struct sioctl_sun_hdl * hdl,struct volume * vol,int idx)279 updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx)
280 {
281 struct mixer_ctrl ctrl;
282 int val, i;
283
284 if (idx == vol->mute_idx)
285 ctrl.type = AUDIO_MIXER_ENUM;
286 else {
287 ctrl.type = AUDIO_MIXER_VALUE;
288 ctrl.un.value.num_channels = vol->nch;
289 }
290 ctrl.dev = idx;
291 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) {
292 DPERROR("sioctl_sun_revents: ioctl\n");
293 hdl->sioctl.eof = 1;
294 return 0;
295 }
296 if (idx == vol->mute_idx) {
297 val = ctrl.un.ord ? 1 : 0;
298 if (vol->mute_val == val)
299 return 1;
300 vol->mute_val = val;
301 for (i = 0; i < vol->nch; i++) {
302 _sioctl_onval_cb(&hdl->sioctl,
303 vol->base_addr + 32 + i, val);
304 }
305 } else {
306 for (i = 0; i < vol->nch; i++) {
307 val = ctrl.un.value.level[i];
308 if (vol->level_val[i] == val)
309 continue;
310 vol->level_val[i] = val;
311 _sioctl_onval_cb(&hdl->sioctl,
312 vol->base_addr + i, val);
313 }
314 }
315 return 1;
316 }
317
318 int
sioctl_sun_getfd(const char * str,unsigned int mode,int nbio)319 sioctl_sun_getfd(const char *str, unsigned int mode, int nbio)
320 {
321 const char *p;
322 char path[DEVPATH_MAX];
323 unsigned int devnum;
324 int fd, flags;
325
326 #ifdef DEBUG
327 _sndio_debug_init();
328 #endif
329 p = _sndio_parsetype(str, "rsnd");
330 if (p == NULL) {
331 DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str);
332 return -1;
333 }
334 switch (*p) {
335 case '/':
336 p++;
337 break;
338 default:
339 DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str);
340 return -1;
341 }
342 if (strcmp(p, "default") == 0) {
343 devnum = 0;
344 } else {
345 p = _sndio_parsenum(p, &devnum, 255);
346 if (p == NULL || *p != '\0') {
347 DPRINTF("sioctl_sun_getfd: %s: number expected after '/'\n", str);
348 return -1;
349 }
350 }
351 snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
352 if (mode == (SIOCTL_READ | SIOCTL_WRITE))
353 flags = O_RDWR;
354 else
355 flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY;
356 while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) {
357 if (errno == EINTR)
358 continue;
359 DPERROR(path);
360 return -1;
361 }
362 return fd;
363 }
364
365 struct sioctl_hdl *
sioctl_sun_fdopen(int fd,unsigned int mode,int nbio)366 sioctl_sun_fdopen(int fd, unsigned int mode, int nbio)
367 {
368 struct sioctl_sun_hdl *hdl;
369
370 #ifdef DEBUG
371 _sndio_debug_init();
372 #endif
373 hdl = malloc(sizeof(struct sioctl_sun_hdl));
374 if (hdl == NULL)
375 return NULL;
376 _sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio);
377 hdl->fd = fd;
378 init(hdl);
379 return (struct sioctl_hdl *)hdl;
380 }
381
382 struct sioctl_hdl *
_sioctl_sun_open(const char * str,unsigned int mode,int nbio)383 _sioctl_sun_open(const char *str, unsigned int mode, int nbio)
384 {
385 struct sioctl_hdl *hdl;
386 int fd;
387
388 fd = sioctl_sun_getfd(str, mode, nbio);
389 if (fd < 0)
390 return NULL;
391 hdl = sioctl_sun_fdopen(fd, mode, nbio);
392 if (hdl != NULL)
393 return hdl;
394 while (close(fd) < 0 && errno == EINTR)
395 ; /* retry */
396 return NULL;
397 }
398
399 static void
sioctl_sun_close(struct sioctl_hdl * addr)400 sioctl_sun_close(struct sioctl_hdl *addr)
401 {
402 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
403
404 close(hdl->fd);
405 free(hdl);
406 }
407
408 static int
sioctl_sun_ondesc(struct sioctl_hdl * addr)409 sioctl_sun_ondesc(struct sioctl_hdl *addr)
410 {
411 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
412
413 if (!scanvol(hdl, &hdl->output) ||
414 !scanvol(hdl, &hdl->input)) {
415 hdl->sioctl.eof = 1;
416 return 0;
417 }
418 _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0);
419 return 1;
420 }
421
422 static int
sioctl_sun_onval(struct sioctl_hdl * addr)423 sioctl_sun_onval(struct sioctl_hdl *addr)
424 {
425 return 1;
426 }
427
428 static int
sioctl_sun_setctl(struct sioctl_hdl * arg,unsigned int addr,unsigned int val)429 sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val)
430 {
431 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
432
433 if (!setvol(hdl, &hdl->output, addr, val) ||
434 !setvol(hdl, &hdl->input, addr, val)) {
435 hdl->sioctl.eof = 1;
436 return 0;
437 }
438 return 1;
439 }
440
441 static int
sioctl_sun_nfds(struct sioctl_hdl * addr)442 sioctl_sun_nfds(struct sioctl_hdl *addr)
443 {
444 return 1;
445 }
446
447 static int
sioctl_sun_pollfd(struct sioctl_hdl * addr,struct pollfd * pfd,int events)448 sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events)
449 {
450 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr;
451
452 pfd->fd = hdl->fd;
453 pfd->events = POLLIN;
454 hdl->events = events;
455 return 1;
456 }
457
458 static int
sioctl_sun_revents(struct sioctl_hdl * arg,struct pollfd * pfd)459 sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd)
460 {
461 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg;
462 struct volume *vol;
463 int idx, n;
464
465 if (pfd->revents & POLLIN) {
466 while (1) {
467 n = read(hdl->fd, &idx, sizeof(int));
468 if (n == -1) {
469 if (errno == EINTR || errno == EAGAIN)
470 break;
471 DPERROR("read");
472 hdl->sioctl.eof = 1;
473 return POLLHUP;
474 }
475 if (n < sizeof(int)) {
476 DPRINTF("sioctl_sun_revents: short read\n");
477 hdl->sioctl.eof = 1;
478 return POLLHUP;
479 }
480
481 if (idx == hdl->output.level_idx ||
482 idx == hdl->output.mute_idx) {
483 vol = &hdl->output;
484 } else if (idx == hdl->input.level_idx ||
485 idx == hdl->input.mute_idx) {
486 vol = &hdl->input;
487 } else
488 continue;
489
490 if (!updatevol(hdl, vol, idx))
491 return POLLHUP;
492 }
493 }
494 return hdl->events & POLLOUT;
495 }
496 #endif
497