Module Name:    src
Committed By:   martin
Date:           Mon Apr 27 14:32:34 UTC 2020

Modified Files:
        src/lib/libossaudio [netbsd-9]: ossaudio.c
        src/sys/compat/ossaudio [netbsd-9]: ossaudio.c

Log Message:
Pull up following revision(s) (requested by nia in ticket #855):

        lib/libossaudio/ossaudio.c: revision 1.41
        lib/libossaudio/ossaudio.c: revision 1.42
        lib/libossaudio/ossaudio.c: revision 1.43
        sys/compat/ossaudio/ossaudio.c: revision 1.80
        sys/compat/ossaudio/ossaudio.c: revision 1.81
        sys/compat/ossaudio/ossaudio.c: revision 1.82
        lib/libossaudio/ossaudio.c: revision 1.39
        sys/compat/ossaudio/ossaudio.c: revision 1.79
        lib/libossaudio/ossaudio.c: revision 1.40

ossaudio: Make SNDCTL_DSP_SPEED more robust when using invalid rates.

>From the perspective of reading the OSSv4 specification, NetBSD's
behaviour when an invalid sample rate is set makes no sense at all:
AUDIO_SETINFO simply returns an error code, and then we immediately
fall through to getting the sample rate, which is still set to the
legacy default of 8000 Hz.

Instead, what OSS applications generally expect is that they will be
able to receive the actual hardware sample rate. This is very, very
unlikely to be 8000 Hz on a modern machine.

No functional change when setting a sample rate between the supported
rates of 1000 and 192000 Hz. When a rate outside this range is requested,
the hardware rate is returned (on modern hardware, generally always 48000
Hz or a multiple of 48000 Hz).

ossaudio: Make SNDCTL_DSP_SETFMT conform with OSSv4.

The OSSv4 spec says we shouldn't really error if an invalid format is
chosen by an application. Things are especially likely to be confused
if we return MULAW, since in OSSv4 terms that means that's the native
hardware format. Instead, set and return the current hardware format
if an invalid format is chosen.

For the 24-bit sample formats, note that the NetBSD kernel currently
can't handle them in its default configuration, and will return an error
code if you attempt to use them. So, if an applicaton requests 24-bit PCM,
promote it to 32-bit PCM. According to the spec, this is valid and
applications should be checking the return value anyway.

In the Linux compat layer, we just use S16LE as a fallback. The OSSv3
headers that are still being shipped with Linux don't contain definitions
for fancier formats and we can reasonably expect all applications to
support S16LE.

ossaudio: If the user's channel count is rejected, use the hardware count

ossaudio: Make SNDCTL_DSP_[GET|SET][PLAY|RECORD]VOL closer to OSSv4

Problems in the previous code include returning values in the 0-255
range NetBSD uses instead of the 0-100 range OSSv4 expects, using
AUDIO_GETBUFINFO (which doesn't even return the mixer bits), and
not encoding channels as specified: "level=(left)|(right << 8)".

In reality, setting the gain in this way (through /dev/audio rather
than /dev/mixer) doesn't seem to work properly, and the mixer-set
value seems to be retained.

However, these changes at least ensure that the return values are
correct and the balance is set correctly.

I've only found one application using this API (audio/audacious), and
OSSv4 support in it is currently disabled precisely because it breaks
when it attempts to set the track volume using it.

ossaudio: Implement SNDCTL_DSP_(SET|GET)TRIGGER.


To generate a diff of this commit:
cvs rdiff -u -r1.36.2.1 -r1.36.2.2 src/lib/libossaudio/ossaudio.c
cvs rdiff -u -r1.74.4.3 -r1.74.4.4 src/sys/compat/ossaudio/ossaudio.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libossaudio/ossaudio.c
diff -u src/lib/libossaudio/ossaudio.c:1.36.2.1 src/lib/libossaudio/ossaudio.c:1.36.2.2
--- src/lib/libossaudio/ossaudio.c:1.36.2.1	Tue Nov 19 11:01:27 2019
+++ src/lib/libossaudio/ossaudio.c	Mon Apr 27 14:32:34 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: ossaudio.c,v 1.36.2.1 2019/11/19 11:01:27 martin Exp $	*/
+/*	$NetBSD: ossaudio.c,v 1.36.2.2 2020/04/27 14:32:34 martin Exp $	*/
 
 /*-
  * Copyright (c) 1997 The NetBSD Foundation, Inc.
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: ossaudio.c,v 1.36.2.1 2019/11/19 11:01:27 martin Exp $");
+__RCSID("$NetBSD: ossaudio.c,v 1.36.2.2 2020/04/27 14:32:34 martin Exp $");
 
 /*
  * This is an OSS (Linux) sound API emulator.
@@ -48,6 +48,7 @@ __RCSID("$NetBSD: ossaudio.c,v 1.36.2.1 
 #include <stdio.h>
 #include <unistd.h>
 #include <stdarg.h>
+#include <stdbool.h>
 
 #include "soundcard.h"
 #undef ioctl
@@ -63,6 +64,10 @@ __RCSID("$NetBSD: ossaudio.c,v 1.36.2.1 
 
 static struct audiodevinfo *getdevinfo(int);
 
+static int getvol(u_int, u_char);
+static void setvol(int, int, bool);
+
+static void setchannels(int, int, int);
 static void setblocksize(int, struct audio_info *);
 
 static int audio_ioctl(int, unsigned long, void *);
@@ -95,7 +100,7 @@ static int
 audio_ioctl(int fd, unsigned long com, void *argp)
 {
 
-	struct audio_info tmpinfo;
+	struct audio_info tmpinfo, hwfmt;
 	struct audio_offset tmpoffs;
 	struct audio_buf_info bufinfo;
 	struct count_info cntinfo;
@@ -134,7 +139,35 @@ audio_ioctl(int fd, unsigned long com, v
 		AUDIO_INITINFO(&tmpinfo);
 		tmpinfo.play.sample_rate =
 		tmpinfo.record.sample_rate = INTARG;
-		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		/*
+		 * The default NetBSD behavior if an unsupported sample rate
+		 * is set is to return an error code and keep the rate at the
+		 * default of 8000 Hz.
+		 * 
+		 * However, OSS specifies that a sample rate supported by the
+		 * hardware is returned if the exact rate could not be set.
+		 * 
+		 * So, if the chosen sample rate is invalid, set and return
+		 * the current hardware rate.
+		 */
+		if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) {
+			/* Don't care that SETINFO failed the first time... */
+			errno = 0;
+			retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt);
+			if (retval < 0)
+				return retval;
+			retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
+			if (retval < 0)
+				return retval;
+			tmpinfo.play.sample_rate =
+			tmpinfo.record.sample_rate =
+			    (tmpinfo.mode == AUMODE_RECORD) ?
+			    hwfmt.record.sample_rate :
+			    hwfmt.play.sample_rate;
+			retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+			if (retval < 0)
+				return retval;
+		}
 		/* FALLTHRU */
 	case SOUND_PCM_READ_RATE:
 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
