---  dev/sound/pcm/buffer.c.orig
+++  dev/sound/pcm/buffer.c
@@ -117,6 +117,11 @@
 		free(b->tmpbuf, M_DEVBUF);
 	b->tmpbuf = NULL;
 
+	if (b->shadbuf)
+		free(b->shadbuf, M_DEVBUF);
+	b->shadbuf = NULL;
+	b->sl = 0;
+
 	if (b->dmamap)
 		bus_dmamap_unload(b->dmatag, b->dmamap);
 
@@ -168,6 +173,7 @@
 sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz)
 {
         u_int8_t *buf, *tmpbuf, *f1, *f2;
+	u_int8_t *shadbuf, *f3;
         unsigned int bufsize;
 	int ret;
 
@@ -189,6 +195,15 @@
 		ret = ENOMEM;
 		goto out;
 	}
+
+	shadbuf = malloc(bufsize, M_DEVBUF, M_WAITOK);
+	if (shadbuf == NULL) {
+		free(buf, M_DEVBUF);
+		free(tmpbuf, M_DEVBUF);
+		ret = ENOMEM;
+		goto out;
+	}
+
 	chn_lock(b->channel);
 
 	b->blkcnt = blkcnt;
@@ -199,6 +214,9 @@
 	f2 = b->tmpbuf;
 	b->buf = buf;
 	b->tmpbuf = tmpbuf;
+	f3 = b->shadbuf;
+	b->shadbuf = shadbuf;
+	b->sl = bufsize;
 
 	sndbuf_reset(b);
 
@@ -207,6 +225,8 @@
 		free(f1, M_DEVBUF);
       	if (f2)
 		free(f2, M_DEVBUF);
+	if (f3)
+		free(f3, M_DEVBUF);
 
 	ret = 0;
 out:
@@ -214,6 +234,15 @@
 	return ret;
 }
 
+/**
+ * @brief Zero out space in buffer free area
+ *
+ * This function clears a chunk of @c length bytes in the buffer free area
+ * (i.e., where the next write will be placed).
+ *
+ * @param b		buffer context
+ * @param length	number of bytes to blank
+ */
 void
 sndbuf_clear(struct snd_dbuf *b, unsigned int length)
 {
@@ -241,6 +270,11 @@
 	}
 }
 
+/**
+ * @brief Zap buffer contents, resetting "ready area" fields
+ *
+ * @param b	buffer context
+ */
 void
 sndbuf_fillsilence(struct snd_dbuf *b)
 {
@@ -260,7 +294,24 @@
 	b->rl = b->bufsize;
 }
 
+/**
+ * @brief Reset buffer w/o flushing statistics
+ *
+ * This function just zeroes out buffer contents and sets the "ready length"
+ * to zero.  This was originally to facilitate minimal playback interruption
+ * (i.e., dropped samples) in SNDCTL_DSP_SILENCE/SKIP ioctls.
+ *
+ * @param b	buffer context
+ */
 void
+sndbuf_softreset(struct snd_dbuf *b)
+{
+	b->rl = 0;
+	if (b->buf && b->bufsize > 0)
+		sndbuf_clear(b, b->bufsize);
+}
+
+void
 sndbuf_reset(struct snd_dbuf *b)
 {
 	b->hp = 0;
@@ -272,6 +323,7 @@
 	b->xrun = 0;
 	if (b->buf && b->bufsize > 0)
 		sndbuf_clear(b, b->bufsize);
+	sndbuf_clearshadow(b);
 }
 
 u_int32_t
@@ -493,6 +545,19 @@
 
 /************************************************************/
 
+/**
+ * @brief Acquire buffer space to extend ready area
+ *
+ * This function extends the ready area length by @c count bytes, and may
+ * optionally copy samples from another location stored in @c from.  The
+ * counter @c snd_dbuf::total is also incremented by @c count bytes.
+ *
+ * @param b	audio buffer
+ * @param from	sample source (optional)
+ * @param count	number of bytes to acquire
+ *
+ * @retval 0	Unconditional
+ */
 int
 sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count)
 {
@@ -516,6 +581,20 @@
 	return 0;
 }
 
+/**
+ * @brief Dispose samples from channel buffer, increasing size of ready area
+ *
+ * This function discards samples from the supplied buffer by advancing the
+ * ready area start pointer and decrementing the ready area length.  If 
+ * @c to is not NULL, then the discard samples will be copied to the location
+ * it points to.
+ *
+ * @param b	PCM channel sound buffer
+ * @param to	destination buffer (optional)
+ * @param count	number of bytes to discard
+ *
+ * @returns 0 unconditionally
+ */
 int
 sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count)
 {
@@ -592,3 +671,49 @@
 		b->flags |= flags;
 }
 
+/**
+ * @brief Clear the shadow buffer by filling with samples equal to zero.
+ *
+ * @param b buffer to clear
+ */
+void
+sndbuf_clearshadow(struct snd_dbuf *b)
+{
+	KASSERT(b != NULL, ("b is a null pointer"));
+	KASSERT(b->sl >= 0, ("illegal shadow length"));
+
+	if ((b->shadbuf != NULL) && (b->sl > 0)) {
+		if (b->fmt & AFMT_SIGNED)
+			memset(b->shadbuf, 0x00, b->sl);
+		else
+			memset(b->shadbuf, 0x80, b->sl);
+	}
+}
+
+#ifdef OSSV4_EXPERIMENT
+/**
+ * @brief Return peak value from samples in buffer ready area.
+ *
+ * Peak ranges from 0-32767.  If channel is monaural, most significant 16
+ * bits will be zero.  For now, only expects to work with 1-2 channel
+ * buffers.
+ *
+ * @note  Currently only operates with linear PCM formats.
+ *
+ * @param b buffer to analyze
+ * @param lpeak pointer to store left peak value
+ * @param rpeak pointer to store right peak value
+ */
+void
+sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp)
+{
+	u_int32_t lpeak, rpeak;
+
+	lpeak = 0;
+	rpeak = 0;
+
+	/**
+	 * @todo fill this in later
+	 */
+}
+#endif
---  dev/sound/pcm/buffer.h.orig
+++  dev/sound/pcm/buffer.h
@@ -38,6 +38,8 @@
 struct snd_dbuf {
 	device_t dev;
 	u_int8_t *buf, *tmpbuf;
+	u_int8_t *shadbuf; /**< shadow buffer used w/ S_D_SILENCE/SKIP */
+	volatile int sl; /**< shadbuf ready length in # of bytes */
 	unsigned int bufsize, maxsize;
 	volatile int dl; /* transfer size */
 	volatile int rp; /* pointers to the ready area */
@@ -70,6 +72,8 @@
 void sndbuf_reset(struct snd_dbuf *b);
 void sndbuf_clear(struct snd_dbuf *b, unsigned int length);
 void sndbuf_fillsilence(struct snd_dbuf *b);
+void sndbuf_softreset(struct snd_dbuf *b);
+void sndbuf_clearshadow(struct snd_dbuf *b);
 
 u_int32_t sndbuf_getfmt(struct snd_dbuf *b);
 int sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt);
@@ -117,3 +121,7 @@
 void sndbuf_dma(struct snd_dbuf *b, int go);
 int sndbuf_dmaptr(struct snd_dbuf *b);
 void sndbuf_dmabounce(struct snd_dbuf *b);
+
+#ifdef OSSV4_EXPERIMENT
+void sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp);
+#endif
---  dev/sound/pcm/channel.c.orig
+++  dev/sound/pcm/channel.c
@@ -68,6 +68,24 @@
 SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW,
 	&report_soft_formats, 1, "report software-emulated formats");
 
+/**
+ * @brief Channel sync group lock
+ *
+ * Clients should acquire this lock @b without holding any channel locks
+ * before touching syncgroups or the main syncgroup list.
+ */
+struct mtx snd_pcm_syncgroups_mtx;
+MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF);
+/**
+ * @brief syncgroups' master list
+ *
+ * Each time a channel syncgroup is created, it's added to this list.  This
+ * list should only be accessed with @sa snd_pcm_syncgroups_mtx held.
+ *
+ * See SNDCTL_DSP_SYNCGROUP for more information.
+ */
+struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(head);
+
 static int chn_buildfeeder(struct pcm_channel *c);
 
 static void
@@ -87,14 +105,23 @@
 		c->lock = snd_mtxcreate(c->name, "pcm fake channel");
 		break;
 	}
+
+	cv_init(&c->cv, c->name);
 }
 
 static void
 chn_lockdestroy(struct pcm_channel *c)
 {
 	snd_mtxfree(c->lock);
+	cv_destroy(&c->cv);
 }
 
+/**
+ * @brief Determine channel is ready for I/O
+ *
+ * @retval 1 = ready for I/O
+ * @retval 0 = not ready for I/O
+ */
 static int
 chn_polltrigger(struct pcm_channel *c)
 {
@@ -112,8 +139,8 @@
 #if 0
 		lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1;
 #endif
-		lim = 1;
-		return (amt >= lim)? 1 : 0;
+		lim = c->lw;
+		return (amt >= lim) ? 1 : 0;
 	}
 	return 0;
 }
@@ -310,12 +337,25 @@
 
 	ret = 0;
 	count = hz;
+
 	while (!ret && (buf->uio_resid > 0) && (count > 0)) {
 		sz = sndbuf_getfree(bs);
 		if (sz == 0) {
 			if (c->flags & CHN_F_NBIO)
 				ret = EWOULDBLOCK;
-			else {
+			else if (c->flags & CHN_F_NOTRIGGER) {
+				/**
+				 * @todo Evaluate whether EAGAIN is truly desirable.
+				 * 	 4Front drivers behave like this, but I'm
+				 * 	 not sure if it at all violates the "write
+				 * 	 should be allowed to block" model.
+				 *
+				 * 	 The idea is that, while set with CHN_F_NOTRIGGER,
+				 * 	 a channel isn't playing, *but* without this we
+				 * 	 end up with "interrupt timeout / channel dead".
+				 */
+				ret = EAGAIN;
+			} else {
 				timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs));
 				if (timeout < 1)
 					timeout = 1;
@@ -783,6 +823,7 @@
 		chn_resetbuf(c);
 		r = CHANNEL_RESETDONE(c->methods, c->devinfo);
 	}
+	chn_syncdestroy(c);
 	return r;
 }
 
@@ -829,6 +870,7 @@
 	c->bufsoft = bs;
 	c->flags = 0;
 	c->feederflags = 0;
+	c->sm = NULL;
 
 	ret = ENODEV;
 	CHN_UNLOCK(c); /* XXX - Unlock for CHANNEL_INIT() malloc() call */
@@ -853,6 +895,19 @@
 	if (ret)
 		goto out;
 
+	/**
+	 * @todo Should this be moved somewhere else?  The primary buffer
+	 * 	 is allocated by the driver or via DMA map setup, and tmpbuf
+	 * 	 seems to only come into existence in sndbuf_resize().
+	 */
+	if (c->direction == PCMDIR_PLAY) {
+		bs->sl = sndbuf_getmaxsize(bs);
+		bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_NOWAIT);
+		if (bs->shadbuf == NULL) {
+			ret = ENOMEM;
+			goto out;
+		}
+	}
 
 out:
 	CHN_UNLOCK(c);
@@ -888,6 +943,7 @@
 	c->flags |= CHN_F_DEAD;
 	sndbuf_destroy(bs);
 	sndbuf_destroy(b);
+	chn_syncdestroy(c);
 	chn_lockdestroy(c);
 	return 0;
 }
@@ -1195,6 +1251,12 @@
 			goto out1;
 	}
 
+	/*
+	 * OSSv4 docs: "By default OSS will set the low water level equal
+	 * to the fragment size which is optimal in most cases."
+	 */
+	c->lw = sndbuf_getblksz(bs);
+
 	chn_resetbuf(c);
 out1:
 	KASSERT(sndbuf_getsize(bs) ==  0 ||
@@ -1243,6 +1305,17 @@
 	return ret;
 }
 
