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