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