+/**
+ * @brief Queries sound driver for sample-aligned hardware buffer pointer index
+ *
+ * This function obtains the hardware pointer location, then aligns it to
+ * the current bytes-per-sample value before returning.  (E.g., a channel
+ * running in 16 bit stereo mode would require 4 bytes per sample, so a
+ * hwptr value ranging from 32-35 would be returned as 32.)
+ *
+ * @param c	PCM channel context	
+ * @returns 	sample-aligned hardware buffer pointer index
+ */
 int
 chn_getptr(struct pcm_channel *c)
 {
@@ -1501,7 +1574,61 @@
 	return 0;
 }
 
+/**
+ * @brief Fetch array of supported discrete sample rates
+ *
+ * Wrapper for CHANNEL_GETRATES.  Please see channel_if.m:getrates() for
+ * detailed information.
+ *
+ * @note If the operation isn't supported, this function will just return 0
+ *       (no rates in the array), and *rates will be set to NULL.  Callers
+ *       should examine rates @b only if this function returns non-zero.
+ *
+ * @param c	pcm channel to examine
+ * @param rates	pointer to array of integers; rate table will be recorded here
+ *
+ * @return number of rates in the array pointed to be @c rates
+ */
+int
+chn_getrates(struct pcm_channel *c, int **rates)
+{
+	KASSERT(rates != NULL, ("rates is null"));
+	CHN_LOCKASSERT(c);
+	return CHANNEL_GETRATES(c->methods, c->devinfo, rates);
+}
+
+/**
+ * @brief Remove channel from a sync group, if there is one.
+ *
+ * This is meant to be called before destroying the channel.
+ *
+ * @param c channel facing imminent destruction
+ */
 void
+chn_syncdestroy(struct pcm_channel *c)
+{
+	struct pcmchan_syncmember *sm;
+	struct pcmchan_syncgroup *sg;
+
+	if (c->sm == NULL)
+		return;
+
+	sm = c->sm;
+	sg = sm->parent;
+	c->sm = NULL;
+
+	KASSERT(sg != NULL, ("syncmember has null parent"));
+
+	SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link);
+	free(sm, M_DEVBUF);
+
+	if (SLIST_EMPTY(&sg->members)) {
+		SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
+		free(sg, M_DEVBUF);
+	}
+}
+
+void
 chn_lock(struct pcm_channel *c)
 {
 	CHN_LOCK(c);
@@ -1512,3 +1639,12 @@
 {
 	CHN_UNLOCK(c);
 }
+
+#ifdef OSSV4_EXPERIMENT
+int
+chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak)
+{
+	CHN_LOCKASSERT(c);
+	return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak);
+}
+#endif
---  dev/sound/pcm/channel.h.orig
+++  dev/sound/pcm/channel.h
@@ -37,6 +37,36 @@
 	u_int32_t caps;
 };
 
+/* Forward declarations */
+struct pcm_channel;
+struct pcmchan_syncgroup;
+struct pcmchan_syncmember;
+
+extern struct mtx snd_pcm_syncgroups_mtx;
+extern SLIST_HEAD(pcm_synclist, pcmchan_syncgroup) snd_pcm_syncgroups;
+
+#define PCM_SG_LOCK()	mtx_lock(&snd_pcm_syncgroups_mtx)
+#define PCM_SG_TRYLOCK() mtx_trylock(&snd_pcm_syncgroups_mtx)
+#define PCM_SG_UNLOCK()	mtx_unlock(&snd_pcm_syncgroups_mtx)
+
+/**
+ * @brief Specifies an audio device sync group
+ */
+struct pcmchan_syncgroup {
+	SLIST_ENTRY(pcmchan_syncgroup) link;
+	SLIST_HEAD(, pcmchan_syncmember) members;
+	int id; /**< Group identifier; set to address of group. */
+};
+
+/**
+ * @brief Specifies a container for members of a sync group
+ */
+struct pcmchan_syncmember {
+	SLIST_ENTRY(pcmchan_syncmember) link;
+	struct pcmchan_syncgroup *parent; /**< group head */
+	struct pcm_channel *ch;
+};
+
 #define	CHN_NAMELEN	32
 struct pcm_channel {
 	kobj_t methods;
@@ -63,6 +93,33 @@
 	device_t dev;
 	char name[CHN_NAMELEN];
 	struct mtx *lock;
+	/**
+	 * Increment,decrement this around operations that temporarily yield
+	 * lock.
+	 */
+	unsigned int inprog;
+	/**
+	 * Special channel operations should examine @c inprog after acquiring
+	 * lock.  If zero, operations may continue.  Else, thread should
+	 * wait on this cv for previous operation to finish.
+	 */
+	struct cv cv;
+	/**
+	 * Low water mark for select()/poll().
+	 *
+	 * This is initialized to the channel's fragment size, and will be
+	 * overwritten if a new fragment size is set.  Users may alter this
+	 * value directly with the @c SNDCTL_DSP_LOW_WATER ioctl.
+	 */
+	unsigned int lw;
+	/**
+	 * If part of a sync group, this will point to the syncmember
+	 * container.
+	 */
+	struct pcmchan_syncmember *sm;
+#ifdef OSSV4_EXPERIMENT
+	u_int16_t lpeak, rpeak;	/**< Peak value from 0-32767. */
+#endif
 	SLIST_HEAD(, pcmchan_children) children;
 };
 
@@ -102,13 +159,22 @@
 void chn_lock(struct pcm_channel *c);
 void chn_unlock(struct pcm_channel *c);
 
+int chn_getrates(struct pcm_channel *c, int **rates);
+void chn_syncdestroy(struct pcm_channel *c);
+
+#ifdef OSSV4_EXPERIMENT
+int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak);
+#endif
+
 #ifdef	USING_MUTEX
 #define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock))
 #define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock))
+#define CHN_TRYLOCK(c) mtx_trylock((struct mtx *)((c)->lock))
 #define CHN_LOCKASSERT(c) mtx_assert((struct mtx *)((c)->lock), MA_OWNED)
 #else
 #define CHN_LOCK(c)
 #define CHN_UNLOCK(c)
+#define CHN_TRYLOCK(c)
 #define CHN_LOCKASSERT(c)
 #endif
 
---  dev/sound/pcm/channel_if.m.orig
+++  dev/sound/pcm/channel_if.m
@@ -70,6 +70,19 @@
 		return 0;
 	}
 
+	static int
+	channel_nogetpeaks(kobj_t obj, void *data, int *lpeak, int *rpeak)
+	{
+		return -1;
+	}
+
+	static int
+	channel_nogetrates(kobj_t obj, void *data, int **rates)
+	{
+		*rates = NULL;
+		return 0;
+	}
+
 };
 
 METHOD void* init {
@@ -140,3 +153,54 @@
 	void *data;
 	u_int32_t changed;
 } DEFAULT channel_nonotify;
+
+/**
+ * @brief Retrieve channel peak values
+ *
+ * This function is intended to obtain peak volume values for samples
+ * played/recorded on a channel.  Values are on a linear scale from 0 to
+ * 32767.  If the channel is monaural, a single value should be recorded
+ * in @c lpeak.
+ *
+ * If hardware support isn't available, the SNDCTL_DSP_GET[IO]PEAKS
+ * operation should return EINVAL.  However, we may opt to provide
+ * software support that the user may toggle via sysctl/mixext.
+ *
+ * @param obj	standard kobj object (usually @c channel->methods)
+ * @param data	driver-specific data (usually @c channel->devinfo)
+ * @param lpeak	pointer to store left peak level
+ * @param rpeak	pointer to store right peak level
+ *
+ * @retval -1	Error; usually operation isn't supported.
+ * @retval 0	success
+ */
+METHOD int getpeaks {
+	kobj_t obj;
+	void *data;
+	int *lpeak;
+	int *rpeak;
+} DEFAULT channel_nogetpeaks;
+
+/**
+ * @brief Retrieve discrete supported sample rates
+ *
+ * Some cards operate at fixed rates, and this call is intended to retrieve
+ * those rates primarily for when in-kernel rate adjustment is undesirable
+ * (e.g., application wants direct DMA access after setting a channel to run
+ * "uncooked").
+ *
+ * The parameter @c rates is a double pointer which will be reset to
+ * point to an array of supported sample rates.  The number of elements
+ * in the array is returned to the caller.
+ *
+ * @param obj	standard kobj object (usually @c channel->methods)
+ * @param data	driver-specific data (usually @c channel->devinfo)
+ * @param rates	rate array pointer
+ *
+ * @return Number of rates in the array
+ */
+METHOD int getrates {
+	kobj_t obj;
+	void *data;
+	int **rates;
+} DEFAULT channel_nogetrates;
---  dev/sound/pcm/dsp.c.orig
+++  dev/sound/pcm/dsp.c
@@ -58,6 +58,20 @@
 static eventhandler_tag dsp_ehtag;
 #endif
 
+static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group);
+static int dsp_oss_syncstart(int sg_id);
+static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy);
+#ifdef OSSV4_EXPERIMENT
+static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled);
+static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map);
+static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map);
+static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label);
+static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label);
+static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song);
+static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song);
+static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name);
+#endif
+
 static struct snddev_info *
 dsp_get_info(struct cdev *dev)
 {
@@ -394,7 +408,17 @@
 	}
 	if (!(wrch->flags & CHN_F_RUNNING))
 		wrch->flags |= CHN_F_RUNNING;
+
+	/*
+	 * Chn_write() must give up channel lock in order to copy bytes from
+	 * userland, so up the "in progress" counter to make sure someone
+	 * else doesn't come along and muss up the buffer.
+	 */
+	++wrch->inprog;
 	ret = chn_write(wrch, buf);
+	--wrch->inprog;
+	cv_signal(&wrch->cv);
+
 	relchns(i_dev, rdch, wrch, SD_F_PRIO_WR);
 
 	return ret;
@@ -407,6 +431,9 @@
 	struct snddev_info *d;
 	int kill;
     	int ret = 0, *arg_i = (int *)arg, tmp;
+	int xcmd;
+
+	xcmd = 0;
 
 	/*
 	 * this is an evil hack to allow broken apps to perform mixer ioctls
@@ -424,6 +451,20 @@
 			return EBADF;
 	}
 
+	/*
+	 * Certain ioctls may be made on any type of device (audio, mixer,
+	 * and MIDI).
+	 */
+	if (IOCGROUP(cmd) == 'X') {
+		switch(cmd) {
+		case SNDCTL_MIXERINFO:
+			return mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg);
+			break;
+		default:
+			return EINVAL;
+		}
+	}
+
 	getchns(i_dev, &rdch, &wrch, 0);
 
 	kill = 0;
@@ -1032,6 +1073,318 @@
 			dsp_set_flags(i_dev, dsp_get_flags(i_dev)^SD_F_SIMPLEX);
 		break;
 
