xref: /openbsd-src/lib/libossaudio/ossaudio.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: ossaudio.c,v 1.6 2003/05/03 19:01:48 avsm Exp $	*/
2 /*	$NetBSD: ossaudio.c,v 1.14 2001/05/10 01:53:48 augustss Exp $	*/
3 
4 /*-
5  * Copyright (c) 1997 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *        This product includes software developed by the NetBSD
19  *        Foundation, Inc. and its contributors.
20  * 4. Neither the name of The NetBSD Foundation nor the names of its
21  *    contributors may be used to endorse or promote products derived
22  *    from this software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
25  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
28  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 /*
38  * This is an OSS (Linux) sound API emulator.
39  * It provides the essentials of the API.
40  */
41 
42 /* XXX This file is essentially the same as sys/compat/ossaudio.c.
43  * With some preprocessor magic it could be the same file.
44  */
45 
46 #include <stdarg.h>
47 #include <string.h>
48 #include <sys/types.h>
49 #include <sys/ioctl.h>
50 #include <sys/audioio.h>
51 #include <sys/stat.h>
52 #include <errno.h>
53 
54 #include "soundcard.h"
55 #undef ioctl
56 
57 #define GET_DEV(com) ((com) & 0xff)
58 
59 #define TO_OSSVOL(x)	(((x) * 100 + 127) / 255)
60 #define FROM_OSSVOL(x)	((((x) > 100 ? 100 : (x)) * 255 + 50) / 100)
61 
62 static struct audiodevinfo *getdevinfo(int);
63 
64 static void setblocksize(int, struct audio_info *);
65 
66 static int audio_ioctl(int, unsigned long, void *);
67 static int mixer_ioctl(int, unsigned long, void *);
68 static int opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq);
69 static int enum_to_ord(struct audiodevinfo *di, int enm);
70 static int enum_to_mask(struct audiodevinfo *di, int enm);
71 
72 #define INTARG (*(int*)argp)
73 
74 int
75 _oss_ioctl(int fd, unsigned long com, ...)
76 {
77 	va_list ap;
78 	void *argp;
79 
80 	va_start(ap, com);
81 	argp = va_arg(ap, void *);
82 	va_end(ap);
83 	if (IOCGROUP(com) == 'P')
84 		return audio_ioctl(fd, com, argp);
85 	else if (IOCGROUP(com) == 'M')
86 		return mixer_ioctl(fd, com, argp);
87 	else
88 		return ioctl(fd, com, argp);
89 }
90 
91 static int
92 audio_ioctl(int fd, unsigned long com, void *argp)
93 {
94 
95 	struct audio_info tmpinfo;
96 	struct audio_offset tmpoffs;
97 	struct audio_buf_info bufinfo;
98 	struct count_info cntinfo;
99 	struct audio_encoding tmpenc;
100 	u_int u;
101 	int idat, idata;
102 	int retval;
103 
104 	switch (com) {
105 	case SNDCTL_DSP_RESET:
106 		retval = ioctl(fd, AUDIO_FLUSH, 0);
107 		if (retval < 0)
108 			return retval;
109 		break;
110 	case SNDCTL_DSP_SYNC:
111 		retval = ioctl(fd, AUDIO_DRAIN, 0);
112 		if (retval < 0)
113 			return retval;
114 		break;
115 	case SNDCTL_DSP_POST:
116 		/* This call is merely advisory, and may be a nop. */
117 		break;
118 	case SNDCTL_DSP_SPEED:
119 		AUDIO_INITINFO(&tmpinfo);
120 		tmpinfo.play.sample_rate =
121 		tmpinfo.record.sample_rate = INTARG;
122 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
123 		/* FALLTHRU */
124 	case SOUND_PCM_READ_RATE:
125 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
126 		if (retval < 0)
127 			return retval;
128 		INTARG = tmpinfo.play.sample_rate;
129 		break;
130 	case SNDCTL_DSP_STEREO:
131 		AUDIO_INITINFO(&tmpinfo);
132 		tmpinfo.play.channels =
133 		tmpinfo.record.channels = INTARG ? 2 : 1;
134 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
135 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
136 		if (retval < 0)
137 			return retval;
138 		INTARG = tmpinfo.play.channels - 1;
139 		break;
140 	case SNDCTL_DSP_GETBLKSIZE:
141 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
142 		if (retval < 0)
143 			return retval;
144 		setblocksize(fd, &tmpinfo);
145 		INTARG = tmpinfo.blocksize;
146 		break;
147 	case SNDCTL_DSP_SETFMT:
148 		AUDIO_INITINFO(&tmpinfo);
149 		switch (INTARG) {
150 		case AFMT_MU_LAW:
151 			tmpinfo.play.precision =
152 			tmpinfo.record.precision = 8;
153 			tmpinfo.play.encoding =
154 			tmpinfo.record.encoding = AUDIO_ENCODING_ULAW;
155 			break;
156 		case AFMT_A_LAW:
157 			tmpinfo.play.precision =
158 			tmpinfo.record.precision = 8;
159 			tmpinfo.play.encoding =
160 			tmpinfo.record.encoding = AUDIO_ENCODING_ALAW;
161 			break;
162 		case AFMT_U8:
163 			tmpinfo.play.precision =
164 			tmpinfo.record.precision = 8;
165 			tmpinfo.play.encoding =
166 			tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR;
167 			break;
168 		case AFMT_S8:
169 			tmpinfo.play.precision =
170 			tmpinfo.record.precision = 8;
171 			tmpinfo.play.encoding =
172 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR;
173 			break;
174 		case AFMT_S16_LE:
175 			tmpinfo.play.precision =
176 			tmpinfo.record.precision = 16;
177 			tmpinfo.play.encoding =
178 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
179 			break;
180 		case AFMT_S16_BE:
181 			tmpinfo.play.precision =
182 			tmpinfo.record.precision = 16;
183 			tmpinfo.play.encoding =
184 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_BE;
185 			break;
186 		case AFMT_U16_LE:
187 			tmpinfo.play.precision =
188 			tmpinfo.record.precision = 16;
189 			tmpinfo.play.encoding =
190 			tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR_LE;
191 			break;
192 		case AFMT_U16_BE:
193 			tmpinfo.play.precision =
194 			tmpinfo.record.precision = 16;
195 			tmpinfo.play.encoding =
196 			tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR_BE;
197 			break;
198 		default:
199 			return EINVAL;
200 		}
201 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
202 		/* FALLTHRU */
203 	case SOUND_PCM_READ_BITS:
204 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
205 		if (retval < 0)
206 			return retval;
207 		switch (tmpinfo.play.encoding) {
208 		case AUDIO_ENCODING_ULAW:
209 			idat = AFMT_MU_LAW;
210 			break;
211 		case AUDIO_ENCODING_ALAW:
212 			idat = AFMT_A_LAW;
213 			break;
214 		case AUDIO_ENCODING_SLINEAR_LE:
215 			if (tmpinfo.play.precision == 16)
216 				idat = AFMT_S16_LE;
217 			else
218 				idat = AFMT_S8;
219 			break;
220 		case AUDIO_ENCODING_SLINEAR_BE:
221 			if (tmpinfo.play.precision == 16)
222 				idat = AFMT_S16_BE;
223 			else
224 				idat = AFMT_S8;
225 			break;
226 		case AUDIO_ENCODING_ULINEAR_LE:
227 			if (tmpinfo.play.precision == 16)
228 				idat = AFMT_U16_LE;
229 			else
230 				idat = AFMT_U8;
231 			break;
232 		case AUDIO_ENCODING_ULINEAR_BE:
233 			if (tmpinfo.play.precision == 16)
234 				idat = AFMT_U16_BE;
235 			else
236 				idat = AFMT_U8;
237 			break;
238 		case AUDIO_ENCODING_ADPCM:
239 			idat = AFMT_IMA_ADPCM;
240 			break;
241 		}
242 		INTARG = idat;
243 		break;
244 	case SNDCTL_DSP_CHANNELS:
245 		AUDIO_INITINFO(&tmpinfo);
246 		tmpinfo.play.channels =
247 		tmpinfo.record.channels = INTARG;
248 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
249 		/* FALLTHRU */
250 	case SOUND_PCM_READ_CHANNELS:
251 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
252 		if (retval < 0)
253 			return retval;
254 		INTARG = tmpinfo.play.channels;
255 		break;
256 	case SOUND_PCM_WRITE_FILTER:
257 	case SOUND_PCM_READ_FILTER:
258 		errno = EINVAL;
259 		return -1; /* XXX unimplemented */
260 	case SNDCTL_DSP_SUBDIVIDE:
261 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
262 		if (retval < 0)
263 			return retval;
264 		setblocksize(fd, &tmpinfo);
265 		idat = INTARG;
266 		if (idat == 0)
267 			idat = tmpinfo.play.buffer_size / tmpinfo.blocksize;
268 		idat = (tmpinfo.play.buffer_size / idat) & -4;
269 		AUDIO_INITINFO(&tmpinfo);
270 		tmpinfo.blocksize = idat;
271 		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
272 		if (retval < 0)
273 			return retval;
274 		INTARG = tmpinfo.play.buffer_size / tmpinfo.blocksize;
275 		break;
276 	case SNDCTL_DSP_SETFRAGMENT:
277 		AUDIO_INITINFO(&tmpinfo);
278 		idat = INTARG;
279 		if ((idat & 0xffff) < 4 || (idat & 0xffff) > 17)
280 			return EINVAL;
281 		tmpinfo.blocksize = 1 << (idat & 0xffff);
282 		tmpinfo.hiwat = ((unsigned)idat >> 16) & 0x7fff;
283 		if (tmpinfo.hiwat == 0)	/* 0 means set to max */
284 			tmpinfo.hiwat = 65536;
285 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
286 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
287 		if (retval < 0)
288 			return retval;
289 		u = tmpinfo.blocksize;
290 		for(idat = 0; u > 1; idat++, u >>= 1)
291 			;
292 		idat |= (tmpinfo.hiwat & 0x7fff) << 16;
293 		INTARG = idat;
294 		break;
295 	case SNDCTL_DSP_GETFMTS:
296 		for(idat = 0, tmpenc.index = 0;
297 		    ioctl(fd, AUDIO_GETENC, &tmpenc) == 0;
298 		    tmpenc.index++) {
299 			switch(tmpenc.encoding) {
300 			case AUDIO_ENCODING_ULAW:
301 				idat |= AFMT_MU_LAW;
302 				break;
303 			case AUDIO_ENCODING_ALAW:
304 				idat |= AFMT_A_LAW;
305 				break;
306 			case AUDIO_ENCODING_SLINEAR:
307 				idat |= AFMT_S8;
308 				break;
309 			case AUDIO_ENCODING_SLINEAR_LE:
310 				if (tmpenc.precision == 16)
311 					idat |= AFMT_S16_LE;
312 				else
313 					idat |= AFMT_S8;
314 				break;
315 			case AUDIO_ENCODING_SLINEAR_BE:
316 				if (tmpenc.precision == 16)
317 					idat |= AFMT_S16_BE;
318 				else
319 					idat |= AFMT_S8;
320 				break;
321 			case AUDIO_ENCODING_ULINEAR:
322 				idat |= AFMT_U8;
323 				break;
324 			case AUDIO_ENCODING_ULINEAR_LE:
325 				if (tmpenc.precision == 16)
326 					idat |= AFMT_U16_LE;
327 				else
328 					idat |= AFMT_U8;
329 				break;
330 			case AUDIO_ENCODING_ULINEAR_BE:
331 				if (tmpenc.precision == 16)
332 					idat |= AFMT_U16_BE;
333 				else
334 					idat |= AFMT_U8;
335 				break;
336 			case AUDIO_ENCODING_ADPCM:
337 				idat |= AFMT_IMA_ADPCM;
338 				break;
339 			default:
340 				break;
341 			}
342 		}
343 		INTARG = idat;
344 		break;
345 	case SNDCTL_DSP_GETOSPACE:
346 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
347 		if (retval < 0)
348 			return retval;
349 		setblocksize(fd, &tmpinfo);
350 		bufinfo.fragsize = tmpinfo.blocksize;
351 		bufinfo.fragments = tmpinfo.hiwat -
352 			(tmpinfo.play.seek + tmpinfo.blocksize - 1)/tmpinfo.blocksize;
353 		bufinfo.fragstotal = tmpinfo.hiwat;
354 		bufinfo.bytes = tmpinfo.hiwat * tmpinfo.blocksize - tmpinfo.play.seek;
355 		*(struct audio_buf_info *)argp = bufinfo;
356 		break;
357 	case SNDCTL_DSP_GETISPACE:
358 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
359 		if (retval < 0)
360 			return retval;
361 		setblocksize(fd, &tmpinfo);
362 		bufinfo.fragsize = tmpinfo.blocksize;
363 		bufinfo.fragments = tmpinfo.hiwat -
364 			(tmpinfo.record.seek + tmpinfo.blocksize - 1)/tmpinfo.blocksize;
365 		bufinfo.fragstotal = tmpinfo.hiwat;
366 		bufinfo.bytes = tmpinfo.hiwat * tmpinfo.blocksize - tmpinfo.record.seek;
367 		*(struct audio_buf_info *)argp = bufinfo;
368 		break;
369 	case SNDCTL_DSP_NONBLOCK:
370 		idat = 1;
371 		retval = ioctl(fd, FIONBIO, &idat);
372 		if (retval < 0)
373 			return retval;
374 		break;
375 	case SNDCTL_DSP_GETCAPS:
376 		retval = ioctl(fd, AUDIO_GETPROPS, &idata);
377 		if (retval < 0)
378 			return retval;
379 		idat = DSP_CAP_TRIGGER; /* pretend we have trigger */
380 		if (idata & AUDIO_PROP_FULLDUPLEX)
381 			idat |= DSP_CAP_DUPLEX;
382 		if (idata & AUDIO_PROP_MMAP)
383 			idat |= DSP_CAP_MMAP;
384 		INTARG = idat;
385 		break;
386 #if 0
387 	case SNDCTL_DSP_GETTRIGGER:
388 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
389 		if (retval < 0)
390 			return retval;
391 		idat = (tmpinfo.play.pause ? 0 : PCM_ENABLE_OUTPUT) |
392 		       (tmpinfo.record.pause ? 0 : PCM_ENABLE_INPUT);
393 		retval = copyout(&idat, SCARG(uap, data), sizeof idat);
394 		if (retval < 0)
395 			return retval;
396 		break;
397 	case SNDCTL_DSP_SETTRIGGER:
398 		AUDIO_INITINFO(&tmpinfo);
399 		retval = copyin(SCARG(uap, data), &idat, sizeof idat);
400 		if (retval < 0)
401 			return retval;
402 		tmpinfo.play.pause = (idat & PCM_ENABLE_OUTPUT) == 0;
403 		tmpinfo.record.pause = (idat & PCM_ENABLE_INPUT) == 0;
404 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
405 		retval = copyout(&idat, SCARG(uap, data), sizeof idat);
406 		if (retval < 0)
407 			return retval;
408 		break;
409 #else
410 	case SNDCTL_DSP_GETTRIGGER:
411 	case SNDCTL_DSP_SETTRIGGER:
412 		/* XXX Do nothing for now. */
413 		INTARG = PCM_ENABLE_OUTPUT;
414 		break;
415 #endif
416 	case SNDCTL_DSP_GETIPTR:
417 		retval = ioctl(fd, AUDIO_GETIOFFS, &tmpoffs);
418 		if (retval < 0)
419 			return retval;
420 		cntinfo.bytes = tmpoffs.samples;
421 		cntinfo.blocks = tmpoffs.deltablks;
422 		cntinfo.ptr = tmpoffs.offset;
423 		*(struct count_info *)argp = cntinfo;
424 		break;
425 	case SNDCTL_DSP_GETOPTR:
426 		retval = ioctl(fd, AUDIO_GETOOFFS, &tmpoffs);
427 		if (retval < 0)
428 			return retval;
429 		cntinfo.bytes = tmpoffs.samples;
430 		cntinfo.blocks = tmpoffs.deltablks;
431 		cntinfo.ptr = tmpoffs.offset;
432 		*(struct count_info *)argp = cntinfo;
433 		break;
434 	case SNDCTL_DSP_SETDUPLEX:
435 		idat = 1;
436 		retval = ioctl(fd, AUDIO_SETFD, &idat);
437 		if (retval < 0)
438 			return retval;
439 		break;
440 	case SNDCTL_DSP_MAPINBUF:
441 	case SNDCTL_DSP_MAPOUTBUF:
442 	case SNDCTL_DSP_SETSYNCRO:
443 	case SNDCTL_DSP_PROFILE:
444 		errno = EINVAL;
445 		return -1; /* XXX unimplemented */
446 	default:
447 		errno = EINVAL;
448 		return -1;
449 	}
450 
451 	return 0;
452 }
453 
454 
455 /* If the NetBSD mixer device should have more than NETBSD_MAXDEVS devices
456  * some will not be available to Linux */
457 #define NETBSD_MAXDEVS 64
458 struct audiodevinfo {
459 	int done;
460 	dev_t dev;
461 	int16_t devmap[SOUND_MIXER_NRDEVICES],
462 	        rdevmap[NETBSD_MAXDEVS];
463 	char names[NETBSD_MAXDEVS][MAX_AUDIO_DEV_LEN];
464 	int enum2opaque[NETBSD_MAXDEVS];
465         u_long devmask, recmask, stereomask;
466 	u_long caps, source;
467 };
468 
469 static int
470 opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq)
471 {
472 	int i, o;
473 
474 	for (i = 0; i < NETBSD_MAXDEVS; i++) {
475 		o = di->enum2opaque[i];
476 		if (o == opq)
477 			break;
478 		if (o == -1 && label != NULL &&
479 		    !strncmp(di->names[i], label->name, sizeof di->names[i])) {
480 			di->enum2opaque[i] = opq;
481 			break;
482 		}
483 	}
484 	if (i >= NETBSD_MAXDEVS)
485 		i = -1;
486 	/*printf("opq_to_enum %s %d -> %d\n", label->name, opq, i);*/
487 	return (i);
488 }
489 
490 static int
491 enum_to_ord(struct audiodevinfo *di, int enm)
492 {
493 	if (enm >= NETBSD_MAXDEVS)
494 		return (-1);
495 
496 	/*printf("enum_to_ord %d -> %d\n", enm, di->enum2opaque[enm]);*/
497 	return (di->enum2opaque[enm]);
498 }
499 
500 static int
501 enum_to_mask(struct audiodevinfo *di, int enm)
502 {
503 	int m;
504 	if (enm >= NETBSD_MAXDEVS)
505 		return (0);
506 
507 	m = di->enum2opaque[enm];
508 	if (m == -1)
509 		m = 0;
510 	/*printf("enum_to_mask %d -> %d\n", enm, di->enum2opaque[enm]);*/
511 	return (m);
512 }
513 
514 /*
515  * Collect the audio device information to allow faster
516  * emulation of the Linux mixer ioctls.  Cache the information
517  * to eliminate the overhead of repeating all the ioctls needed
518  * to collect the information.
519  */
520 static struct audiodevinfo *
521 getdevinfo(int fd)
522 {
523 	mixer_devinfo_t mi;
524 	int i, j, e;
525 	static struct {
526 		char *name;
527 		int code;
528 	} *dp, devs[] = {
529 		{ AudioNmicrophone,	SOUND_MIXER_MIC },
530 		{ AudioNline,		SOUND_MIXER_LINE },
531 		{ AudioNcd,		SOUND_MIXER_CD },
532 		{ AudioNdac,		SOUND_MIXER_PCM },
533 		{ AudioNaux,		SOUND_MIXER_LINE1 },
534 		{ AudioNrecord,		SOUND_MIXER_IMIX },
535 		{ AudioNmaster,		SOUND_MIXER_VOLUME },
536 		{ AudioNtreble,		SOUND_MIXER_TREBLE },
537 		{ AudioNbass,		SOUND_MIXER_BASS },
538 		{ AudioNspeaker,	SOUND_MIXER_SPEAKER },
539 /*		{ AudioNheadphone,	?? },*/
540 		{ AudioNoutput,		SOUND_MIXER_OGAIN },
541 		{ AudioNinput,		SOUND_MIXER_IGAIN },
542 /*		{ AudioNmaster,		SOUND_MIXER_SPEAKER },*/
543 /*		{ AudioNstereo,		?? },*/
544 /*		{ AudioNmono,		?? },*/
545 		{ AudioNfmsynth,	SOUND_MIXER_SYNTH },
546 /*		{ AudioNwave,		SOUND_MIXER_PCM },*/
547 		{ AudioNmidi,		SOUND_MIXER_SYNTH },
548 /*		{ AudioNmixerout,	?? },*/
549 		{ 0, -1 }
550 	};
551 	static struct audiodevinfo devcache = { 0 };
552 	struct audiodevinfo *di = &devcache;
553 	struct stat sb;
554 
555 	/* Figure out what device it is so we can check if the
556 	 * cached data is valid.
557 	 */
558 	if (fstat(fd, &sb) < 0)
559 		return 0;
560 	if (di->done && di->dev == sb.st_dev)
561 		return di;
562 
563 	di->done = 1;
564 	di->dev = sb.st_dev;
565 	di->devmask = 0;
566 	di->recmask = 0;
567 	di->stereomask = 0;
568 	di->source = ~0;
569 	di->caps = 0;
570 	for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
571 		di->devmap[i] = -1;
572 	for(i = 0; i < NETBSD_MAXDEVS; i++) {
573 		di->rdevmap[i] = -1;
574 		di->names[i][0] = '\0';
575 		di->enum2opaque[i] = -1;
576 	}
577 	for(i = 0; i < NETBSD_MAXDEVS; i++) {
578 		mi.index = i;
579 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
580 			break;
581 		switch(mi.type) {
582 		case AUDIO_MIXER_VALUE:
583 			for(dp = devs; dp->name; dp++)
584 		    		if (strcmp(dp->name, mi.label.name) == 0)
585 					break;
586 			if (dp->code >= 0) {
587 				di->devmap[dp->code] = i;
588 				di->rdevmap[i] = dp->code;
589 				di->devmask |= 1 << dp->code;
590 				if (mi.un.v.num_channels == 2)
591 					di->stereomask |= 1 << dp->code;
592 				strncpy(di->names[i], mi.label.name,
593 					sizeof di->names[i]);
594 			}
595 			break;
596 		}
597 	}
598 	for(i = 0; i < NETBSD_MAXDEVS; i++) {
599 		mi.index = i;
600 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) < 0)
601 			break;
602 		if (strcmp(mi.label.name, AudioNsource) != 0)
603 			continue;
604 		di->source = i;
605 		switch(mi.type) {
606 		case AUDIO_MIXER_ENUM:
607 			for(j = 0; j < mi.un.e.num_mem; j++) {
608 				e = opaque_to_enum(di,
609 						   &mi.un.e.member[j].label,
610 						   mi.un.e.member[j].ord);
611 				if (e >= 0)
612 					di->recmask |= 1 << di->rdevmap[e];
613 			}
614 			di->caps = SOUND_CAP_EXCL_INPUT;
615 			break;
616 		case AUDIO_MIXER_SET:
617 			for(j = 0; j < mi.un.s.num_mem; j++) {
618 				e = opaque_to_enum(di,
619 						   &mi.un.s.member[j].label,
620 						   mi.un.s.member[j].mask);
621 				if (e >= 0)
622 					di->recmask |= 1 << di->rdevmap[e];
623 			}
624 			break;
625 		}
626 	}
627 	return di;
628 }
629 
630 int
631 mixer_ioctl(int fd, unsigned long com, void *argp)
632 {
633 	struct audiodevinfo *di;
634 	struct mixer_info *omi;
635 	struct audio_device adev;
636 	mixer_ctrl_t mc;
637 	int idat = 0;
638 	int i;
639 	int retval;
640 	int l, r, n, error, e;
641 
642 	di = getdevinfo(fd);
643 	if (di == 0)
644 		return -1;
645 
646 	switch (com) {
647 	case OSS_GETVERSION:
648 		idat = SOUND_VERSION;
649 		break;
650 	case SOUND_MIXER_INFO:
651 	case SOUND_OLD_MIXER_INFO:
652 		error = ioctl(fd, AUDIO_GETDEV, &adev);
653 		if (error)
654 			return (error);
655 		omi = argp;
656 		if (com == SOUND_MIXER_INFO)
657 			omi->modify_counter = 1;
658 		strncpy(omi->id, adev.name, sizeof omi->id);
659 		strncpy(omi->name, adev.name, sizeof omi->name);
660 		return 0;
661 	case SOUND_MIXER_READ_RECSRC:
662 		if (di->source == -1)
663 			return EINVAL;
664 		mc.dev = di->source;
665 		if (di->caps & SOUND_CAP_EXCL_INPUT) {
666 			mc.type = AUDIO_MIXER_ENUM;
667 			retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
668 			if (retval < 0)
669 				return retval;
670 			e = opaque_to_enum(di, NULL, mc.un.ord);
671 			if (e >= 0)
672 				idat = 1 << di->rdevmap[e];
673 		} else {
674 			mc.type = AUDIO_MIXER_SET;
675 			retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
676 			if (retval < 0)
677 				return retval;
678 			e = opaque_to_enum(di, NULL, mc.un.mask);
679 			if (e >= 0)
680 				idat = 1 << di->rdevmap[e];
681 		}
682 		break;
683 	case SOUND_MIXER_READ_DEVMASK:
684 		idat = di->devmask;
685 		break;
686 	case SOUND_MIXER_READ_RECMASK:
687 		idat = di->recmask;
688 		break;
689 	case SOUND_MIXER_READ_STEREODEVS:
690 		idat = di->stereomask;
691 		break;
692 	case SOUND_MIXER_READ_CAPS:
693 		idat = di->caps;
694 		break;
695 	case SOUND_MIXER_WRITE_RECSRC:
696 	case SOUND_MIXER_WRITE_R_RECSRC:
697 		if (di->source == -1)
698 			return EINVAL;
699 		mc.dev = di->source;
700 		idat = INTARG;
701 		if (di->caps & SOUND_CAP_EXCL_INPUT) {
702 			mc.type = AUDIO_MIXER_ENUM;
703 			for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
704 				if (idat & (1 << i))
705 					break;
706 			if (i >= SOUND_MIXER_NRDEVICES ||
707 			    di->devmap[i] == -1)
708 				return EINVAL;
709 			mc.un.ord = enum_to_ord(di, di->devmap[i]);
710 		} else {
711 			mc.type = AUDIO_MIXER_SET;
712 			mc.un.mask = 0;
713 			for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
714 				if (idat & (1 << i)) {
715 					if (di->devmap[i] == -1)
716 						return EINVAL;
717 					mc.un.mask |= enum_to_mask(di, di->devmap[i]);
718 				}
719 			}
720 		}
721 		return ioctl(fd, AUDIO_MIXER_WRITE, &mc);
722 	default:
723 		if (MIXER_READ(SOUND_MIXER_FIRST) <= com &&
724 		    com < MIXER_READ(SOUND_MIXER_NRDEVICES)) {
725 			n = GET_DEV(com);
726 			if (di->devmap[n] == -1)
727 				return EINVAL;
728 			mc.dev = di->devmap[n];
729 			mc.type = AUDIO_MIXER_VALUE;
730 		    doread:
731 			mc.un.value.num_channels = di->stereomask & (1<<n) ? 2 : 1;
732 			retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
733 			if (retval < 0)
734 				return retval;
735 			if (mc.type != AUDIO_MIXER_VALUE)
736 				return EINVAL;
737 			if (mc.un.value.num_channels != 2) {
738 				l = r = mc.un.value.level[AUDIO_MIXER_LEVEL_MONO];
739 			} else {
740 				l = mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
741 				r = mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
742 			}
743 			idat = TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
744 			break;
745 		} else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com &&
746 			   com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) ||
747 			   (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
748 			   com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) {
749 			n = GET_DEV(com);
750 			if (di->devmap[n] == -1)
751 				return EINVAL;
752 			idat = INTARG;
753 			l = FROM_OSSVOL( idat       & 0xff);
754 			r = FROM_OSSVOL((idat >> 8) & 0xff);
755 			mc.dev = di->devmap[n];
756 			mc.type = AUDIO_MIXER_VALUE;
757 			if (di->stereomask & (1<<n)) {
758 				mc.un.value.num_channels = 2;
759 				mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
760 				mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
761 			} else {
762 				mc.un.value.num_channels = 1;
763 				mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] = (l+r)/2;
764 			}
765 			retval = ioctl(fd, AUDIO_MIXER_WRITE, &mc);
766 			if (retval < 0)
767 				return retval;
768 			if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
769 			   com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))
770 				return 0;
771 			goto doread;
772 		} else {
773 			errno = EINVAL;
774 			return -1;
775 		}
776 	}
777 	INTARG = idat;
778 	return 0;
779 }
780 
781 /*
782  * Check that the blocksize is a power of 2 as OSS wants.
783  * If not, set it to be.
784  */
785 static void
786 setblocksize(int fd, struct audio_info *info)
787 {
788 	struct audio_info set;
789 	int s;
790 
791 	if (info->blocksize & (info->blocksize-1)) {
792 		for(s = 32; s < info->blocksize; s <<= 1)
793 			;
794 		AUDIO_INITINFO(&set);
795 		set.blocksize = s;
796 		ioctl(fd, AUDIO_SETINFO, &set);
797 		ioctl(fd, AUDIO_GETINFO, info);
798 	}
799 }
800