1 /* $NetBSD: oss4_mixer.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */
2
3 /*-
4 * Copyright (c) 2020-2021 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Nia Alarie.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31 #include <sys/audioio.h>
32 #include <sys/fcntl.h>
33 #include <sys/stat.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include <stdio.h>
37 #include <unistd.h>
38 #include "internal.h"
39
40 static int get_audio_count(void);
41 static int get_mixer_count(void);
42 static int get_mixer_control_count(int);
43
44 oss_private int
_oss4_mixer_ioctl(int fd,unsigned long com,void * argp)45 _oss4_mixer_ioctl(int fd, unsigned long com, void *argp)
46 {
47 oss_audioinfo *tmpai;
48 oss_card_info *cardinfo;
49 oss_mixext *ext;
50 oss_mixext_root root;
51 oss_mixer_enuminfo *ei;
52 oss_mixer_value *mv;
53 oss_mixerinfo *mi;
54 oss_sysinfo sysinfo;
55 dev_t devno;
56 struct stat tmpstat;
57 struct audio_device dev;
58 struct audio_format_query fmtq;
59 struct mixer_devinfo mdi;
60 struct mixer_ctrl mc;
61 char devname[32];
62 size_t len;
63 int newfd = -1, tmperrno;
64 int i, noffs;
65 int retval;
66
67 /*
68 * Note: it is difficult to translate the NetBSD concept of a "set"
69 * mixer control type to the OSSv4 API, as far as I can tell.
70 *
71 * This means they are treated like enums, i.e. only one entry in the
72 * set can be selected at a time.
73 */
74
75 switch (com) {
76 case SNDCTL_AUDIOINFO:
77 /*
78 * SNDCTL_AUDIOINFO_EX is intended for underlying hardware devices
79 * that are to be opened in "exclusive mode" (bypassing the normal
80 * kernel mixer for exclusive control). NetBSD does not support
81 * bypassing the kernel mixer, so it's an alias of SNDCTL_AUDIOINFO.
82 */
83 case SNDCTL_AUDIOINFO_EX:
84 case SNDCTL_ENGINEINFO:
85 devno = 0;
86 tmpai = (struct oss_audioinfo*)argp;
87 if (tmpai == NULL) {
88 errno = EINVAL;
89 return -1;
90 }
91
92 /*
93 * If the input device is -1, guess the device related to
94 * the open mixer device.
95 */
96 if (tmpai->dev < 0) {
97 fstat(fd, &tmpstat);
98 if ((tmpstat.st_rdev & 0xff00) == 0x2a00)
99 devno = tmpstat.st_rdev & 0xff;
100 if (devno >= 0x80)
101 tmpai->dev = devno & 0x7f;
102 }
103 if (tmpai->dev < 0)
104 tmpai->dev = 0;
105
106 snprintf(tmpai->devnode, sizeof(tmpai->devnode),
107 "/dev/audio%d", tmpai->dev);
108
109 if ((newfd = open(tmpai->devnode, O_WRONLY)) < 0) {
110 if ((newfd = open(tmpai->devnode, O_RDONLY)) < 0) {
111 return newfd;
112 }
113 }
114
115 retval = ioctl(newfd, AUDIO_GETDEV, &dev);
116 if (retval < 0) {
117 tmperrno = errno;
118 close(newfd);
119 errno = tmperrno;
120 return retval;
121 }
122 if (_oss_get_caps(newfd, &tmpai->caps) < 0) {
123 tmperrno = errno;
124 close(newfd);
125 errno = tmperrno;
126 return retval;
127 }
128 snprintf(tmpai->name, sizeof(tmpai->name),
129 "%s %s", dev.name, dev.version);
130 tmpai->busy = 0;
131 tmpai->pid = -1;
132 _oss_dsp_ioctl(newfd, SNDCTL_DSP_GETFMTS, &tmpai->iformats);
133 tmpai->oformats = tmpai->iformats;
134 tmpai->magic = -1; /* reserved for "internal use" */
135 memset(tmpai->cmd, 0, sizeof(tmpai->cmd));
136 tmpai->card_number = -1;
137 memset(tmpai->song_name, 0,
138 sizeof(tmpai->song_name));
139 memset(tmpai->label, 0, sizeof(tmpai->label));
140 tmpai->port_number = 0;
141 tmpai->mixer_dev = tmpai->dev;
142 tmpai->legacy_device = tmpai->dev;
143 tmpai->enabled = 1;
144 tmpai->flags = -1; /* reserved for "future versions" */
145 tmpai->min_rate = 1000;
146 tmpai->max_rate = 192000;
147 tmpai->nrates = 0;
148 tmpai->min_channels = 1;
149 tmpai->max_channels = 2;
150 for (fmtq.index = 0;
151 ioctl(newfd, AUDIO_QUERYFORMAT, &fmtq) != -1; ++fmtq.index) {
152 if (fmtq.fmt.channels > (unsigned)tmpai->max_channels)
153 tmpai->max_channels = fmtq.fmt.channels;
154 }
155 tmpai->binding = -1; /* reserved for "future versions" */
156 tmpai->rate_source = -1;
157 /*
158 * 'handle' is supposed to be globally unique. The closest
159 * we have to that is probably device nodes.
160 */
161 strlcpy(tmpai->handle, tmpai->devnode,
162 sizeof(tmpai->handle));
163 tmpai->next_play_engine = 0;
164 tmpai->next_rec_engine = 0;
165 argp = tmpai;
166 close(newfd);
167 break;
168 case SNDCTL_CARDINFO:
169 cardinfo = (oss_card_info *)argp;
170 if (cardinfo == NULL) {
171 errno = EINVAL;
172 return -1;
173 }
174 if (cardinfo->card != -1) {
175 snprintf(devname, sizeof(devname),
176 "/dev/audio%d", cardinfo->card);
177 newfd = open(devname, O_RDONLY);
178 if (newfd < 0)
179 return newfd;
180 } else {
181 newfd = fd;
182 }
183 retval = ioctl(newfd, AUDIO_GETDEV, &dev);
184 tmperrno = errno;
185 if (newfd != fd)
186 close(newfd);
187 if (retval < 0) {
188 errno = tmperrno;
189 return retval;
190 }
191 strlcpy(cardinfo->shortname, dev.name,
192 sizeof(cardinfo->shortname));
193 snprintf(cardinfo->longname, sizeof(cardinfo->longname),
194 "%s %s %s", dev.name, dev.version, dev.config);
195 memset(cardinfo->hw_info, 0, sizeof(cardinfo->hw_info));
196 /*
197 * OSSv4 does not document this ioctl, and claims it should
198 * not be used by applications and is provided for "utiltiy
199 * programs included in OSS". We follow the Solaris
200 * implementation (which is documented) and leave these fields
201 * unset.
202 */
203 cardinfo->flags = 0;
204 cardinfo->intr_count = 0;
205 cardinfo->ack_count = 0;
206 break;
207 case SNDCTL_SYSINFO:
208 memset(&sysinfo, 0, sizeof(sysinfo));
209 strlcpy(sysinfo.product,
210 "OSS/NetBSD", sizeof(sysinfo.product));
211 strlcpy(sysinfo.version,
212 "4.01", sizeof(sysinfo.version));
213 strlcpy(sysinfo.license,
214 "BSD", sizeof(sysinfo.license));
215 sysinfo.versionnum = SOUND_VERSION;
216 sysinfo.numaudios =
217 sysinfo.numcards =
218 get_audio_count();
219 sysinfo.numaudioengines = 1;
220 sysinfo.numsynths = 1;
221 sysinfo.nummidis = -1;
222 sysinfo.numtimers = -1;
223 sysinfo.nummixers = get_mixer_count();
224 *(struct oss_sysinfo *)argp = sysinfo;
225 break;
226 case SNDCTL_MIXERINFO:
227 mi = (oss_mixerinfo *)argp;
228 if (mi == NULL) {
229 errno = EINVAL;
230 return -1;
231 }
232 snprintf(devname, sizeof(devname), "/dev/mixer%d", mi->dev);
233 if ((newfd = open(devname, O_RDONLY)) < 0)
234 return newfd;
235 retval = ioctl(newfd, AUDIO_GETDEV, &dev);
236 if (retval < 0) {
237 tmperrno = errno;
238 close(newfd);
239 errno = tmperrno;
240 return retval;
241 }
242 strlcpy(mi->id, devname, sizeof(mi->id));
243 strlcpy(mi->handle, devname, sizeof(mi->handle));
244 snprintf(mi->name, sizeof(mi->name),
245 "%s %s", dev.name, dev.version);
246 mi->card_number = mi->dev;
247 mi->port_number = 0;
248 mi->magic = 0;
249 mi->enabled = 1;
250 mi->caps = 0;
251 mi->flags = 0;
252 mi->nrext = get_mixer_control_count(newfd) + 1;
253 mi->priority = UCHAR_MAX - mi->dev;
254 strlcpy(mi->devnode, devname, sizeof(mi->devnode));
255 mi->legacy_device = mi->dev;
256 break;
257 case SNDCTL_MIX_DESCRIPTION:
258 /* No description available. */
259 errno = ENOSYS;
260 return -1;
261 case SNDCTL_MIX_NRMIX:
262 INTARG = get_mixer_count();
263 break;
264 case SNDCTL_MIX_NREXT:
265 snprintf(devname, sizeof(devname), "/dev/mixer%d", INTARG);
266 if ((newfd = open(devname, O_RDONLY)) < 0)
267 return newfd;
268 INTARG = get_mixer_control_count(newfd) + 1;
269 close(newfd);
270 break;
271 case SNDCTL_MIX_EXTINFO:
272 ext = (oss_mixext *)argp;
273 snprintf(devname, sizeof(devname), "/dev/mixer%d", ext->dev);
274 if ((newfd = open(devname, O_RDONLY)) < 0)
275 return newfd;
276 if (ext->ctrl == 0) {
277 /*
278 * NetBSD has no concept of a "root mixer control", but
279 * OSSv4 requires one to work. We fake one at 0 and
280 * simply add 1 to all real control indexes.
281 */
282 retval = ioctl(newfd, AUDIO_GETDEV, &dev);
283 tmperrno = errno;
284 close(newfd);
285 if (retval < 0) {
286 errno = tmperrno;
287 return -1;
288 }
289 memset(&root, 0, sizeof(root));
290 strlcpy(root.id, devname, sizeof(root.id));
291 snprintf(root.name, sizeof(root.name),
292 "%s %s", dev.name, dev.version);
293 strlcpy(ext->id, devname, sizeof(ext->id));
294 snprintf(ext->extname, sizeof(ext->extname),
295 "%s %s", dev.name, dev.version);
296 strlcpy(ext->extname, "root", sizeof(ext->extname));
297 ext->type = MIXT_DEVROOT;
298 ext->minvalue = 0;
299 ext->maxvalue = 0;
300 ext->flags = 0;
301 ext->parent = -1;
302 ext->control_no = -1;
303 ext->update_counter = 0;
304 ext->rgbcolor = 0;
305 memcpy(&ext->data, &root,
306 sizeof(root) > sizeof(ext->data) ?
307 sizeof(ext->data) : sizeof(root));
308 return 0;
309 }
310 mdi.index = ext->ctrl - 1;
311 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
312 if (retval < 0) {
313 tmperrno = errno;
314 close(newfd);
315 errno = tmperrno;
316 return retval;
317 }
318 ext->flags = MIXF_READABLE | MIXF_WRITEABLE | MIXF_POLL;
319 ext->parent = mdi.mixer_class + 1;
320 strlcpy(ext->id, mdi.label.name, sizeof(ext->id));
321 strlcpy(ext->extname, mdi.label.name, sizeof(ext->extname));
322 len = strlen(ext->extname);
323 memset(ext->data, 0, sizeof(ext->data));
324 ext->control_no = -1;
325 ext->update_counter = 0;
326 ext->rgbcolor = 0;
327 switch (mdi.type) {
328 case AUDIO_MIXER_CLASS:
329 ext->type = MIXT_GROUP;
330 ext->parent = 0;
331 ext->minvalue = 0;
332 ext->maxvalue = 0;
333 break;
334 case AUDIO_MIXER_ENUM:
335 ext->maxvalue = mdi.un.e.num_mem;
336 ext->minvalue = 0;
337 for (i = 0; i < mdi.un.e.num_mem; ++i) {
338 ext->enum_present[i / 8] |= (1 << (i % 8));
339 }
340 if (mdi.un.e.num_mem == 2) {
341 if (!strcmp(mdi.un.e.member[0].label.name, AudioNoff) &&
342 !strcmp(mdi.un.e.member[1].label.name, AudioNon)) {
343 ext->type = MIXT_MUTE;
344 } else {
345 ext->type = MIXT_ENUM;
346 }
347 } else {
348 ext->type = MIXT_ENUM;
349 }
350 break;
351 case AUDIO_MIXER_SET:
352 ext->maxvalue = mdi.un.s.num_mem;
353 ext->minvalue = 0;
354 #ifdef notyet
355 /*
356 * XXX: This is actually the correct type for "set"
357 * controls, but it seems no real world software
358 * supports it. The only documentation exists in
359 * the OSSv4 headers and describes it as "reserved
360 * for Sun's implementation".
361 */
362 ext->type = MIXT_ENUM_MULTI;
363 #else
364 ext->type = MIXT_ENUM;
365 #endif
366 for (i = 0; i < mdi.un.s.num_mem; ++i) {
367 ext->enum_present[i / 8] |= (1 << (i % 8));
368 }
369 break;
370 case AUDIO_MIXER_VALUE:
371 ext->maxvalue = UCHAR_MAX + 1;
372 ext->minvalue = 0;
373 if (mdi.un.v.num_channels == 2) {
374 ext->type = MIXT_STEREOSLIDER;
375 } else {
376 ext->type = MIXT_MONOSLIDER;
377 }
378 break;
379 }
380 close(newfd);
381 break;
382 case SNDCTL_MIX_ENUMINFO:
383 ei = (oss_mixer_enuminfo *)argp;
384 if (ei == NULL) {
385 errno = EINVAL;
386 return -1;
387 }
388 if (ei->ctrl == 0) {
389 errno = EINVAL;
390 return -1;
391 }
392 snprintf(devname, sizeof(devname), "/dev/mixer%d", ei->dev);
393 if ((newfd = open(devname, O_RDONLY)) < 0)
394 return newfd;
395 mdi.index = ei->ctrl - 1;
396 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
397 tmperrno = errno;
398 close(newfd);
399 if (retval < 0) {
400 errno = tmperrno;
401 return retval;
402 }
403 ei->version = 0;
404 switch (mdi.type) {
405 case AUDIO_MIXER_ENUM:
406 ei->nvalues = mdi.un.e.num_mem;
407 noffs = 0;
408 for (i = 0; i < ei->nvalues; ++i) {
409 ei->strindex[i] = noffs;
410 len = strlen(mdi.un.e.member[i].label.name) + 1;
411 if ((noffs + len) >= sizeof(ei->strings)) {
412 errno = ENOMEM;
413 return -1;
414 }
415 memcpy(ei->strings + noffs,
416 mdi.un.e.member[i].label.name, len);
417 noffs += len;
418 }
419 break;
420 case AUDIO_MIXER_SET:
421 ei->nvalues = mdi.un.s.num_mem;
422 noffs = 0;
423 for (i = 0; i < ei->nvalues; ++i) {
424 ei->strindex[i] = noffs;
425 len = strlen(mdi.un.s.member[i].label.name) + 1;
426 if ((noffs + len) >= sizeof(ei->strings)) {
427 errno = ENOMEM;
428 return -1;
429 }
430 memcpy(ei->strings + noffs,
431 mdi.un.s.member[i].label.name, len);
432 noffs += len;
433 }
434 break;
435 default:
436 errno = EINVAL;
437 return -1;
438 }
439 break;
440 case SNDCTL_MIX_WRITE:
441 mv = (oss_mixer_value *)argp;
442 if (mv == NULL) {
443 errno = EINVAL;
444 return -1;
445 }
446 if (mv->ctrl == 0) {
447 errno = EINVAL;
448 return -1;
449 }
450 snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev);
451 if ((newfd = open(devname, O_RDWR)) < 0)
452 return newfd;
453 mdi.index = mc.dev = mv->ctrl - 1;
454 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
455 if (retval < 0) {
456 tmperrno = errno;
457 close(newfd);
458 errno = tmperrno;
459 return retval;
460 }
461 mc.type = mdi.type;
462 switch (mdi.type) {
463 case AUDIO_MIXER_ENUM:
464 if (mv->value >= mdi.un.e.num_mem) {
465 close(newfd);
466 errno = EINVAL;
467 return -1;
468 }
469 mc.un.ord = mdi.un.e.member[mv->value].ord;
470 break;
471 case AUDIO_MIXER_SET:
472 if (mv->value >= mdi.un.s.num_mem) {
473 close(newfd);
474 errno = EINVAL;
475 return -1;
476 }
477 #ifdef notyet
478 mc.un.mask = 0;
479 for (i = 0; i < mdi.un.s.num_mem; ++i) {
480 if (mv->value & (1 << i)) {
481 mc.un.mask |= mdi.un.s.member[mv->value].mask;
482 }
483 }
484 #else
485 mc.un.mask = mdi.un.s.member[mv->value].mask;
486 #endif
487 break;
488 case AUDIO_MIXER_VALUE:
489 mc.un.value.num_channels = mdi.un.v.num_channels;
490 if (mdi.un.v.num_channels != 2) {
491 for (i = 0; i < mdi.un.v.num_channels; ++i) {
492 mc.un.value.level[i] = mv->value;
493 }
494 } else {
495 mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] =
496 (mv->value >> 0) & 0xFF;
497 mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] =
498 (mv->value >> 8) & 0xFF;
499 }
500 break;
501 }
502 retval = ioctl(newfd, AUDIO_MIXER_WRITE, &mc);
503 if (retval < 0) {
504 tmperrno = errno;
505 close(newfd);
506 errno = tmperrno;
507 return retval;
508 }
509 close(newfd);
510 break;
511 case SNDCTL_MIX_READ:
512 mv = (oss_mixer_value *)argp;
513 if (mv == NULL) {
514 errno = EINVAL;
515 return -1;
516 }
517 if (mv->ctrl == 0) {
518 errno = EINVAL;
519 return -1;
520 }
521 snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev);
522 if ((newfd = open(devname, O_RDWR)) < 0)
523 return newfd;
524 mdi.index = mc.dev = (mv->ctrl - 1);
525 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
526 if (retval < 0) {
527 tmperrno = errno;
528 close(newfd);
529 errno = tmperrno;
530 return retval;
531 }
532 mc.dev = mdi.index;
533 mc.type = mdi.type;
534 if (mdi.type == AUDIO_MIXER_VALUE)
535 mc.un.value.num_channels = mdi.un.v.num_channels;
536 retval = ioctl(newfd, AUDIO_MIXER_READ, &mc);
537 if (retval < 0) {
538 tmperrno = errno;
539 close(newfd);
540 errno = tmperrno;
541 return retval;
542 }
543 close(newfd);
544 mv->value = 0;
545 switch (mdi.type) {
546 case AUDIO_MIXER_ENUM:
547 for (i = 0; i < mdi.un.e.num_mem; ++i) {
548 if (mc.un.ord == mdi.un.e.member[i].ord) {
549 mv->value = i;
550 break;
551 }
552 }
553 break;
554 case AUDIO_MIXER_SET:
555 for (i = 0; i < mdi.un.s.num_mem; ++i) {
556 #ifdef notyet
557 if (mc.un.mask & mdi.un.s.member[i].mask)
558 mv->value |= (1 << i);
559 #else
560 if (mc.un.mask == mdi.un.s.member[i].mask) {
561 mv->value = i;
562 break;
563 }
564 #endif
565 }
566 break;
567 case AUDIO_MIXER_VALUE:
568 if (mdi.un.v.num_channels != 2) {
569 mv->value = mc.un.value.level[0];
570 } else {
571 mv->value = \
572 ((mc.un.value.level[1] & 0xFF) << 8) |
573 ((mc.un.value.level[0] & 0xFF) << 0);
574 }
575 break;
576 default:
577 errno = EINVAL;
578 return -1;
579 }
580 break;
581 default:
582 errno = EINVAL;
583 return -1;
584 }
585 return 0;
586 }
587
588 static int
get_audio_count(void)589 get_audio_count(void)
590 {
591 char devname[32];
592 int ndevs = 0;
593 int tmpfd;
594 int tmperrno = errno;
595
596 do {
597 snprintf(devname, sizeof(devname),
598 "/dev/audio%d", ndevs);
599 if ((tmpfd = open(devname, O_RDONLY)) != -1 ||
600 (tmpfd = open(devname, O_WRONLY)) != -1) {
601 ndevs++;
602 close(tmpfd);
603 }
604 } while (tmpfd != -1);
605 errno = tmperrno;
606 return ndevs;
607 }
608
609 static int
get_mixer_count(void)610 get_mixer_count(void)
611 {
612 char devname[32];
613 int ndevs = 0;
614 int tmpfd;
615 int tmperrno = errno;
616
617 do {
618 snprintf(devname, sizeof(devname),
619 "/dev/mixer%d", ndevs);
620 if ((tmpfd = open(devname, O_RDONLY)) != -1) {
621 ndevs++;
622 close(tmpfd);
623 }
624 } while (tmpfd != -1);
625 errno = tmperrno;
626 return ndevs;
627 }
628
629 static int
get_mixer_control_count(int fd)630 get_mixer_control_count(int fd)
631 {
632 struct mixer_devinfo mdi;
633 int ndevs = 0;
634
635 do {
636 mdi.index = ndevs++;
637 } while (ioctl(fd, AUDIO_MIXER_DEVINFO, &mdi) != -1);
638
639 return ndevs > 0 ? ndevs - 1 : 0;
640 }
641