+	case SNDCTL_SYSINFO:
+		sound_oss_sysinfo((oss_sysinfo *)arg);
+		break;
+	case SNDCTL_AUDIOINFO:
+		ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg);
+		break;
+
+	/*
+	 * The following four ioctls are simple wrappers around mixer_ioctl
+	 * with no further processing.  xcmd is short for "translated
+	 * command".
+	 */
+	case SNDCTL_DSP_GETRECVOL:
+		if (xcmd == 0)
+			xcmd = SOUND_MIXER_READ_RECLEV;
+		/* FALLTHROUGH */
+	case SNDCTL_DSP_SETRECVOL:
+		if (xcmd == 0)
+			xcmd = SOUND_MIXER_WRITE_RECLEV;
+		/* FALLTHROUGH */
+	case SNDCTL_DSP_GETPLAYVOL:
+		if (xcmd == 0)
+			xcmd = SOUND_MIXER_READ_PCM;
+		/* FALLTHROUGH */
+	case SNDCTL_DSP_SETPLAYVOL:
+		if (xcmd == 0)
+			xcmd = SOUND_MIXER_WRITE_PCM;
+
+		if (d->mixer_dev != NULL)
+			ret = mixer_ioctl(d->mixer_dev, xcmd, arg, -1, td);
+		else
+			ret = ENOTSUP;
+		break;
+
+	case SNDCTL_DSP_GET_RECSRC_NAMES:
+	case SNDCTL_DSP_GET_RECSRC:
+	case SNDCTL_DSP_SET_RECSRC:
+		if (d->mixer_dev != NULL)
+			ret = mixer_ioctl(d->mixer_dev, cmd, arg, -1, td);
+		else
+			ret = ENOTSUP;
+		break;
+
+	/*
+	 * The following 3 ioctls aren't very useful at the moment.  For
+	 * now, only a single channel is associated with a cdev (/dev/dspN
+	 * instance), so there's only a single output routing to use (i.e.,
+	 * the wrch bound to this cdev).
+	 */
+	case SNDCTL_DSP_GET_PLAYTGT_NAMES:
+		{
+			oss_mixer_enuminfo *ei;
+			ei = (oss_mixer_enuminfo *)arg;
+			ei->dev = 0;
+			ei->ctrl = 0;
+			ei->version = 0; /* static for now */
+			ei->strindex[0] = 0;
+
+			if (wrch != NULL) {
+				ei->nvalues = 1;
+				strlcpy(ei->strings, wrch->name,
+					sizeof(ei->strings));
+			} else {
+				ei->nvalues = 0;
+				ei->strings[0] = '\0';
+			}
+		}
+		break;
+	case SNDCTL_DSP_GET_PLAYTGT:
+	case SNDCTL_DSP_SET_PLAYTGT:	/* yes, they are the same for now */
+		/*
+		 * Re: SET_PLAYTGT
+		 *   OSSv4: "The value that was accepted by the device will
+		 *   be returned back in the variable pointed by the
+		 *   argument."
+		 */
+		if (wrch != NULL)
+			*arg_i = 0;
+		else
+			ret = EINVAL;
+		break;
+
+	case SNDCTL_DSP_SILENCE:
+	/*
+	 * Flush the software (pre-feed) buffer, but try to minimize playback
+	 * interruption.  (I.e., record unplayed samples with intent to
+	 * restore by SNDCTL_DSP_SKIP.) Intended for application "pause"
+	 * functionality.
+	 */
+		if (wrch == NULL)
+			ret = EINVAL;
+		else {
+			struct snd_dbuf *bs;
+			CHN_LOCK(wrch);
+			while (wrch->inprog != 0)
+				cv_wait(&wrch->cv, wrch->lock);
+			bs = wrch->bufsoft;
+			if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) {
+				bs->sl = sndbuf_getready(bs);
+				sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs));
+				sndbuf_fillsilence(bs);
+				chn_start(wrch, 0);
+			}
+			CHN_UNLOCK(wrch);
+		}
+		break;
+
+	case SNDCTL_DSP_SKIP:
+	/*
+	 * OSSv4 docs: "This ioctl call discards all unplayed samples in the
+	 * playback buffer by moving the current write position immediately
+	 * before the point where the device is currently reading the samples."
+	 */
+		if (wrch == NULL)
+			ret = EINVAL;
+		else {
+			struct snd_dbuf *bs;
+			CHN_LOCK(wrch);
+			while (wrch->inprog != 0)
+				cv_wait(&wrch->cv, wrch->lock);
+			bs = wrch->bufsoft;
+			if ((bs->shadbuf != NULL) && (bs->sl > 0)) {
+				sndbuf_softreset(bs);
+				sndbuf_acquire(bs, bs->shadbuf, bs->sl);
+				bs->sl = 0;
+				chn_start(wrch, 0);
+			}
+			CHN_UNLOCK(wrch);
+		}
+		break;
+
+	case SNDCTL_DSP_CURRENT_OPTR:
+	case SNDCTL_DSP_CURRENT_IPTR:
+	/**
+	 * @note Changing formats resets the buffer counters, which differs
+	 * 	 from the 4Front drivers.  However, I don't expect this to be
+	 * 	 much of a problem.
+	 *
+	 * @note In a test where @c CURRENT_OPTR is called immediately after write
+	 * 	 returns, this driver is about 32K samples behind whereas
+	 * 	 4Front's is about 8K samples behind.  Should determine source
+	 * 	 of discrepancy, even if only out of curiosity.
+	 *
+	 * @todo Actually test SNDCTL_DSP_CURRENT_IPTR.
+	 */
+		chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch;
+		if (chn == NULL) 
+			ret = EINVAL;
+		else {
+			struct snd_dbuf *bs;
+			/* int tmp; */
+
+			oss_count_t *oc = (oss_count_t *)arg;
+
+			CHN_LOCK(chn);
+			bs = chn->bufsoft;
+#if 0
+			tmp = (sndbuf_getsize(b) + chn_getptr(chn) - sndbuf_gethwptr(b)) % sndbuf_getsize(b);
+			oc->samples = (sndbuf_gettotal(b) + tmp) / sndbuf_getbps(b);
+			oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getbps(b);
+#else
+			oc->samples = sndbuf_gettotal(bs) / sndbuf_getbps(bs);
+			oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getbps(bs);
+#endif
+			CHN_UNLOCK(chn);
+		}
+		break;
+
+	case SNDCTL_DSP_HALT_OUTPUT:
+	case SNDCTL_DSP_HALT_INPUT:
+		chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch;
+		if (chn == NULL)
+			ret = EINVAL;
+		else {
+			CHN_LOCK(chn);
+			chn_abort(chn);
+			CHN_UNLOCK(chn);
+		}
+		break;
+
+	case SNDCTL_DSP_LOW_WATER:
+	/*
+	 * Set the number of bytes required to attract attention by
+	 * select/poll.
+	 */
+		if (wrch != NULL) {
+			CHN_LOCK(wrch);
+			wrch->lw = (*arg_i > 1) ? *arg_i : 1;
+			CHN_UNLOCK(wrch);
+		}
+		if (rdch != NULL) {
+			CHN_LOCK(rdch);
+			rdch->lw = (*arg_i > 1) ? *arg_i : 1;
+			CHN_UNLOCK(rdch);
+		}
+		break;
+
+	case SNDCTL_DSP_GETERROR:
+	/*
+	 * OSSv4 docs:  "All errors and counters will automatically be
+	 * cleared to zeroes after the call so each call will return only
+	 * the errors that occurred after the previous invocation. ... The
+	 * play_underruns and rec_overrun fields are the only usefull fields
+	 * returned by OSS 4.0."
+	 */
+		{
+			audio_errinfo *ei = (audio_errinfo *)arg;
+
+			bzero((void *)ei, sizeof(*ei));
+
+			if (wrch != NULL) {
+				CHN_LOCK(wrch);
+				ei->play_underruns = wrch->xruns;
+				wrch->xruns = 0;
+				CHN_UNLOCK(wrch);
+			}
+			if (rdch != NULL) {
+				CHN_LOCK(rdch);
+				ei->rec_overruns = rdch->xruns;
+				rdch->xruns = 0;
+				CHN_UNLOCK(rdch);
+			}
+		}
+		break;
+
+	case SNDCTL_DSP_SYNCGROUP:
+		ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg);
+		break;
+
+	case SNDCTL_DSP_SYNCSTART:
+		ret = dsp_oss_syncstart(*arg_i);
+		break;
+
+	case SNDCTL_DSP_POLICY:
+		ret = dsp_oss_policy(wrch, rdch, *arg_i);
+		break;
+
+#ifdef	OSSV4_EXPERIMENT
+	/*
+	 * XXX The following ioctls are not yet supported and just return
+	 * EINVAL.
+	 */
+	case SNDCTL_DSP_GETOPEAKS:
+	case SNDCTL_DSP_GETIPEAKS:
+		chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch;
+		if (chn == NULL)
+			ret = EINVAL;
+		else {
+			oss_peaks_t *op = (oss_peaks_t *)arg;
+			int lpeak, rpeak;
+
+			CHN_LOCK(chn);
+			ret = chn_getpeaks(chn, &lpeak, &rpeak);
+			if (ret == -1)
+				ret = EINVAL;
+			else {
+				(*op)[0] = lpeak;
+				(*op)[1] = rpeak;
+			}
+			CHN_UNLOCK(chn);
+		}
+		break;
+
+	case SNDCTL_DSP_COOKEDMODE:
+		ret = dsp_oss_cookedmode(wrch, rdch, *arg_i);
+		break;
+	case SNDCTL_DSP_GET_CHNORDER:
+		ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg);
+		break;
+	case SNDCTL_DSP_SET_CHNORDER:
+		ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg);
+		break;
+	case SNDCTL_GETLABEL:
+		ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg);
+		break;
+	case SNDCTL_SETLABEL:
+		ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg);
+		break;
+	case SNDCTL_GETSONG:
+		ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg);
+		break;
+	case SNDCTL_SETSONG:
+		ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg);
+		break;
+	case SNDCTL_SETNAME:
+		ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg);
+		break;
+#if 0
+	/**
+	 * @note The SNDCTL_CARDINFO ioctl was omitted per 4Front developer
+	 * documentation.  "The usability of this call is very limited. It's
+	 * provided only for completeness of the API. OSS API doesn't have
+	 * any concept of card. Any information returned by this ioctl calld
+	 * is reserved exclusively for the utility programs included in the
+	 * OSS package. Applications should not try to use for this
+	 * information in any ways."
+	 */
+	case SNDCTL_CARDINFO:
+		ret = EINVAL;
+		break;
+	/**
+	 * @note The S/PDIF interface ioctls, @c SNDCTL_DSP_READCTL and
+	 * @c SNDCTL_DSP_WRITECTL have been omitted at the suggestion of
+	 * 4Front Technologies.
+	 */
+	case SNDCTL_DSP_READCTL:
+	case SNDCTL_DSP_WRITECTL:
+		ret = EINVAL;
+		break;
+#endif	/* !0 (explicitly omitted ioctls) */
+
+#endif	/* !OSSV4_EXPERIMENT */
     	case SNDCTL_DSP_MAPINBUF:
     	case SNDCTL_DSP_MAPOUTBUF:
     	case SNDCTL_DSP_SETSYNCRO:
@@ -1206,4 +1559,762 @@
 SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL);
 #endif
 
