Module Name:    src
Committed By:   nia
Date:           Sat Oct 17 23:23:06 UTC 2020

Modified Files:
        src/lib/libossaudio: ossaudio.c soundcard.h

Log Message:
ossaudio(3): Add initial support for the OSSv4.1 Mixer API

One or two calls from this API were supported previously and have been
moved to the correct place.

Mapping the controls correctly is a difficult task. There is a define
hidden in the OSS headers that would allow an AUDIO_MIXER_SET control
to be represented perfectly, but it seems to _only_ exist there, and
no software supports it. So for now only one member of a set can be
set at a time - unfortunate. I've hidden code that should unlock
doing this the proper way under #notyet.

I'm not too happy with the way this code is managing file descriptors.
Currently it has to open a new fd for each ioctl due to OSSv4 deciding
to specify the device number in a structure rather than in the filename.
In the future, we could reuse the file descriptor if the correct one is
detected open.

This allows the mixer programs provided with the OSSv4 sources to compile
and work cleanly. I've observed problems with it failing to work on
secondary devices, and should investigate this later. There may be
a fd leak somewhere.


To generate a diff of this commit:
cvs rdiff -u -r1.48 -r1.49 src/lib/libossaudio/ossaudio.c
cvs rdiff -u -r1.25 -r1.26 src/lib/libossaudio/soundcard.h

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.48 src/lib/libossaudio/ossaudio.c:1.49
--- src/lib/libossaudio/ossaudio.c:1.48	Fri Oct 16 20:24:35 2020
+++ src/lib/libossaudio/ossaudio.c	Sat Oct 17 23:23:06 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: ossaudio.c,v 1.48 2020/10/16 20:24:35 nia Exp $	*/
+/*	$NetBSD: ossaudio.c,v 1.49 2020/10/17 23:23:06 nia Exp $	*/
 
 /*-
  * Copyright (c) 1997, 2020 The NetBSD Foundation, Inc.
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: ossaudio.c,v 1.48 2020/10/16 20:24:35 nia Exp $");
+__RCSID("$NetBSD: ossaudio.c,v 1.49 2020/10/17 23:23:06 nia Exp $");
 
 /*
  * This is an Open Sound System compatibility layer, which provides
@@ -49,6 +49,7 @@ __RCSID("$NetBSD: ossaudio.c,v 1.48 2020
 #include <fcntl.h>
 #include <stdio.h>
 #include <unistd.h>
+#include <limits.h>
 #include <stdarg.h>
 #include <stdbool.h>
 
@@ -66,6 +67,9 @@ __RCSID("$NetBSD: ossaudio.c,v 1.48 2020
 
 static struct audiodevinfo *getdevinfo(int);
 
+static int getmixercount(void);
+static int getmixercontrolcount(int);
+
 static int getvol(u_int, u_char);
 static void setvol(int, int, bool);
 
@@ -73,7 +77,8 @@ static void setchannels(int, int, int);
 static void setblocksize(int, struct audio_info *);
 
 static int audio_ioctl(int, unsigned long, void *);
-static int mixer_ioctl(int, unsigned long, void *);
+static int mixer_oss3_ioctl(int, unsigned long, void *);
+static int mixer_oss4_ioctl(int, unsigned long, void *);
 static int opaque_to_enum(struct audiodevinfo *, audio_mixer_name_t *, int);
 static int enum_to_ord(struct audiodevinfo *, int);
 static int enum_to_mask(struct audiodevinfo *, int);
@@ -93,7 +98,9 @@ _oss_ioctl(int fd, unsigned long com, ..
 	if (IOCGROUP(com) == 'P')
 		return audio_ioctl(fd, com, argp);
 	else if (IOCGROUP(com) == 'M')
-		return mixer_ioctl(fd, com, argp);
+		return mixer_oss3_ioctl(fd, com, argp);
+	else if (IOCGROUP(com) == 'X')
+		return mixer_oss4_ioctl(fd, com, argp);
 	else
 		return ioctl(fd, com, argp);
 }
@@ -105,17 +112,9 @@ audio_ioctl(int fd, unsigned long com, v
 	struct audio_info tmpinfo, hwfmt;
 	struct audio_offset tmpoffs;
 	struct audio_buf_info bufinfo;
-	struct audio_format_query fmtq;
 	struct audio_errinfo *tmperrinfo;
 	struct count_info cntinfo;
 	struct audio_encoding tmpenc;
-	struct oss_sysinfo tmpsysinfo;
-	struct oss_audioinfo *tmpaudioinfo;
-	audio_device_t tmpaudiodev;
-	struct stat tmpstat;
-	dev_t devno;
-	char version[32] = "4.01";
-	char license[16] = "NetBSD";
 	u_int u;
 	u_int encoding;
 	u_int precision;
@@ -123,9 +122,7 @@ audio_ioctl(int fd, unsigned long com, v
 	static int totalperrors = 0;
 	static int totalrerrors = 0;
 	int idat, idata;
-	int props;
 	int retval;
-	int newfd;
 
 	idat = 0;
 
@@ -589,117 +586,6 @@ audio_ioctl(int fd, unsigned long com, v
 		cntinfo.ptr = tmpoffs.offset;
 		*(struct count_info *)argp = cntinfo;
 		break;
-	case SNDCTL_SYSINFO:
-		strlcpy(tmpsysinfo.product, "OSS/NetBSD",
-		    sizeof tmpsysinfo.product);
-		strlcpy(tmpsysinfo.version, version, sizeof tmpsysinfo.version);
-		strlcpy(tmpsysinfo.license, license, sizeof tmpsysinfo.license);
-		tmpsysinfo.versionnum = SOUND_VERSION;
-		memset(tmpsysinfo.options, 0, 8);
-		tmpsysinfo.numaudios = OSS_MAX_AUDIO_DEVS;
-		tmpsysinfo.numaudioengines = 1;
-		memset(tmpsysinfo.openedaudio, 0, sizeof(tmpsysinfo.openedaudio));
-		tmpsysinfo.numsynths = 1;
-		tmpsysinfo.nummidis = -1;
-		tmpsysinfo.numtimers = -1;
-		tmpsysinfo.nummixers = 1;
-		tmpsysinfo.numcards = 1;
-		memset(tmpsysinfo.openedmidi, 0, sizeof(tmpsysinfo.openedmidi));
-		*(struct oss_sysinfo *)argp = tmpsysinfo;
-		break;
-	case SNDCTL_ENGINEINFO:
-	case SNDCTL_AUDIOINFO:
-		devno = 0;
-		tmpaudioinfo = (struct oss_audioinfo*)argp;
-		if (tmpaudioinfo == NULL)
-			return EINVAL;
-
-		/*
-		 * Takes the audio dev node as input, since this ioctl is
-		 * supposed to work on the OSS /dev/mixer to query all
-		 * all available audio devices.
-		 *
-		 * If the input device is -1, guess the device related to
-		 * the open mixer device.
-		 */
-		if (tmpaudioinfo->dev < 0) {
-			fstat(fd, &tmpstat);
-			if ((tmpstat.st_rdev & 0xff00) == 0x2a00)
-				devno = tmpstat.st_rdev & 0xff;
-			if (devno >= 0x80)
-				tmpaudioinfo->dev = devno & 0x7f;
-		}
-		if (tmpaudioinfo->dev < 0)
-			tmpaudioinfo->dev = 0;
-
-		snprintf(tmpaudioinfo->devnode, OSS_DEVNODE_SIZE,
-		    "/dev/audio%d", tmpaudioinfo->dev);
-
-		if ((newfd = open(tmpaudioinfo->devnode, O_WRONLY)) < 0) {
-			if ((newfd = open(tmpaudioinfo->devnode, O_RDONLY)) < 0) {
-				return newfd;
-			}
-		}
-
-		retval = ioctl(newfd, AUDIO_GETDEV, &tmpaudiodev);
-		if (retval < 0) {
-			close(newfd);
-			return retval;
-		}
-		retval = ioctl(newfd, AUDIO_GETPROPS, &props);
-		if (retval < 0) {
-			close(newfd);
-			return retval;
-		}
-		idat = DSP_CAP_TRIGGER;
-		if (props & AUDIO_PROP_FULLDUPLEX)
-			idat |= DSP_CAP_DUPLEX;
-		if (props & AUDIO_PROP_MMAP)
-			idat |= DSP_CAP_MMAP;
-		if (props & AUDIO_PROP_CAPTURE)
-			idat |= PCM_CAP_INPUT;
-		if (props & AUDIO_PROP_PLAYBACK)
-			idat |= PCM_CAP_OUTPUT;
-		snprintf(tmpaudioinfo->name, sizeof(tmpaudioinfo->name),
-		    "%s %s", tmpaudiodev.name, tmpaudiodev.version);
-		tmpaudioinfo->busy = 0;
-		tmpaudioinfo->pid = -1;
-		tmpaudioinfo->caps = idat;
-		ioctl(newfd, SNDCTL_DSP_GETFMTS, &tmpaudioinfo->iformats);
-		tmpaudioinfo->oformats = tmpaudioinfo->iformats;
-		tmpaudioinfo->magic = -1; /* reserved for "internal use" */
-		memset(tmpaudioinfo->cmd, 0, sizeof(tmpaudioinfo->cmd));
-		tmpaudioinfo->card_number = -1;
-		memset(tmpaudioinfo->song_name, 0,
-		    sizeof(tmpaudioinfo->song_name));
-		memset(tmpaudioinfo->label, 0, sizeof(tmpaudioinfo->label));
-		tmpaudioinfo->port_number = 0;
-		tmpaudioinfo->mixer_dev = tmpaudioinfo->dev;
-		tmpaudioinfo->legacy_device = tmpaudioinfo->dev;
-		tmpaudioinfo->enabled = 1;
-		tmpaudioinfo->flags = -1; /* reserved for "future versions" */
-		tmpaudioinfo->min_rate = 1000;
-		tmpaudioinfo->max_rate = 192000;
-		tmpaudioinfo->nrates = 0;
-		tmpaudioinfo->min_channels = 1;
-		tmpaudioinfo->max_channels = 2;
-		for (fmtq.index = 0; ioctl(newfd, AUDIO_QUERYFORMAT, &fmtq) != -1; ++fmtq.index) {
-			if (fmtq.fmt.channels > (unsigned)tmpaudioinfo->max_channels)
-				tmpaudioinfo->max_channels = fmtq.fmt.channels;
-		}
-		tmpaudioinfo->binding = -1; /* reserved for "future versions" */
-		tmpaudioinfo->rate_source = -1;
-		/*
-		 * 'handle' is supposed to be globally unique. The closest
-		 * we have to that is probably device nodes.
-		 */
-		strlcpy(tmpaudioinfo->handle, tmpaudioinfo->devnode,
-		    sizeof(tmpaudioinfo->handle));
-		tmpaudioinfo->next_play_engine = 0;
-		tmpaudioinfo->next_rec_engine = 0;
-		argp = tmpaudioinfo;
-		close(newfd);
-		break;
 	case SNDCTL_DSP_SETPLAYVOL:
 		setvol(fd, INTARG, false);
 		/* FALLTHRU */