@@ -210,24 +243,19 @@ audio_ioctl(int fd, unsigned long com, v
 			tmpinfo.play.encoding =
 			tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR_BE;
 			break;
+		/*
+		 * XXX: When the kernel supports 24-bit LPCM by default,
+		 * the 24-bit formats should be handled properly instead
+		 * of falling back to 32 bits.
+		 */
 		case AFMT_S24_LE:
-			tmpinfo.play.precision =
-			tmpinfo.record.precision = 24;
-			tmpinfo.play.encoding =
-			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
-			break;
-		case AFMT_S24_BE:
-			tmpinfo.play.precision =
-			tmpinfo.record.precision = 24;
-			tmpinfo.play.encoding =
-			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_BE;
-			break;
 		case AFMT_S32_LE:
 			tmpinfo.play.precision =
 			tmpinfo.record.precision = 32;
 			tmpinfo.play.encoding =
 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
 			break;
+		case AFMT_S24_BE:
 		case AFMT_S32_BE:
 			tmpinfo.play.precision =
 			tmpinfo.record.precision = 32;
@@ -241,9 +269,36 @@ audio_ioctl(int fd, unsigned long com, v
 			tmpinfo.record.encoding = AUDIO_ENCODING_AC3;
 			break;
 		default:
-			return EINVAL;
+			/*
+			 * OSSv4 specifies that if an invalid format is chosen
+			 * by an application then a sensible format supported
+			 * by the hardware is returned.
+			 *
+			 * In this case, we pick the current hardware format.
+			 */
+			retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt);
+			if (retval < 0)
+				return retval;
+			retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
+			if (retval < 0)
+				return retval;
+			tmpinfo.play.encoding =
+			tmpinfo.record.encoding =
+			    (tmpinfo.mode == AUMODE_RECORD) ?
+			    hwfmt.record.encoding : hwfmt.play.encoding;
+			tmpinfo.play.precision =
+			tmpinfo.record.precision =
+			    (tmpinfo.mode == AUMODE_RECORD) ?
+			    hwfmt.record.precision : hwfmt.play.precision ;
+			break;
 		}