+/**
+ * @brief Handler for SNDCTL_AUDIOINFO.
+ *
+ * Gathers information about the audio device specified in ai->dev.  If
+ * ai->dev == -1, then this function gathers information about the current
+ * device.  If the call comes in on a non-audio device and ai->dev == -1,
+ * return EINVAL.
+ *
+ * This routine is supposed to go practically straight to the hardware,
+ * getting capabilities directly from the sound card driver, side-stepping
+ * the intermediate channel interface.
+ *
+ * Note, however, that the usefulness of this command is significantly
+ * decreased when requesting info about any device other than the one serving
+ * the request. While each snddev_channel refers to a specific device node,
+ * the converse is *not* true.  Currently, when a sound device node is opened,
+ * the sound subsystem scans for an available audio channel (or channels, if
+ * opened in read+write) and then assigns them to the si_drv[12] private
+ * data fields.  As a result, any information returned linking a channel to
+ * a specific character device isn't necessarily accurate.
+ *
+ * @note
+ * Calling threads must not hold any snddev_info or pcm_channel locks.
+ * 
+ * @param dev		device on which the ioctl was issued
+ * @param ai		ioctl request data container
+ *
+ * @retval 0		success
+ * @retval EINVAL	ai->dev specifies an invalid device
+ *
+ * @todo Verify correctness of Doxygen tags.  ;)
+ */
+int
+dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai)
+{
+	struct snddev_channel *sce;
+	struct pcmchan_caps *caps;
+	struct pcm_channel *ch;
+	struct snddev_info *d;
+	struct cdev *t_cdev;
+	uint32_t fmts;
+	int i, nchan, ret, *rates, minch, maxch;
+
+	/*
+	 * If probing the device that received the ioctl, make sure it's a
+	 * DSP device.  (Users may use this ioctl with /dev/mixer and
+	 * /dev/midi.)
+	 */
+	if ((ai->dev == -1) && (i_dev->si_devsw != &dsp_cdevsw))
+		return EINVAL;
+
+	ch = NULL;
+	t_cdev = NULL;
+	nchan = 0;
+	ret = 0;
+	
+	/*
+	 * Search for the requested audio device (channel).  Start by
+	 * iterating over pcm devices.
+	 */ 
+	for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
+		d = devclass_get_softc(pcm_devclass, i);
+		if (d == NULL)
+			continue;
+
+		/* See the note in function docblock */
+		mtx_assert(d->lock, MA_NOTOWNED);
+		pcm_inprog(d, 1);
+		pcm_lock(d);
+
+		SLIST_FOREACH(sce, &d->channels, link) {
+			ch = sce->channel;
+			mtx_assert(ch->lock, MA_NOTOWNED);
+			CHN_LOCK(ch);
+			if (ai->dev == -1) {
+				if ((ch == i_dev->si_drv1) ||	/* record ch */
+				    (ch == i_dev->si_drv2)) {	/* playback ch */
+					t_cdev = i_dev;
+					goto dspfound;
+				}
+			} else if (ai->dev == nchan) {
+				t_cdev = sce->dsp_devt;
+				goto dspfound;
+			}
+			CHN_UNLOCK(ch);
+			++nchan;
+		}
+
+		pcm_unlock(d);
+		pcm_inprog(d, -1);
+	}
+
+	/* Exhausted the search -- nothing is locked, so return. */
+	return EINVAL;
+
+dspfound:
+	/* Should've found the device, but something isn't right */
+	if (t_cdev == NULL) {
+		ret = EINVAL;
+		goto out;
+	}
+
+	/*
+	 * At this point, the following synchronization stuff has happened:
+	 *   - a specific PCM device is locked and its "in progress
+	 *     operations" counter has been incremented, so be sure to unlock
+	 *     and decrement when exiting;
+	 *   - a specific audio channel has been locked, so be sure to unlock
+	 *     when exiting;
+	 */
+
+	caps = chn_getcaps(ch);
 