@@ -938,8 +824,8 @@ getdevinfo(int fd)
 	return di;
 }
 
-int
-mixer_ioctl(int fd, unsigned long com, void *argp)
+static int
+mixer_oss3_ioctl(int fd, unsigned long com, void *argp)
 {
 	struct audiodevinfo *di;
 	struct mixer_info *omi;
@@ -1095,6 +981,544 @@ mixer_ioctl(int fd, unsigned long com, v
 }
 
 static int
+mixer_oss4_ioctl(int fd, unsigned long com, void *argp)
+{
+	oss_audioinfo *tmpai;
+	oss_mixext *ext;
+	oss_mixext_root root;
+	oss_mixer_enuminfo *ei;
+	oss_mixer_value *mv;
+	oss_mixerinfo *mi;
+	oss_sysinfo sysinfo;
+	dev_t devno;
+	struct stat tmpstat;
+	struct audio_device dev;
+	struct audio_format_query fmtq;
+	struct mixer_devinfo mdi;
+	struct mixer_ctrl mc;
+	char devname[32];
+	size_t len;
+	int newfd = -1, tmperrno;
+	int i, noffs;
+	int retval;
+	int props, caps;
+
+	/*
+	 * Note: it is difficult to translate the NetBSD concept of a "set"
+	 * mixer control type to the OSSv4 API, as far as I can tell.
+	 *
+	 * This means they are treated like enums, i.e. only one entry in the
+	 * set can be selected at a time.
+	 */
+
+	switch (com) {
+	case SNDCTL_AUDIOINFO:
+	case SNDCTL_ENGINEINFO:
+		devno = 0;
+		tmpai = (struct oss_audioinfo*)argp;
+		if (tmpai == NULL) {
+			errno = EINVAL;
+			return -1;
+		}
+
+		/*
+		 * If the input device is -1, guess the device related to
+		 * the open mixer device.
+		 */
+		if (tmpai->dev < 0) {
+			fstat(fd, &tmpstat);
+			if ((tmpstat.st_rdev & 0xff00) == 0x2a00)
+				devno = tmpstat.st_rdev & 0xff;
+			if (devno >= 0x80)
+				tmpai->dev = devno & 0x7f;
+		}
+		if (tmpai->dev < 0)
+			tmpai->dev = 0;
+
+		snprintf(tmpai->devnode, sizeof(tmpai->devnode),
+		    "/dev/audio%d", tmpai->dev);
+
+		if ((newfd = open(tmpai->devnode, O_WRONLY)) < 0) {
+			if ((newfd = open(tmpai->devnode, O_RDONLY)) < 0) {
+				return newfd;
+			}
+		}
+
+		retval = ioctl(newfd, AUDIO_GETDEV, &dev);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		retval = ioctl(newfd, AUDIO_GETPROPS, &props);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		caps = DSP_CAP_TRIGGER;
+		if (props & AUDIO_PROP_FULLDUPLEX)
+			caps |= DSP_CAP_DUPLEX;
+		if (props & AUDIO_PROP_MMAP)
+			caps |= DSP_CAP_MMAP;
+		if (props & AUDIO_PROP_CAPTURE)
+			caps |= PCM_CAP_INPUT;
+		if (props & AUDIO_PROP_PLAYBACK)
+			caps |= PCM_CAP_OUTPUT;
+		snprintf(tmpai->name, sizeof(tmpai->name),
+		    "%s %s", dev.name, dev.version);
+		tmpai->busy = 0;
+		tmpai->pid = -1;
+		tmpai->caps = caps;
+		ioctl(newfd, SNDCTL_DSP_GETFMTS, &tmpai->iformats);
+		tmpai->oformats = tmpai->iformats;
+		tmpai->magic = -1; /* reserved for "internal use" */
+		memset(tmpai->cmd, 0, sizeof(tmpai->cmd));
+		tmpai->card_number = -1;
+		memset(tmpai->song_name, 0,
+		    sizeof(tmpai->song_name));
+		memset(tmpai->label, 0, sizeof(tmpai->label));
+		tmpai->port_number = 0;
+		tmpai->mixer_dev = tmpai->dev;
+		tmpai->legacy_device = tmpai->dev;
+		tmpai->enabled = 1;
+		tmpai->flags = -1; /* reserved for "future versions" */
+		tmpai->min_rate = 1000;
+		tmpai->max_rate = 192000;
+		tmpai->nrates = 0;
+		tmpai->min_channels = 1;
+		tmpai->max_channels = 2;
+		for (fmtq.index = 0;
+		    ioctl(newfd, AUDIO_QUERYFORMAT, &fmtq) != -1; ++fmtq.index) {
+			if (fmtq.fmt.channels > (unsigned)tmpai->max_channels)
+				tmpai->max_channels = fmtq.fmt.channels;
+		}
+		tmpai->binding = -1; /* reserved for "future versions" */
+		tmpai->rate_source = -1;
+		/*
+		 * 'handle' is supposed to be globally unique. The closest
+		 * we have to that is probably device nodes.
+		 */
+		strlcpy(tmpai->handle, tmpai->devnode,
+		    sizeof(tmpai->handle));
+		tmpai->next_play_engine = 0;
+		tmpai->next_rec_engine = 0;
+		argp = tmpai;
+		close(newfd);
+		break;
+	case SNDCTL_SYSINFO:
+		memset(&sysinfo, 0, sizeof(sysinfo));
+		strlcpy(sysinfo.product,
+		    "OSS/NetBSD", sizeof(sysinfo.product));
+		strlcpy(sysinfo.version,
+		    "4.01", sizeof(sysinfo.version));
+		strlcpy(sysinfo.license,
+		    "BSD", sizeof(sysinfo.license));
+		sysinfo.versionnum = SOUND_VERSION;
+		sysinfo.numaudios = OSS_MAX_AUDIO_DEVS;
+		sysinfo.numaudioengines = 1;
+		sysinfo.numsynths = 1;
+		sysinfo.nummidis = -1;
+		sysinfo.numtimers = -1;
+		sysinfo.nummixers = OSS_MAX_AUDIO_DEVS;
+		sysinfo.numcards = 1;
+		*(struct oss_sysinfo *)argp = sysinfo;
+		break;
+	case SNDCTL_MIXERINFO:
+		mi = (oss_mixerinfo *)argp;
+		if (mi == NULL) {
+			errno = EINVAL;
+			return -1;
+		}
+		snprintf(devname, sizeof(devname), "/dev/mixer%d", mi->dev);
+		if ((newfd = open(devname, O_RDONLY)) < 0)
+			return newfd;
+		retval = ioctl(newfd, AUDIO_GETDEV, &dev);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		strlcpy(mi->id, devname, sizeof(mi->id));
+		snprintf(mi->name, sizeof(mi->name),
+		    "%s %s", dev.name, dev.version);
+		mi->card_number = mi->dev;
+		mi->port_number = 0;
+		mi->magic = 0;
+		mi->enabled = 1;
+		mi->caps = 0;
+		mi->flags = 0;
+		mi->nrext = getmixercontrolcount(newfd) + 1;
+		mi->priority = UCHAR_MAX - mi->dev;
+		strlcpy(mi->devnode, devname, sizeof(mi->devnode));
+		mi->legacy_device = mi->dev;
+		break;
+	case SNDCTL_MIX_DESCRIPTION:
+		/* No description available. */
+		errno = ENOSYS;
+		return -1;
+	case SNDCTL_MIX_NRMIX:
+		INTARG = getmixercount();
+		break;
+	case SNDCTL_MIX_NREXT:
+		snprintf(devname, sizeof(devname), "/dev/mixer%d", INTARG);
+		if ((newfd = open(devname, O_RDONLY)) < 0)
+			return newfd;
+		INTARG = getmixercontrolcount(newfd) + 1;
+		close(newfd);
+		break;
+	case SNDCTL_MIX_EXTINFO:
+		ext = (oss_mixext *)argp;
+		snprintf(devname, sizeof(devname), "/dev/mixer%d", ext->dev);
+		if ((newfd = open(devname, O_RDONLY)) < 0)
+			return newfd;
+		if (ext->ctrl == 0) {
+			/*
+			 * NetBSD has no concept of a "root mixer control", but
+ 			 * OSSv4 requires one to work. We fake one at 0 and
+			 * simply add 1 to all real control indexes.
+			 */
+			retval = ioctl(newfd, AUDIO_GETDEV, &dev);
+			tmperrno = errno;
+			close(newfd);
+			if (retval < 0) {
+				errno = tmperrno;
+				return -1;
+			}
+			memset(&root, 0, sizeof(root));
+			strlcpy(root.id, devname, sizeof(root.id));
+			snprintf(root.name, sizeof(root.name),
+			    "%s %s", dev.name, dev.version);
+			strlcpy(ext->id, devname, sizeof(ext->id));
+			snprintf(ext->extname, sizeof(ext->extname),
+			    "%s %s", dev.name, dev.version);
+			strlcpy(ext->extname, "root", sizeof(ext->extname));
+			ext->type = MIXT_DEVROOT;
+			ext->minvalue = 0;
+			ext->maxvalue = 0;
+			ext->flags = 0;
+			ext->parent = -1;
+			ext->control_no = -1;
+			ext->update_counter = 0;
+			ext->rgbcolor = 0;
+			memcpy(&ext->data, &root,
+			    sizeof(root) > sizeof(ext->data) ?
+			    sizeof(ext->data) : sizeof(root));
+			return 0;
+		}
+		mdi.index = ext->ctrl - 1;
+		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		ext->flags = MIXF_READABLE | MIXF_WRITEABLE | MIXF_POLL;
+		ext->parent = mdi.mixer_class + 1;
+		strlcpy(ext->id, mdi.label.name, sizeof(ext->id));
+		strlcpy(ext->extname, mdi.label.name, sizeof(ext->extname));
+		len = strlen(ext->extname);
+		memset(ext->data, 0, sizeof(ext->data));
+		ext->control_no = -1;
+		ext->update_counter = 0;
+		ext->rgbcolor = 0;
+		switch (mdi.type) {
+		case AUDIO_MIXER_CLASS:
+			ext->type = MIXT_GROUP;
+			ext->parent = 0;
+			ext->minvalue = 0;
+			ext->maxvalue = 0;
+			break;
+		case AUDIO_MIXER_ENUM:
+			ext->maxvalue = mdi.un.e.num_mem;
+			ext->minvalue = 0;
+			for (i = 0; i < mdi.un.e.num_mem; ++i) {
+				ext->enum_present[i / 8] |= (1 << (i % 8));
+			}
+			if (mdi.un.e.num_mem == 2) {
+				if (!strcmp(mdi.un.e.member[0].label.name, AudioNoff) &&
+				    !strcmp(mdi.un.e.member[1].label.name, AudioNon)) {
+					ext->type = MIXT_MUTE;
+				} else {
+					ext->type = MIXT_ENUM;
+				}
+			} else {
+				ext->type = MIXT_ENUM;
+			}
+			break;
+		case AUDIO_MIXER_SET:
+			ext->maxvalue = mdi.un.s.num_mem;
+			ext->minvalue = 0;
+#ifdef notyet
+			/*
+			 * XXX: This is actually the correct type for "set"
+			 * controls, but it seems no real world software
+			 * supports it. The only documentation exists in
+			 * the OSSv4 headers and describes it as "reserved
+			 * for Sun's implementation".
+			 */
+			ext->type = MIXT_ENUM_MULTI;
+#else
+			ext->type = MIXT_ENUM;
+#endif
+			for (i = 0; i < mdi.un.s.num_mem; ++i) {
+				ext->enum_present[i / 8] |= (1 << (i % 8));
+			}
+			break;
+		case AUDIO_MIXER_VALUE:
+			ext->maxvalue = UCHAR_MAX + 1;
+			ext->minvalue = 0;
+			if (mdi.un.v.num_channels == 2) {
+				ext->type = MIXT_STEREOSLIDER;
+			} else {
+				ext->type = MIXT_MONOSLIDER;
+			}
+			break;
+		}
+		close(newfd);
+		break;
+	case SNDCTL_MIX_ENUMINFO:
+		ei = (oss_mixer_enuminfo *)argp;
+		if (ei == NULL) {
+			errno = EINVAL;
+			return -1;
+		}
+		if (ei->ctrl == 0) {
+			errno = EINVAL;
+			return -1;
+		}
+		snprintf(devname, sizeof(devname), "/dev/mixer%d", ei->dev);
+		if ((newfd = open(devname, O_RDONLY)) < 0)
+			return newfd;
+		mdi.index = ei->ctrl - 1;
+		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		ei->version = 0;
+		switch (mdi.type) {
+		case AUDIO_MIXER_ENUM:
+			ei->nvalues = mdi.un.e.num_mem;
+			noffs = 0;
+			for (i = 0; i < ei->nvalues; ++i) {
+				ei->strindex[i] = noffs;
+				len = strlen(mdi.un.e.member[i].label.name) + 1;
+				if ((noffs + len) >= sizeof(ei->strings)) {
+				    close(newfd);
+				    errno = ENOMEM;
+				    return -1;
+				}
+				memcpy(ei->strings + noffs,
+				    mdi.un.e.member[i].label.name, len);
+				noffs += len;
+			}
+			break;
+		case AUDIO_MIXER_SET:
+			ei->nvalues = mdi.un.s.num_mem;
+			noffs = 0;
+			for (i = 0; i < ei->nvalues; ++i) {
+				ei->strindex[i] = noffs;
+				len = strlen(mdi.un.s.member[i].label.name) + 1;
+				if ((noffs + len) >= sizeof(ei->strings)) {
+				    close(newfd);
+				    errno = ENOMEM;
+				    return -1;
+				}
+				memcpy(ei->strings + noffs,
+				    mdi.un.s.member[i].label.name, len);
+				noffs += len;
+			}
+			break;
+		default:
+			close(newfd);
+			errno = EINVAL;
+			return -1;
+		}
+		break;
+	case SNDCTL_MIX_WRITE:
+		mv = (oss_mixer_value *)argp;
+		if (mv == NULL) {
+			errno = EINVAL;
+			return -1;
+		}
+		if (mv->ctrl == 0) {
+			errno = EINVAL;
+			return -1;
+		}
+		snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev);
+		if ((newfd = open(devname, O_RDWR)) < 0)
+			return newfd;
+		mdi.index = mc.dev = mv->ctrl - 1;
+		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		switch (mdi.type) {
+		case AUDIO_MIXER_ENUM:
+			if (mv->value >= mdi.un.e.num_mem) {
+				close(newfd);
+				errno = EINVAL;
+				return -1;
+			}
+			mc.un.ord = mdi.un.e.member[mv->value].ord;
+			break;
+		case AUDIO_MIXER_SET:
+			if (mv->value >= mdi.un.s.num_mem) {
+				close(newfd);
+				errno = EINVAL;
+				return -1;
+			}
+#ifdef notyet
+			mc.un.mask = 0;
+			for (i = 0; i < mdi.un.s.num_mem; ++i) {
+				if (mv->value & (1 << i)) {
+					mc.un.mask |= mdi.un.s.member[mv->value].mask;
+				}
+			}
+#else
+			mc.un.mask = mdi.un.s.member[mv->value].mask;
+#endif
+			break;
+		case AUDIO_MIXER_VALUE:
+			if (mdi.un.v.num_channels != 2) {
+				for (i = 0; i < mdi.un.v.num_channels; ++i) {
+					mc.un.value.level[i] = mv->value;
+				}
+			} else {
+			    mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = 
+				(mv->value >> 0) & 0xFF;
+			    mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] =
+				(mv->value >> 8) & 0xFF;
+			}
+			break;
+		}
+		retval = ioctl(newfd, AUDIO_MIXER_WRITE, &mc);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		close(newfd);
+		break;
+	case SNDCTL_MIX_READ:
+		mv = (oss_mixer_value *)argp;
+		if (mv == NULL) {
+			errno = EINVAL;
+			return -1;
+		}
+		if (mv->ctrl == 0) {
+			errno = EINVAL;
+			return -1;
+		}
+		snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev);
+		if ((newfd = open(devname, O_RDWR)) < 0)
+			return newfd;
+		mdi.index = mc.dev = (mv->ctrl - 1);
+		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		mc.dev = mdi.index;
+		retval = ioctl(newfd, AUDIO_MIXER_READ, &mc);
+		if (retval < 0) {
+			tmperrno = errno;
+			close(newfd);
+			errno = tmperrno;
+			return retval;
+		}
+		close(newfd);
+		mv->value = 0;
+		switch (mdi.type) {
+		case AUDIO_MIXER_ENUM:
+			for (i = 0; i < mdi.un.e.num_mem; ++i) {
+				if (mc.un.ord == mdi.un.e.member[i].ord) {
+					mv->value = i;
+					break;
+				}
+			}
+			break;
+		case AUDIO_MIXER_SET:
+			for (i = 0; i < mdi.un.s.num_mem; ++i) {
+#ifdef notyet
+				if (mc.un.mask & mdi.un.s.member[i].mask)
+					mv->value |= (1 << i);
+#else
+				if (mc.un.mask == mdi.un.s.member[i].mask) {
+					mv->value = i;
+					break;
+				}
+#endif
+			}
+			break;
+		case AUDIO_MIXER_VALUE:
+			if (mdi.un.v.num_channels != 2) {
+				mv->value = mc.un.value.level[0];
+			} else {
+				mv->value = \
+				    ((mc.un.value.level[1] & 0xFF) << 8) |
+				    ((mc.un.value.level[0] & 0xFF) << 0);
+			}
+			break;
+		default:
+			errno = EINVAL;
+			return -1;
+		}
+		break;
+	default:
+		errno = EINVAL;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+getmixercount(void)
+{
+	char devname[32];
+	int ndevs = 0;
+	int tmpfd;
+	int tmperrno = errno;
+
+	do {
+		snprintf(devname, sizeof(devname),
+		    "/dev/mixer%d", ndevs);
+		if ((tmpfd = open(devname, O_RDONLY)) != -1) {
+			ndevs++;
+			close(tmpfd);
+		}
+	} while (tmpfd != -1);
+	errno = tmperrno;
+	return ndevs;
+}
+
+static int
+getmixercontrolcount(int fd)
+{
+	struct mixer_devinfo mdi;
+	int ndevs = 0;
+
+	do {
+		mdi.index = ndevs++;
+	} while (ioctl(fd, AUDIO_MIXER_DEVINFO, &mdi) != -1);
+
+	return ndevs > 0 ? ndevs - 1 : 0;
+}
+
+static int
 getvol(u_int gain, u_char balance)
 {
 	u_int l, r;

Index: src/lib/libossaudio/soundcard.h
diff -u src/lib/libossaudio/soundcard.h:1.25 src/lib/libossaudio/soundcard.h:1.26
--- src/lib/libossaudio/soundcard.h:1.25	Fri Oct 16 20:24:35 2020
+++ src/lib/libossaudio/soundcard.h	Sat Oct 17 23:23:06 2020
@@ -1,11 +1,11 @@
-/*	$NetBSD: soundcard.h,v 1.25 2020/10/16 20:24:35 nia Exp $	*/
+/*	$NetBSD: soundcard.h,v 1.26 2020/10/17 23:23:06 nia Exp $	*/
 
 /*-
- * Copyright (c) 1997 The NetBSD Foundation, Inc.
+ * Copyright (c) 1997, 2020 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
- * by Lennart Augustsson.
+ * by Lennart Augustsson and Nia Alarie.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -31,9 +31,9 @@
 
 /*
  * WARNING!  WARNING!
- * This is an OSS (Linux) audio emulator.
- * Use the Native NetBSD API for developing new code, and this
- * only for compiling Linux programs.
+ * This is an Open Sound System compatibility layer.
+ * Use the Native NetBSD API in <sys/audioio.h> for developing new code,
+ * and this only for compiling programs written for other operating systems.
  */
 
 #ifndef _SOUNDCARD_H_
@@ -318,9 +318,6 @@ typedef struct buffmem_desc {
 #define OSS_LONGNAME_SIZE		64
 #define OSS_MAX_AUDIO_DEVS		64
 
-#define SNDCTL_SYSINFO			_IOR ('P',24, struct oss_sysinfo)
-#define SNDCTL_AUDIOINFO		_IOWR ('P',25, struct oss_audioinfo)
-#define SNDCTL_ENGINEINFO		_IOWR ('P',26, struct oss_audioinfo)
 #define SNDCTL_DSP_GETPLAYVOL		_IOR ('P',27, uint)
 #define SNDCTL_DSP_SETPLAYVOL		_IOW ('P',28, uint)
 #define SNDCTL_DSP_GETRECVOL		_IOR ('P',29, uint)
@@ -398,6 +395,145 @@ typedef struct oss_audioinfo {
 	int filler[184];			/* For expansion */
 } oss_audioinfo;
 
+#define SNDCTL_SYSINFO		_IOR ('X', 1, oss_sysinfo)
+#define OSS_SYSINFO		SNDCTL_SYSINFO /* Old name */
+#define SNDCTL_MIX_NRMIX	_IOR ('X',2, int)
+#define SNDCTL_MIX_NREXT	_IOWR ('X',3, int)
+#define SNDCTL_MIX_EXTINFO	_IOWR ('X',4, oss_mixext)
+#define SNDCTL_MIX_READ		_IOWR ('X',5, oss_mixer_value)
+#define SNDCTL_MIX_WRITE	_IOWR ('X',6, oss_mixer_value)
+#define SNDCTL_AUDIOINFO	_IOWR ('X',7, oss_audioinfo)
+#define SNDCTL_MIX_ENUMINFO	_IOWR ('X',8, oss_mixer_enuminfo)
+#define SNDCTL_MIXERINFO	_IOWR ('X',10, oss_mixerinfo)
+#define SNDCTL_ENGINEINFO	_IOWR ('X',12, oss_audioinfo)
+#define SNDCTL_MIX_DESCRIPTION	_IOWR ('X',14, oss_mixer_enuminfo)
+
+#define MIXT_DEVROOT	 	0 /* Used for default classes */
+#define MIXT_GROUP	 	1 /* Used for classes */
+#define MIXT_ONOFF	 	2 /* Used for mute controls */
+#define MIXT_ENUM	 	3 /* Used for enum controls */
+#define MIXT_MONOSLIDER	 	4 /* Used for mono and surround controls */
+#define MIXT_STEREOSLIDER 	5 /* Used for stereo controls */
+#define MIXT_MESSAGE	 	6 /* OSS compat, unused on NetBSD */
+#define MIXT_MONOVU	 	7 /* OSS compat, unused on NetBSD */
+#define MIXT_STEREOVU	 	8 /* OSS compat, unused on NetBSD */
+#define MIXT_MONOPEAK	 	9 /* OSS compat, unused on NetBSD */
+#define MIXT_STEREOPEAK		10 /* OSS compat, unused on NetBSD */
+#define MIXT_RADIOGROUP		11 /* OSS compat, unused on NetBSD */
+#define MIXT_MARKER		12 /* OSS compat, unused on NetBSD */
+#define MIXT_VALUE		13 /* OSS compat, unused on NetBSD */
+#define MIXT_HEXVALUE		14 /* OSS compat, unused on NetBSD */
+#define MIXT_MONODB		15 /* OSS compat, unused on NetBSD */
+#define MIXT_STEREODB		16 /* OSS compat, unused on NetBSD */
+#define MIXT_SLIDER		17 /* OSS compat, unused on NetBSD */
+#define MIXT_3D			18 /* OSS compat, unused on NetBSD */
+#define MIXT_MONOSLIDER16	19 /* OSS compat, unused on NetBSD */
+#define MIXT_STEREOSLIDER16	20 /* OSS compat, unused on NetBSD */
+#define MIXT_MUTE		21 /* OSS compat, unused on NetBSD */
+/*
+ * Should be used for Set controls. 
+ * In practice nothing uses this because it's "reserved for Sun's
+ * implementation".
+ */
+#define MIXT_ENUM_MULTI		22
+
+#define MIXF_READABLE	0x00000001 /* Value is readable: always true */
+#define MIXF_WRITEABLE	0x00000002 /* Value is writable: always true */
+#define MIXF_POLL	0x00000004 /* Can change between reads: always true */
+#define MIXF_HZ		0x00000008 /* OSS compat, unused on NetBSD */
+#define MIXF_STRING	0x00000010 /* OSS compat, unused on NetBSD */
+#define MIXF_DYNAMIC	0x00000010 /* OSS compat, unused on NetBSD */
+#define MIXF_OKFAIL	0x00000020 /* OSS compat, unused on NetBSD */
+#define MIXF_FLAT	0x00000040 /* OSS compat, unused on NetBSD */
+#define MIXF_LEGACY	0x00000080 /* OSS compat, unused on NetBSD */
+#define MIXF_CENTIBEL	0x00000100 /* OSS compat, unused on NetBSD */
+#define MIXF_DECIBEL	0x00000200 /* OSS compat, unused on NetBSD */
+#define MIXF_MAINVOL	0x00000400 /* OSS compat, unused on NetBSD */
+#define MIXF_PCMVOL	0x00000800 /* OSS compat, unused on NetBSD */
+#define MIXF_RECVOL	0x00001000 /* OSS compat, unused on NetBSD */
+#define MIXF_MONVOL	0x00002000 /* OSS compat, unused on NetBSD */
+#define MIXF_WIDE	0x00004000 /* OSS compat, unused on NetBSD */
+#define MIXF_DESCR	0x00008000 /* OSS compat, unused on NetBSD */
+#define MIXF_DISABLED	0x00010000 /* OSS compat, unused on NetBSD */
+
+/* None of the mixer capabilities are set on NetBSD. */
+#define MIXER_CAP_VIRTUAL	0x00000001	/* Virtual device */
+#define MIXER_CAP_LAYOUT_B	0x00000002	/* "Internal use only" */
+#define MIXER_CAP_NARROW	0x00000004	/* "Conserve screen space" */
+
+#define OSS_ID_SIZE		16
+typedef char oss_id_t[OSS_ID_SIZE];
+#define OSS_DEVNODE_SIZE	32
+typedef char oss_devnode_t[OSS_DEVNODE_SIZE];
+#define OSS_HANDLE_SIZE		32
+typedef char oss_handle_t[OSS_HANDLE_SIZE];
+
+typedef struct oss_mixext_root {
+	oss_id_t id;
+	char name[48];
+} oss_mixext_root;
+
+typedef struct oss_mixerinfo {
+	int dev;
+	oss_id_t id;
+	char name[32];	
+	int modify_counter;
+	int card_number;
+	int port_number;
+	oss_handle_t handle;
+	int magic;		/* "Reserved for internal use" */
+	int enabled;
+	int caps;
+	int flags;		/* "Reserved for internal use" */
+	int nrext;
+	int priority;
+	oss_devnode_t devnode;
+	int legacy_device;
+	int filler[245];
+} oss_mixerinfo;
+
+typedef struct oss_mixer_value {
+	int dev;	/* Set by caller */
+	int ctrl;	/* Set by caller */
+	int value;
+	int flags;	/* Reserved for "future use" */
+	int timestamp;
+	int filler[8];	/* Reserved for "future use" */
+} oss_mixer_value;
+
+#define OSS_ENUM_MAXVALUE	255
+#define OSS_ENUM_STRINGSIZE	3000
+
+typedef struct oss_mixer_enuminfo {
+	int dev;	/* Set by caller */
+	int ctrl;	/* Set by caller */
+	int nvalues;
+	int version;
+	short strindex[OSS_ENUM_MAXVALUE];
+	char strings[OSS_ENUM_STRINGSIZE];
+} oss_mixer_enuminfo;
+
+typedef struct oss_mixext {
+	int dev;
+	int ctrl;
+	int type;
+	int maxvalue;
+	int minvalue;
+	int flags;
+	oss_id_t id;
+	int parent;
+	int dummy;
+	int timestamp;
+	char data[64];
+	unsigned char enum_present[32];
+	int control_no;
+	unsigned int desc;
+	char extname[32];
+	int update_counter;
+	int rgbcolor;
+	int filler[6];
+} oss_mixext;
+
 #define ioctl _oss_ioctl
 /*
  * If we already included <sys/ioctl.h>, then we define our own prototype,

Reply via email to