xref: /netbsd-src/lib/libossaudio/oss_dsp.c (revision 200ab436dc608a14e2d682029b9671dee905663a)
1*200ab436Snia /*	$NetBSD: oss_dsp.c,v 1.2 2021/06/08 19:26:48 nia Exp $	*/
28170080dSnia 
38170080dSnia /*-
48170080dSnia  * Copyright (c) 1997-2021 The NetBSD Foundation, Inc.
58170080dSnia  * All rights reserved.
68170080dSnia  *
78170080dSnia  * Redistribution and use in source and binary forms, with or without
88170080dSnia  * modification, are permitted provided that the following conditions
98170080dSnia  * are met:
108170080dSnia  * 1. Redistributions of source code must retain the above copyright
118170080dSnia  *    notice, this list of conditions and the following disclaimer.
128170080dSnia  * 2. Redistributions in binary form must reproduce the above copyright
138170080dSnia  *    notice, this list of conditions and the following disclaimer in the
148170080dSnia  *    documentation and/or other materials provided with the distribution.
158170080dSnia  *
168170080dSnia  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
178170080dSnia  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
188170080dSnia  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
198170080dSnia  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
208170080dSnia  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
218170080dSnia  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
228170080dSnia  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
238170080dSnia  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
248170080dSnia  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
258170080dSnia  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
268170080dSnia  * POSSIBILITY OF SUCH DAMAGE.
278170080dSnia  */
288170080dSnia 
298170080dSnia #include <sys/cdefs.h>
30*200ab436Snia __RCSID("$NetBSD: oss_dsp.c,v 1.2 2021/06/08 19:26:48 nia Exp $");
318170080dSnia 
328170080dSnia #include <sys/audioio.h>
338170080dSnia #include <stdbool.h>
348170080dSnia #include <errno.h>
358170080dSnia #include "internal.h"
368170080dSnia 
378170080dSnia #define GETPRINFO(info, name)	\
388170080dSnia 	(((info)->mode == AUMODE_RECORD) \
398170080dSnia 	    ? (info)->record.name : (info)->play.name)
408170080dSnia 
41*200ab436Snia static int encoding_to_format(u_int, u_int);
42*200ab436Snia static int format_to_encoding(int, struct audio_info *);
43*200ab436Snia 
448170080dSnia static int get_vol(u_int, u_char);
458170080dSnia static void set_vol(int, int, bool);
468170080dSnia 
478170080dSnia static void set_channels(int, int, int);
488170080dSnia 
498170080dSnia oss_private int
_oss_dsp_ioctl(int fd,unsigned long com,void * argp)508170080dSnia _oss_dsp_ioctl(int fd, unsigned long com, void *argp)
518170080dSnia {
528170080dSnia 
538170080dSnia 	struct audio_info tmpinfo, hwfmt;
548170080dSnia 	struct audio_offset tmpoffs;
558170080dSnia 	struct audio_buf_info bufinfo;
568170080dSnia 	struct audio_errinfo *tmperrinfo;
578170080dSnia 	struct count_info cntinfo;
588170080dSnia 	struct audio_encoding tmpenc;
598170080dSnia 	u_int u;
608170080dSnia 	int perrors, rerrors;
618170080dSnia 	static int totalperrors = 0;
628170080dSnia 	static int totalrerrors = 0;
638170080dSnia 	oss_mixer_enuminfo *ei;
648170080dSnia 	oss_count_t osscount;
658170080dSnia 	int idat;
668170080dSnia 	int retval;
678170080dSnia 
688170080dSnia 	idat = 0;
698170080dSnia 
708170080dSnia 	switch (com) {
718170080dSnia 	case SNDCTL_DSP_HALT_INPUT:
728170080dSnia 	case SNDCTL_DSP_HALT_OUTPUT:
738170080dSnia 	case SNDCTL_DSP_RESET:
748170080dSnia 		retval = ioctl(fd, AUDIO_FLUSH, 0);
758170080dSnia 		if (retval < 0)
768170080dSnia 			return retval;
778170080dSnia 		break;
788170080dSnia 	case SNDCTL_DSP_SYNC:
798170080dSnia 		retval = ioctl(fd, AUDIO_DRAIN, 0);
808170080dSnia 		if (retval < 0)
818170080dSnia 			return retval;
828170080dSnia 		break;
838170080dSnia 	case SNDCTL_DSP_GETERROR:
848170080dSnia 		tmperrinfo = (struct audio_errinfo *)argp;
858170080dSnia 		if (tmperrinfo == NULL) {
868170080dSnia 			errno = EINVAL;
878170080dSnia 			return -1;
888170080dSnia 		}
898170080dSnia 		memset(tmperrinfo, 0, sizeof(struct audio_errinfo));
908170080dSnia 		if ((retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo)) < 0)
918170080dSnia 			return retval;
928170080dSnia 		/*
938170080dSnia 		 * OSS requires that we return counters that are relative to
948170080dSnia 		 * the last call. We must maintain state here...
958170080dSnia 		 */
968170080dSnia 		if (ioctl(fd, AUDIO_PERROR, &perrors) != -1) {
978170080dSnia 			perrors /= ((tmpinfo.play.precision / NBBY) *
988170080dSnia 			    tmpinfo.play.channels);
998170080dSnia 			tmperrinfo->play_underruns =
1008170080dSnia 			    (perrors / tmpinfo.blocksize) - totalperrors;
1018170080dSnia 			totalperrors += tmperrinfo->play_underruns;
1028170080dSnia 		}
1038170080dSnia 		if (ioctl(fd, AUDIO_RERROR, &rerrors) != -1) {
1048170080dSnia 			rerrors /= ((tmpinfo.record.precision / NBBY) *
1058170080dSnia 			    tmpinfo.record.channels);
1068170080dSnia 			tmperrinfo->rec_overruns =
1078170080dSnia 			    (rerrors / tmpinfo.blocksize) - totalrerrors;
1088170080dSnia 			totalrerrors += tmperrinfo->rec_overruns;
1098170080dSnia 		}
1108170080dSnia 		break;
1118170080dSnia 	case SNDCTL_DSP_COOKEDMODE:
1128170080dSnia 		/*
1138170080dSnia 		 * NetBSD is always running in "cooked mode" - the kernel
1148170080dSnia 		 * always performs format conversions.
1158170080dSnia 		 */
1168170080dSnia 		INTARG = 1;
1178170080dSnia 		break;
1188170080dSnia 	case SNDCTL_DSP_POST:
1198170080dSnia 		/* This call is merely advisory, and may be a nop. */
1208170080dSnia 		break;
1218170080dSnia 	case SNDCTL_DSP_SPEED:
1228170080dSnia 		/*
1238170080dSnia 		 * In Solaris, 0 is used a special value to query the
1248170080dSnia 		 * current rate. This seems useful to support.
1258170080dSnia 		 */
1268170080dSnia 		if (INTARG == 0) {
1278170080dSnia 			retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
1288170080dSnia 			if (retval < 0)
1298170080dSnia 				return retval;
1308170080dSnia 			retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt);
1318170080dSnia 			if (retval < 0)
1328170080dSnia 				return retval;
1338170080dSnia 			INTARG = (tmpinfo.mode == AUMODE_RECORD) ?
1348170080dSnia 			    hwfmt.record.sample_rate :
1358170080dSnia 			    hwfmt.play.sample_rate;
1368170080dSnia 		}
1378170080dSnia 		/*
1388170080dSnia 		 * Conform to kernel limits.
1398170080dSnia 		 * NetBSD will reject unsupported sample rates, but OSS
1408170080dSnia 		 * applications need to be able to negotiate a supported one.
1418170080dSnia 		 */
1428170080dSnia 		if (INTARG < 1000)
1438170080dSnia 			INTARG = 1000;
1448170080dSnia 		if (INTARG > 192000)
1458170080dSnia 			INTARG = 192000;
1468170080dSnia 		AUDIO_INITINFO(&tmpinfo);
1478170080dSnia 		tmpinfo.play.sample_rate =
1488170080dSnia 		tmpinfo.record.sample_rate = INTARG;
1498170080dSnia 		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
1508170080dSnia 		if (retval < 0)
1518170080dSnia 			return retval;
1528170080dSnia 		/* FALLTHRU */
1538170080dSnia 	case SOUND_PCM_READ_RATE:
1548170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
1558170080dSnia 		if (retval < 0)
1568170080dSnia 			return retval;
1578170080dSnia 		INTARG = GETPRINFO(&tmpinfo, sample_rate);
1588170080dSnia 		break;
1598170080dSnia 	case SNDCTL_DSP_STEREO:
1608170080dSnia 		AUDIO_INITINFO(&tmpinfo);
1618170080dSnia 		tmpinfo.play.channels =
1628170080dSnia 		tmpinfo.record.channels = INTARG ? 2 : 1;
1638170080dSnia 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
1648170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
1658170080dSnia 		if (retval < 0)
1668170080dSnia 			return retval;
1678170080dSnia 		INTARG = GETPRINFO(&tmpinfo, channels) - 1;
1688170080dSnia 		break;
1698170080dSnia 	case SNDCTL_DSP_GETBLKSIZE:
1708170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
1718170080dSnia 		if (retval < 0)
1728170080dSnia 			return retval;
1738170080dSnia 		INTARG = tmpinfo.blocksize;
1748170080dSnia 		break;
1758170080dSnia 	case SNDCTL_DSP_SETFMT:
1768170080dSnia 		AUDIO_INITINFO(&tmpinfo);
177*200ab436Snia 		retval = format_to_encoding(INTARG, &tmpinfo);
178*200ab436Snia 		if (retval < 0) {
1798170080dSnia 			/*
1808170080dSnia 			 * OSSv4 specifies that if an invalid format is chosen
1818170080dSnia 			 * by an application then a sensible format supported
1828170080dSnia 			 * by the hardware is returned.
1838170080dSnia 			 *
1848170080dSnia 			 * In this case, we pick the current hardware format.
1858170080dSnia 			 */
1868170080dSnia 			retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt);
1878170080dSnia 			if (retval < 0)
1888170080dSnia 				return retval;
1898170080dSnia 			retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
1908170080dSnia 			if (retval < 0)
1918170080dSnia 				return retval;
1928170080dSnia 			tmpinfo.play.encoding =
1938170080dSnia 			tmpinfo.record.encoding =
1948170080dSnia 			    (tmpinfo.mode == AUMODE_RECORD) ?
1958170080dSnia 			    hwfmt.record.encoding : hwfmt.play.encoding;
1968170080dSnia 			tmpinfo.play.precision =
1978170080dSnia 			tmpinfo.record.precision =
1988170080dSnia 			    (tmpinfo.mode == AUMODE_RECORD) ?
1998170080dSnia 			    hwfmt.record.precision : hwfmt.play.precision ;
2008170080dSnia 		}
2018170080dSnia 		/*
2028170080dSnia 		 * In the post-kernel-mixer world, assume that any error means
2038170080dSnia 		 * it's fatal rather than an unsupported format being selected.
2048170080dSnia 		 */
2058170080dSnia 		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
2068170080dSnia 		if (retval < 0)
2078170080dSnia 			return retval;
2088170080dSnia 		/* FALLTHRU */
2098170080dSnia 	case SOUND_PCM_READ_BITS:
2108170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
2118170080dSnia 		if (retval < 0)
2128170080dSnia 			return retval;
213*200ab436Snia 		if (tmpinfo.mode == AUMODE_RECORD)
214*200ab436Snia 			retval = encoding_to_format(tmpinfo.record.encoding,
215*200ab436Snia 			    tmpinfo.record.precision);
2168170080dSnia 		else
217*200ab436Snia 			retval = encoding_to_format(tmpinfo.play.encoding,
218*200ab436Snia 			    tmpinfo.play.precision);
219*200ab436Snia 		if (retval < 0) {
220*200ab436Snia 			errno = EINVAL;
221*200ab436Snia 			return retval;
2228170080dSnia 		}
223*200ab436Snia 		INTARG = retval;
2248170080dSnia 		break;
2258170080dSnia 	case SNDCTL_DSP_CHANNELS:
2268170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
2278170080dSnia 		if (retval < 0)
2288170080dSnia 			return retval;
2298170080dSnia 		set_channels(fd, tmpinfo.mode, INTARG);
2308170080dSnia 		/* FALLTHRU */
2318170080dSnia 	case SOUND_PCM_READ_CHANNELS:
2328170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
2338170080dSnia 		if (retval < 0)
2348170080dSnia 			return retval;
2358170080dSnia 		INTARG = GETPRINFO(&tmpinfo, channels);
2368170080dSnia 		break;
2378170080dSnia 	case SOUND_PCM_WRITE_FILTER:
2388170080dSnia 	case SOUND_PCM_READ_FILTER:
2398170080dSnia 		errno = EINVAL;
2408170080dSnia 		return -1; /* XXX unimplemented */
2418170080dSnia 	case SNDCTL_DSP_SUBDIVIDE:
2428170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
2438170080dSnia 		if (retval < 0)
2448170080dSnia 			return retval;
2458170080dSnia 		idat = INTARG;
2468170080dSnia 		if (idat == 0)
2478170080dSnia 			idat = tmpinfo.play.buffer_size / tmpinfo.blocksize;
2488170080dSnia 		idat = (tmpinfo.play.buffer_size / idat) & -4;
2498170080dSnia 		AUDIO_INITINFO(&tmpinfo);
2508170080dSnia 		tmpinfo.blocksize = idat;
2518170080dSnia 		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
2528170080dSnia 		if (retval < 0)
2538170080dSnia 			return retval;
2548170080dSnia 		INTARG = tmpinfo.play.buffer_size / tmpinfo.blocksize;
2558170080dSnia 		break;
2568170080dSnia 	case SNDCTL_DSP_SETFRAGMENT:
2578170080dSnia 		AUDIO_INITINFO(&tmpinfo);
2588170080dSnia 		idat = INTARG;
2598170080dSnia 		tmpinfo.blocksize = 1 << (idat & 0xffff);
2608170080dSnia 		tmpinfo.hiwat = ((unsigned)idat >> 16) & 0x7fff;
2618170080dSnia 		if (tmpinfo.hiwat == 0)	/* 0 means set to max */
2628170080dSnia 			tmpinfo.hiwat = 65536;
2638170080dSnia 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
2648170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
2658170080dSnia 		if (retval < 0)
2668170080dSnia 			return retval;
2678170080dSnia 		u = tmpinfo.blocksize;
2688170080dSnia 		for(idat = 0; u > 1; idat++, u >>= 1)
2698170080dSnia 			;
2708170080dSnia 		idat |= (tmpinfo.hiwat & 0x7fff) << 16;
2718170080dSnia 		INTARG = idat;
2728170080dSnia 		break;
2738170080dSnia 	case SNDCTL_DSP_GETFMTS:
2748170080dSnia 		for(idat = 0, tmpenc.index = 0;
2758170080dSnia 		    ioctl(fd, AUDIO_GETENC, &tmpenc) == 0;
2768170080dSnia 		    tmpenc.index++) {
277*200ab436Snia 			retval = encoding_to_format(tmpenc.encoding,
278*200ab436Snia 			    tmpenc.precision);
279*200ab436Snia 			if (retval != -1)
280*200ab436Snia 				idat |= retval;
2818170080dSnia 		}
2828170080dSnia 		INTARG = idat;
2838170080dSnia 		break;
2848170080dSnia 	case SNDCTL_DSP_GETOSPACE:
2858170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
2868170080dSnia 		if (retval < 0)
2878170080dSnia 			return retval;
2888170080dSnia 		bufinfo.fragsize = tmpinfo.blocksize;
2898170080dSnia 		bufinfo.fragments = tmpinfo.hiwat - (tmpinfo.play.seek
2908170080dSnia 		    + tmpinfo.blocksize - 1) / tmpinfo.blocksize;
2918170080dSnia 		bufinfo.fragstotal = tmpinfo.hiwat;
2928170080dSnia 		bufinfo.bytes = tmpinfo.hiwat * tmpinfo.blocksize
2938170080dSnia 		    - tmpinfo.play.seek;
2948170080dSnia 		*(struct audio_buf_info *)argp = bufinfo;
2958170080dSnia 		break;
2968170080dSnia 	case SNDCTL_DSP_GETISPACE:
2978170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
2988170080dSnia 		if (retval < 0)
2998170080dSnia 			return retval;
3008170080dSnia 		bufinfo.fragsize = tmpinfo.blocksize;
3018170080dSnia 		bufinfo.fragments = tmpinfo.record.seek / tmpinfo.blocksize;
3028170080dSnia 		bufinfo.fragstotal =
3038170080dSnia 		    tmpinfo.record.buffer_size / tmpinfo.blocksize;
3048170080dSnia 		bufinfo.bytes = tmpinfo.record.seek;
3058170080dSnia 		*(struct audio_buf_info *)argp = bufinfo;
3068170080dSnia 		break;
3078170080dSnia 	case SNDCTL_DSP_NONBLOCK:
3088170080dSnia 		idat = 1;
3098170080dSnia 		retval = ioctl(fd, FIONBIO, &idat);
3108170080dSnia 		if (retval < 0)
3118170080dSnia 			return retval;
3128170080dSnia 		break;
3138170080dSnia 	case SNDCTL_DSP_GETCAPS:
3148170080dSnia 		retval = _oss_get_caps(fd, (int *)argp);
3158170080dSnia 		if (retval < 0)
3168170080dSnia 			return retval;
3178170080dSnia 		break;
3188170080dSnia 	case SNDCTL_DSP_SETTRIGGER:
3198170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
3208170080dSnia 		if (retval < 0)
3218170080dSnia 			return retval;
3228170080dSnia 		AUDIO_INITINFO(&tmpinfo);
3238170080dSnia 		if (tmpinfo.mode & AUMODE_PLAY)
3248170080dSnia 			tmpinfo.play.pause = (INTARG & PCM_ENABLE_OUTPUT) == 0;
3258170080dSnia 		if (tmpinfo.mode & AUMODE_RECORD)
3268170080dSnia 			tmpinfo.record.pause = (INTARG & PCM_ENABLE_INPUT) == 0;
3278170080dSnia 		(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
3288170080dSnia 		/* FALLTHRU */
3298170080dSnia 	case SNDCTL_DSP_GETTRIGGER:
3308170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
3318170080dSnia 		if (retval < 0)
3328170080dSnia 			return retval;
3338170080dSnia 		idat = 0;
3348170080dSnia 		if ((tmpinfo.mode & AUMODE_PLAY) && !tmpinfo.play.pause)
3358170080dSnia 			idat |= PCM_ENABLE_OUTPUT;
3368170080dSnia 		if ((tmpinfo.mode & AUMODE_RECORD) && !tmpinfo.record.pause)
3378170080dSnia 			idat |= PCM_ENABLE_INPUT;
3388170080dSnia 		INTARG = idat;
3398170080dSnia 		break;
3408170080dSnia 	case SNDCTL_DSP_GETIPTR:
3418170080dSnia 		retval = ioctl(fd, AUDIO_GETIOFFS, &tmpoffs);
3428170080dSnia 		if (retval < 0)
3438170080dSnia 			return retval;
3448170080dSnia 		cntinfo.bytes = tmpoffs.samples;
3458170080dSnia 		cntinfo.blocks = tmpoffs.deltablks;
3468170080dSnia 		cntinfo.ptr = tmpoffs.offset;
3478170080dSnia 		*(struct count_info *)argp = cntinfo;
3488170080dSnia 		break;
3498170080dSnia 	case SNDCTL_DSP_CURRENT_IPTR:
3508170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
3518170080dSnia 		if (retval < 0)
3528170080dSnia 			return retval;
3538170080dSnia 		/* XXX: 'samples' may wrap */
3548170080dSnia 		memset(osscount.filler, 0, sizeof(osscount.filler));
3558170080dSnia 		osscount.samples = tmpinfo.record.samples /
3568170080dSnia 		    ((tmpinfo.record.precision / NBBY) *
3578170080dSnia 			tmpinfo.record.channels);
3588170080dSnia 		osscount.fifo_samples = tmpinfo.record.seek /
3598170080dSnia 		    ((tmpinfo.record.precision / NBBY) *
3608170080dSnia 			tmpinfo.record.channels);
3618170080dSnia 		*(oss_count_t *)argp = osscount;
3628170080dSnia 		break;
3638170080dSnia 	case SNDCTL_DSP_GETOPTR:
3648170080dSnia 		retval = ioctl(fd, AUDIO_GETOOFFS, &tmpoffs);
3658170080dSnia 		if (retval < 0)
3668170080dSnia 			return retval;
3678170080dSnia 		cntinfo.bytes = tmpoffs.samples;
3688170080dSnia 		cntinfo.blocks = tmpoffs.deltablks;
3698170080dSnia 		cntinfo.ptr = tmpoffs.offset;
3708170080dSnia 		*(struct count_info *)argp = cntinfo;
3718170080dSnia 		break;
3728170080dSnia 	case SNDCTL_DSP_CURRENT_OPTR:
3738170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
3748170080dSnia 		if (retval < 0)
3758170080dSnia 			return retval;
3768170080dSnia 		/* XXX: 'samples' may wrap */
3778170080dSnia 		memset(osscount.filler, 0, sizeof(osscount.filler));
3788170080dSnia 		osscount.samples = tmpinfo.play.samples /
3798170080dSnia 		    ((tmpinfo.play.precision / NBBY) *
3808170080dSnia 			tmpinfo.play.channels);
3818170080dSnia 		osscount.fifo_samples = tmpinfo.play.seek /
3828170080dSnia 		    ((tmpinfo.play.precision / NBBY) *
3838170080dSnia 			tmpinfo.play.channels);
3848170080dSnia 		*(oss_count_t *)argp = osscount;
3858170080dSnia 		break;
3868170080dSnia 	case SNDCTL_DSP_SETPLAYVOL:
3878170080dSnia 		set_vol(fd, INTARG, false);
3888170080dSnia 		/* FALLTHRU */
3898170080dSnia 	case SNDCTL_DSP_GETPLAYVOL:
3908170080dSnia 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
3918170080dSnia 		if (retval < 0)
3928170080dSnia 			return retval;
3938170080dSnia 		INTARG = get_vol(tmpinfo.play.gain, tmpinfo.play.balance);
3948170080dSnia 		break;
3958170080dSnia 	case SNDCTL_DSP_SETRECVOL:
3968170080dSnia 		set_vol(fd, INTARG, true);
3978170080dSnia 		/* FALLTHRU */
3988170080dSnia 	case SNDCTL_DSP_GETRECVOL:
3998170080dSnia 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
4008170080dSnia 		if (retval < 0)
4018170080dSnia 			return retval;
4028170080dSnia 		INTARG = get_vol(tmpinfo.record.gain, tmpinfo.record.balance);
4038170080dSnia 		break;
4048170080dSnia 	case SNDCTL_DSP_SKIP:
4058170080dSnia 	case SNDCTL_DSP_SILENCE:
4068170080dSnia 		errno = EINVAL;
4078170080dSnia 		return -1;
4088170080dSnia 	case SNDCTL_DSP_SETDUPLEX:
4098170080dSnia 		idat = 1;
4108170080dSnia 		retval = ioctl(fd, AUDIO_SETFD, &idat);
4118170080dSnia 		if (retval < 0)
4128170080dSnia 			return retval;
4138170080dSnia 		break;
4148170080dSnia 	case SNDCTL_DSP_GETODELAY:
4158170080dSnia 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
4168170080dSnia 		if (retval < 0)
4178170080dSnia 			return retval;
4188170080dSnia 		idat = tmpinfo.play.seek + tmpinfo.blocksize / 2;
4198170080dSnia 		INTARG = idat;
4208170080dSnia 		break;
4218170080dSnia 	case SNDCTL_DSP_PROFILE:
4228170080dSnia 		/* This gives just a hint to the driver,
4238170080dSnia 		 * implementing it as a NOP is ok
4248170080dSnia 		 */
4258170080dSnia 		break;
4268170080dSnia 	case SNDCTL_DSP_MAPINBUF:
4278170080dSnia 	case SNDCTL_DSP_MAPOUTBUF:
4288170080dSnia 	case SNDCTL_DSP_SETSYNCRO:
4298170080dSnia 		errno = EINVAL;
4308170080dSnia 		return -1; /* XXX unimplemented */
4318170080dSnia 	case SNDCTL_DSP_GET_PLAYTGT_NAMES:
4328170080dSnia 	case SNDCTL_DSP_GET_RECSRC_NAMES:
4338170080dSnia 		ei = (oss_mixer_enuminfo *)argp;
4348170080dSnia 		ei->nvalues = 1;
4358170080dSnia 		ei->version = 0;
4368170080dSnia 		ei->strindex[0] = 0;
4378170080dSnia 		strlcpy(ei->strings, "primary", OSS_ENUM_STRINGSIZE);
4388170080dSnia 		break;
4398170080dSnia 	case SNDCTL_DSP_SET_PLAYTGT:
4408170080dSnia 	case SNDCTL_DSP_SET_RECSRC:
4418170080dSnia 	case SNDCTL_DSP_GET_PLAYTGT:
4428170080dSnia 	case SNDCTL_DSP_GET_RECSRC:
4438170080dSnia 		/* We have one recording source and play target. */
4448170080dSnia 		INTARG = 0;
4458170080dSnia 		break;
4468170080dSnia 	default:
4478170080dSnia 		errno = EINVAL;
4488170080dSnia 		return -1;
4498170080dSnia 	}
4508170080dSnia 
4518170080dSnia 	return 0;
4528170080dSnia }
4538170080dSnia 
4548170080dSnia static int
get_vol(u_int gain,u_char balance)4558170080dSnia get_vol(u_int gain, u_char balance)
4568170080dSnia {
4578170080dSnia 	u_int l, r;
4588170080dSnia 
4598170080dSnia 	if (balance == AUDIO_MID_BALANCE) {
4608170080dSnia 		l = r = gain;
4618170080dSnia 	} else if (balance < AUDIO_MID_BALANCE) {
4628170080dSnia 		l = gain;
4638170080dSnia 		r = (balance * gain) / AUDIO_MID_BALANCE;
4648170080dSnia 	} else {
4658170080dSnia 		r = gain;
4668170080dSnia 		l = ((AUDIO_RIGHT_BALANCE - balance) * gain)
4678170080dSnia 		    / AUDIO_MID_BALANCE;
4688170080dSnia 	}
4698170080dSnia 
4708170080dSnia 	return TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
4718170080dSnia }
4728170080dSnia 
4738170080dSnia static void
set_vol(int fd,int volume,bool record)4748170080dSnia set_vol(int fd, int volume, bool record)
4758170080dSnia {
4768170080dSnia 	u_int lgain, rgain;
4778170080dSnia 	struct audio_info tmpinfo;
4788170080dSnia 	struct audio_prinfo *prinfo;
4798170080dSnia 
4808170080dSnia 	AUDIO_INITINFO(&tmpinfo);
4818170080dSnia 	prinfo = record ? &tmpinfo.record : &tmpinfo.play;
4828170080dSnia 
4838170080dSnia 	lgain = FROM_OSSVOL((volume >> 0) & 0xff);
4848170080dSnia 	rgain = FROM_OSSVOL((volume >> 8) & 0xff);
4858170080dSnia 
4868170080dSnia 	if (lgain == rgain) {
4878170080dSnia 		prinfo->gain = lgain;
4888170080dSnia 		prinfo->balance = AUDIO_MID_BALANCE;
4898170080dSnia 	} else if (lgain < rgain) {
4908170080dSnia 		prinfo->gain = rgain;
4918170080dSnia 		prinfo->balance = AUDIO_RIGHT_BALANCE -
4928170080dSnia 		    (AUDIO_MID_BALANCE * lgain) / rgain;
4938170080dSnia 	} else {
4948170080dSnia 		prinfo->gain = lgain;
4958170080dSnia 		prinfo->balance = (AUDIO_MID_BALANCE * rgain) / lgain;
4968170080dSnia 	}
4978170080dSnia 
4988170080dSnia 	(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
4998170080dSnia }
5008170080dSnia 
5018170080dSnia /*
5028170080dSnia  * When AUDIO_SETINFO fails to set a channel count, the application's chosen
5038170080dSnia  * number is out of range of what the kernel allows.
5048170080dSnia  *
5058170080dSnia  * When this happens, we use the current hardware settings. This is just in
5068170080dSnia  * case an application is abusing SNDCTL_DSP_CHANNELS - OSSv4 always sets and
5078170080dSnia  * returns a reasonable value, even if it wasn't what the user requested.
5088170080dSnia  *
5098170080dSnia  * Solaris guarantees this behaviour if nchannels = 0.
5108170080dSnia  *
5118170080dSnia  * XXX: If a device is opened for both playback and recording, and supports
5128170080dSnia  * fewer channels for recording than playback, applications that do both will
5138170080dSnia  * behave very strangely. OSS doesn't allow for reporting separate channel
5148170080dSnia  * counts for recording and playback. This could be worked around by always
5158170080dSnia  * mixing recorded data up to the same number of channels as is being used
5168170080dSnia  * for playback.
5178170080dSnia  */
5188170080dSnia static void
set_channels(int fd,int mode,int nchannels)5198170080dSnia set_channels(int fd, int mode, int nchannels)
5208170080dSnia {
5218170080dSnia 	struct audio_info tmpinfo, hwfmt;
5228170080dSnia 
5238170080dSnia 	if (ioctl(fd, AUDIO_GETFORMAT, &hwfmt) < 0) {
5248170080dSnia 		errno = 0;
5258170080dSnia 		hwfmt.record.channels = hwfmt.play.channels = 2;
5268170080dSnia 	}
5278170080dSnia 
5288170080dSnia 	if (mode & AUMODE_PLAY) {
5298170080dSnia 		AUDIO_INITINFO(&tmpinfo);
5308170080dSnia 		tmpinfo.play.channels = nchannels;
5318170080dSnia 		if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) {
5328170080dSnia 			errno = 0;
5338170080dSnia 			AUDIO_INITINFO(&tmpinfo);
5348170080dSnia 			tmpinfo.play.channels = hwfmt.play.channels;
5358170080dSnia 			(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
5368170080dSnia 		}
5378170080dSnia 	}
5388170080dSnia 
5398170080dSnia 	if (mode & AUMODE_RECORD) {
5408170080dSnia 		AUDIO_INITINFO(&tmpinfo);
5418170080dSnia 		tmpinfo.record.channels = nchannels;
5428170080dSnia 		if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) {
5438170080dSnia 			errno = 0;
5448170080dSnia 			AUDIO_INITINFO(&tmpinfo);
5458170080dSnia 			tmpinfo.record.channels = hwfmt.record.channels;
5468170080dSnia 			(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
5478170080dSnia 		}
5488170080dSnia 	}
5498170080dSnia }
550*200ab436Snia 
551*200ab436Snia /* Convert a NetBSD "encoding" to a OSS "format". */
552*200ab436Snia static int
encoding_to_format(u_int encoding,u_int precision)553*200ab436Snia encoding_to_format(u_int encoding, u_int precision)
554*200ab436Snia {
555*200ab436Snia 	switch(encoding) {
556*200ab436Snia 	case AUDIO_ENCODING_ULAW:
557*200ab436Snia 		return AFMT_MU_LAW;
558*200ab436Snia 	case AUDIO_ENCODING_ALAW:
559*200ab436Snia 		return AFMT_A_LAW;
560*200ab436Snia 	case AUDIO_ENCODING_SLINEAR:
561*200ab436Snia 		if (precision == 32)
562*200ab436Snia 			return AFMT_S32_NE;
563*200ab436Snia 		else if (precision == 24)
564*200ab436Snia 			return AFMT_S24_NE;
565*200ab436Snia 		else if (precision == 16)
566*200ab436Snia 			return AFMT_S16_NE;
567*200ab436Snia 		return AFMT_S8;
568*200ab436Snia 	case AUDIO_ENCODING_SLINEAR_LE:
569*200ab436Snia 		if (precision == 32)
570*200ab436Snia 			return AFMT_S32_LE;
571*200ab436Snia 		else if (precision == 24)
572*200ab436Snia 			return AFMT_S24_LE;
573*200ab436Snia 		else if (precision == 16)
574*200ab436Snia 			return AFMT_S16_LE;
575*200ab436Snia 		return AFMT_S8;
576*200ab436Snia 	case AUDIO_ENCODING_SLINEAR_BE:
577*200ab436Snia 		if (precision == 32)
578*200ab436Snia 			return AFMT_S32_BE;
579*200ab436Snia 		else if (precision == 24)
580*200ab436Snia 			return AFMT_S24_BE;
581*200ab436Snia 		else if (precision == 16)
582*200ab436Snia 			return AFMT_S16_BE;
583*200ab436Snia 		return AFMT_S8;
584*200ab436Snia 	case AUDIO_ENCODING_ULINEAR:
585*200ab436Snia 		if (precision == 16)
586*200ab436Snia 			return AFMT_U16_NE;
587*200ab436Snia 		return AFMT_U8;
588*200ab436Snia 	case AUDIO_ENCODING_ULINEAR_LE:
589*200ab436Snia 		if (precision == 16)
590*200ab436Snia 			return AFMT_U16_LE;
591*200ab436Snia 		return AFMT_U8;
592*200ab436Snia 	case AUDIO_ENCODING_ULINEAR_BE:
593*200ab436Snia 		if (precision == 16)
594*200ab436Snia 			return AFMT_U16_BE;
595*200ab436Snia 		return AFMT_U8;
596*200ab436Snia 	case AUDIO_ENCODING_ADPCM:
597*200ab436Snia 		return AFMT_IMA_ADPCM;
598*200ab436Snia 	case AUDIO_ENCODING_AC3:
599*200ab436Snia 		return AFMT_AC3;
600*200ab436Snia 	}
601*200ab436Snia 	return -1;
602*200ab436Snia }
603*200ab436Snia 
604*200ab436Snia /* Convert an OSS "format" to a NetBSD "encoding". */
605*200ab436Snia static int
format_to_encoding(int fmt,struct audio_info * tmpinfo)606*200ab436Snia format_to_encoding(int fmt, struct audio_info *tmpinfo)
607*200ab436Snia {
608*200ab436Snia 	switch (fmt) {
609*200ab436Snia 	case AFMT_MU_LAW:
610*200ab436Snia 		tmpinfo->record.precision =
611*200ab436Snia 		tmpinfo->play.precision = 8;
612*200ab436Snia 		tmpinfo->record.encoding =
613*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_ULAW;
614*200ab436Snia 		return 0;
615*200ab436Snia 	case AFMT_A_LAW:
616*200ab436Snia 		tmpinfo->record.precision =
617*200ab436Snia 		tmpinfo->play.precision = 8;
618*200ab436Snia 		tmpinfo->record.encoding =
619*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_ALAW;
620*200ab436Snia 		return 0;
621*200ab436Snia 	case AFMT_U8:
622*200ab436Snia 		tmpinfo->record.precision =
623*200ab436Snia 		tmpinfo->play.precision = 8;
624*200ab436Snia 		tmpinfo->record.encoding =
625*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_ULINEAR;
626*200ab436Snia 		return 0;
627*200ab436Snia 	case AFMT_S8:
628*200ab436Snia 		tmpinfo->record.precision =
629*200ab436Snia 		tmpinfo->play.precision = 8;
630*200ab436Snia 		tmpinfo->record.encoding =
631*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_SLINEAR;
632*200ab436Snia 		return 0;
633*200ab436Snia 	case AFMT_S16_LE:
634*200ab436Snia 		tmpinfo->record.precision =
635*200ab436Snia 		tmpinfo->play.precision = 16;
636*200ab436Snia 		tmpinfo->record.encoding =
637*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_SLINEAR_LE;
638*200ab436Snia 		return 0;
639*200ab436Snia 	case AFMT_S16_BE:
640*200ab436Snia 		tmpinfo->record.precision =
641*200ab436Snia 		tmpinfo->play.precision = 16;
642*200ab436Snia 		tmpinfo->record.encoding =
643*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_SLINEAR_BE;
644*200ab436Snia 		return 0;
645*200ab436Snia 	case AFMT_U16_LE:
646*200ab436Snia 		tmpinfo->record.precision =
647*200ab436Snia 		tmpinfo->play.precision = 16;
648*200ab436Snia 		tmpinfo->record.encoding =
649*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_ULINEAR_LE;
650*200ab436Snia 		return 0;
651*200ab436Snia 	case AFMT_U16_BE:
652*200ab436Snia 		tmpinfo->record.precision =
653*200ab436Snia 		tmpinfo->play.precision = 16;
654*200ab436Snia 		tmpinfo->record.encoding =
655*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_ULINEAR_BE;
656*200ab436Snia 		return 0;
657*200ab436Snia 	/*
658*200ab436Snia 	 * XXX: When the kernel supports 24-bit LPCM by default,
659*200ab436Snia 	 * the 24-bit formats should be handled properly instead
660*200ab436Snia 	 * of falling back to 32 bits.
661*200ab436Snia 	 */
662*200ab436Snia 	case AFMT_S24_PACKED:
663*200ab436Snia 	case AFMT_S24_LE:
664*200ab436Snia 	case AFMT_S32_LE:
665*200ab436Snia 		tmpinfo->record.precision =
666*200ab436Snia 		tmpinfo->play.precision = 32;
667*200ab436Snia 		tmpinfo->record.encoding =
668*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_SLINEAR_LE;
669*200ab436Snia 		return 0;
670*200ab436Snia 	case AFMT_S24_BE:
671*200ab436Snia 	case AFMT_S32_BE:
672*200ab436Snia 		tmpinfo->record.precision =
673*200ab436Snia 		tmpinfo->play.precision = 32;
674*200ab436Snia 		tmpinfo->record.encoding =
675*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_SLINEAR_BE;
676*200ab436Snia 		return 0;
677*200ab436Snia 	case AFMT_AC3:
678*200ab436Snia 		tmpinfo->record.precision =
679*200ab436Snia 		tmpinfo->play.precision = 16;
680*200ab436Snia 		tmpinfo->record.encoding =
681*200ab436Snia 		tmpinfo->play.encoding = AUDIO_ENCODING_AC3;
682*200ab436Snia 		return 0;
683*200ab436Snia 	}
684*200ab436Snia 	return -1;
685*200ab436Snia }
686