+	/*
+	 * With all handles collected, zero out the user's container and
+	 * begin filling in its fields.
+	 */
+	bzero((void *)ai, sizeof(oss_audioinfo));
+
+	ai->dev = nchan;
+	strlcpy(ai->name, ch->name,  sizeof(ai->name));
+
+	if ((ch->flags & CHN_F_BUSY) == 0)
+		ai->busy = 0;
+	else
+		ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ;
+
+	/**
+	 * @note
+	 * @c cmd - OSSv4 docs: "Only supported under Linux at this moment."
+	 * 	Cop-out, I know, but I'll save running around in the process
+	 * 	table for later.  Is there a risk of leaking information?
+	 */
+	ai->pid = ch->pid;
+	
+	/*
+	 * These flags stolen from SNDCTL_DSP_GETCAPS handler.  Note, however,
+	 * that a single channel operates in only one direction, so
+	 * DSP_CAP_DUPLEX is out.
+	 */
+	/**
+	 * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep these in
+	 * 	 pcmchan::caps?
+	 */
+	ai->caps = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER;
+
+	/*
+	 * Collect formats supported @b natively by the device.  Also
+	 * determine min/max channels.  (I.e., mono, stereo, or both?)
+	 *
+	 * If any channel is stereo, maxch = 2;
+	 * if all channels are stereo, minch = 2, too;
+	 * if any channel is mono, minch = 1;
+	 * and if all channels are mono, maxch = 1.
+	 */
+	minch = 0;
+	maxch = 0;
+	fmts = 0;
+	for (i = 0; caps->fmtlist[i]; i++) {
+		fmts |= caps->fmtlist[i];
+		if (caps->fmtlist[i] & AFMT_STEREO) {
+			minch = (minch == 0) ? 2 : minch;
+			maxch = 2;
+		} else {
+			minch = 1;
+			maxch = (maxch == 0) ? 1 : maxch;
+		}
+	}
+
+	if (ch->direction == PCMDIR_PLAY)
+		ai->oformats = fmts;
+	else
+		ai->iformats = fmts;
+
+	/**
+	 * @note
+	 * @c magic - OSSv4 docs: "Reserved for internal use by OSS."
+	 *
+	 * @par
+	 * @c card_number - OSSv4 docs: "Number of the sound card where this
+	 * 	device belongs or -1 if this information is not available.
+	 * 	Applications should normally not use this field for any
+	 * 	purpose."
+	 */
+	ai->card_number = -1;
+	/**
+	 * @todo @c song_name - depends first on SNDCTL_[GS]ETSONG
+	 * @todo @c label - depends on SNDCTL_[GS]ETLABEL
+	 * @todo @c port_number - routing information?
+	 */
+	ai->port_number = -1;
+	ai->mixer_dev = (d->mixer_dev != NULL) ? PCMUNIT(d->mixer_dev) : -1;
+	/**
+	 * @note
+	 * @c real_device - OSSv4 docs:  "Obsolete."
+	 */
+	ai->real_device = -1;
+	strlcpy(ai->devnode, t_cdev->si_name, sizeof(ai->devnode));
+	ai->enabled = device_is_attached(d->dev) ? 1 : 0;
+	/**
+	 * @note
+	 * @c flags - OSSv4 docs: "Reserved for future use."
+	 *
+	 * @note
+	 * @c binding - OSSv4 docs: "Reserved for future use."
+	 *
+	 * @todo @c handle - haven't decided how to generate this yet; bus,
+	 * 	vendor, device IDs?
+	 */
+	ai->min_rate = caps->minspeed;
+	ai->max_rate = caps->maxspeed;
+
+	ai->min_channels = minch;
+	ai->max_channels = maxch;
+
+	ai->nrates = chn_getrates(ch, &rates);
+	if (ai->nrates > MAX_SAMPLE_RATES)
+		ai->nrates = MAX_SAMPLE_RATES;
+
+	for (i = 0; i < ai->nrates; i++)
+		ai->rates[i] = rates[i];
+
+out:
+	CHN_UNLOCK(ch);
+	pcm_unlock(d);
+	pcm_inprog(d, -1);
+
+	return ret;
+}
+
+/**
+ * @brief Assigns a PCM channel to a sync group.
+ *
+ * Sync groups are used to enable audio operations on multiple devices
+ * simultaneously.  They may be used with any number of devices and may
+ * span across applications.  Devices are added to groups with
+ * the SNDCTL_DSP_SYNCGROUP ioctl, and operations are triggered with the
+ * SNDCTL_DSP_SYNCSTART ioctl.
+ *
+ * If the @c id field of the @c group parameter is set to zero, then a new
+ * sync group is created.  Otherwise, wrch and rdch (if set) are added to
+ * the group specified.
+ *
+ * @todo As far as memory allocation, should we assume that things are
+ * 	 okay and allocate with M_WAITOK before acquiring channel locks,
+ * 	 freeing later if not?
+ *
+ * @param wrch	output channel associated w/ device (if any)
+ * @param rdch	input channel associated w/ device (if any)
+ * @param group Sync group parameters
+ *
+ * @retval 0		success
+ * @retval non-zero	error to be propagated upstream
+ */
+static int
+dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group)
+{
+	struct pcmchan_syncmember *smrd, *smwr;
+	struct pcmchan_syncgroup *sg;
+	int ret;
+
+	smrd = NULL;
+	smwr = NULL;
+	sg = NULL;
+	ret = 0;
+
+	PCM_SG_LOCK();
+
+	/*
+	 * - Insert channel(s) into group's member list.
+	 * - Set CHN_F_NOTRIGGER on channel(s).
+	 * - Stop channel(s).  
+	 */
+
+	/*
+	 * If device's channels are already mapped to a group, unmap them.
+	 */
+	if (wrch) {
+		CHN_LOCK(wrch);
+		chn_syncdestroy(wrch);
+	}
+
+	if (rdch) {
+		CHN_LOCK(rdch);
+		chn_syncdestroy(rdch);
+	}
+
+	/*
+	 * Verify that mode matches character device properites.
+	 *  - Bail if PCM_ENABLE_OUTPUT && wrch == NULL.
+	 *  - Bail if PCM_ENABLE_INPUT && rdch == NULL.
+	 */
+	if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) ||
+	    ((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) {
+		ret = EINVAL;
+		goto bad;
+	}
+
+	/*
+	 * An id of zero indicates the user wants to create a new
+	 * syncgroup.
+	 */
+	if (group->id == 0) {
+		sg = (struct pcmchan_syncgroup *)malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT);
+		if (sg != NULL) {
+			SLIST_INIT(&sg->members);
+			sg->id = (int)sg;
+
+			group->id = sg->id;
+			SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link);
+		} else
+			ret = ENOMEM;
+	} else {
+		SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) {
+			if (sg->id == group->id)
+				break;
+		}
+		if (sg == NULL)
+			ret = EINVAL;
+	}
+
+	/* Couldn't create or find a syncgroup.  Fail. */
+	if (sg == NULL)
+		goto bad;
+
+	/*
+	 * Allocate a syncmember, assign it and a channel together, and
+	 * insert into syncgroup.
+	 */
+	if (group->mode & PCM_ENABLE_INPUT) {
+		smrd = (struct pcmchan_syncmember *)malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT);
+		if (smrd == NULL) {
+			ret = ENOMEM;
+			goto bad;
+		}
+
+		SLIST_INSERT_HEAD(&sg->members, smrd, link);
+		smrd->parent = sg;
+		smrd->ch = rdch;
+
+		chn_abort(rdch);
+		rdch->flags |= CHN_F_NOTRIGGER;
+		rdch->sm = smrd;
+	}
+
+	if (group->mode & PCM_ENABLE_OUTPUT) {
+		smwr = (struct pcmchan_syncmember *)malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT);
+		if (smwr == NULL) {
+			ret = ENOMEM;
+			goto bad;
+		}
+
+		SLIST_INSERT_HEAD(&sg->members, smwr, link);
+		smwr->parent = sg;
+		smwr->ch = wrch;
+
+		chn_abort(wrch);
+		wrch->flags |= CHN_F_NOTRIGGER;
+		wrch->sm = smwr;
+	}
+
+
+	if (wrch)
+		CHN_UNLOCK(wrch);
+	if (rdch)
+		CHN_UNLOCK(rdch);
+
+	PCM_SG_UNLOCK();
+
+	return 0;
+
+bad:
+	if (smrd != NULL)
+		free(smrd, M_DEVBUF);
+	if (smwr != NULL)
+		free(smwr, M_DEVBUF);
+	if ((group->id == 0) && (sg != NULL)) {
+		SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
+		free(sg, M_DEVBUF);
+	}
+
+	if (wrch) {
+		wrch->sm = NULL;
+		CHN_UNLOCK(wrch);
+	}
+	if (rdch) {
+		rdch->sm = NULL;
+		CHN_UNLOCK(rdch);
+	}
+
+	PCM_SG_UNLOCK();
+
+	return ret;
+}
+
+/**
+ * @brief Launch a sync group into action
+ *
+ * Sync groups are established via SNDCTL_DSP_SYNCGROUP.  This function
+ * iterates over all members, triggering them along the way.
+ *
+ * @note Caller must not hold any channel locks.
+ *
+ * @param sg_id	sync group identifier
+ *
+ * @retval 0	success
+ * @retval non-zero	error worthy of propagating upstream to user
+ */
+static int
+dsp_oss_syncstart(int sg_id)
+{
+	struct pcmchan_syncmember *sm, *sm_tmp;
+	struct pcmchan_syncgroup *sg;
+	struct pcm_channel *c;
+	int ret, needlocks;
+	
+	/* Get the synclists lock */
+	PCM_SG_LOCK();
+
+	do {
+		ret = 0;
+		needlocks = 0;
+
+		/* Search for syncgroup by ID */
+		SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) {
+			if (sg->id == sg_id)
+				break;
+		}
+
+		/* Return EINVAL if not found */
+		if (sg == NULL) {
+			ret = EINVAL;
+			break;
+		}
+
+		/* Any removals resulting in an empty group should've handled this */
+		KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup"));
+
+		/*
+		 * Attempt to lock all member channels - if any are already
+		 * locked, unlock those acquired, sleep for a bit, and try
+		 * again.
+		 */
+		SLIST_FOREACH(sm, &sg->members, link) {
+			if (CHN_TRYLOCK(sm->ch) == 0) {
+				int timo = hz * 5/1000; 
+				if (timo < 1)
+					timo = 1;
+
+				/* Release all locked channels so far, retry */
+				SLIST_FOREACH(sm_tmp, &sg->members, link) {
+					/* sm is the member already locked */
+					if (sm == sm_tmp)
+						break;
+					CHN_UNLOCK(sm_tmp->ch);
+				}
+
+				/** @todo Is PRIBIO correct/ */
+				ret = msleep(sm, &snd_pcm_syncgroups_mtx, PRIBIO | PCATCH, "pcmsgrp", timo);
+				if (ret == EINTR || ret == ERESTART)
+					break;
+
+				needlocks = 1;
+				ret = 0; /* Assumes ret == EWOULDBLOCK... */
+			}
+		}
+	} while (needlocks && ret == 0);
+
+	/* Proceed only if no errors encountered. */
+	if (ret == 0) {
+		/* Launch channels */
+		while((sm = SLIST_FIRST(&sg->members)) != NULL) {
+			SLIST_REMOVE_HEAD(&sg->members, link);
+
+			c = sm->ch;
+			c->sm = NULL;
+			chn_start(c, 1);
+			c->flags &= ~CHN_F_NOTRIGGER;
+			CHN_UNLOCK(c);
+
+			free(sm, M_DEVBUF);
+		}
+
+		SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
+		free(sg, M_DEVBUF);
+	}
+
+	PCM_SG_UNLOCK();
+	return ret;
+}
+
+/**
+ * @brief Handler for SNDCTL_DSP_POLICY
+ *
+ * The SNDCTL_DSP_POLICY ioctl is a simpler interface to control fragment
+ * size and count like with SNDCTL_DSP_SETFRAGMENT.  Instead of the user
+ * specifying those two parameters, s/he simply selects a number from 0..10
+ * which corresponds to a buffer size.  Smaller numbers request smaller
+ * buffers with lower latencies (at greater overhead from more frequent
+ * interrupts), while greater numbers behave in the opposite manner.
+ *
+ * The 4Front spec states that a value of 5 should be the default.  However,
+ * this implementation deviates slightly by using a linear scale without
+ * consulting drivers.  I.e., even though drivers may have different default
+ * buffer sizes, a policy argument of 5 will have the same result across
+ * all drivers.
+ *
+ * See http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html for
+ * more information.
+ *
+ * @todo When SNDCTL_DSP_COOKEDMODE is supported, it'll be necessary to
+ * 	 work with hardware drivers directly.
+ *
+ * @note PCM channel arguments must not be locked by caller.
+ *
+ * @param wrch	Pointer to opened playback channel (optional; may be NULL)
+ * @param rdch	" recording channel (optional; may be NULL)
+ * @param policy Integer from [0:10]
+ *
+ * @retval 0	constant (for now)
+ */
+static int
+dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy)
+{
+	int fragln, fragsz, maxfrags, ret;
+
+	/* Default: success */
+	ret = 0;
+
+	/* Scale policy [0..10] to fragment size [2^4..2^16]. */
+	fragln = policy;
+	RANGE(fragln, 0, 10);
+	fragln += 4;
+	fragsz = 1 << fragln;
+
+	maxfrags = CHN_2NDBUFMAXSIZE / fragsz;
+
+	if (rdch) {
+		CHN_LOCK(rdch);
+		ret = chn_setblocksize(rdch, maxfrags, fragsz);
+		CHN_UNLOCK(rdch);
+	}
+
+	if (wrch && ret == 0) {
+		CHN_LOCK(wrch);
+		ret = chn_setblocksize(wrch, maxfrags, fragsz);
+		CHN_UNLOCK(wrch);
+	}
+
+	return ret;
+}
+
+#ifdef OSSV4_EXPERIMENT
+/**
+ * @brief Enable or disable "cooked" mode
+ *
+ * This is a handler for @c SNDCTL_DSP_COOKEDMODE.  When in cooked mode, which
+ * is the default, the sound system handles rate and format conversions
+ * automatically (ex: user writing 11025Hz/8 bit/unsigned but card only
+ * operates with 44100Hz/16bit/signed samples).
+ *
+ * Disabling cooked mode is intended for applications wanting to mmap()
+ * a sound card's buffer space directly, bypassing the FreeBSD 2-stage
+ * feeder architecture, presumably to gain as much control over audio
+ * hardware as possible.
+ *
+ * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_COOKEDMODE.html
+ * for more details.
+ *
+ * @note Currently, this function is just a stub that always returns EINVAL.
+ *
+ * @todo Figure out how to and actually implement this.
+ *
+ * @param wrch		playback channel (optional; may be NULL)
+ * @param rdch		recording channel (optional; may be NULL)
+ * @param enabled	0 = raw mode, 1 = cooked mode
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled)
+{
+	return EINVAL;
+}
+
+/**
+ * @brief Retrieve channel interleaving order
+ *
+ * This is the handler for @c SNDCTL_DSP_GET_CHNORDER.
+ *
+ * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_GET_CHNORDER.html
+ * for more details.
+ *
+ * @note As the ioctl definition is still under construction, FreeBSD
+ * 	 does not currently support SNDCTL_DSP_GET_CHNORDER.
+ *
+ * @param wrch	playback channel (optional; may be NULL)
+ * @param rdch	recording channel (optional; may be NULL)
+ * @param map	channel map (result will be stored there)
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map)
+{
+	return EINVAL;
+}
+
+/**
+ * @brief Specify channel interleaving order
+ *
+ * This is the handler for @c SNDCTL_DSP_SET_CHNORDER.
+ *
+ * @note As the ioctl definition is still under construction, FreeBSD
+ * 	 does not currently support @c SNDCTL_DSP_SET_CHNORDER.
+ *
+ * @param wrch	playback channel (optional; may be NULL)
+ * @param rdch	recording channel (optional; may be NULL)
+ * @param map	channel map
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map)
+{
+	return EINVAL;
+}
+
+/**
+ * @brief Retrieve an audio device's label
+ *
+ * This is a handler for the @c SNDCTL_GETLABEL ioctl.
+ *
+ * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html
+ * for more details.
+ *
+ * From Hannu@4Front:  "For example ossxmix (just like some HW mixer
+ * consoles) can show variable "labels" for certain controls. By default
+ * the application name (say quake) is shown as the label but
+ * applications may change the labels themselves."
+ *
+ * @note As the ioctl definition is still under construction, FreeBSD
+ * 	 does not currently support @c SNDCTL_GETLABEL.
+ *
+ * @param wrch	playback channel (optional; may be NULL)
+ * @param rdch	recording channel (optional; may be NULL)
+ * @param label	label gets copied here
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label)
+{
+	return EINVAL;
+}
+
+/**
+ * @brief Specify an audio device's label
+ *
+ * This is a handler for the @c SNDCTL_SETLABEL ioctl.  Please see the
+ * comments for @c dsp_oss_getlabel immediately above.
+ *
+ * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html
+ * for more details.
+ *
+ * @note As the ioctl definition is still under construction, FreeBSD
+ * 	 does not currently support SNDCTL_SETLABEL.
+ *
+ * @param wrch	playback channel (optional; may be NULL)
+ * @param rdch	recording channel (optional; may be NULL)
+ * @param label	label gets copied from here
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label)
+{
+	return EINVAL;
+}
+
+/**
+ * @brief Retrieve name of currently played song
+ *
+ * This is a handler for the @c SNDCTL_GETSONG ioctl.  Audio players could
+ * tell the system the name of the currently playing song, which would be
+ * visible in @c /dev/sndstat.
+ *
+ * See @c http://manuals.opensound.com/developer/SNDCTL_GETSONG.html
+ * for more details.
+ *
+ * @note As the ioctl definition is still under construction, FreeBSD
+ * 	 does not currently support SNDCTL_GETSONG.
+ *
+ * @param wrch	playback channel (optional; may be NULL)
+ * @param rdch	recording channel (optional; may be NULL)
+ * @param song	song name gets copied here
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song)
+{
+	return EINVAL;
+}
+
+/**
+ * @brief Retrieve name of currently played song
+ *
+ * This is a handler for the @c SNDCTL_SETSONG ioctl.  Audio players could
+ * tell the system the name of the currently playing song, which would be
+ * visible in @c /dev/sndstat.
+ *
+ * See @c http://manuals.opensound.com/developer/SNDCTL_SETSONG.html
+ * for more details.
+ *
+ * @note As the ioctl definition is still under construction, FreeBSD
+ * 	 does not currently support SNDCTL_SETSONG.
+ *
+ * @param wrch	playback channel (optional; may be NULL)
+ * @param rdch	recording channel (optional; may be NULL)
+ * @param song	song name gets copied from here
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song)
+{
+	return EINVAL;
+}
+
+/**
+ * @brief Rename a device
+ *
+ * This is a handler for the @c SNDCTL_SETNAME ioctl.
+ *
+ * See @c http://manuals.opensound.com/developer/SNDCTL_SETNAME.html for
+ * more details.
+ *
+ * From Hannu@4Front:  "This call is used to change the device name
+ * reported in /dev/sndstat and ossinfo. So instead of  using some generic
+ * 'OSS loopback audio (MIDI) driver' the device may be given a meaningfull
+ * name depending on the current context (for example 'OSS virtual wave table
+ * synth' or 'VoIP link to London')."
+ *
+ * @note As the ioctl definition is still under construction, FreeBSD
+ * 	 does not currently support SNDCTL_SETNAME.
+ *
+ * @param wrch	playback channel (optional; may be NULL)
+ * @param rdch	recording channel (optional; may be NULL)
+ * @param name	new device name gets copied from here
+ *
+ * @retval EINVAL	Operation not yet supported.
+ */
+static int
+dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name)
+{
+	return EINVAL;
+}
+#endif	/* !OSSV4_EXPERIMENT */
---  dev/sound/pcm/dsp.h.orig
+++  dev/sound/pcm/dsp.h
@@ -1,3 +1,5 @@
+#ifndef _PCMDSP_H_
+#define _PCMDSP_H_
 /*-
  * Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
  * All rights reserved.
@@ -27,3 +29,7 @@
  */
 
 extern struct cdevsw dsp_cdevsw;
+
+int dsp_oss_audioinfo(struct cdev *, oss_audioinfo *);
+
+#endif /* !_PCMDSP_H_ */
---  dev/sound/pcm/mixer.c.orig
+++  dev/sound/pcm/mixer.c
@@ -49,6 +49,13 @@
 	u_int16_t level[32];
 	char name[MIXER_NAMELEN];
 	struct mtx *lock;
+	oss_mixer_enuminfo enuminfo;
+	/** 
+	 * Counter is incremented when applications change any of this
+	 * mixer's controls.  A change in value indicates that persistent
+	 * mixer applications should update their displays.
+	 */
+	int modify_counter;
 };
 
 static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = {
@@ -83,6 +90,11 @@
 	.d_name =	"mixer",
 };
 
+/**
+ * Keeps a count of mixer devices; used only by OSSv4 SNDCTL_SYSINFO ioctl.
+ */
+int mixer_count = 0;
+
 #ifdef USING_DEVFS
 static eventhandler_tag mixer_ehtag;
 #endif
@@ -181,6 +193,83 @@
 	return mixer->recsrc;
 }
 