-		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		/*
+		 * In the post-kernel-mixer world, assume that any error means
+		 * it's fatal rather than an unsupported format being selected.
+		 */
+		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		if (retval < 0)
+			return retval;
 		/* FALLTHRU */
 	case SOUND_PCM_READ_BITS:
 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
@@ -300,12 +355,10 @@ audio_ioctl(int fd, unsigned long com, v
 		INTARG = idat;
 		break;
 	case SNDCTL_DSP_CHANNELS:
-		AUDIO_INITINFO(&tmpinfo);
-		tmpinfo.play.channels = INTARG;
-		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
-		AUDIO_INITINFO(&tmpinfo);
-		tmpinfo.record.channels = INTARG;
-		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
+		if (retval < 0)
+			return retval;
+		setchannels(fd, tmpinfo.mode, INTARG);
 		/* FALLTHRU */
 	case SOUND_PCM_READ_CHANNELS:
 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
@@ -448,43 +501,35 @@ audio_ioctl(int fd, unsigned long com, v
 		retval = ioctl(fd, AUDIO_GETPROPS, &idata);
 		if (retval < 0)
 			return retval;
-		idat = DSP_CAP_TRIGGER; /* pretend we have trigger */
+		idat = DSP_CAP_TRIGGER;
 		if (idata & AUDIO_PROP_FULLDUPLEX)
 			idat |= DSP_CAP_DUPLEX;
 		if (idata & AUDIO_PROP_MMAP)
 			idat |= DSP_CAP_MMAP;
 		INTARG = idat;
 		break;
-#if 0
-	case SNDCTL_DSP_GETTRIGGER:
+	case SNDCTL_DSP_SETTRIGGER:
 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
 		if (retval < 0)
 			return retval;
-		idat = (tmpinfo.play.pause ? 0 : PCM_ENABLE_OUTPUT) |
-		       (tmpinfo.record.pause ? 0 : PCM_ENABLE_INPUT);
-		retval = copyout(&idat, SCARG(uap, data), sizeof idat);
-		if (retval < 0)
-			return retval;
-		break;
-	case SNDCTL_DSP_SETTRIGGER:
 		AUDIO_INITINFO(&tmpinfo);
-		retval = copyin(SCARG(uap, data), &idat, sizeof idat);
-		if (retval < 0)
-			return retval;
-		tmpinfo.play.pause = (idat & PCM_ENABLE_OUTPUT) == 0;
-		tmpinfo.record.pause = (idat & PCM_ENABLE_INPUT) == 0;
-		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
-		retval = copyout(&idat, SCARG(uap, data), sizeof idat);
+		if (tmpinfo.mode & AUMODE_PLAY)
+			tmpinfo.play.pause = (INTARG & PCM_ENABLE_OUTPUT) == 0;
+		if (tmpinfo.mode & AUMODE_RECORD)
+			tmpinfo.record.pause = (INTARG & PCM_ENABLE_INPUT) == 0;
+		(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		/* FALLTHRU */
+	case SNDCTL_DSP_GETTRIGGER:
+		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
 		if (retval < 0)
 			return retval;
+		idat = 0;
+		if ((tmpinfo.mode & AUMODE_PLAY) && !tmpinfo.play.pause)
+			idat |= PCM_ENABLE_OUTPUT;
+		if ((tmpinfo.mode & AUMODE_RECORD) && !tmpinfo.record.pause)
+			idat |= PCM_ENABLE_INPUT;
+		INTARG = idat;
 		break;
-#else
-	case SNDCTL_DSP_GETTRIGGER:
-	case SNDCTL_DSP_SETTRIGGER:
-		/* XXX Do nothing for now. */
-		INTARG = PCM_ENABLE_OUTPUT;
-		break;
-#endif
 	case SNDCTL_DSP_GETIPTR:
 		retval = ioctl(fd, AUDIO_GETIOFFS, &tmpoffs);
 		if (retval < 0)
@@ -586,41 +631,23 @@ audio_ioctl(int fd, unsigned long com, v
 		tmpaudioinfo->next_rec_engine = 0;
 		argp = tmpaudioinfo;
 		break;
-	case SNDCTL_DSP_GETPLAYVOL:
-		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
-		if (retval < 0)
-			return retval;
-		*(uint *)argp = tmpinfo.play.gain;
-		break;
 	case SNDCTL_DSP_SETPLAYVOL:
-		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
-		if (retval < 0)
-			return retval;
-		if (*(uint *)argp > 255)
-			tmpinfo.play.gain = 255;
-		else
-			tmpinfo.play.gain = *(uint *)argp;
-		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
-		if (retval < 0)
-			return retval;
-		break;
-	case SNDCTL_DSP_GETRECVOL:
-		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
+		setvol(fd, INTARG, false);
+		/* FALLTHRU */
+	case SNDCTL_DSP_GETPLAYVOL:
+		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
 		if (retval < 0)
 			return retval;
-		*(uint *)argp = tmpinfo.record.gain;
+		INTARG = getvol(tmpinfo.play.gain, tmpinfo.play.balance);
 		break;
 	case SNDCTL_DSP_SETRECVOL:
-		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
-		if (retval < 0)
-			return retval;
-		if (*(uint *)argp > 255)
-			tmpinfo.record.gain = 255;
-		else
-			tmpinfo.record.gain = *(uint *)argp;
-		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		setvol(fd, INTARG, true);
+		/* FALLTHRU */
+	case SNDCTL_DSP_GETRECVOL:
+		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
 		if (retval < 0)
 			return retval;
+		INTARG = getvol(tmpinfo.record.gain, tmpinfo.record.balance);
 		break;
 	case SNDCTL_DSP_SKIP:
 	case SNDCTL_DSP_SILENCE:
@@ -998,6 +1025,101 @@ mixer_ioctl(int fd, unsigned long com, v
 	return 0;
 }
 
+static int
+getvol(u_int gain, u_char balance)
+{
+	u_int l, r;
+
+	if (balance == AUDIO_MID_BALANCE) {
+		l = r = gain;
+	} else if (balance < AUDIO_MID_BALANCE) {
+		l = gain;
+		r = (balance * gain) / AUDIO_MID_BALANCE;
+	} else {
+		r = gain;
+		l = ((AUDIO_RIGHT_BALANCE - balance) * gain)
+		    / AUDIO_MID_BALANCE;
+	}
+
+	return TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
+}
+
+static void
+setvol(int fd, int volume, bool record)
+{
+	u_int lgain, rgain;
+	struct audio_info tmpinfo;
+	struct audio_prinfo *prinfo;
+
+	AUDIO_INITINFO(&tmpinfo);
+	prinfo = record ? &tmpinfo.record : &tmpinfo.play;
+
+	lgain = FROM_OSSVOL((volume >> 0) & 0xff);
+	rgain = FROM_OSSVOL((volume >> 8) & 0xff);
+
+	if (lgain == rgain) {
+		prinfo->gain = lgain;
+		prinfo->balance = AUDIO_MID_BALANCE;
+	} else if (lgain < rgain) {
+		prinfo->gain = rgain;
+		prinfo->balance = AUDIO_RIGHT_BALANCE -
+		    (AUDIO_MID_BALANCE * lgain) / rgain;
+	} else {
+		prinfo->gain = lgain;
+		prinfo->balance = (AUDIO_MID_BALANCE * rgain) / lgain;
+	}
+
+	(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+}
+
+/*
+ * When AUDIO_SETINFO fails to set a channel count, the application's chosen
+ * number is out of range of what the kernel allows.
+ *
+ * When this happens, we use the current hardware settings. This is just in
+ * case an application is abusing SNDCTL_DSP_CHANNELS - OSSv4 always sets and
+ * returns a reasonable value, even if it wasn't what the user requested.
+ *
+ * XXX: If a device is opened for both playback and recording, and supports
+ * fewer channels for recording than playback, applications that do both will
+ * behave very strangely. OSS doesn't allow for reporting separate channel
+ * counts for recording and playback. This could be worked around by always
+ * mixing recorded data up to the same number of channels as is being used
+ * for playback.
+ */
+static void
+setchannels(int fd, int mode, int nchannels)
+{
+	struct audio_info tmpinfo, hwfmt;
+
+	if (ioctl(fd, AUDIO_GETFORMAT, &hwfmt) < 0) {
+		errno = 0;
+		hwfmt.record.channels = hwfmt.play.channels = 2;
+	}
+
+	if (mode & AUMODE_PLAY) {
+		AUDIO_INITINFO(&tmpinfo);
+		tmpinfo.play.channels = nchannels;
+		if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) {
+			errno = 0;
+			AUDIO_INITINFO(&tmpinfo);
+			tmpinfo.play.channels = hwfmt.play.channels;
+			(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		}
+	}
+
+	if (mode & AUMODE_RECORD) {
+		AUDIO_INITINFO(&tmpinfo);
+		tmpinfo.record.channels = nchannels;
+		if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) {
+			errno = 0;
+			AUDIO_INITINFO(&tmpinfo);
+			tmpinfo.record.channels = hwfmt.record.channels;
+			(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
+		}
+	}
+}
+
 /*
  * Check that the blocksize is a power of 2 as OSS wants.
  * If not, set it to be.

Index: src/sys/compat/ossaudio/ossaudio.c
diff -u src/sys/compat/ossaudio/ossaudio.c:1.74.4.3 src/sys/compat/ossaudio/ossaudio.c:1.74.4.4
--- src/sys/compat/ossaudio/ossaudio.c:1.74.4.3	Wed Apr 22 18:12:26 2020
+++ src/sys/compat/ossaudio/ossaudio.c	Mon Apr 27 14:32:34 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: ossaudio.c,v 1.74.4.3 2020/04/22 18:12:26 martin Exp $	*/
+/*	$NetBSD: ossaudio.c,v 1.74.4.4 2020/04/27 14:32:34 martin Exp $	*/
 
 /*-
  * Copyright (c) 1997, 2008 The NetBSD Foundation, Inc.
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ossaudio.c,v 1.74.4.3 2020/04/22 18:12:26 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ossaudio.c,v 1.74.4.4 2020/04/27 14:32:34 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/proc.h>
@@ -68,6 +68,7 @@ static int opaque_to_enum(struct audiode
 static int enum_to_ord(struct audiodevinfo *di, int enm);
 static int enum_to_mask(struct audiodevinfo *di, int enm);
 
+static void setchannels(file_t *, int, int);
 static void setblocksize(file_t *, struct audio_info *);
 
 #ifdef AUDIO_DEBUG
@@ -175,7 +176,7 @@ oss_ioctl_audio(struct lwp *l, const str
 	} */
 	file_t *fp;
 	u_long com;
-	struct audio_info tmpinfo;
+	struct audio_info tmpinfo, hwfmt;
 	struct audio_offset tmpoffs;
 	struct oss_audio_buf_info bufinfo;
 	struct oss_count_info cntinfo;
@@ -230,11 +231,41 @@ oss_ioctl_audio(struct lwp *l, const str
 		tmpinfo.play.sample_rate =
 		tmpinfo.record.sample_rate = idat;
 		DPRINTF(("%s: SNDCTL_DSP_SPEED > %d\n", __func__, idat));
-		error = ioctlf(fp, AUDIO_SETINFO, &tmpinfo);
-		if (error) {
-			DPRINTF(("%s: SNDCTL_DSP_SPEED %d = %d\n",
-			     __func__, idat, error));
-			goto out;
+		/*
+		 * The default NetBSD behavior if an unsupported sample rate
+		 * is set is to return an error code and keep the rate at the
+		 * default of 8000 Hz.
+		 *
+		 * However, the OSS expectation is a sample rate supported by
+		 * the hardware is returned if the exact rate could not be set.
+		 *
+		 * So, if the chosen sample rate is invalid, set and return
+		 * the current hardware rate.
+		 */
+		if (ioctlf(fp, AUDIO_SETINFO, &tmpinfo) != 0) {
+			error = ioctlf(fp, AUDIO_GETFORMAT, &hwfmt);
+			if (error) {
+				DPRINTF(("%s: AUDIO_GETFORMAT %d\n",
+				     __func__, error));
+				goto out;
+			}
+			error = ioctlf(fp, AUDIO_GETINFO, &tmpinfo);
+			if (error) {
+				DPRINTF(("%s: AUDIO_GETINFO %d\n",
+				     __func__, error));
+				goto out;
+			}
+			tmpinfo.play.sample_rate =
+			tmpinfo.record.sample_rate =
+			    (tmpinfo.mode == AUMODE_RECORD) ?
+			    hwfmt.record.sample_rate :
+			    hwfmt.play.sample_rate;
+			error = ioctlf(fp, AUDIO_SETINFO, &tmpinfo);
+			if (error) {
+				DPRINTF(("%s: SNDCTL_DSP_SPEED %d = %d\n",
+				     __func__, idat, error));
+				goto out;
+			}
 		}
 		/* FALLTHROUGH */
 	case OSS_SOUND_PCM_READ_RATE:
@@ -365,10 +396,19 @@ oss_ioctl_audio(struct lwp *l, const str
 			tmpinfo.record.encoding = AUDIO_ENCODING_AC3;
 			break;
 		default:
-			DPRINTF(("%s: SNDCTL_DSP_SETFMT bad fmt %d\n",
-			     __func__, idat));
-			error = EINVAL;
-			goto out;
+			/*
+			 * OSSv4 specifies that if an invalid format is chosen
+			 * by an application then a sensible format supported
+			 * by the hardware is returned.
+			 *
+			 * In this case, we pick S16LE since it's reasonably
+			 * assumed to be supported by applications.
+			 */
+			tmpinfo.play.precision =
+			tmpinfo.record.precision = 16;
+			tmpinfo.play.encoding =
+			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
+			break;
 		}
 		DPRINTF(("%s: SNDCTL_DSP_SETFMT > 0x%x\n",
 		    __func__, idat));
@@ -448,15 +488,13 @@ oss_ioctl_audio(struct lwp *l, const str
 			     __func__, error));
 			goto out;
 		}
-		tmpinfo.play.channels =
-		tmpinfo.record.channels = idat;
-		DPRINTF(("%s: SNDCTL_DSP_CHANNELS > %d\n", __func__, idat));
-		error = ioctlf(fp, AUDIO_SETINFO, &tmpinfo);
+		error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo);
 		if (error) {
-			DPRINTF(("%s: AUDIO_SETINFO %d\n",
+			DPRINTF(("%s: AUDIO_GETBUFINFO %d\n",
 			     __func__, error));
 			goto out;
 		}
+		setchannels(fp, tmpinfo.mode, idat);
 		/* FALLTHROUGH */
 	case OSS_SOUND_PCM_READ_CHANNELS:
 		error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo);
@@ -677,7 +715,7 @@ oss_ioctl_audio(struct lwp *l, const str
 			     __func__, error));
 			goto out;
 		}
-		idat = OSS_DSP_CAP_TRIGGER; /* pretend we have trigger */
+		idat = OSS_DSP_CAP_TRIGGER;
 		if (idata & AUDIO_PROP_FULLDUPLEX)
 			idat |= OSS_DSP_CAP_DUPLEX;
 		if (idata & AUDIO_PROP_MMAP)
@@ -692,64 +730,45 @@ oss_ioctl_audio(struct lwp *l, const str
 			goto out;
 		}
 		break;
-#if 0
-	case OSS_SNDCTL_DSP_GETTRIGGER:
-		error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo);
-		if (error) {
-			DPRINTF(("%s: AUDIO_GETBUFINFO %d\n",
-			     __func__, error));
-			goto out;
-		}
-		idat = (tmpinfo.play.pause ? 0 : OSS_PCM_ENABLE_OUTPUT) |
-		       (tmpinfo.record.pause ? 0 : OSS_PCM_ENABLE_INPUT);
-		error = copyout(&idat, SCARG(uap, data), sizeof idat);
-		if (error) {
-			DPRINTF(("%s: SNDCTL_DSP_SETRIGGER %x = %d\n",
-			    __func__, idat, error));
-			goto out;
-		}
-		break;
 	case OSS_SNDCTL_DSP_SETTRIGGER:
-		error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo, p);
+		error = copyin(SCARG(uap, data), &idat, sizeof idat);
 		if (error) {
-			DPRINTF(("%s: AUDIO_GETBUFINFO %d\n",
+			DPRINTF(("%s: SNDCTL_DSP_SETTRIGGER: %d\n",
 			     __func__, error));
 			goto out;
 		}
-		error = copyin(SCARG(uap, data), &idat, sizeof idat);
+		error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo);
 		if (error) {
 			DPRINTF(("%s: AUDIO_GETBUFINFO %d\n",
 			     __func__, error));
 			goto out;
 		}
-		tmpinfo.play.pause = (idat & OSS_PCM_ENABLE_OUTPUT) == 0;
-		tmpinfo.record.pause = (idat & OSS_PCM_ENABLE_INPUT) == 0;
-		error = ioctlf(fp, AUDIO_SETINFO, &tmpinfo);
+		AUDIO_INITINFO(&tmpinfo);
+		if (tmpinfo.mode & AUMODE_PLAY)
+			tmpinfo.play.pause = (idat & OSS_PCM_ENABLE_OUTPUT) == 0;
+		if (tmpinfo.mode & AUMODE_RECORD)
+			tmpinfo.record.pause = (idat & OSS_PCM_ENABLE_INPUT) == 0;
+		(void)ioctlf(fp, AUDIO_SETINFO, &tmpinfo);
+		/* FALLTHRU */
+	case OSS_SNDCTL_DSP_GETTRIGGER:
+		error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo);
 		if (error) {
-			DPRINTF(("%s: AUDIO_SETINFO %d\n",
+			DPRINTF(("%s: AUDIO_GETBUFINFO %d\n",
 			     __func__, error));
 			goto out;
 		}
+		idat = 0;
+		if ((tmpinfo.mode & AUMODE_PLAY) && !tmpinfo.play.pause)
+			idat |= OSS_PCM_ENABLE_OUTPUT;
+		if ((tmpinfo.mode & AUMODE_RECORD) && !tmpinfo.record.pause)
+			idat |= OSS_PCM_ENABLE_INPUT;
 		error = copyout(&idat, SCARG(uap, data), sizeof idat);
 		if (error) {
-			DPRINTF(("%s: SNDCTL_DSP_SETRIGGER %x = %d\n",
-			    __func__, idat, error));
-			goto out;
-		}
-		break;
-#else
-	case OSS_SNDCTL_DSP_GETTRIGGER:
-	case OSS_SNDCTL_DSP_SETTRIGGER:
-		/* XXX Do nothing for now. */
-		idat = OSS_PCM_ENABLE_OUTPUT;
-		error = copyout(&idat, SCARG(uap, data), sizeof idat);
-		if (error) {
-			DPRINTF(("%s: SNDCTL_DSP_{GET,SET}RIGGER %x = %d\n",
+			DPRINTF(("%s: SNDCTL_DSP_GETTRIGGER = %x = %d\n",
 			    __func__, idat, error));
 			goto out;
 		}
 		break;
-#endif
 	case OSS_SNDCTL_DSP_GETIPTR:
 		error = ioctlf(fp, AUDIO_GETIOFFS, &tmpoffs);
 		if (error) {
@@ -1494,6 +1513,54 @@ oss_ioctl_sequencer(struct lwp *l, const
 }
 
 /*
+ * When AUDIO_SETINFO fails to set a channel count, the application's chosen
+ * number is out of range of what the kernel allows.
+ *
+ * When this happens, we use the current hardware settings. This is just in
+ * case an application is abusing SNDCTL_DSP_CHANNELS - OSSv4 always sets and
+ * returns a reasonable value, even if it wasn't what the user requested.
+ *
+ * XXX: If a device is opened for both playback and recording, and supports
+ * fewer channels for recording than playback, applications that do both will
+ * behave very strangely. OSS doesn't allow for reporting separate channel
+ * counts for recording and playback. This could be worked around by always
+ * mixing recorded data up to the same number of channels as is being used
+ * for playback.
+ */
+static void
+setchannels(file_t *fp, int mode, int nchannels)
+{
+	struct audio_info tmpinfo, hwfmt;
+	int (*ioctlf)(file_t *, u_long, void *);
+
+	ioctlf = fp->f_ops->fo_ioctl;
+
+	if (ioctlf(fp, AUDIO_GETFORMAT, &hwfmt) < 0) {
+		hwfmt.record.channels = hwfmt.play.channels = 2;
+	}
+
+	if (mode & AUMODE_PLAY) {
+		AUDIO_INITINFO(&tmpinfo);
+		tmpinfo.play.channels = nchannels;
+		if (ioctlf(fp, AUDIO_SETINFO, &tmpinfo) < 0) {
+			AUDIO_INITINFO(&tmpinfo);
+			tmpinfo.play.channels = hwfmt.play.channels;
+			(void)ioctlf(fp, AUDIO_SETINFO, &tmpinfo);
+		}
+	}
+
+	if (mode & AUMODE_RECORD) {
+		AUDIO_INITINFO(&tmpinfo);
+		tmpinfo.record.channels = nchannels;
+		if (ioctlf(fp, AUDIO_SETINFO, &tmpinfo) < 0) {
+			AUDIO_INITINFO(&tmpinfo);
+			tmpinfo.record.channels = hwfmt.record.channels;
+			(void)ioctlf(fp, AUDIO_SETINFO, &tmpinfo);
+		}
+	}
+}
+
+/*
  * Check that the blocksize is a power of 2 as OSS wants.
  * If not, set it to be.
  */

Reply via email to