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