+/**
+ * @brief Retrieve the route number of the current recording device
+ *
+ * OSSv4 assigns routing numbers to recording devices, unlike the previous
+ * API which relied on a fixed table of device numbers and names.  This
+ * function returns the routing number of the device currently selected
+ * for recording.
+ *
+ * For now, this function is kind of a goofy compatibility stub atop the
+ * existing sound system.  (For example, in theory, the old sound system
+ * allows multiple recording devices to be specified via a bitmask.)
+ *
+ * @param m	mixer context container thing
+ *
+ * @retval 0		success
+ * @retval EIDRM	no recording device found (generally not possible)
+ * @todo Ask about error code
+ */
+static int
+mixer_get_recroute(struct snd_mixer *m, int *route)
+{
+	int i, cnt;
+
+	cnt = 0;
+
+	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+		/** @todo can user set a multi-device mask? (== or &?) */
+		if ((1 << i) == m->recsrc)
+			break;
+		if ((1 << i) & m->recdevs)
+			++cnt;
+	}
+
+	if (i == SOUND_MIXER_NRDEVICES)
+		return EIDRM;
+
+	*route = cnt;
+	return 0;
+}
+
+/**
+ * @brief Select a device for recording
+ *
+ * This function sets a recording source based on a recording device's
+ * routing number.  Said number is translated to an old school recdev
+ * mask and passed over mixer_setrecsrc. 
+ *
+ * @param m	mixer context container thing
+ *
+ * @retval 0		success(?)
+ * @retval EINVAL	User specified an invalid device number
+ * @retval otherwise	error from mixer_setrecsrc
+ */
+static int
+mixer_set_recroute(struct snd_mixer *m, int route)
+{
+	int i, cnt, ret;
+
+	ret = 0;
+	cnt = 0;
+
+	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+		if ((1 << i) & m->recdevs) {
+			if (route == cnt)
+				break;
+			++cnt;
+		}
+	}
+
+	if (i == SOUND_MIXER_NRDEVICES)
+		ret = EINVAL;
+	else
+		ret = mixer_setrecsrc(m, (1 << i));
+
+	return ret;
+}
+
 void
 mix_setdevs(struct snd_mixer *m, u_int32_t v)
 {
@@ -190,9 +279,71 @@
 	m->devs = v;
 }
 
+/**
+ * @brief Record mask of available recording devices
+ *
+ * Calling functions are responsible for defining the mask of available
+ * recording devices.  This function records that value in a structure
+ * used by the rest of the mixer code.
+ *
+ * This function also populates a structure used by the SNDCTL_DSP_*RECSRC*
+ * family of ioctls that are part of OSSV4.  All recording device labels
+ * are concatenated in ascending order corresponding to their routing
+ * numbers.  (Ex:  a system might have 0 => 'vol', 1 => 'cd', 2 => 'line',
+ * etc.)  For now, these labels are just the standard recording device
+ * names (cd, line1, etc.), but will eventually be fully dynamic and user
+ * controlled.
+ *
+ * @param m	mixer device context container thing
+ * @param v	mask of recording devices
+ */
 void
 mix_setrecdevs(struct snd_mixer *m, u_int32_t v)
 {
+	oss_mixer_enuminfo *ei;
+	char *loc;
+	int i, nvalues, nwrote, nleft, ncopied;
+
+	ei = &m->enuminfo;
+
+	nvalues = 0;
+	nwrote = 0;
+	nleft = sizeof(ei->strings);
+	loc = ei->strings;
+
+	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+		if ((1 << i) & v) {
+			ei->strindex[nvalues] = nwrote;
+			ncopied = strlcpy(loc, snd_mixernames[i], nleft) + 1;
+			    /* strlcpy retval doesn't include terminator */
+
+			nwrote += ncopied;
+			nleft -= ncopied;
+			nvalues++;
+
+			/*
+			 * XXX I don't think this should ever be possible.
+			 * Even with a move to dynamic device/channel names,
+			 * each label is limited to ~16 characters, so that'd
+			 * take a LOT to fill this buffer.
+			 */
+			if ((nleft <= 0) || (nvalues >= OSS_ENUM_MAXVALUE)) {
+				device_printf(m->dev,
+				    "mix_setrecdevs:  Not enough room to store device names--please file a bug report.\n");
+				device_printf(m->dev, 
+				    "mix_setrecdevs:  Please include details about your sound hardware, OS version, etc.\n");
+				break;
+			}
+
+			loc = &ei->strings[nwrote];
+		}
+	}
+
+	/*
+	 * NB:	The SNDCTL_DSP_GET_RECSRC_NAMES ioctl ignores the dev
+	 * 	and ctrl fields.
+	 */
+	ei->nvalues = nvalues;
 	m->recdevs = v;
 }
 
@@ -256,6 +407,8 @@
 	snddev = device_get_softc(dev);
 	snddev->mixer_dev = pdev;
 
+	++mixer_count;
+
 	return 0;
 
 bad:
@@ -300,6 +453,8 @@
 
 	d->mixer_dev = NULL;
 
+	--mixer_count;
+
 	return 0;
 }
 
@@ -481,6 +636,11 @@
 		return EBADF;
 	}
 
+	if (cmd == SNDCTL_MIXERINFO) {
+		snd_mtxunlock(m->lock);
+		return mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg);
+	}
+
 	if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) {
 		if (j == SOUND_MIXER_RECSRC)
 			ret = mixer_setrecsrc(m, *arg_i);
@@ -513,8 +673,32 @@
 		snd_mtxunlock(m->lock);
 		return (v != -1)? 0 : ENXIO;
 	}
+
+	ret = 0;
+
+	switch (cmd) {
+ 	/** @todo Double check return values, error codes. */
+	case SNDCTL_SYSINFO:
+		sound_oss_sysinfo((oss_sysinfo *)arg);
+		break;
+	case SNDCTL_AUDIOINFO:
+		ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg);
+		break;
+	case SNDCTL_DSP_GET_RECSRC_NAMES:
+		bcopy((void *)&m->enuminfo, arg, sizeof(oss_mixer_enuminfo));
+		break;
+	case SNDCTL_DSP_GET_RECSRC:
+		ret = mixer_get_recroute(m, arg_i);
+		break;
+	case SNDCTL_DSP_SET_RECSRC:
+		ret = mixer_set_recroute(m, *arg_i);
+		break;
+	default:
+		ret = ENXIO;
+	}
+
 	snd_mtxunlock(m->lock);
-	return ENXIO;
+	return ret;
 }
 
 #ifdef USING_DEVFS
@@ -552,4 +736,154 @@
 SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL);
 #endif
 
+/**
+ * @brief Handler for SNDCTL_MIXERINFO
+ *
+ * This function searches for a mixer based on the numeric ID stored
+ * in oss_miserinfo::dev.  If set to -1, then information about the
+ * current mixer handling the request is provided.  Note, however, that
+ * this ioctl may be made with any sound device (audio, mixer, midi).
+ *
+ * @note Caller must not hold any PCM device, channel, or mixer locks.
+ *
+ * See http://manuals.opensound.com/developer/SNDCTL_MIXERINFO.html for
+ * more information.
+ *
+ * @param i_dev	character device on which the ioctl arrived
+ * @param arg	user argument (oss_mixerinfo *)
+ *
+ * @retval EINVAL	oss_mixerinfo::dev specified a bad value
+ * @retval 0		success
+ */
+int
+mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi)
+{
+	struct snddev_info *d;
+	struct snd_mixer *m;
+	struct cdev *t_cdev;
+	int nmix, ret, pcmunit, i;
+
+	/*
+	 * If probing the device handling the ioctl, make sure it's a mixer
+	 * device.  (This ioctl is valid on audio, mixer, and midi devices.)
+	 */
+	if ((mi->dev == -1) && (i_dev->si_devsw != &mixer_cdevsw))
+		return EINVAL;
+
+	m = NULL;
+	t_cdev = NULL;
+	nmix = 0;
+	ret = 0;
+	pcmunit = -1; /* pcmX */
+
+	/*
+	 * There's a 1:1 relationship between mixers and PCM devices, so
+	 * begin by iterating over PCM devices and search for our mixer.
+	 */
+	for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
+		d = devclass_get_softc(pcm_devclass, i);
+		if (d == NULL)
+			continue;
+
+		/* See the note in function docblock. */
+		mtx_assert(d->lock, MA_NOTOWNED);
+		pcm_inprog(d, 1);
+		pcm_lock(d);
+
+		if (d->mixer_dev != NULL) {
+			if (((mi->dev == -1) && (d->mixer_dev == i_dev)) || (mi->dev == nmix)) {
+				t_cdev = d->mixer_dev;
+				pcmunit = i;
+				break;
+			}
+			++nmix;
+		}
+
+		pcm_unlock(d);
+		pcm_inprog(d, -1);
+	}
+
+	/*
+	 * If t_cdev is NULL, then search was exhausted and device wasn't
+	 * found.  No locks are held, so just return.
+	 */
+	if (t_cdev == NULL)
+		return EINVAL;
+
+	m = t_cdev->si_drv1;
+	mtx_lock(m->lock);
+
+	/*
+	 * At this point, the following synchronization stuff has happened:
+	 *   - a specific PCM device is locked and its "in progress
+	 *     operations" counter has been incremented, so be sure to unlock
+	 *     and decrement when exiting;
+	 *   - a specific mixer device has been locked, so be sure to unlock
+	 *     when existing.
+	 */
+
+	bzero((void *)mi, sizeof(*mi));
+
+	mi->dev = nmix;
+	snprintf(mi->id, sizeof(mi->id), "mixer%d", dev2unit(t_cdev));
+	strlcpy(mi->name, m->name, sizeof(mi->name));
+	mi->modify_counter = m->modify_counter;
+	mi->card_number = pcmunit;
+	/*
+	 * Currently, FreeBSD assumes 1:1 relationship between a pcm and
+	 * mixer devices, so this is hardcoded to 0.
+	 */
+	mi->port_number = 0;
+
+	/**
+	 * @todo Fill in @sa oss_mixerinfo::mixerhandle.
+	 * @note From 4Front:  "mixerhandle is an arbitrary string that
+	 * 	 identifies the mixer better than the device number
+	 * 	 (mixerinfo.dev). Device numbers may change depending on
+	 * 	 the order the drivers are loaded. However the handle
+	 * 	 should remain the same provided that the sound card is
+	 * 	 not moved to another PCI slot."
+	 */
+
+	/**
+	 * @note
+	 * @sa oss_mixerinfo::magic is a reserved field.
+	 * 
+	 * @par
+	 * From 4Front:  "magic is usually 0. However some devices may have
+	 * dedicated setup utilities and the magic field may contain an
+	 * unique driver specific value (managed by [4Front])."
+	 */
+
+	mi->enabled = device_is_attached(m->dev) ? 1 : 0;
+	/**
+	 * The only flag for @sa oss_mixerinfo::caps is currently
+	 * MIXER_CAP_VIRTUAL, which I'm not sure we really worry about.
+	 */
+	/**
+	 * Mixer extensions currently aren't supported, so leave 
+	 * @sa oss_mixerinfo::nrext blank for now.
+	 */
+	/**
+	 * @todo Fill in @sa oss_mixerinfo::priority (requires touching
+	 * 	 drivers?)
+	 * @note The priority field is for mixer applets to determine which
+	 * mixer should be the default, with 0 being least preferred and 10
+	 * being most preferred.  From 4Front:  "OSS drivers like ICH use
+	 * higher values (10) because such chips are known to be used only
+	 * on motherboards.  Drivers for high end pro devices use 0 because
+	 * they will never be the default mixer. Other devices use values 1
+	 * to 9 depending on the estimated probability of being the default
+	 * device.
+	 *
+	 * XXX Described by Hannu@4Front, but not found in soundcard.h.
+	strlcpy(mi->devnode, t_cdev->si_name, sizeof(mi->devnode));
+	mi->legacy_device = pcmunit;
+	 */
+
+	mtx_unlock(m->lock);
+	pcm_unlock(d);
+	pcm_inprog(d, -1);
 
+	return ret;
+}
---  dev/sound/pcm/mixer.h.orig
+++  dev/sound/pcm/mixer.h
@@ -30,6 +30,7 @@
 int mixer_uninit(device_t dev);
 int mixer_reinit(device_t dev);
 int mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td);
+int mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi);
 
 int mixer_hwvol_init(device_t dev);
 void mixer_hwvol_mute(device_t dev);
@@ -41,10 +42,13 @@
 u_int32_t mix_getrecdevs(struct snd_mixer *m);
 void *mix_getdevinfo(struct snd_mixer *m);
 
+extern int mixer_count;
+
 /*
  * this is a kludge to allow hiding of the struct snd_mixer definition
  * 512 should be enough for all architectures
  */
-#define	MIXER_SIZE	(512 + sizeof(struct kobj))
+#define	MIXER_SIZE	(512 + sizeof(struct kobj) + \
+			 sizeof(oss_mixer_enuminfo))
 
 #define MIXER_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, MIXER_SIZE)
---  dev/sound/pcm/sound.c.orig
+++  dev/sound/pcm/sound.c
@@ -1126,6 +1126,118 @@
 
 /************************************************************************/
 
+/**
+ * @brief	Handle OSSv4 SNDCTL_SYSINFO ioctl.
+ *
+ * @param si	Pointer to oss_sysinfo struct where information about the
+ * 		sound subsystem will be written/copied.
+ *
+ * This routine returns information about the sound system, such as the
+ * current OSS version, number of audio, MIDI, and mixer drivers, etc.
+ * Also includes a bitmask showing which of the above types of devices
+ * are open (busy).
+ *
+ * @note
+ * Calling threads must not hold any snddev_info or pcm_channel locks.
+ *
+ * @author	Ryan Beasley <ryanb@FreeBSD.org>
+ */
+void
+sound_oss_sysinfo(oss_sysinfo *si)
+{
+	static char si_product[] = "FreeBSD native OSS ABI";
+	static char si_version[] = __XSTRING(__FreeBSD_version);
+	static int intnbits = sizeof(int) * 8;	/* Better suited as macro?
+						   Must pester a C guru. */
+
+	struct snddev_channel *sce;
+	struct snddev_info *d;
+	struct pcm_channel *c;
+	int i, j, ncards;
+	
+	ncards = 0;
+
+	strlcpy(si->product, si_product, sizeof(si->product));
+	strlcpy(si->version, si_version, sizeof(si->version));
+	si->versionnum = SOUND_VERSION;
+
+	/*
+	 * Iterate over PCM devices and their channels, gathering up data
+	 * for the numaudios, ncards, and openedaudio fields.
+	 */
+	si->numaudios = 0;
+	bzero((void *)&si->openedaudio, sizeof(si->openedaudio));
+
+	if (pcm_devclass != NULL) {
+		j = 0;
+
+		for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
+			d = devclass_get_softc(pcm_devclass, i);
+			if (!d)
+				continue;
+
+			/* See note in function's docblock */
+			mtx_assert(d->lock, MA_NOTOWNED);
+			/* Increment device's "operations in progress" */
+			pcm_inprog(d, 1);
+			pcm_lock(d);
+
+			si->numaudios += d->devcount;
+			++ncards;
+
+			SLIST_FOREACH(sce, &d->channels, link) {
+				c = sce->channel;
+				mtx_assert(c->lock, MA_NOTOWNED);
+				CHN_LOCK(c);
+				if (c->flags & CHN_F_BUSY)
+					si->openedaudio[j / intnbits] |=
+					    (1 << (j % intnbits));
+				CHN_UNLOCK(c);
+				j++;
+			}
+
+			pcm_unlock(d);
+			pcm_inprog(d, -1);
+		}
+	}
+
+	si->numsynths = 0;	/* OSSv4 docs:  this field is obsolete */
+	/**
+	 * @todo	Collect num{midis,timers}.
+	 *
+	 * Need access to sound/midi/midi.c::midistat_lock in order
+	 * to safely touch midi_devices and get a head count of, well,
+	 * MIDI devices.  midistat_lock is a global static (i.e., local to
+	 * midi.c), but midi_devices is a regular global; should the mutex
+	 * be publicized, or is there another way to get this information?
+	 *
+	 * NB:	MIDI/sequencer stuff is currently on hold.
+	 */
+	si->nummidis = 0;
+	si->numtimers = 0;
+	si->nummixers = mixer_count;
+	si->numcards = ncards;
+		/* OSSv4 docs:	Intended only for test apps; API doesn't
+		   really have much of a concept of cards.  Shouldn't be
+		   used by applications. */
+
+	/**
+	 * @todo	Fill in "busy devices" fields.
+	 *
+	 *  si->openedmidi = " MIDI devices
+	 */
+	bzero((void *)&si->openedmidi, sizeof(si->openedmidi));
+
+	/*
+	 * Si->filler is a reserved array, but according to docs each
+	 * element should be set to -1.
+	 */
+	for (i = 0; i < sizeof(si->filler)/sizeof(si->filler[0]); i++)
+		si->filler[i] = -1;
+}
+
+/************************************************************************/
+
 static int
 sound_modevent(module_t mod, int type, void *data)
 {
---  dev/sound/pcm/sound.h.orig
+++  dev/sound/pcm/sound.h
@@ -73,6 +73,7 @@
 #if __FreeBSD_version > 500000
 #include <sys/lock.h>
 #include <sys/mutex.h>
+#include <sys/condvar.h>
 
 #define USING_MUTEX
 #define USING_DEVFS
@@ -92,6 +93,7 @@
 #include <dev/sound/pcm/channel.h>
 #include <dev/sound/pcm/feeder.h>
 #include <dev/sound/pcm/mixer.h>
+#include <dev/sound/pcm/dsp.h>
 
 #define	PCM_SOFTC_SIZE	512
 
@@ -304,6 +306,8 @@
 
 };
 
+void	sound_oss_sysinfo(oss_sysinfo *);
+
 #ifdef	PCM_DEBUG_MTX
 #define	pcm_lock(d) mtx_lock(((struct snddev_info *)(d))->lock)
 #define	pcm_unlock(d) mtx_unlock(((struct snddev_info *)(d))->lock)
---  sys/soundcard.h.orig
+++  sys/soundcard.h
@@ -3,7 +3,7 @@
  */
 
 /*-
- * Copyright by Hannu Savolainen 1993
+ * Copyright by Hannu Savolainen 1993 / 4Front Technologies 1993-2006
  * Modified for the new FreeBSD sound driver by Luigi Rizzo, 1997
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,6 +32,12 @@
  * $FreeBSD: src/sys/sys/soundcard.h,v 1.45 2005/07/31 16:08:03 netchild Exp $
  */
 
+/*
+ * Unless coordinating changes with 4Front Technologies, do NOT make any
+ * modifications to ioctl commands, types, etc. that would break
+ * compatibility with the OSS API.
+ */
+
 #ifndef _SYS_SOUNDCARD_H_
 #define _SYS_SOUNDCARD_H_
  /*
@@ -1437,4 +1443,392 @@
 #define SOUND_PCM_MAPINBUF	SNDCTL_DSP_MAPINBUF
 #define SOUND_PCM_MAPOUTBUF	SNDCTL_DSP_MAPOUTBUF
 
+/***********************************************************************/
+
+/**
+ * XXX OSSv4 defines -- some bits taken straight out of the new
+ * sys/soundcard.h bundled with recent OSS releases.
+ *
+ * NB:  These macros and structures will be reorganized and inserted
+ * 	in appropriate places throughout this file once the code begins
+ * 	to take shape.
+ *
+ * @todo reorganize layout more like the 4Front version
+ * @todo ask about maintaining __SIOWR vs. _IOWR ioctl cmd defines
+ */
+
+/**
+ * @note The @c OSSV4_EXPERIMENT macro is meant to wrap new development code
+ * in the sound system relevant to adopting 4Front's OSSv4 specification.
+ * Users should not enable this!  Really!
+ */
+#if 0
+# define OSSV4_EXPERIMENT 1
+#else
+# undef OSSV4_EXPERIMENT
+#endif
+
+#ifdef SOUND_VERSION
+# undef SOUND_VERSION
+# define SOUND_VERSION	0x040000
+#endif	/* !SOUND_VERSION */
+
+#define OSS_LONGNAME_SIZE	64
+#define OSS_LABEL_SIZE		16
+#define OSS_DEVNODE_SIZE        32
+typedef char oss_longname_t[OSS_LONGNAME_SIZE];
+typedef char oss_label_t[OSS_LABEL_SIZE];
+typedef char oss_devnode_t[OSS_DEVNODE_SIZE];
+
+typedef struct audio_errinfo
+{
+	int		play_underruns;
+	int		rec_overruns;
+	unsigned int	play_ptradjust;
+	unsigned int	rec_ptradjust;
+	int		play_errorcount;
+	int		rec_errorcount;
+	int		play_lasterror;
+	int		rec_lasterror;
+	long		play_errorparm;
+	long		rec_errorparm;
+	int		filler[16];
+} audio_errinfo;
+
+#define SNDCTL_DSP_GETPLAYVOL           _IOR ('P', 24, int)
+#define SNDCTL_DSP_SETPLAYVOL           _IOWR('P', 24, int)
+#define SNDCTL_DSP_GETERROR             _IOR ('P', 25, audio_errinfo)
+
+
+/*
+ ****************************************************************************
+ * Sync groups for audio devices
+ */
+typedef struct oss_syncgroup
+{
+  int id;
+  int mode;
+  int filler[16];
+} oss_syncgroup;
+
+#define SNDCTL_DSP_SYNCGROUP            _IOWR('P', 28, oss_syncgroup)
+#define SNDCTL_DSP_SYNCSTART            _IOW ('P', 29, int)
+
+/*
+ **************************************************************************
+ * "cooked" mode enables software based conversions for sample rate, sample
+ * format (bits) and number of channels (mono/stereo). These conversions are
+ * required with some devices that support only one sample rate or just stereo
+ * to let the applications to use other formats. The cooked mode is enabled by
+ * default. However it's necessary to disable this mode when mmap() is used or
+ * when very deterministic timing is required. SNDCTL_DSP_COOKEDMODE is an
+ * optional call introduced in OSS 3.9.6f. It's _error return must be ignored_
+ * since normally this call will return erno=EINVAL.
+ *
+ * SNDCTL_DSP_COOKEDMODE must be called immediately after open before doing
+ * anything else. Otherwise the call will not have any effect.
+ */
+#define SNDCTL_DSP_COOKEDMODE           _IOW ('P', 30, int)
+
+/*
+ **************************************************************************
+ * SNDCTL_DSP_SILENCE and SNDCTL_DSP_SKIP are new calls in OSS 3.99.0
+ * that can be used to implement pause/continue during playback (no effect
+ * on recording).
+ */
+#define SNDCTL_DSP_SILENCE              _IO  ('P', 31)
+#define SNDCTL_DSP_SKIP                 _IO  ('P', 32)
+
+/*
+ ****************************************************************************
+ * Abort transfer (reset) functions for input and output
+ */
+#define SNDCTL_DSP_HALT_INPUT		_IO  ('P', 33)
+#define SNDCTL_DSP_RESET_INPUT	SNDCTL_DSP_HALT_INPUT	/* Old name */
+#define SNDCTL_DSP_HALT_OUTPUT		_IO  ('P', 34)
+#define SNDCTL_DSP_RESET_OUTPUT	SNDCTL_DSP_HALT_OUTPUT	/* Old name */
+
+/*
+ ****************************************************************************
+ * Low water level control
+ */
+#define SNDCTL_DSP_LOW_WATER		_IOW ('P', 34, int)
+
+/** @todo Get rid of OSS_NO_LONG_LONG references? */
+
+/*
+ ****************************************************************************
+ * 64 bit pointer support. Only available in environments that support
+ * the 64 bit (long long) integer type.
+ */
+#ifndef OSS_NO_LONG_LONG
+typedef struct
+{
+  long long samples;
+  int fifo_samples;
+  int filler[32];		/* For future use */
+} oss_count_t;
+
+#define SNDCTL_DSP_CURRENT_IPTR		_IOR ('P', 35, oss_count_t)
+#define SNDCTL_DSP_CURRENT_OPTR		_IOR ('P', 36, oss_count_t)
+#endif
+
+/*
+ ****************************************************************************
+ * Interface for selecting recording sources and playback output routings.
+ */
+#define SNDCTL_DSP_GET_RECSRC_NAMES     _IOR ('P', 37, oss_mixer_enuminfo)
+#define SNDCTL_DSP_GET_RECSRC           _IOR ('P', 38, int)
+#define SNDCTL_DSP_SET_RECSRC           _IOWR('P', 38, int)
+
+#define SNDCTL_DSP_GET_PLAYTGT_NAMES    _IOR ('P', 39, oss_mixer_enuminfo)
+#define SNDCTL_DSP_GET_PLAYTGT          _IOR ('P', 40, int)
+#define SNDCTL_DSP_SET_PLAYTGT          _IOWR('P', 40, int)
+#define SNDCTL_DSP_GETRECVOL            _IOR ('P', 41, int)
+#define SNDCTL_DSP_SETRECVOL            _IOWR('P', 41, int)
+
+/*
+ ***************************************************************************
+ * Some calls for setting the channel assignment with multi channel devices
+ * (see the manual for details).                                                 */
+#define SNDCTL_DSP_GET_CHNORDER         _IOR ('P', 42, unsigned long long)
+#define SNDCTL_DSP_SET_CHNORDER         _IOWR('P', 42, unsigned long long)
+#       define CHID_UNDEF       0
+#       define CHID_L           1                                               #       define CHID_R           2
+#       define CHID_C           3
+#       define CHID_LFE         4
+#       define CHID_LS          5
+#       define CHID_RS          6
+#       define CHID_LR          7
+#       define CHID_RR          8
+#define CHNORDER_UNDEF          0x0000000000000000ULL
+#define CHNORDER_NORMAL         0x0000000087654321ULL
+
+#define MAX_PEAK_CHANNELS	128
+typedef unsigned short oss_peaks_t[MAX_PEAK_CHANNELS];
+#define SNDCTL_DSP_GETIPEAKS		_IOR('P', 43, oss_peaks_t)
+#define SNDCTL_DSP_GETOPEAKS		_IOR('P', 44, oss_peaks_t)
+#define SNDCTL_DSP_POLICY               _IOW('P', 45, int)    /* See the manual */
+
+/**
+ * @brief	Argument for SNDCTL_SYSINFO ioctl.
+ *
+ * For use w/ the SNDCTL_SYSINFO ioctl available on audio (/dev/dsp*),
+ * mixer, and MIDI devices.
+ */
+typedef struct oss_sysinfo
+{
+	char	product[32];	/* For example OSS/Free, OSS/Linux or
+				   OSS/Solaris */
+	char	version[32];	/* For example 4.0a */
+	int	versionnum;	/* See OSS_GETVERSION */
+	char	options[128];	/* Reserved */
+
+	int	numaudios;	/* # of audio/dsp devices */
+	int	openedaudio[8];	/* Bit mask telling which audio devices
+				   are busy */
+
+	int	numsynths;	/* # of availavle synth devices */
+	int	nummidis;	/* # of available MIDI ports */
+	int	numtimers;	/* # of available timer devices */
+	int	nummixers;	/* # of mixer devices */
+
+	int	openedmidi[8];	/* Bit mask telling which midi devices
+				   are busy */
+	int	numcards;	/* Number of sound cards in the system */
+	int	filler[241];	/* For future expansion (set to -1) */
+} oss_sysinfo;
+
+typedef struct oss_mixext
+{
+  int dev;			/* Mixer device number */
+  int ctrl;			/* Controller number */
+  int type;			/* Entry type */
+#	define MIXT_DEVROOT	 0	/* Device root entry */
+#	define MIXT_GROUP	 1	/* Controller group */
+#	define MIXT_ONOFF	 2	/* OFF (0) or ON (1) */
+#	define MIXT_ENUM	 3	/* Enumerated (0 to maxvalue) */
+#	define MIXT_MONOSLIDER	 4	/* Mono slider (0 to 100) */
+#	define MIXT_STEREOSLIDER 5	/* Stereo slider (dual 0 to 100) */
+#	define MIXT_MESSAGE	 6	/* (Readable) textual message */
+#	define MIXT_MONOVU	 7	/* VU meter value (mono) */
+#	define MIXT_STEREOVU	 8	/* VU meter value (stereo) */
+#	define MIXT_MONOPEAK	 9	/* VU meter peak value (mono) */
+#	define MIXT_STEREOPEAK	10	/* VU meter peak value (stereo) */
+#	define MIXT_RADIOGROUP	11	/* Radio button group */
+#	define MIXT_MARKER	12	/* Separator between normal and extension entries */
+#	define MIXT_VALUE	13	/* Decimal value entry */
+#	define MIXT_HEXVALUE	14	/* Hexadecimal value entry */
+#	define MIXT_MONODB	15	/* Mono atten. slider (0 to -144) */
+#	define MIXT_STEREODB	16	/* Stereo atten. slider (dual 0 to -144) */
+#	define MIXT_SLIDER	17	/* Slider (mono) with full integer range */
+#	define MIXT_3D		18
+
+  /* Possible value range (minvalue to maxvalue) */
+  /* Note that maxvalue may also be smaller than minvalue */
+  int maxvalue;
+  int minvalue;
+
+  int flags;
+#	define MIXF_READABLE	0x00000001	/* Has readable value */
+#	define MIXF_WRITEABLE	0x00000002	/* Has writeable value */
+#	define MIXF_POLL	0x00000004	/* May change itself */
+#	define MIXF_HZ		0x00000008	/* Herz scale */
+#	define MIXF_STRING	0x00000010	/* Use dynamic extensions for value */
+#	define MIXF_DYNAMIC	0x00000010	/* Supports dynamic extensions */
+#	define MIXF_OKFAIL	0x00000020	/* Interpret value as 1=OK, 0=FAIL */
+#	define MIXF_FLAT	0x00000040	/* Flat vertical space requirements */
+#	define MIXF_LEGACY	0x00000080	/* Legacy mixer control group */
+  char id[16];			/* Mnemonic ID (mainly for internal use) */
+  int parent;			/* Entry# of parent (group) node (-1 if root) */
+
+  int dummy;			/* Internal use */
+
+  int timestamp;
+
+  char data[64];		/* Misc data (entry type dependent) */
+  unsigned char enum_present[32];	/* Mask of allowed enum values */
+  int control_no;		/* SOUND_MIXER_VOLUME..SOUND_MIXER_MIDI */
+  /* (-1 means not indicated) */
+
+/*
+ * The desc field is reserved for internal purposes of OSS. It should not be 
+ * used by applications.
+ */
+  unsigned int desc;
+#define MIXEXT_SCOPE_MASK			0x0000003f
+#define MIXEXT_SCOPE_OTHER			0x00000000
+#define MIXEXT_SCOPE_INPUT			0x00000001
+#define MIXEXT_SCOPE_OUTPUT			0x00000002
+#define MIXEXT_SCOPE_MONITOR			0x00000003
+#define MIXEXT_SCOPE_RECSWITCH			0x00000004
+
+  char extname[32];
+  int update_counter;
+  int filler[7];
+} oss_mixext;
+
+typedef struct oss_mixext_root
+{
+  char id[16];
+  char name[48];
+} oss_mixext_root;
+
+typedef struct oss_mixer_value
+{
+  int dev;
+  int ctrl;
+  int value;
+  int flags;			/* Reserved for future use. Initialize to 0 */
+  int timestamp;		/* Must be set to oss_mixext.timestamp */
+  int filler[8];		/* Reserved for future use. Initialize to 0 */
+} oss_mixer_value;
+
+#define OSS_ENUM_MAXVALUE       255
+typedef struct oss_mixer_enuminfo
+{
+	int	dev;
+	int	ctrl;
+	int	nvalues;
+	int	version;                  /* Read the manual */
+	short	strindex[OSS_ENUM_MAXVALUE];
+	char	strings[3000];
+} oss_mixer_enuminfo;
+
+#define OPEN_READ       PCM_ENABLE_INPUT
+#define OPEN_WRITE      PCM_ENABLE_OUTPUT
+#define OPEN_READWRITE  (OPEN_READ|OPEN_WRITE)
+
+/**
+ * @brief	Argument for SNDCTL_AUDIOINFO ioctl.
+ *
+ * For use w/ the SNDCTL_AUDIOINFO ioctl available on audio (/dev/dsp*)
+ * devices.
+ */
+typedef struct oss_audioinfo
+{
+	int	dev;		/* Audio device number */
+	char	name[64];
+	int	busy;		/* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */
+	int	pid;
+	int	caps;		/* DSP_CAP_INPUT, DSP_CAP_OUTPUT */
+	int	iformats;
+	int	oformats;
+	int	magic;		/* Reserved for internal use */
+	char 	cmd[64];	/* Command using the device (if known) */
+	int	card_number;
+	int	port_number;
+	int	mixer_dev;
+	int	real_device;	/* Obsolete field. Replaced by devnode */
+	int	enabled;	/* 1=enabled, 0=device not ready at this
+				   moment */
+	int	flags;		/* For internal use only - no practical
+				   meaning */
+	int	min_rate;	/* Sample rate limits */
+	int	max_rate;
+	int	min_channels;	/* Number of channels supported */
+	int	max_channels;
+	int	binding;	/* DSP_BIND_FRONT, etc. 0 means undefined */
+	int	rate_source;
+	char	handle[32];
+	#define MAX_SAMPLE_RATES	20	/* Cannot be changed  */
+	unsigned int nrates;
+	unsigned int rates[MAX_SAMPLE_RATES]; /* Please read the manual before using these */
+	oss_longname_t	song_name;	/* Song name (if given) */
+	oss_label_t	label;		/* Device label (if given) */
+	int		latency;	/* In usecs, -1=unknown */
+	oss_devnode_t	devnode;	/* Device special file name (inside
+					   /dev) */
+	int filler[186];
+} oss_audioinfo;
+
+typedef struct oss_mixerinfo
+{
+  int dev;
+  char id[16];
+  char name[32];
+  int modify_counter;
+  int card_number;
+  int port_number;
+  char handle[32];
+  int magic;			/* Reserved */
+  int enabled;			/* Reserved */
+  int caps;
+#define MIXER_CAP_VIRTUAL				0x00000001
+  int flags;			/* Reserved */
+  int nrext;
+  /*
+   * The priority field can be used to select the default (motherboard)
+   * mixer device. The mixer with the highest priority is the
+   * most preferred one. -2 or less means that this device cannot be used
+   * as the default mixer.
+   */
+  int priority;
+  int filler[254];		/* Reserved */
+} oss_mixerinfo;
+
+#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_MIDIINFO		_IOWR('X', 9, oss_midi_info)
+#define SNDCTL_MIXERINFO	_IOWR('X',10, oss_mixerinfo)
+#define SNDCTL_CARDINFO		_IOWR('X',11, oss_card_info)
+
+/*
+ * Few more "globally" available ioctl calls.
+ */
+#define SNDCTL_SETSONG          _IOW ('Y', 2, oss_longname_t)
+#define SNDCTL_GETSONG          _IOR ('Y', 2, oss_longname_t)
+#define SNDCTL_SETNAME          _IOW ('Y', 3, oss_longname_t)
+#define SNDCTL_SETLABEL         _IOW ('Y', 4, oss_label_t)
+#define SNDCTL_GETLABEL         _IOR ('Y', 4, oss_label_t)
+
 #endif	/* !_SYS_SOUNDCARD_H_ */
