diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c
index 38c578ba8282..4d56eee6847e 100644
--- a/sys/dev/sound/pcm/channel.c
+++ b/sys/dev/sound/pcm/channel.c
@@ -1,2520 +1,2591 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  *
  * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
  * Portions Copyright (c) Ryan Beasley <ryan.beasley@gmail.com> - GSoC 2006
  * Copyright (c) 1999 Cameron Grant <cg@FreeBSD.org>
  * Portions Copyright (c) Luigi Rizzo <luigi@FreeBSD.org> - 1997-99
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #include "opt_isa.h"
 
 #ifdef HAVE_KERNEL_OPTION_HEADERS
 #include "opt_snd.h"
 #endif
 
 #include <dev/sound/pcm/sound.h>
 #include <dev/sound/pcm/vchan.h>
 
 #include "feeder_if.h"
 
 SND_DECLARE_FILE("$FreeBSD$");
 
 int report_soft_formats = 1;
 SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW,
 	&report_soft_formats, 0, "report software-emulated formats");
 
 int report_soft_matrix = 1;
 SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_matrix, CTLFLAG_RW,
 	&report_soft_matrix, 0, "report software-emulated channel matrixing");
 
 int chn_latency = CHN_LATENCY_DEFAULT;
 
 static int
 sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS)
 {
 	int err, val;
 
 	val = chn_latency;
 	err = sysctl_handle_int(oidp, &val, 0, req);
 	if (err != 0 || req->newptr == NULL)
 		return err;
 	if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX)
 		err = EINVAL;
 	else
 		chn_latency = val;
 
 	return err;
 }
 SYSCTL_PROC(_hw_snd, OID_AUTO, latency,
     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
     sysctl_hw_snd_latency, "I",
     "buffering latency (0=low ... 10=high)");
 
 int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT;
 
 static int
 sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS)
 {
 	int err, val;
 
 	val = chn_latency_profile;
 	err = sysctl_handle_int(oidp, &val, 0, req);
 	if (err != 0 || req->newptr == NULL)
 		return err;
 	if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX)
 		err = EINVAL;
 	else
 		chn_latency_profile = val;
 
 	return err;
 }
 SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile,
     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
     sysctl_hw_snd_latency_profile, "I",
     "buffering latency profile (0=aggressive 1=safe)");
 
 static int chn_timeout = CHN_TIMEOUT;
 
 static int
 sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS)
 {
 	int err, val;
 
 	val = chn_timeout;
 	err = sysctl_handle_int(oidp, &val, 0, req);
 	if (err != 0 || req->newptr == NULL)
 		return err;
 	if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX)
 		err = EINVAL;
 	else
 		chn_timeout = val;
 
 	return err;
 }
 SYSCTL_PROC(_hw_snd, OID_AUTO, timeout,
     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
     sysctl_hw_snd_timeout, "I",
     "interrupt timeout (1 - 10) seconds");
 
 static int chn_vpc_autoreset = 1;
 SYSCTL_INT(_hw_snd, OID_AUTO, vpc_autoreset, CTLFLAG_RWTUN,
 	&chn_vpc_autoreset, 0, "automatically reset channels volume to 0db");
 
 static int chn_vol_0db_pcm = SND_VOL_0DB_PCM;
 
 static void
 chn_vpc_proc(int reset, int db)
 {
 	struct snddev_info *d;
 	struct pcm_channel *c;
 	int i;
 
 	for (i = 0; pcm_devclass != NULL &&
 	    i < devclass_get_maxunit(pcm_devclass); i++) {
 		d = devclass_get_softc(pcm_devclass, i);
 		if (!PCM_REGISTERED(d))
 			continue;
 		PCM_LOCK(d);
 		PCM_WAIT(d);
 		PCM_ACQUIRE(d);
 		CHN_FOREACH(c, d, channels.pcm) {
 			CHN_LOCK(c);
 			CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_VOL_0DB, db);
 			if (reset != 0)
 				chn_vpc_reset(c, SND_VOL_C_PCM, 1);
 			CHN_UNLOCK(c);
 		}
 		PCM_RELEASE(d);
 		PCM_UNLOCK(d);
 	}
 }
 
 static int
 sysctl_hw_snd_vpc_0db(SYSCTL_HANDLER_ARGS)
 {
 	int err, val;
 
 	val = chn_vol_0db_pcm;
 	err = sysctl_handle_int(oidp, &val, 0, req);
 	if (err != 0 || req->newptr == NULL)
 		return (err);
 	if (val < SND_VOL_0DB_MIN || val > SND_VOL_0DB_MAX)
 		return (EINVAL);
 
 	chn_vol_0db_pcm = val;
 	chn_vpc_proc(0, val);
 
 	return (0);
 }
 SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_0db,
     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
     sysctl_hw_snd_vpc_0db, "I",
     "0db relative level");
 
 static int
 sysctl_hw_snd_vpc_reset(SYSCTL_HANDLER_ARGS)
 {
 	int err, val;
 
 	val = 0;
 	err = sysctl_handle_int(oidp, &val, 0, req);
 	if (err != 0 || req->newptr == NULL || val == 0)
 		return (err);
 
 	chn_vol_0db_pcm = SND_VOL_0DB_PCM;
 	chn_vpc_proc(1, SND_VOL_0DB_PCM);
 
 	return (0);
 }
 SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_reset,
     CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, sizeof(int),
     sysctl_hw_snd_vpc_reset, "I",
     "reset volume on all channels");
 
 static int chn_usefrags = 0;
 static int chn_syncdelay = -1;
 
 SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RWTUN,
 	&chn_usefrags, 0, "prefer setfragments() over setblocksize()");
 SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RWTUN,
 	&chn_syncdelay, 0,
 	"append (0-1000) millisecond trailing buffer delay on each sync");
 
 /**
  * @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(snd_pcm_syncgroups);
 
 static void
 chn_lockinit(struct pcm_channel *c, int dir)
 {
 	switch (dir) {
 	case PCMDIR_PLAY:
 		c->lock = snd_mtxcreate(c->name, "pcm play channel");
 		cv_init(&c->intr_cv, "pcmwr");
 		break;
 	case PCMDIR_PLAY_VIRTUAL:
 		c->lock = snd_mtxcreate(c->name, "pcm virtual play channel");
 		cv_init(&c->intr_cv, "pcmwrv");
 		break;
 	case PCMDIR_REC:
 		c->lock = snd_mtxcreate(c->name, "pcm record channel");
 		cv_init(&c->intr_cv, "pcmrd");
 		break;
 	case PCMDIR_REC_VIRTUAL:
 		c->lock = snd_mtxcreate(c->name, "pcm virtual record channel");
 		cv_init(&c->intr_cv, "pcmrdv");
 		break;
 	default:
 		panic("%s(): Invalid direction=%d", __func__, dir);
 		break;
 	}
 
 	cv_init(&c->cv, "pcmchn");
 }
 
 static void
 chn_lockdestroy(struct pcm_channel *c)
 {
 	CHN_LOCKASSERT(c);
 
 	CHN_BROADCAST(&c->cv);
 	CHN_BROADCAST(&c->intr_cv);
 
 	cv_destroy(&c->cv);
 	cv_destroy(&c->intr_cv);
 
 	snd_mtxfree(c->lock);
 }
 
 /**
  * @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)
 {
 	struct snd_dbuf *bs = c->bufsoft;
 	u_int delta;
 
 	CHN_LOCKASSERT(c);
 
 	if (c->flags & CHN_F_MMAP) {
 		if (sndbuf_getprevtotal(bs) < c->lw)
 			delta = c->lw;
 		else
 			delta = sndbuf_gettotal(bs) - sndbuf_getprevtotal(bs);
 	} else {
 		if (c->direction == PCMDIR_PLAY)
 			delta = sndbuf_getfree(bs);
 		else
 			delta = sndbuf_getready(bs);
 	}
 
 	return ((delta < c->lw) ? 0 : 1);
 }
 
 static void
 chn_pollreset(struct pcm_channel *c)
 {
 
 	CHN_LOCKASSERT(c);
 	sndbuf_updateprevtotal(c->bufsoft);
 }
 
 static void
 chn_wakeup(struct pcm_channel *c)
 {
 	struct snd_dbuf *bs;
 	struct pcm_channel *ch;
 
 	CHN_LOCKASSERT(c);
 
 	bs = c->bufsoft;
 
 	if (CHN_EMPTY(c, children.busy)) {
 		if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c))
 			selwakeuppri(sndbuf_getsel(bs), PRIBIO);
 		if (c->flags & CHN_F_SLEEPING) {
 			/*
 			 * Ok, I can just panic it right here since it is
 			 * quite obvious that we never allow multiple waiters
 			 * from userland. I'm too generous...
 			 */
 			CHN_BROADCAST(&c->intr_cv);
 		}
 	} else {
 		CHN_FOREACH(ch, c, children.busy) {
 			CHN_LOCK(ch);
 			chn_wakeup(ch);
 			CHN_UNLOCK(ch);
 		}
 	}
 }
 
 static int
 chn_sleep(struct pcm_channel *c, int timeout)
 {
 	int ret;
 
 	CHN_LOCKASSERT(c);
 
 	if (c->flags & CHN_F_DEAD)
 		return (EINVAL);
 
 	c->flags |= CHN_F_SLEEPING;
 	ret = cv_timedwait_sig(&c->intr_cv, c->lock, timeout);
 	c->flags &= ~CHN_F_SLEEPING;
 
 	return ((c->flags & CHN_F_DEAD) ? EINVAL : ret);
 }
 
 /*
  * chn_dmaupdate() tracks the status of a dma transfer,
  * updating pointers.
  */
 
 static unsigned int
 chn_dmaupdate(struct pcm_channel *c)
 {
 	struct snd_dbuf *b = c->bufhard;
 	unsigned int delta, old, hwptr, amt;
 
 	KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0"));
 	CHN_LOCKASSERT(c);
 
 	old = sndbuf_gethwptr(b);
 	hwptr = chn_getptr(c);
 	delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b);
 	sndbuf_sethwptr(b, hwptr);
 
 	if (c->direction == PCMDIR_PLAY) {
 		amt = min(delta, sndbuf_getready(b));
 		amt -= amt % sndbuf_getalign(b);
 		if (amt > 0)
 			sndbuf_dispose(b, NULL, amt);
 	} else {
 		amt = min(delta, sndbuf_getfree(b));
 		amt -= amt % sndbuf_getalign(b);
 		if (amt > 0)
 		       sndbuf_acquire(b, NULL, amt);
 	}
 	if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) {
 		device_printf(c->dev, "WARNING: %s DMA completion "
 			"too fast/slow ! hwptr=%u, old=%u "
 			"delta=%u amt=%u ready=%u free=%u\n",
 			CHN_DIRSTR(c), hwptr, old, delta, amt,
 			sndbuf_getready(b), sndbuf_getfree(b));
 	}
 
 	return delta;
 }
 
 static void
 chn_wrfeed(struct pcm_channel *c)
 {
     	struct snd_dbuf *b = c->bufhard;
     	struct snd_dbuf *bs = c->bufsoft;
 	unsigned int amt, want, wasfree;
 
 	CHN_LOCKASSERT(c);
 
 	if ((c->flags & CHN_F_MMAP) && !(c->flags & CHN_F_CLOSING))
 		sndbuf_acquire(bs, NULL, sndbuf_getfree(bs));
 
 	wasfree = sndbuf_getfree(b);
 	want = min(sndbuf_getsize(b),
 	    imax(0, sndbuf_xbytes(sndbuf_getsize(bs), bs, b) -
 	     sndbuf_getready(b)));
 	amt = min(wasfree, want);
 	if (amt > 0)
 		sndbuf_feed(bs, b, c, c->feeder, amt);
 
 	/*
 	 * Possible xruns. There should be no empty space left in buffer.
 	 */
 	if (sndbuf_getready(b) < want)
 		c->xruns++;
 
 	if (sndbuf_getfree(b) < wasfree)
 		chn_wakeup(c);
 }
 
 #if 0
 static void
 chn_wrupdate(struct pcm_channel *c)
 {
 
 	CHN_LOCKASSERT(c);
 	KASSERT(c->direction == PCMDIR_PLAY, ("%s(): bad channel", __func__));
 
 	if ((c->flags & (CHN_F_MMAP | CHN_F_VIRTUAL)) || CHN_STOPPED(c))
 		return;
 	chn_dmaupdate(c);
 	chn_wrfeed(c);
 	/* tell the driver we've updated the primary buffer */
 	chn_trigger(c, PCMTRIG_EMLDMAWR);
 }
 #endif
 
 static void
 chn_wrintr(struct pcm_channel *c)
 {
 
 	CHN_LOCKASSERT(c);
 	/* update pointers in primary buffer */
 	chn_dmaupdate(c);
 	/* ...and feed from secondary to primary */
 	chn_wrfeed(c);
 	/* tell the driver we've updated the primary buffer */
 	chn_trigger(c, PCMTRIG_EMLDMAWR);
 }
 
 /*
  * user write routine - uiomove data into secondary buffer, trigger if necessary
  * if blocking, sleep, rinse and repeat.
  *
  * called externally, so must handle locking
  */
 
 int
 chn_write(struct pcm_channel *c, struct uio *buf)
 {
 	struct snd_dbuf *bs = c->bufsoft;
 	void *off;
 	int ret, timeout, sz, t, p;
 
 	CHN_LOCKASSERT(c);
 
 	ret = 0;
 	timeout = chn_timeout * hz;
 
 	while (ret == 0 && buf->uio_resid > 0) {
 		sz = min(buf->uio_resid, sndbuf_getfree(bs));
 		if (sz > 0) {
 			/*
 			 * The following assumes that the free space in
 			 * the buffer can never be less around the
 			 * unlock-uiomove-lock sequence.
 			 */
 			while (ret == 0 && sz > 0) {
 				p = sndbuf_getfreeptr(bs);
 				t = min(sz, sndbuf_getsize(bs) - p);
 				off = sndbuf_getbufofs(bs, p);
 				CHN_UNLOCK(c);
 				ret = uiomove(off, t, buf);
 				CHN_LOCK(c);
 				sz -= t;
 				sndbuf_acquire(bs, NULL, t);
 			}
 			ret = 0;
 			if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) {
 				ret = chn_start(c, 0);
 				if (ret != 0)
 					c->flags |= CHN_F_DEAD;
 			}
 		} else if (c->flags & (CHN_F_NBIO | 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 {
    			ret = chn_sleep(c, timeout);
 			if (ret == EAGAIN) {
 				ret = EINVAL;
 				c->flags |= CHN_F_DEAD;
 				device_printf(c->dev, "%s(): %s: "
 				    "play interrupt timeout, channel dead\n",
 				    __func__, c->name);
 			} else if (ret == ERESTART || ret == EINTR)
 				c->flags |= CHN_F_ABORTING;
 		}
 	}
 
 	return (ret);
 }
 
 /*
  * Feed new data from the read buffer. Can be called in the bottom half.
  */
 static void
 chn_rdfeed(struct pcm_channel *c)
 {
     	struct snd_dbuf *b = c->bufhard;
     	struct snd_dbuf *bs = c->bufsoft;
 	unsigned int amt;
 
 	CHN_LOCKASSERT(c);
 
 	if (c->flags & CHN_F_MMAP)
 		sndbuf_dispose(bs, NULL, sndbuf_getready(bs));
 
 	amt = sndbuf_getfree(bs);
 	if (amt > 0)
 		sndbuf_feed(b, bs, c, c->feeder, amt);
 
 	amt = sndbuf_getready(b);
 	if (amt > 0) {
 		c->xruns++;
 		sndbuf_dispose(b, NULL, amt);
 	}
 
 	if (sndbuf_getready(bs) > 0)
 		chn_wakeup(c);
 }
 
 #if 0
 static void
 chn_rdupdate(struct pcm_channel *c)
 {
 
 	CHN_LOCKASSERT(c);
 	KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel"));
 
 	if ((c->flags & (CHN_F_MMAP | CHN_F_VIRTUAL)) || CHN_STOPPED(c))
 		return;
 	chn_trigger(c, PCMTRIG_EMLDMARD);
 	chn_dmaupdate(c);
 	chn_rdfeed(c);
 }
 #endif
 
 /* read interrupt routine. Must be called with interrupts blocked. */
 static void
 chn_rdintr(struct pcm_channel *c)
 {
 
 	CHN_LOCKASSERT(c);
 	/* tell the driver to update the primary buffer if non-dma */
 	chn_trigger(c, PCMTRIG_EMLDMARD);
 	/* update pointers in primary buffer */
 	chn_dmaupdate(c);
 	/* ...and feed from primary to secondary */
 	chn_rdfeed(c);
 }
 
 /*
  * user read routine - trigger if necessary, uiomove data from secondary buffer
  * if blocking, sleep, rinse and repeat.
  *
  * called externally, so must handle locking
  */
 
 int
 chn_read(struct pcm_channel *c, struct uio *buf)
 {
 	struct snd_dbuf *bs = c->bufsoft;
 	void *off;
 	int ret, timeout, sz, t, p;
 
 	CHN_LOCKASSERT(c);
 
 	if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) {
 		ret = chn_start(c, 0);
 		if (ret != 0) {
 			c->flags |= CHN_F_DEAD;
 			return (ret);
 		}
 	}
 
 	ret = 0;
 	timeout = chn_timeout * hz;
 
 	while (ret == 0 && buf->uio_resid > 0) {
 		sz = min(buf->uio_resid, sndbuf_getready(bs));
 		if (sz > 0) {
 			/*
 			 * The following assumes that the free space in
 			 * the buffer can never be less around the
 			 * unlock-uiomove-lock sequence.
 			 */
 			while (ret == 0 && sz > 0) {
 				p = sndbuf_getreadyptr(bs);
 				t = min(sz, sndbuf_getsize(bs) - p);
 				off = sndbuf_getbufofs(bs, p);
 				CHN_UNLOCK(c);
 				ret = uiomove(off, t, buf);
 				CHN_LOCK(c);
 				sz -= t;
 				sndbuf_dispose(bs, NULL, t);
 			}
 			ret = 0;
 		} else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER))
 			ret = EAGAIN;
 		else {
    			ret = chn_sleep(c, timeout);
 			if (ret == EAGAIN) {
 				ret = EINVAL;
 				c->flags |= CHN_F_DEAD;
 				device_printf(c->dev, "%s(): %s: "
 				    "record interrupt timeout, channel dead\n",
 				    __func__, c->name);
 			} else if (ret == ERESTART || ret == EINTR)
 				c->flags |= CHN_F_ABORTING;
 		}
 	}
 
 	return (ret);
 }
 
 void
 chn_intr_locked(struct pcm_channel *c)
 {
 
 	CHN_LOCKASSERT(c);
 
 	c->interrupts++;
 
 	if (c->direction == PCMDIR_PLAY)
 		chn_wrintr(c);
 	else
 		chn_rdintr(c);
 }
 
 void
 chn_intr(struct pcm_channel *c)
 {
 
 	if (CHN_LOCKOWNED(c)) {
 		chn_intr_locked(c);
 		return;
 	}
 
 	CHN_LOCK(c);
 	chn_intr_locked(c);
 	CHN_UNLOCK(c);
 }
 
 u_int32_t
 chn_start(struct pcm_channel *c, int force)
 {
 	u_int32_t i, j;
 	struct snd_dbuf *b = c->bufhard;
 	struct snd_dbuf *bs = c->bufsoft;
 	int err;
 
 	CHN_LOCKASSERT(c);
 	/* if we're running, or if we're prevented from triggering, bail */
 	if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force))
 		return (EINVAL);
 
 	err = 0;
 
 	if (force) {
 		i = 1;
 		j = 0;
 	} else {
 		if (c->direction == PCMDIR_REC) {
 			i = sndbuf_getfree(bs);
 			j = (i > 0) ? 1 : sndbuf_getready(b);
 		} else {
 			if (sndbuf_getfree(bs) == 0) {
 				i = 1;
 				j = 0;
 			} else {
 				struct snd_dbuf *pb;
 
 				pb = CHN_BUF_PARENT(c, b);
 				i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb);
 				j = sndbuf_getalign(pb);
 			}
 		}
 		if (snd_verbose > 3 && CHN_EMPTY(c, children))
 			device_printf(c->dev, "%s(): %s (%s) threshold "
 			    "i=%d j=%d\n", __func__, CHN_DIRSTR(c),
 			    (c->flags & CHN_F_VIRTUAL) ? "virtual" :
 			    "hardware", i, j);
 	}
 
 	if (i >= j) {
 		c->flags |= CHN_F_TRIGGERED;
 		sndbuf_setrun(b, 1);
 		if (c->flags & CHN_F_CLOSING)
 			c->feedcount = 2;
 		else {
 			c->feedcount = 0;
 			c->interrupts = 0;
 			c->xruns = 0;
 		}
 		if (c->parentchannel == NULL) {
 			if (c->direction == PCMDIR_PLAY)
 				sndbuf_fillsilence_rl(b,
 				    sndbuf_xbytes(sndbuf_getsize(bs), bs, b));
 			if (snd_verbose > 3)
 				device_printf(c->dev,
 				    "%s(): %s starting! (%s/%s) "
 				    "(ready=%d force=%d i=%d j=%d "
 				    "intrtimeout=%u latency=%dms)\n",
 				    __func__,
 				    (c->flags & CHN_F_HAS_VCHAN) ?
 				    "VCHAN PARENT" : "HW", CHN_DIRSTR(c),
 				    (c->flags & CHN_F_CLOSING) ? "closing" :
 				    "running",
 				    sndbuf_getready(b),
 				    force, i, j, c->timeout,
 				    (sndbuf_getsize(b) * 1000) /
 				    (sndbuf_getalign(b) * sndbuf_getspd(b)));
 		}
 		err = chn_trigger(c, PCMTRIG_START);
 	}
 
 	return (err);
 }
 
 void
 chn_resetbuf(struct pcm_channel *c)
 {
 	struct snd_dbuf *b = c->bufhard;
 	struct snd_dbuf *bs = c->bufsoft;
 
 	c->blocks = 0;
 	sndbuf_reset(b);
 	sndbuf_reset(bs);
 }
 
 /*
  * chn_sync waits until the space in the given channel goes above
  * a threshold. The threshold is checked against fl or rl respectively.
  * Assume that the condition can become true, do not check here...
  */
 int
 chn_sync(struct pcm_channel *c, int threshold)
 {
     	struct snd_dbuf *b, *bs;
 	int ret, count, hcount, minflush, resid, residp, syncdelay, blksz;
 	u_int32_t cflag;
 
 	CHN_LOCKASSERT(c);
 
 	if (c->direction != PCMDIR_PLAY)
 		return (EINVAL);
 
 	bs = c->bufsoft;
 
 	if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) ||
 	    (threshold < 1 && sndbuf_getready(bs) < 1))
 		return (0);
 
 	/* if we haven't yet started and nothing is buffered, else start*/
 	if (CHN_STOPPED(c)) {
 		if (threshold > 0 || sndbuf_getready(bs) > 0) {
 			ret = chn_start(c, 1);
 			if (ret != 0)
 				return (ret);
 		} else
 			return (0);
 	}
 
 	b = CHN_BUF_PARENT(c, c->bufhard);
 
 	minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs);
 
 	syncdelay = chn_syncdelay;
 
 	if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0))
 		minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs);
 
 	/*
 	 * Append (0-1000) millisecond trailing buffer (if needed)
 	 * for slower / high latency hardwares (notably USB audio)
 	 * to avoid audible truncation.
 	 */
 	if (syncdelay > 0)
 		minflush += (sndbuf_getalign(bs) * sndbuf_getspd(bs) *
 		    ((syncdelay > 1000) ? 1000 : syncdelay)) / 1000;
 
 	minflush -= minflush % sndbuf_getalign(bs);
 
 	if (minflush > 0) {
 		threshold = min(minflush, sndbuf_getfree(bs));
 		sndbuf_clear(bs, threshold);
 		sndbuf_acquire(bs, NULL, threshold);
 		minflush -= threshold;
 	}
 
 	resid = sndbuf_getready(bs);
 	residp = resid;
 	blksz = sndbuf_getblksz(b);
 	if (blksz < 1) {
 		device_printf(c->dev,
 		    "%s(): WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n",
 		    __func__, sndbuf_getmaxsize(b), sndbuf_getsize(b),
 		    sndbuf_getblksz(b), sndbuf_getblkcnt(b));
 		if (sndbuf_getblkcnt(b) > 0)
 			blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b);
 		if (blksz < 1)
 			blksz = 1;
 	}
 	count = sndbuf_xbytes(minflush + resid, bs, b) / blksz;
 	hcount = count;
 	ret = 0;
 
 	if (snd_verbose > 3)
 		device_printf(c->dev, "%s(): [begin] timeout=%d count=%d "
 		    "minflush=%d resid=%d\n", __func__, c->timeout, count,
 		    minflush, resid);
 
 	cflag = c->flags & CHN_F_CLOSING;
 	c->flags |= CHN_F_CLOSING;
 	while (count > 0 && (resid > 0 || minflush > 0)) {
 		ret = chn_sleep(c, c->timeout);
     		if (ret == ERESTART || ret == EINTR) {
 			c->flags |= CHN_F_ABORTING;
 			break;
 		} else if (ret == 0 || ret == EAGAIN) {
 			resid = sndbuf_getready(bs);
 			if (resid == residp) {
 				--count;
 				if (snd_verbose > 3)
 					device_printf(c->dev,
 					    "%s(): [stalled] timeout=%d "
 					    "count=%d hcount=%d "
 					    "resid=%d minflush=%d\n",
 					    __func__, c->timeout, count,
 					    hcount, resid, minflush);
 			} else if (resid < residp && count < hcount) {
 				++count;
 				if (snd_verbose > 3)
 					device_printf(c->dev,
 					    "%s((): [resume] timeout=%d "
 					    "count=%d hcount=%d "
 					    "resid=%d minflush=%d\n",
 					    __func__, c->timeout, count,
 					    hcount, resid, minflush);
 			}
 			if (minflush > 0 && sndbuf_getfree(bs) > 0) {
 				threshold = min(minflush,
 				    sndbuf_getfree(bs));
 				sndbuf_clear(bs, threshold);
 				sndbuf_acquire(bs, NULL, threshold);
 				resid = sndbuf_getready(bs);
 				minflush -= threshold;
 			}
 			residp = resid;
 		} else
 			break;
 	}
 	c->flags &= ~CHN_F_CLOSING;
 	c->flags |= cflag;
 
 	if (snd_verbose > 3)
 		device_printf(c->dev,
 		    "%s(): timeout=%d count=%d hcount=%d resid=%d residp=%d "
 		    "minflush=%d ret=%d\n",
 		    __func__, c->timeout, count, hcount, resid, residp,
 		    minflush, ret);
 
     	return (0);
 }
 
 /* called externally, handle locking */
 int
 chn_poll(struct pcm_channel *c, int ev, struct thread *td)
 {
 	struct snd_dbuf *bs = c->bufsoft;
 	int ret;
 
 	CHN_LOCKASSERT(c);
 
     	if (!(c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED))) {
 		ret = chn_start(c, 1);
 		if (ret != 0)
 			return (0);
 	}
 
 	ret = 0;
 	if (chn_polltrigger(c)) {
 		chn_pollreset(c);
 		ret = ev;
 	} else
 		selrecord(td, sndbuf_getsel(bs));
 
 	return (ret);
 }
 
 /*
  * chn_abort terminates a running dma transfer.  it may sleep up to 200ms.
  * it returns the number of bytes that have not been transferred.
  *
  * called from: dsp_close, dsp_ioctl, with channel locked
  */
 int
 chn_abort(struct pcm_channel *c)
 {
     	int missing = 0;
     	struct snd_dbuf *b = c->bufhard;
     	struct snd_dbuf *bs = c->bufsoft;
 
 	CHN_LOCKASSERT(c);
 	if (CHN_STOPPED(c))
 		return 0;
 	c->flags |= CHN_F_ABORTING;
 
 	c->flags &= ~CHN_F_TRIGGERED;
 	/* kill the channel */
 	chn_trigger(c, PCMTRIG_ABORT);
 	sndbuf_setrun(b, 0);
 	if (!(c->flags & CHN_F_VIRTUAL))
 		chn_dmaupdate(c);
     	missing = sndbuf_getready(bs);
 
 	c->flags &= ~CHN_F_ABORTING;
 	return missing;
 }
 
 /*
  * this routine tries to flush the dma transfer. It is called
  * on a close of a playback channel.
  * first, if there is data in the buffer, but the dma has not yet
  * begun, we need to start it.
  * next, we wait for the play buffer to drain
  * finally, we stop the dma.
  *
  * called from: dsp_close, not valid for record channels.
  */
 
 int
 chn_flush(struct pcm_channel *c)
 {
     	struct snd_dbuf *b = c->bufhard;
 
 	CHN_LOCKASSERT(c);
 	KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel"));
     	DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags));
 
 	c->flags |= CHN_F_CLOSING;
 	chn_sync(c, 0);
 	c->flags &= ~CHN_F_TRIGGERED;
 	/* kill the channel */
 	chn_trigger(c, PCMTRIG_ABORT);
 	sndbuf_setrun(b, 0);
 
     	c->flags &= ~CHN_F_CLOSING;
     	return 0;
 }
 
 int
 snd_fmtvalid(uint32_t fmt, uint32_t *fmtlist)
 {
 	int i;
 
 	for (i = 0; fmtlist[i] != 0; i++) {
 		if (fmt == fmtlist[i] ||
 		    ((fmt & AFMT_PASSTHROUGH) &&
 		    (AFMT_ENCODING(fmt) & fmtlist[i])))
 			return (1);
 	}
 
 	return (0);
 }
 
 static const struct {
 	char *name, *alias1, *alias2;
 	uint32_t afmt;
 } afmt_tab[] = {
 	{  "alaw",  NULL, NULL, AFMT_A_LAW  },
 	{ "mulaw",  NULL, NULL, AFMT_MU_LAW },
 	{    "u8",   "8", NULL, AFMT_U8     },
 	{    "s8",  NULL, NULL, AFMT_S8     },
 #if BYTE_ORDER == LITTLE_ENDIAN
 	{ "s16le", "s16", "16", AFMT_S16_LE },
 	{ "s16be",  NULL, NULL, AFMT_S16_BE },
 #else
 	{ "s16le",  NULL, NULL, AFMT_S16_LE },
 	{ "s16be", "s16", "16", AFMT_S16_BE },
 #endif
 	{ "u16le",  NULL, NULL, AFMT_U16_LE },
 	{ "u16be",  NULL, NULL, AFMT_U16_BE },
 	{ "s24le",  NULL, NULL, AFMT_S24_LE },
 	{ "s24be",  NULL, NULL, AFMT_S24_BE },
 	{ "u24le",  NULL, NULL, AFMT_U24_LE },
 	{ "u24be",  NULL, NULL, AFMT_U24_BE },
 #if BYTE_ORDER == LITTLE_ENDIAN
 	{ "s32le", "s32", "32", AFMT_S32_LE },
 	{ "s32be",  NULL, NULL, AFMT_S32_BE },
 #else
 	{ "s32le",  NULL, NULL, AFMT_S32_LE },
 	{ "s32be", "s32", "32", AFMT_S32_BE },
 #endif
 	{ "u32le",  NULL, NULL, AFMT_U32_LE },
 	{ "u32be",  NULL, NULL, AFMT_U32_BE },
 	{   "ac3",  NULL, NULL, AFMT_AC3    },
 	{    NULL,  NULL, NULL, 0           }
 };
 
 uint32_t
 snd_str2afmt(const char *req)
 {
 	int ext;
 	int ch;
 	int i;
 	char b1[8];
 	char b2[8];
 
 	memset(b1, 0, sizeof(b1));
 	memset(b2, 0, sizeof(b2));
 
 	i = sscanf(req, "%5[^:]:%6s", b1, b2);
 
 	if (i == 1) {
 		if (strlen(req) != strlen(b1))
 			return (0);
 		strlcpy(b2, "2.0", sizeof(b2));
 	} else if (i == 2) {
 		if (strlen(req) != (strlen(b1) + 1 + strlen(b2)))
 			return (0);
 	} else
 		return (0);
 
 	i = sscanf(b2, "%d.%d", &ch, &ext);
 
 	if (i == 0) {
 		if (strcasecmp(b2, "mono") == 0) {
 			ch = 1;
 			ext = 0;
 		} else if (strcasecmp(b2, "stereo") == 0) {
 			ch = 2;
 			ext = 0;
 		} else if (strcasecmp(b2, "quad") == 0) {
 			ch = 4;
 			ext = 0;
 		} else
 			return (0);
 	} else if (i == 1) {
 		if (ch < 1 || ch > AFMT_CHANNEL_MAX)
 			return (0);
 		ext = 0;
 	} else if (i == 2) {
 		if (ext < 0 || ext > AFMT_EXTCHANNEL_MAX)
 			return (0);
 		if (ch < 1 || (ch + ext) > AFMT_CHANNEL_MAX)
 			return (0);
 	} else
 		return (0);
 
 	for (i = 0; afmt_tab[i].name != NULL; i++) {
 		if (strcasecmp(afmt_tab[i].name, b1) != 0) {
 			if (afmt_tab[i].alias1 == NULL)
 				continue;
 			if (strcasecmp(afmt_tab[i].alias1, b1) != 0) {
 				if (afmt_tab[i].alias2 == NULL)
 					continue;
 				if (strcasecmp(afmt_tab[i].alias2, b1) != 0)
 					continue;
 			}
 		}
 		/* found a match */
 		return (SND_FORMAT(afmt_tab[i].afmt, ch + ext, ext));	
 	}
 	/* not a valid format */
 	return (0);
 }
 
 uint32_t
 snd_afmt2str(uint32_t afmt, char *buf, size_t len)
 {
 	uint32_t enc;
 	uint32_t ext;
 	uint32_t ch;
 	int i;
 
 	if (buf == NULL || len < AFMTSTR_LEN)
 		return (0);
 
 	memset(buf, 0, len);
 
 	enc = AFMT_ENCODING(afmt);
 	ch = AFMT_CHANNEL(afmt);
 	ext = AFMT_EXTCHANNEL(afmt);
 	/* check there is at least one channel */
 	if (ch <= ext)
 		return (0);
 	for (i = 0; afmt_tab[i].name != NULL; i++) {
 		if (enc != afmt_tab[i].afmt)
 			continue;
 		/* found a match */
 		snprintf(buf, len, "%s:%d.%d",
 		    afmt_tab[i].name, ch - ext, ext);
 		return (SND_FORMAT(enc, ch, ext));
 	}
 	return (0);
 }
 
 int
 chn_reset(struct pcm_channel *c, uint32_t fmt, uint32_t spd)
 {
 	int r;
 
 	CHN_LOCKASSERT(c);
 	c->feedcount = 0;
 	c->flags &= CHN_F_RESET;
 	c->interrupts = 0;
 	c->timeout = 1;
 	c->xruns = 0;
 
 	c->flags |= (pcm_getflags(c->dev) & SD_F_BITPERFECT) ?
 	    CHN_F_BITPERFECT : 0;
 
 	r = CHANNEL_RESET(c->methods, c->devinfo);
 	if (r == 0 && fmt != 0 && spd != 0) {
 		r = chn_setparam(c, fmt, spd);
 		fmt = 0;
 		spd = 0;
 	}
 	if (r == 0 && fmt != 0)
 		r = chn_setformat(c, fmt);
 	if (r == 0 && spd != 0)
 		r = chn_setspeed(c, spd);
 	if (r == 0)
 		r = chn_setlatency(c, chn_latency);
 	if (r == 0) {
 		chn_resetbuf(c);
 		r = CHANNEL_RESETDONE(c->methods, c->devinfo);
 	}
 	return r;
 }
 
 int
 chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction)
 {
 	struct feeder_class *fc;
 	struct snd_dbuf *b, *bs;
 	int i, ret;
 
 	if (chn_timeout < CHN_TIMEOUT_MIN || chn_timeout > CHN_TIMEOUT_MAX)
 		chn_timeout = CHN_TIMEOUT;
 
 	chn_lockinit(c, dir);
 
 	b = NULL;
 	bs = NULL;
 	CHN_INIT(c, children);
 	CHN_INIT(c, children.busy);
 	c->devinfo = NULL;
 	c->feeder = NULL;
 	c->latency = -1;
 	c->timeout = 1;
 
 	ret = ENOMEM;
 	b = sndbuf_create(c->dev, c->name, "primary", c);
 	if (b == NULL)
 		goto out;
 	bs = sndbuf_create(c->dev, c->name, "secondary", c);
 	if (bs == NULL)
 		goto out;
 
 	CHN_LOCK(c);
 
 	ret = EINVAL;
 	fc = feeder_getclass(NULL);
 	if (fc == NULL)
 		goto out;
 	if (chn_addfeeder(c, fc, NULL))
 		goto out;
 
 	/*
 	 * XXX - sndbuf_setup() & sndbuf_resize() expect to be called
 	 *	 with the channel unlocked because they are also called
 	 *	 from driver methods that don't know about locking
 	 */
 	CHN_UNLOCK(c);
 	sndbuf_setup(bs, NULL, 0);
 	CHN_LOCK(c);
 	c->bufhard = b;
 	c->bufsoft = bs;
 	c->flags = 0;
 	c->feederflags = 0;
 	c->sm = NULL;
 	c->format = SND_FORMAT(AFMT_U8, 1, 0);
 	c->speed = DSP_DEFAULT_SPEED;
 
 	c->matrix = *feeder_matrix_id_map(SND_CHN_MATRIX_1_0);
 	c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL;
 
 	for (i = 0; i < SND_CHN_T_MAX; i++) {
 		c->volume[SND_VOL_C_MASTER][i] = SND_VOL_0DB_MASTER;
 	}
 
 	c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER;
 	c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm;
 
+	memset(c->muted, 0, sizeof(c->muted));
+
 	chn_vpc_reset(c, SND_VOL_C_PCM, 1);
 
 	ret = ENODEV;
 	CHN_UNLOCK(c); /* XXX - Unlock for CHANNEL_INIT() malloc() call */
 	c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, direction);
 	CHN_LOCK(c);
 	if (c->devinfo == NULL)
 		goto out;
 
 	ret = ENOMEM;
 	if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0))
 		goto out;
 
 	ret = 0;
 	c->direction = direction;
 
 	sndbuf_setfmt(b, c->format);
 	sndbuf_setspd(b, c->speed);
 	sndbuf_setfmt(bs, c->format);
 	sndbuf_setspd(bs, c->speed);
 
 	/**
 	 * @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);
 	if (ret) {
 		if (c->devinfo) {
 			if (CHANNEL_FREE(c->methods, c->devinfo))
 				sndbuf_free(b);
 		}
 		if (bs)
 			sndbuf_destroy(bs);
 		if (b)
 			sndbuf_destroy(b);
 		CHN_LOCK(c);
 		c->flags |= CHN_F_DEAD;
 		chn_lockdestroy(c);
 
 		return ret;
 	}
 
 	return 0;
 }
 
 int
 chn_kill(struct pcm_channel *c)
 {
     	struct snd_dbuf *b = c->bufhard;
     	struct snd_dbuf *bs = c->bufsoft;
 
 	if (CHN_STARTED(c)) {
 		CHN_LOCK(c);
 		chn_trigger(c, PCMTRIG_ABORT);
 		CHN_UNLOCK(c);
 	}
 	while (chn_removefeeder(c) == 0)
 		;
 	if (CHANNEL_FREE(c->methods, c->devinfo))
 		sndbuf_free(b);
 	sndbuf_destroy(bs);
 	sndbuf_destroy(b);
 	CHN_LOCK(c);
 	c->flags |= CHN_F_DEAD;
 	chn_lockdestroy(c);
 
 	return (0);
 }
 
 /* XXX Obsolete. Use *_matrix() variant instead. */
 int
 chn_setvolume(struct pcm_channel *c, int left, int right)
 {
 	int ret;
 
 	ret = chn_setvolume_matrix(c, SND_VOL_C_MASTER, SND_CHN_T_FL, left);
 	ret |= chn_setvolume_matrix(c, SND_VOL_C_MASTER, SND_CHN_T_FR,
 	    right) << 8;
 
 	return (ret);
 }
 
 int
 chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right,
     int center)
 {
 	int i, ret;
 
 	ret = 0;
 
 	for (i = 0; i < SND_CHN_T_MAX; i++) {
 		if ((1 << i) & SND_CHN_LEFT_MASK)
 			ret |= chn_setvolume_matrix(c, vc, i, left);
 		else if ((1 << i) & SND_CHN_RIGHT_MASK)
 			ret |= chn_setvolume_matrix(c, vc, i, right) << 8;
 		else
 			ret |= chn_setvolume_matrix(c, vc, i, center) << 16;
 	}
 
 	return (ret);
 }
 
 int
 chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val)
 {
 	int i;
 
 	KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX &&
 	    (vc == SND_VOL_C_MASTER || (vc & 1)) &&
 	    (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN &&
 	    vt <= SND_CHN_T_END)) && (vt != SND_CHN_T_VOL_0DB ||
 	    (val >= SND_VOL_0DB_MIN && val <= SND_VOL_0DB_MAX)),
 	    ("%s(): invalid volume matrix c=%p vc=%d vt=%d val=%d",
 	    __func__, c, vc, vt, val));
 	CHN_LOCKASSERT(c);
 
 	if (val < 0)
 		val = 0;
 	if (val > 100)
 		val = 100;
 
 	c->volume[vc][vt] = val;
 
 	/*
 	 * Do relative calculation here and store it into class + 1
 	 * to ease the job of feeder_volume.
 	 */
 	if (vc == SND_VOL_C_MASTER) {
 		for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END;
 		    vc += SND_VOL_C_STEP)
 			c->volume[SND_VOL_C_VAL(vc)][vt] =
 			    SND_VOL_CALC_VAL(c->volume, vc, vt);
 	} else if (vc & 1) {
 		if (vt == SND_CHN_T_VOL_0DB)
 			for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END;
 			    i += SND_CHN_T_STEP) {
 				c->volume[SND_VOL_C_VAL(vc)][i] =
 				    SND_VOL_CALC_VAL(c->volume, vc, i);
 			}
 		else
 			c->volume[SND_VOL_C_VAL(vc)][vt] =
 			    SND_VOL_CALC_VAL(c->volume, vc, vt);
 	}
 
 	return (val);
 }
 
 int
 chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt)
 {
 	KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX &&
 	    (vt == SND_CHN_T_VOL_0DB ||
 	    (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)),
 	    ("%s(): invalid volume matrix c=%p vc=%d vt=%d",
 	    __func__, c, vc, vt));
 	CHN_LOCKASSERT(c);
 
 	return (c->volume[vc][vt]);
 }
 
+int
+chn_setmute_multi(struct pcm_channel *c, int vc, int mute)
+{
+	int i, ret;
+
+	ret = 0;
+
+	for (i = 0; i < SND_CHN_T_MAX; i++) {
+		if ((1 << i) & SND_CHN_LEFT_MASK)
+			ret |= chn_setmute_matrix(c, vc, i, mute);
+		else if ((1 << i) & SND_CHN_RIGHT_MASK)
+			ret |= chn_setmute_matrix(c, vc, i, mute) << 8;
+		else
+			ret |= chn_setmute_matrix(c, vc, i, mute) << 16;
+	}
+	return (ret);
+}
+
+int
+chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute)
+{
+	int i;
+
+	KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX &&
+	    (vc == SND_VOL_C_MASTER || (vc & 1)) &&
+	    (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)),
+	    ("%s(): invalid mute matrix c=%p vc=%d vt=%d mute=%d",
+	    __func__, c, vc, vt, mute));
+
+	CHN_LOCKASSERT(c);
+
+	mute = (mute != 0);
+
+	c->muted[vc][vt] = mute;
+
+	/*
+	 * Do relative calculation here and store it into class + 1
+	 * to ease the job of feeder_volume.
+	 */
+	if (vc == SND_VOL_C_MASTER) {
+		for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END;
+		    vc += SND_VOL_C_STEP)
+			c->muted[SND_VOL_C_VAL(vc)][vt] = mute;
+	} else if (vc & 1) {
+		if (vt == SND_CHN_T_VOL_0DB) {
+			for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END;
+			    i += SND_CHN_T_STEP) {
+				c->muted[SND_VOL_C_VAL(vc)][i] = mute;
+			}
+		} else {
+			c->muted[SND_VOL_C_VAL(vc)][vt] = mute;
+		}
+	}
+	return (mute);
+}
+
+int
+chn_getmute_matrix(struct pcm_channel *c, int vc, int vt)
+{
+	KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX &&
+	    (vt == SND_CHN_T_VOL_0DB ||
+	    (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)),
+	    ("%s(): invalid mute matrix c=%p vc=%d vt=%d",
+	    __func__, c, vc, vt));
+	CHN_LOCKASSERT(c);
+
+	return (c->muted[vc][vt]);
+}
+
 struct pcmchan_matrix *
 chn_getmatrix(struct pcm_channel *c)
 {
 
 	KASSERT(c != NULL, ("%s(): NULL channel", __func__));
 	CHN_LOCKASSERT(c);
 
 	if (!(c->format & AFMT_CONVERTIBLE))
 		return (NULL);
 
 	return (&c->matrix);
 }
 
 int
 chn_setmatrix(struct pcm_channel *c, struct pcmchan_matrix *m)
 {
 
 	KASSERT(c != NULL && m != NULL,
 	    ("%s(): NULL channel or matrix", __func__));
 	CHN_LOCKASSERT(c);
 
 	if (!(c->format & AFMT_CONVERTIBLE))
 		return (EINVAL);
 
 	c->matrix = *m;
 	c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL;
 
 	return (chn_setformat(c, SND_FORMAT(c->format, m->channels, m->ext)));
 }
 
 /*
  * XXX chn_oss_* exists for the sake of compatibility.
  */
 int
 chn_oss_getorder(struct pcm_channel *c, unsigned long long *map)
 {
 
 	KASSERT(c != NULL && map != NULL,
 	    ("%s(): NULL channel or map", __func__));
 	CHN_LOCKASSERT(c);
 
 	if (!(c->format & AFMT_CONVERTIBLE))
 		return (EINVAL);
 
 	return (feeder_matrix_oss_get_channel_order(&c->matrix, map));
 }
 
 int
 chn_oss_setorder(struct pcm_channel *c, unsigned long long *map)
 {
 	struct pcmchan_matrix m;
 	int ret;
 
 	KASSERT(c != NULL && map != NULL,
 	    ("%s(): NULL channel or map", __func__));
 	CHN_LOCKASSERT(c);
 
 	if (!(c->format & AFMT_CONVERTIBLE))
 		return (EINVAL);
 
 	m = c->matrix;
 	ret = feeder_matrix_oss_set_channel_order(&m, map);
 	if (ret != 0)
 		return (ret);
 
 	return (chn_setmatrix(c, &m));
 }
 
 #define SND_CHN_OSS_FRONT	(SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR)
 #define SND_CHN_OSS_SURR	(SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR)
 #define SND_CHN_OSS_CENTER_LFE	(SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF)
 #define SND_CHN_OSS_REAR	(SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR)
 
 int
 chn_oss_getmask(struct pcm_channel *c, uint32_t *retmask)
 {
 	struct pcmchan_matrix *m;
 	struct pcmchan_caps *caps;
 	uint32_t i, format;
 
 	KASSERT(c != NULL && retmask != NULL,
 	    ("%s(): NULL channel or retmask", __func__));
 	CHN_LOCKASSERT(c);
 
 	caps = chn_getcaps(c);
 	if (caps == NULL || caps->fmtlist == NULL)
 		return (ENODEV);
 
 	for (i = 0; caps->fmtlist[i] != 0; i++) {
 		format = caps->fmtlist[i];
 		if (!(format & AFMT_CONVERTIBLE)) {
 			*retmask |= DSP_BIND_SPDIF;
 			continue;
 		}
 		m = CHANNEL_GETMATRIX(c->methods, c->devinfo, format);
 		if (m == NULL)
 			continue;
 		if (m->mask & SND_CHN_OSS_FRONT)
 			*retmask |= DSP_BIND_FRONT;
 		if (m->mask & SND_CHN_OSS_SURR)
 			*retmask |= DSP_BIND_SURR;
 		if (m->mask & SND_CHN_OSS_CENTER_LFE)
 			*retmask |= DSP_BIND_CENTER_LFE;
 		if (m->mask & SND_CHN_OSS_REAR)
 			*retmask |= DSP_BIND_REAR;
 	}
 
 	/* report software-supported binding mask */
 	if (!CHN_BITPERFECT(c) && report_soft_matrix)
 		*retmask |= DSP_BIND_FRONT | DSP_BIND_SURR |
 		    DSP_BIND_CENTER_LFE | DSP_BIND_REAR;
 
 	return (0);
 }
 
 void
 chn_vpc_reset(struct pcm_channel *c, int vc, int force)
 {
 	int i;
 
 	KASSERT(c != NULL && vc >= SND_VOL_C_BEGIN && vc <= SND_VOL_C_END,
 	    ("%s(): invalid reset c=%p vc=%d", __func__, c, vc));
 	CHN_LOCKASSERT(c);
 
 	if (force == 0 && chn_vpc_autoreset == 0)
 		return;
 
 	for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP)
 		CHN_SETVOLUME(c, vc, i, c->volume[vc][SND_CHN_T_VOL_0DB]);
 }
 
 static u_int32_t
 round_pow2(u_int32_t v)
 {
 	u_int32_t ret;
 
 	if (v < 2)
 		v = 2;
 	ret = 0;
 	while (v >> ret)
 		ret++;
 	ret = 1 << (ret - 1);
 	while (ret < v)
 		ret <<= 1;
 	return ret;
 }
 
 static u_int32_t
 round_blksz(u_int32_t v, int round)
 {
 	u_int32_t ret, tmp;
 
 	if (round < 1)
 		round = 1;
 
 	ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1);
 
 	if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2))
 		ret >>= 1;
 
 	tmp = ret - (ret % round);
 	while (tmp < 16 || tmp < round) {
 		ret <<= 1;
 		tmp = ret - (ret % round);
 	}
 
 	return ret;
 }
 
 /*
  * 4Front call it DSP Policy, while we call it "Latency Profile". The idea
  * is to keep 2nd buffer short so that it doesn't cause long queue during
  * buffer transfer.
  *
  *    Latency reference table for 48khz stereo 16bit: (PLAY)
  *
  *      +---------+------------+-----------+------------+
  *      | Latency | Blockcount | Blocksize | Buffersize |
  *      +---------+------------+-----------+------------+
  *      |     0   |       2    |   64      |    128     |
  *      +---------+------------+-----------+------------+
  *      |     1   |       4    |   128     |    512     |
  *      +---------+------------+-----------+------------+
  *      |     2   |       8    |   512     |    4096    |
  *      +---------+------------+-----------+------------+
  *      |     3   |      16    |   512     |    8192    |
  *      +---------+------------+-----------+------------+
  *      |     4   |      32    |   512     |    16384   |
  *      +---------+------------+-----------+------------+
  *      |     5   |      32    |   1024    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     6   |      16    |   2048    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     7   |       8    |   4096    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     8   |       4    |   8192    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     9   |       2    |   16384   |    32768   |
  *      +---------+------------+-----------+------------+
  *      |    10   |       2    |   32768   |    65536   |
  *      +---------+------------+-----------+------------+
  *
  * Recording need a different reference table. All we care is
  * gobbling up everything within reasonable buffering threshold.
  *
  *    Latency reference table for 48khz stereo 16bit: (REC)
  *
  *      +---------+------------+-----------+------------+
  *      | Latency | Blockcount | Blocksize | Buffersize |
  *      +---------+------------+-----------+------------+
  *      |     0   |     512    |   32      |    16384   |
  *      +---------+------------+-----------+------------+
  *      |     1   |     256    |   64      |    16384   |
  *      +---------+------------+-----------+------------+
  *      |     2   |     128    |   128     |    16384   |
  *      +---------+------------+-----------+------------+
  *      |     3   |      64    |   256     |    16384   |
  *      +---------+------------+-----------+------------+
  *      |     4   |      32    |   512     |    16384   |
  *      +---------+------------+-----------+------------+
  *      |     5   |      32    |   1024    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     6   |      16    |   2048    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     7   |       8    |   4096    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     8   |       4    |   8192    |    32768   |
  *      +---------+------------+-----------+------------+
  *      |     9   |       2    |   16384   |    32768   |
  *      +---------+------------+-----------+------------+
  *      |    10   |       2    |   32768   |    65536   |
  *      +---------+------------+-----------+------------+
  *
  * Calculations for other data rate are entirely based on these reference
  * tables. For normal operation, Latency 5 seems give the best, well
  * balanced performance for typical workload. Anything below 5 will
  * eat up CPU to keep up with increasing context switches because of
  * shorter buffer space and usually require the application to handle it
  * aggresively through possibly real time programming technique.
  *
  */
 #define CHN_LATENCY_PBLKCNT_REF				\
 	{{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1},		\
 	{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}}
 #define CHN_LATENCY_PBUFSZ_REF				\
 	{{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16},	\
 	{11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}}
 
 #define CHN_LATENCY_RBLKCNT_REF				\
 	{{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1},		\
 	{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}}
 #define CHN_LATENCY_RBUFSZ_REF				\
 	{{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16},	\
 	{15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}}
 
 #define CHN_LATENCY_DATA_REF	192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */
 
 static int
 chn_calclatency(int dir, int latency, int bps, u_int32_t datarate,
 				u_int32_t max, int *rblksz, int *rblkcnt)
 {
 	static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
 	    CHN_LATENCY_PBLKCNT_REF;
 	static int  pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
 	    CHN_LATENCY_PBUFSZ_REF;
 	static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
 	    CHN_LATENCY_RBLKCNT_REF;
 	static int  rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
 	    CHN_LATENCY_RBUFSZ_REF;
 	u_int32_t bufsz;
 	int lprofile, blksz, blkcnt;
 
 	if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX ||
 	    bps < 1 || datarate < 1 ||
 	    !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) {
 		if (rblksz != NULL)
 			*rblksz = CHN_2NDBUFMAXSIZE >> 1;
 		if (rblkcnt != NULL)
 			*rblkcnt = 2;
 		printf("%s(): FAILED dir=%d latency=%d bps=%d "
 		    "datarate=%u max=%u\n",
 		    __func__, dir, latency, bps, datarate, max);
 		return CHN_2NDBUFMAXSIZE;
 	}
 
 	lprofile = chn_latency_profile;
 
 	if (dir == PCMDIR_PLAY) {
 		blkcnt = pblkcnts[lprofile][latency];
 		bufsz = pbufszs[lprofile][latency];
 	} else {
 		blkcnt = rblkcnts[lprofile][latency];
 		bufsz = rbufszs[lprofile][latency];
 	}
 
 	bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF,
 	    datarate));
 	if (bufsz > max)
 		bufsz = max;
 	blksz = round_blksz(bufsz >> blkcnt, bps);
 
 	if (rblksz != NULL)
 		*rblksz = blksz;
 	if (rblkcnt != NULL)
 		*rblkcnt = 1 << blkcnt;
 
 	return blksz << blkcnt;
 }
 
 static int
 chn_resizebuf(struct pcm_channel *c, int latency,
 					int blkcnt, int blksz)
 {
 	struct snd_dbuf *b, *bs, *pb;
 	int sblksz, sblkcnt, hblksz, hblkcnt, limit = 0, nsblksz, nsblkcnt;
 	int ret;
 
 	CHN_LOCKASSERT(c);
 
 	if ((c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED)) ||
 	    !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC))
 		return EINVAL;
 
 	if (latency == -1) {
 		c->latency = -1;
 		latency = chn_latency;
 	} else if (latency == -2) {
 		latency = c->latency;
 		if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX)
 			latency = chn_latency;
 	} else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX)
 		return EINVAL;
 	else {
 		c->latency = latency;
 	}
 
 	bs = c->bufsoft;
 	b = c->bufhard;
 
 	if (!(blksz == 0 || blkcnt == -1) &&
 	    (blksz < 16 || blksz < sndbuf_getalign(bs) || blkcnt < 2 ||
 	    (blksz * blkcnt) > CHN_2NDBUFMAXSIZE))
 		return EINVAL;
 
 	chn_calclatency(c->direction, latency, sndbuf_getalign(bs),
 	    sndbuf_getalign(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE,
 	    &sblksz, &sblkcnt);
 
 	if (blksz == 0 || blkcnt == -1) {
 		if (blkcnt == -1)
 			c->flags &= ~CHN_F_HAS_SIZE;
 		if (c->flags & CHN_F_HAS_SIZE) {
 			blksz = sndbuf_getblksz(bs);
 			blkcnt = sndbuf_getblkcnt(bs);
 		}
 	} else
 		c->flags |= CHN_F_HAS_SIZE;
 
 	if (c->flags & CHN_F_HAS_SIZE) {
 		/*
 		 * The application has requested their own blksz/blkcnt.
 		 * Just obey with it, and let them toast alone. We can
 		 * clamp it to the nearest latency profile, but that would
 		 * defeat the purpose of having custom control. The least
 		 * we can do is round it to the nearest ^2 and align it.
 		 */
 		sblksz = round_blksz(blksz, sndbuf_getalign(bs));
 		sblkcnt = round_pow2(blkcnt);
 	}
 
 	if (c->parentchannel != NULL) {
 		pb = c->parentchannel->bufsoft;
 		CHN_UNLOCK(c);
 		CHN_LOCK(c->parentchannel);
 		chn_notify(c->parentchannel, CHN_N_BLOCKSIZE);
 		CHN_UNLOCK(c->parentchannel);
 		CHN_LOCK(c);
 		if (c->direction == PCMDIR_PLAY) {
 			limit = (pb != NULL) ?
 			    sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0;
 		} else {
 			limit = (pb != NULL) ?
 			    sndbuf_xbytes(sndbuf_getblksz(pb), pb, bs) * 2 : 0;
 		}
 	} else {
 		hblkcnt = 2;
 		if (c->flags & CHN_F_HAS_SIZE) {
 			hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b),
 			    sndbuf_getalign(b));
 			hblkcnt = round_pow2(sndbuf_getblkcnt(bs));
 		} else
 			chn_calclatency(c->direction, latency,
 			    sndbuf_getalign(b),
 			    sndbuf_getalign(b) * sndbuf_getspd(b),
 			    CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt);
 
 		if ((hblksz << 1) > sndbuf_getmaxsize(b))
 			hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1,
 			    sndbuf_getalign(b));
 
 		while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) {
 			if (hblkcnt < 4)
 				hblksz >>= 1;
 			else
 				hblkcnt >>= 1;
 		}
 
 		hblksz -= hblksz % sndbuf_getalign(b);
 
 #if 0
 		hblksz = sndbuf_getmaxsize(b) >> 1;
 		hblksz -= hblksz % sndbuf_getalign(b);
 		hblkcnt = 2;
 #endif
 
 		CHN_UNLOCK(c);
 		if (chn_usefrags == 0 ||
 		    CHANNEL_SETFRAGMENTS(c->methods, c->devinfo,
 		    hblksz, hblkcnt) != 0)
 			sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods,
 			    c->devinfo, hblksz));
 		CHN_LOCK(c);
 
 		if (!CHN_EMPTY(c, children)) {
 			nsblksz = round_blksz(
 			    sndbuf_xbytes(sndbuf_getblksz(b), b, bs),
 			    sndbuf_getalign(bs));
 			nsblkcnt = sndbuf_getblkcnt(b);
 			if (c->direction == PCMDIR_PLAY) {
 				do {
 					nsblkcnt--;
 				} while (nsblkcnt >= 2 &&
 				    nsblksz * nsblkcnt >= sblksz * sblkcnt);
 				nsblkcnt++;
 			}
 			sblksz = nsblksz;
 			sblkcnt = nsblkcnt;
 			limit = 0;
 		} else
 			limit = sndbuf_xbytes(sndbuf_getblksz(b), b, bs) * 2;
 	}
 
 	if (limit > CHN_2NDBUFMAXSIZE)
 		limit = CHN_2NDBUFMAXSIZE;
 
 #if 0
 	while (limit > 0 && (sblksz * sblkcnt) > limit) {
 		if (sblkcnt < 4)
 			break;
 		sblkcnt >>= 1;
 	}
 #endif
 
 	while ((sblksz * sblkcnt) < limit)
 		sblkcnt <<= 1;
 
 	while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) {
 		if (sblkcnt < 4)
 			sblksz >>= 1;
 		else
 			sblkcnt >>= 1;
 	}
 
 	sblksz -= sblksz % sndbuf_getalign(bs);
 
 	if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz ||
 	    sndbuf_getsize(bs) != (sblkcnt * sblksz)) {
 		ret = sndbuf_remalloc(bs, sblkcnt, sblksz);
 		if (ret != 0) {
 			device_printf(c->dev, "%s(): Failed: %d %d\n",
 			    __func__, sblkcnt, sblksz);
 			return ret;
 		}
 	}
 
 	/*
 	 * Interrupt timeout
 	 */
 	c->timeout = ((u_int64_t)hz * sndbuf_getsize(bs)) /
 	    ((u_int64_t)sndbuf_getspd(bs) * sndbuf_getalign(bs));
 	if (c->parentchannel != NULL)
 		c->timeout = min(c->timeout, c->parentchannel->timeout);
 	if (c->timeout < 1)
 		c->timeout = 1;
 
 	/*
 	 * 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);
 
 	if (snd_verbose > 3)
 		device_printf(c->dev, "%s(): %s (%s) timeout=%u "
 		    "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n",
 		    __func__, CHN_DIRSTR(c),
 		    (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware",
 		    c->timeout,
 		    sndbuf_getsize(b), sndbuf_getblksz(b),
 		    sndbuf_getblkcnt(b),
 		    sndbuf_getsize(bs), sndbuf_getblksz(bs),
 		    sndbuf_getblkcnt(bs), limit);
 
 	return 0;
 }
 
 int
 chn_setlatency(struct pcm_channel *c, int latency)
 {
 	CHN_LOCKASSERT(c);
 	/* Destroy blksz/blkcnt, enforce latency profile. */
 	return chn_resizebuf(c, latency, -1, 0);
 }
 
 int
 chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz)
 {
 	CHN_LOCKASSERT(c);
 	/* Destroy latency profile, enforce blksz/blkcnt */
 	return chn_resizebuf(c, -1, blkcnt, blksz);
 }
 
 int
 chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed)
 {
 	struct pcmchan_caps *caps;
 	uint32_t hwspeed, delta;
 	int ret;
 
 	CHN_LOCKASSERT(c);
 
 	if (speed < 1 || format == 0 || CHN_STARTED(c))
 		return (EINVAL);
 
 	c->format = format;
 	c->speed = speed;
 
 	caps = chn_getcaps(c);
 
 	hwspeed = speed;
 	RANGE(hwspeed, caps->minspeed, caps->maxspeed);
 
 	sndbuf_setspd(c->bufhard, CHANNEL_SETSPEED(c->methods, c->devinfo,
 	    hwspeed));
 	hwspeed = sndbuf_getspd(c->bufhard);
 
 	delta = (hwspeed > speed) ? (hwspeed - speed) : (speed - hwspeed);
 
 	if (delta <= feeder_rate_round)
 		c->speed = hwspeed;
 
 	ret = feeder_chain(c);
 
 	if (ret == 0)
 		ret = CHANNEL_SETFORMAT(c->methods, c->devinfo,
 		    sndbuf_getfmt(c->bufhard));
 
 	if (ret == 0)
 		ret = chn_resizebuf(c, -2, 0, 0);
 
 	return (ret);
 }
 
 int
 chn_setspeed(struct pcm_channel *c, uint32_t speed)
 {
 	uint32_t oldformat, oldspeed, format;
 	int ret;
 
 #if 0
 	/* XXX force 48k */
 	if (c->format & AFMT_PASSTHROUGH)
 		speed = AFMT_PASSTHROUGH_RATE;
 #endif
 
 	oldformat = c->format;
 	oldspeed = c->speed;
 	format = oldformat;
 
 	ret = chn_setparam(c, format, speed);
 	if (ret != 0) {
 		if (snd_verbose > 3)
 			device_printf(c->dev,
 			    "%s(): Setting speed %d failed, "
 			    "falling back to %d\n",
 			    __func__, speed, oldspeed);
 		chn_setparam(c, c->format, oldspeed);
 	}
 
 	return (ret);
 }
 
 int
 chn_setformat(struct pcm_channel *c, uint32_t format)
 {
 	uint32_t oldformat, oldspeed, speed;
 	int ret;
 
 	/* XXX force stereo */
 	if ((format & AFMT_PASSTHROUGH) && AFMT_CHANNEL(format) < 2) {
 		format = SND_FORMAT(format, AFMT_PASSTHROUGH_CHANNEL,
 		    AFMT_PASSTHROUGH_EXTCHANNEL);
 	}
 
 	oldformat = c->format;
 	oldspeed = c->speed;
 	speed = oldspeed;
 
 	ret = chn_setparam(c, format, speed);
 	if (ret != 0) {
 		if (snd_verbose > 3)
 			device_printf(c->dev,
 			    "%s(): Format change 0x%08x failed, "
 			    "falling back to 0x%08x\n",
 			    __func__, format, oldformat);
 		chn_setparam(c, oldformat, oldspeed);
 	}
 
 	return (ret);
 }
 
 void
 chn_syncstate(struct pcm_channel *c)
 {
 	struct snddev_info *d;
 	struct snd_mixer *m;
 
 	d = (c != NULL) ? c->parentsnddev : NULL;
 	m = (d != NULL && d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 :
 	    NULL;
 
 	if (d == NULL || m == NULL)
 		return;
 
 	CHN_LOCKASSERT(c);
 
 	if (c->feederflags & (1 << FEEDER_VOLUME)) {
 		uint32_t parent;
 		int vol, pvol, left, right, center;
 
 		if (c->direction == PCMDIR_PLAY &&
 		    (d->flags & SD_F_SOFTPCMVOL)) {
 			/* CHN_UNLOCK(c); */
 			vol = mix_get(m, SOUND_MIXER_PCM);
 			parent = mix_getparent(m, SOUND_MIXER_PCM);
 			if (parent != SOUND_MIXER_NONE)
 				pvol = mix_get(m, parent);
 			else
 				pvol = 100 | (100 << 8);
 			/* CHN_LOCK(c); */
 		} else {
 			vol = 100 | (100 << 8);
 			pvol = vol;
 		}
 
 		if (vol == -1) {
 			device_printf(c->dev,
 			    "Soft PCM Volume: Failed to read pcm "
 			    "default value\n");
 			vol = 100 | (100 << 8);
 		}
 
 		if (pvol == -1) {
 			device_printf(c->dev,
 			    "Soft PCM Volume: Failed to read parent "
 			    "default value\n");
 			pvol = 100 | (100 << 8);
 		}
 
 		left = ((vol & 0x7f) * (pvol & 0x7f)) / 100;
 		right = (((vol >> 8) & 0x7f) * ((pvol >> 8) & 0x7f)) / 100;
 		center = (left + right) >> 1;
 
 		chn_setvolume_multi(c, SND_VOL_C_MASTER, left, right, center);
 	}
 
 	if (c->feederflags & (1 << FEEDER_EQ)) {
 		struct pcm_feeder *f;
 		int treble, bass, state;
 
 		/* CHN_UNLOCK(c); */
 		treble = mix_get(m, SOUND_MIXER_TREBLE);
 		bass = mix_get(m, SOUND_MIXER_BASS);
 		/* CHN_LOCK(c); */
 
 		if (treble == -1)
 			treble = 50;
 		else
 			treble = ((treble & 0x7f) +
 			    ((treble >> 8) & 0x7f)) >> 1;
 
 		if (bass == -1)
 			bass = 50;
 		else
 			bass = ((bass & 0x7f) + ((bass >> 8) & 0x7f)) >> 1;
 
 		f = chn_findfeeder(c, FEEDER_EQ);
 		if (f != NULL) {
 			if (FEEDER_SET(f, FEEDEQ_TREBLE, treble) != 0)
 				device_printf(c->dev,
 				    "EQ: Failed to set treble -- %d\n",
 				    treble);
 			if (FEEDER_SET(f, FEEDEQ_BASS, bass) != 0)
 				device_printf(c->dev,
 				    "EQ: Failed to set bass -- %d\n",
 				    bass);
 			if (FEEDER_SET(f, FEEDEQ_PREAMP, d->eqpreamp) != 0)
 				device_printf(c->dev,
 				    "EQ: Failed to set preamp -- %d\n",
 				    d->eqpreamp);
 			if (d->flags & SD_F_EQ_BYPASSED)
 				state = FEEDEQ_BYPASS;
 			else if (d->flags & SD_F_EQ_ENABLED)
 				state = FEEDEQ_ENABLE;
 			else
 				state = FEEDEQ_DISABLE;
 			if (FEEDER_SET(f, FEEDEQ_STATE, state) != 0)
 				device_printf(c->dev,
 				    "EQ: Failed to set state -- %d\n", state);
 		}
 	}
 }
 
 int
 chn_trigger(struct pcm_channel *c, int go)
 {
 #ifdef DEV_ISA
     	struct snd_dbuf *b = c->bufhard;
 #endif
 	struct snddev_info *d = c->parentsnddev;
 	int ret;
 
 	CHN_LOCKASSERT(c);
 #ifdef DEV_ISA
 	if (SND_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD))
 		sndbuf_dmabounce(b);
 #endif
 	if (!PCMTRIG_COMMON(go))
 		return (CHANNEL_TRIGGER(c->methods, c->devinfo, go));
 
 	if (go == c->trigger)
 		return (0);
 
 	ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go);
 	if (ret != 0)
 		return (ret);
 
 	switch (go) {
 	case PCMTRIG_START:
 		if (snd_verbose > 3)
 			device_printf(c->dev,
 			    "%s() %s: calling go=0x%08x , "
 			    "prev=0x%08x\n", __func__, c->name, go,
 			    c->trigger);
 		if (c->trigger != PCMTRIG_START) {
 			c->trigger = go;
 			CHN_UNLOCK(c);
 			PCM_LOCK(d);
 			CHN_INSERT_HEAD(d, c, channels.pcm.busy);
 			PCM_UNLOCK(d);
 			CHN_LOCK(c);
 			chn_syncstate(c);
 		}
 		break;
 	case PCMTRIG_STOP:
 	case PCMTRIG_ABORT:
 		if (snd_verbose > 3)
 			device_printf(c->dev,
 			    "%s() %s: calling go=0x%08x , "
 			    "prev=0x%08x\n", __func__, c->name, go,
 			    c->trigger);
 		if (c->trigger == PCMTRIG_START) {
 			c->trigger = go;
 			CHN_UNLOCK(c);
 			PCM_LOCK(d);
 			CHN_REMOVE(d, c, channels.pcm.busy);
 			PCM_UNLOCK(d);
 			CHN_LOCK(c);
 		}
 		break;
 	default:
 		break;
 	}
 
 	return (0);
 }
 
 /**
  * @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)
 {
 	int hwptr;
 
 	CHN_LOCKASSERT(c);
 	hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0;
 	return (hwptr - (hwptr % sndbuf_getalign(c->bufhard)));
 }
 
 struct pcmchan_caps *
 chn_getcaps(struct pcm_channel *c)
 {
 	CHN_LOCKASSERT(c);
 	return CHANNEL_GETCAPS(c->methods, c->devinfo);
 }
 
 u_int32_t
 chn_getformats(struct pcm_channel *c)
 {
 	u_int32_t *fmtlist, fmts;
 	int i;
 
 	fmtlist = chn_getcaps(c)->fmtlist;
 	fmts = 0;
 	for (i = 0; fmtlist[i]; i++)
 		fmts |= fmtlist[i];
 
 	/* report software-supported formats */
 	if (!CHN_BITPERFECT(c) && report_soft_formats)
 		fmts |= AFMT_CONVERTIBLE;
 
 	return (AFMT_ENCODING(fmts));
 }
 
 int
 chn_notify(struct pcm_channel *c, u_int32_t flags)
 {
 	struct pcm_channel *ch;
 	struct pcmchan_caps *caps;
 	uint32_t bestformat, bestspeed, besthwformat, *vchanformat, *vchanrate;
 	uint32_t vpflags;
 	int dirty, err, run, nrun;
 
 	CHN_LOCKASSERT(c);
 
 	if (CHN_EMPTY(c, children))
 		return (ENODEV);
 
 	err = 0;
 
 	/*
 	 * If the hwchan is running, we can't change its rate, format or
 	 * blocksize
 	 */
 	run = (CHN_STARTED(c)) ? 1 : 0;
 	if (run)
 		flags &= CHN_N_VOLUME | CHN_N_TRIGGER;
 
 	if (flags & CHN_N_RATE) {
 		/*
 		 * XXX I'll make good use of this someday.
 		 *     However this is currently being superseded by
 		 *     the availability of CHN_F_VCHAN_DYNAMIC.
 		 */
 	}
 
 	if (flags & CHN_N_FORMAT) {
 		/*
 		 * XXX I'll make good use of this someday.
 		 *     However this is currently being superseded by
 		 *     the availability of CHN_F_VCHAN_DYNAMIC.
 		 */
 	}
 
 	if (flags & CHN_N_VOLUME) {
 		/*
 		 * XXX I'll make good use of this someday, though
 		 *     soft volume control is currently pretty much
 		 *     integrated.
 		 */
 	}
 
 	if (flags & CHN_N_BLOCKSIZE) {
 		/*
 		 * Set to default latency profile
 		 */
 		chn_setlatency(c, chn_latency);
 	}
 
 	if ((flags & CHN_N_TRIGGER) && !(c->flags & CHN_F_VCHAN_DYNAMIC)) {
 		nrun = CHN_EMPTY(c, children.busy) ? 0 : 1;
 		if (nrun && !run)
 			err = chn_start(c, 1);
 		if (!nrun && run)
 			chn_abort(c);
 		flags &= ~CHN_N_TRIGGER;
 	}
 
 	if (flags & CHN_N_TRIGGER) {
 		if (c->direction == PCMDIR_PLAY) {
 			vchanformat = &c->parentsnddev->pvchanformat;
 			vchanrate = &c->parentsnddev->pvchanrate;
 		} else {
 			vchanformat = &c->parentsnddev->rvchanformat;
 			vchanrate = &c->parentsnddev->rvchanrate;
 		}
 
 		/* Dynamic Virtual Channel */
 		if (!(c->flags & CHN_F_VCHAN_ADAPTIVE)) {
 			bestformat = *vchanformat;
 			bestspeed = *vchanrate;
 		} else {
 			bestformat = 0;
 			bestspeed = 0;
 		}
 
 		besthwformat = 0;
 		nrun = 0;
 		caps = chn_getcaps(c);
 		dirty = 0;
 		vpflags = 0;
 
 		CHN_FOREACH(ch, c, children.busy) {
 			CHN_LOCK(ch);
 			if ((ch->format & AFMT_PASSTHROUGH) &&
 			    snd_fmtvalid(ch->format, caps->fmtlist)) {
 				bestformat = ch->format;
 				bestspeed = ch->speed;
 				CHN_UNLOCK(ch);
 				vpflags = CHN_F_PASSTHROUGH;
 				nrun++;
 				break;
 			}
 			if ((ch->flags & CHN_F_EXCLUSIVE) && vpflags == 0) {
 				if (c->flags & CHN_F_VCHAN_ADAPTIVE) {
 					bestspeed = ch->speed;
 					RANGE(bestspeed, caps->minspeed,
 					    caps->maxspeed);
 					besthwformat = snd_fmtbest(ch->format,
 					    caps->fmtlist);
 					if (besthwformat != 0)
 						bestformat = besthwformat;
 				}
 				CHN_UNLOCK(ch);
 				vpflags = CHN_F_EXCLUSIVE;
 				nrun++;
 				continue;
 			}
 			if (!(c->flags & CHN_F_VCHAN_ADAPTIVE) ||
 			    vpflags != 0) {
 				CHN_UNLOCK(ch);
 				nrun++;
 				continue;
 			}
 			if (ch->speed > bestspeed) {
 				bestspeed = ch->speed;
 				RANGE(bestspeed, caps->minspeed,
 				    caps->maxspeed);
 			}
 			besthwformat = snd_fmtbest(ch->format, caps->fmtlist);
 			if (!(besthwformat & AFMT_VCHAN)) {
 				CHN_UNLOCK(ch);
 				nrun++;
 				continue;
 			}
 			if (AFMT_CHANNEL(besthwformat) >
 			    AFMT_CHANNEL(bestformat))
 				bestformat = besthwformat;
 			else if (AFMT_CHANNEL(besthwformat) ==
 			    AFMT_CHANNEL(bestformat) &&
 			    AFMT_BIT(besthwformat) > AFMT_BIT(bestformat))
 				bestformat = besthwformat;
 			CHN_UNLOCK(ch);
 			nrun++;
 		}
 
 		if (bestformat == 0)
 			bestformat = c->format;
 		if (bestspeed == 0)
 			bestspeed = c->speed;
 
 		if (bestformat != c->format || bestspeed != c->speed)
 			dirty = 1;
 
 		c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE);
 		c->flags |= vpflags;
 
 		if (nrun && !run) {
 			if (dirty) {
 				bestspeed = CHANNEL_SETSPEED(c->methods,
 				    c->devinfo, bestspeed);
 				err = chn_reset(c, bestformat, bestspeed);
 			}
 			if (err == 0 && dirty) {
 				CHN_FOREACH(ch, c, children.busy) {
 					CHN_LOCK(ch);
 					if (VCHAN_SYNC_REQUIRED(ch))
 						vchan_sync(ch);
 					CHN_UNLOCK(ch);
 				}
 			}
 			if (err == 0) {
 				if (dirty)
 					c->flags |= CHN_F_DIRTY;
 				err = chn_start(c, 1);
 			}
 		}
 
 		if (nrun && run && dirty) {
 			chn_abort(c);
 			bestspeed = CHANNEL_SETSPEED(c->methods, c->devinfo,
 			    bestspeed);
 			err = chn_reset(c, bestformat, bestspeed);
 			if (err == 0) {
 				CHN_FOREACH(ch, c, children.busy) {
 					CHN_LOCK(ch);
 					if (VCHAN_SYNC_REQUIRED(ch))
 						vchan_sync(ch);
 					CHN_UNLOCK(ch);
 				}
 			}
 			if (err == 0) {
 				c->flags |= CHN_F_DIRTY;
 				err = chn_start(c, 1);
 			}
 		}
 
 		if (err == 0 && !(bestformat & AFMT_PASSTHROUGH) &&
 		    (bestformat & AFMT_VCHAN)) {
 			*vchanformat = bestformat;
 			*vchanrate = bestspeed;
 		}
 
 		if (!nrun && run) {
 			c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE);
 			bestformat = *vchanformat;
 			bestspeed = *vchanrate;
 			chn_abort(c);
 			if (c->format != bestformat || c->speed != bestspeed)
 				chn_reset(c, bestformat, bestspeed);
 		}
 	}
 
 	return (err);
 }
 
 /**
  * @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 function is initially intended for the following conditions:
  *   - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl)
  *   - Closing a device.  (A channel can't be destroyed if it's still in use.)
  *
  * @note Before calling this function, the syncgroup list mutex must be
  * held.  (Consider pcm_channel::sm protected by the SG list mutex
  * whether @c c is locked or not.)
  *
  * @param c	channel device to be started or closed
  * @returns	If this channel was the only member of a group, the group ID
  * 		is returned to the caller so that the caller can release it
  * 		via free_unr() after giving up the syncgroup lock.  Else it
  * 		returns 0.
  */
 int
 chn_syncdestroy(struct pcm_channel *c)
 {
 	struct pcmchan_syncmember *sm;
 	struct pcmchan_syncgroup *sg;
 	int sg_id;
 
 	sg_id = 0;
 
 	PCM_SG_LOCKASSERT(MA_OWNED);
 
 	if (c->sm != NULL) {
 		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);
 			sg_id = sg->id;
 			free(sg, M_DEVBUF);
 		}
 	}
 
 	return sg_id;
 }
 
 #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
diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h
index 34d62f4e15c2..60b7b3416cc3 100644
--- a/sys/dev/sound/pcm/channel.h
+++ b/sys/dev/sound/pcm/channel.h
@@ -1,445 +1,451 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  *
  * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
  * Portions Copyright (c) Ryan Beasley <ryan.beasley@gmail.com> - GSoC 2006
  * Copyright (c) 1999 Cameron Grant <cg@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 struct pcmchan_caps {
 	u_int32_t minspeed, maxspeed;
 	u_int32_t *fmtlist;
 	u_int32_t caps;
 };
 
 struct pcmchan_matrix {
 	int id;
 	uint8_t channels, ext;
 	struct {
 		int type;
 		uint32_t members;
 	} map[SND_CHN_T_MAX + 1];
 	uint32_t mask;
 	int8_t offset[SND_CHN_T_MAX];
 };
 
 /* 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)
 #define PCM_SG_LOCKASSERT(arg)	mtx_assert(&snd_pcm_syncgroups_mtx, arg)
 
 /**
  * @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
 #define	CHN_COMM_UNUSED		"<UNUSED>"
 #define	CHN_COMM_UNKNOWN	"<UNKNOWN>"
 
 struct pcm_channel {
 	kobj_t methods;
 
 	pid_t pid;
 	int refcount;
 	struct pcm_feeder *feeder;
 	u_int32_t align;
 
 	int latency;
 	u_int32_t speed;
 	u_int32_t format;
 	u_int32_t flags;
 	u_int32_t feederflags;
 	u_int64_t blocks;
 
 	int direction;
 	unsigned int interrupts, xruns, feedcount;
 	unsigned int timeout;
 	struct snd_dbuf *bufhard, *bufsoft;
 	struct snddev_info *parentsnddev;
 	struct pcm_channel *parentchannel;
 	void *devinfo;
 	device_t dev;
 	int unit;
 	char name[CHN_NAMELEN];
 	char comm[MAXCOMLEN + 1];
 	struct mtx *lock;
 	int trigger;
 	/**
 	 * For interrupt manipulations.
 	 */
 	struct cv intr_cv;
 	/**
 	 * 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
 
 	struct {
 		SLIST_HEAD(, pcm_channel) head;
 		SLIST_ENTRY(pcm_channel) link;
 		struct {
 			SLIST_HEAD(, pcm_channel) head;
 			SLIST_ENTRY(pcm_channel) link;
 		} busy;
 	} children;
 
 	struct {
 		struct {
 			SLIST_ENTRY(pcm_channel) link;
 			struct {
 				SLIST_ENTRY(pcm_channel) link;
 			} busy;
 			struct {
 				SLIST_ENTRY(pcm_channel) link;
 			} opened;
 		} pcm;
 	} channels;
 
 	struct pcmchan_matrix matrix;
   	struct pcmchan_matrix matrix_scratch;
 
-	int volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX];
+	int16_t volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX];
+  	int8_t muted[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX];
 
 	void *data1, *data2;
 };
 
 #define CHN_HEAD(x, y)			&(x)->y.head
 #define CHN_INIT(x, y)			SLIST_INIT(CHN_HEAD(x, y))
 #define CHN_LINK(y)			y.link
 #define CHN_EMPTY(x, y)			SLIST_EMPTY(CHN_HEAD(x, y))
 #define CHN_FIRST(x, y)			SLIST_FIRST(CHN_HEAD(x, y))
 
 #define CHN_FOREACH(x, y, z)						\
 	SLIST_FOREACH(x, CHN_HEAD(y, z), CHN_LINK(z))
 
 #define CHN_FOREACH_SAFE(w, x, y, z)					\
 	SLIST_FOREACH_SAFE(w, CHN_HEAD(x, z), CHN_LINK(z), y)
 
 #define CHN_INSERT_HEAD(x, y, z)					\
 	SLIST_INSERT_HEAD(CHN_HEAD(x, z), y, CHN_LINK(z))
 
 #define CHN_INSERT_AFTER(x, y, z)					\
 	SLIST_INSERT_AFTER(x, y, CHN_LINK(z))
 
 #define CHN_REMOVE(x, y, z)						\
 	SLIST_REMOVE(CHN_HEAD(x, z), y, pcm_channel, CHN_LINK(z))
 
 #define CHN_INSERT_HEAD_SAFE(x, y, z)		do {			\
 	struct pcm_channel *t = NULL;					\
 	CHN_FOREACH(t, x, z) {						\
 		if (t == y)						\
 			break;						\
 	} 								\
 	if (t != y)							\
 		CHN_INSERT_HEAD(x, y, z);				\
 } while (0)
 
 #define CHN_INSERT_AFTER_SAFE(w, x, y, z)	do {			\
 	struct pcm_channel *t = NULL;					\
 	CHN_FOREACH(t, w, z) {						\
 		if (t == y)						\
 			break;						\
 	} 								\
 	if (t != y)							\
 		CHN_INSERT_AFTER(x, y, z);				\
 } while (0)
 
 #define CHN_REMOVE_SAFE(x, y, z)		do {			\
 	struct pcm_channel *t = NULL;					\
 	CHN_FOREACH(t, x, z) {						\
 		if (t == y)						\
 			break;						\
 	} 								\
 	if (t == y)							\
 		CHN_REMOVE(x, y, z);					\
 } while (0)
 
 #define CHN_INSERT_SORT(w, x, y, z)		do {			\
 	struct pcm_channel *t, *a = NULL;				\
 	CHN_FOREACH(t, x, z) {						\
 		if ((y)->unit w t->unit)				\
 			a = t;						\
 		else							\
 			break;						\
 	}								\
 	if (a != NULL)							\
 		CHN_INSERT_AFTER(a, y, z);				\
 	else								\
 		CHN_INSERT_HEAD(x, y, z);				\
 } while (0)
 
 #define CHN_INSERT_SORT_ASCEND(x, y, z)		CHN_INSERT_SORT(>, x, y, z)
 #define CHN_INSERT_SORT_DESCEND(x, y, z)	CHN_INSERT_SORT(<, x, y, z)
 
 #define CHN_UNIT(x)	(snd_unit2u((x)->unit))
 #define CHN_DEV(x)	(snd_unit2d((x)->unit))
 #define CHN_CHAN(x)	(snd_unit2c((x)->unit))
 
 #define CHN_BUF_PARENT(x, y)						\
 	(((x) != NULL && (x)->parentchannel != NULL &&			\
 	(x)->parentchannel->bufhard != NULL) ?				\
 	(x)->parentchannel->bufhard : (y))
 
 #define CHN_BROADCAST(x)	do {					\
 	if ((x)->cv_waiters != 0)					\
 		cv_broadcastpri(x, PRIBIO);				\
 } while (0)
 
 #include "channel_if.h"
 
 int chn_reinit(struct pcm_channel *c);
 int chn_write(struct pcm_channel *c, struct uio *buf);
 int chn_read(struct pcm_channel *c, struct uio *buf);
 u_int32_t chn_start(struct pcm_channel *c, int force);
 int chn_sync(struct pcm_channel *c, int threshold);
 int chn_flush(struct pcm_channel *c);
 int chn_poll(struct pcm_channel *c, int ev, struct thread *td);
 
 int chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction);
 int chn_kill(struct pcm_channel *c);
 int chn_reset(struct pcm_channel *c, u_int32_t fmt, u_int32_t spd);
 int chn_setvolume(struct pcm_channel *c, int left, int right);
 int chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right,
     int center);
 int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val);
 int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt);
+int chn_setmute_multi(struct pcm_channel *c, int vc, int mute);
+int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute);
+int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt);
 void chn_vpc_reset(struct pcm_channel *c, int vc, int force);
 int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed);
 int chn_setspeed(struct pcm_channel *c, uint32_t speed);
 int chn_setformat(struct pcm_channel *c, uint32_t format);
 int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz);
 int chn_setlatency(struct pcm_channel *c, int latency);
 void chn_syncstate(struct pcm_channel *c);
 int chn_trigger(struct pcm_channel *c, int go);
 int chn_getptr(struct pcm_channel *c);
 struct pcmchan_caps *chn_getcaps(struct pcm_channel *c);
 u_int32_t chn_getformats(struct pcm_channel *c);
 
 struct pcmchan_matrix *chn_getmatrix(struct pcm_channel *);
 int chn_setmatrix(struct pcm_channel *, struct pcmchan_matrix *);
 
 int chn_oss_getorder(struct pcm_channel *, unsigned long long *);
 int chn_oss_setorder(struct pcm_channel *, unsigned long long *);
 int chn_oss_getmask(struct pcm_channel *, uint32_t *);
 
 void chn_resetbuf(struct pcm_channel *c);
 void chn_intr_locked(struct pcm_channel *c);
 void chn_intr(struct pcm_channel *c);
 int chn_abort(struct pcm_channel *c);
 
 int chn_notify(struct pcm_channel *c, u_int32_t flags);
 
 int chn_getrates(struct pcm_channel *c, int **rates);
 int chn_syncdestroy(struct pcm_channel *c);
 
 #define CHN_SETVOLUME(...)		chn_setvolume_matrix(__VA_ARGS__)
 #if defined(SND_DIAGNOSTIC) || defined(INVARIANTS)
 #define CHN_GETVOLUME(...)		chn_getvolume_matrix(__VA_ARGS__)
 #else
 #define CHN_GETVOLUME(x, y, z)		((x)->volume[y][z])
 #endif
 
+#define CHN_GETMUTE(x, y, z)		((x)->muted[y][z])
+
 #ifdef OSSV4_EXPERIMENT
 int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak);
 #endif
 
 #define CHN_LOCKOWNED(c)	mtx_owned((c)->lock)
 #define CHN_LOCK(c)		mtx_lock((c)->lock)
 #define CHN_UNLOCK(c)		mtx_unlock((c)->lock)
 #define CHN_TRYLOCK(c)		mtx_trylock((c)->lock)
 #define CHN_LOCKASSERT(c)	mtx_assert((c)->lock, MA_OWNED)
 #define CHN_UNLOCKASSERT(c)	mtx_assert((c)->lock, MA_NOTOWNED)
 
 int snd_fmtvalid(uint32_t fmt, uint32_t *fmtlist);
 
 uint32_t snd_str2afmt(const char *);
 uint32_t snd_afmt2str(uint32_t, char *, size_t);
 
 #define AFMTSTR_LEN	16
 
 extern int chn_latency;
 extern int chn_latency_profile;
 extern int report_soft_formats;
 extern int report_soft_matrix;
 
 #define PCMDIR_PLAY		1
 #define PCMDIR_PLAY_VIRTUAL	2
 #define PCMDIR_REC		-1
 #define PCMDIR_REC_VIRTUAL	-2
 
 #define PCMTRIG_START 1
 #define PCMTRIG_EMLDMAWR 2
 #define PCMTRIG_EMLDMARD 3
 #define PCMTRIG_STOP 0
 #define PCMTRIG_ABORT -1
 
 #define PCMTRIG_COMMON(x)	((x) == PCMTRIG_START ||		\
 				 (x) == PCMTRIG_STOP ||			\
 				 (x) == PCMTRIG_ABORT)
 
 #define CHN_F_CLOSING           0x00000001  /* a pending close */
 #define CHN_F_ABORTING          0x00000002  /* a pending abort */
 #define CHN_F_RUNNING		0x00000004  /* dma is running */
 #define CHN_F_TRIGGERED		0x00000008
 #define CHN_F_NOTRIGGER		0x00000010
 #define CHN_F_SLEEPING		0x00000020
 
 #define CHN_F_NBIO              0x00000040  /* do non-blocking i/o */
 #define CHN_F_MMAP		0x00000080  /* has been mmap()ed */
 
 #define CHN_F_BUSY              0x00000100  /* has been opened 	*/
 #define CHN_F_DIRTY		0x00000200  /* need re-config */
 #define CHN_F_DEAD		0x00000400  /* too many errors, dead, mdk */
 #define CHN_F_SILENCE		0x00000800  /* silence, nil, null, yada */
 
 #define	CHN_F_HAS_SIZE		0x00001000  /* user set block size */
 #define CHN_F_HAS_VCHAN		0x00002000  /* vchan master */
 
 #define CHN_F_VCHAN_PASSTHROUGH	0x00004000  /* digital ac3/dts passthrough */
 #define CHN_F_VCHAN_ADAPTIVE	0x00008000  /* adaptive format/rate selection */
 #define CHN_F_VCHAN_DYNAMIC	(CHN_F_VCHAN_PASSTHROUGH | CHN_F_VCHAN_ADAPTIVE)
 
 #define	CHN_F_VIRTUAL		0x10000000  /* not backed by hardware */
 #define CHN_F_BITPERFECT	0x20000000  /* un-cooked, Heh.. */
 #define CHN_F_PASSTHROUGH	0x40000000  /* passthrough re-config */
 #define CHN_F_EXCLUSIVE		0x80000000  /* exclusive access */
 
 #define CHN_F_BITS		"\020"					\
 				"\001CLOSING"				\
 				"\002ABORTING"				\
 				"\003RUNNING"				\
 				"\004TRIGGERED"				\
 				"\005NOTRIGGER"				\
 				"\006SLEEPING"				\
 				"\007NBIO"				\
 				"\010MMAP"				\
 				"\011BUSY"				\
 				"\012DIRTY"				\
 				"\013DEAD"				\
 				"\014SILENCE"				\
 				"\015HAS_SIZE"				\
 				"\016HAS_VCHAN"				\
 				"\017VCHAN_PASSTHROUGH"			\
 				"\020VCHAN_ADAPTIVE"			\
 				"\035VIRTUAL"				\
 				"\036BITPERFECT"			\
 				"\037PASSTHROUGH"			\
 				"\040EXCLUSIVE"
 
 #define CHN_F_RESET		(CHN_F_BUSY | CHN_F_DEAD |		\
 				 CHN_F_VIRTUAL | CHN_F_HAS_VCHAN |	\
 				 CHN_F_VCHAN_DYNAMIC |			\
 				 CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE)
 
 #define CHN_F_MMAP_INVALID	(CHN_F_DEAD | CHN_F_RUNNING)
 
 					
 
 #define CHN_N_RATE		0x00000001
 #define CHN_N_FORMAT		0x00000002
 #define CHN_N_VOLUME		0x00000004
 #define CHN_N_BLOCKSIZE		0x00000008
 #define CHN_N_TRIGGER		0x00000010
 
 #define CHN_LATENCY_MIN		0
 #define CHN_LATENCY_MAX		10
 #define	CHN_LATENCY_DEFAULT	2	/* 21.3ms total buffering */
 #define CHN_POLICY_MIN		CHN_LATENCY_MIN
 #define CHN_POLICY_MAX		CHN_LATENCY_MAX
 #define CHN_POLICY_DEFAULT	CHN_LATENCY_DEFAULT
 
 #define CHN_LATENCY_PROFILE_MIN		0
 #define CHN_LATENCY_PROFILE_MAX		1
 #define CHN_LATENCY_PROFILE_DEFAULT	CHN_LATENCY_PROFILE_MAX
 
 #define CHN_STARTED(c)		((c)->flags & CHN_F_TRIGGERED)
 #define CHN_STOPPED(c)		(!CHN_STARTED(c))
 #define CHN_DIRSTR(c)		(((c)->direction == PCMDIR_PLAY) ? \
 				"PCMDIR_PLAY" : "PCMDIR_REC")
 #define CHN_BITPERFECT(c)	((c)->flags & CHN_F_BITPERFECT)
 #define CHN_PASSTHROUGH(c)	((c)->flags & CHN_F_PASSTHROUGH)
 
 #define CHN_TIMEOUT		5
 #define CHN_TIMEOUT_MIN		1
 #define CHN_TIMEOUT_MAX		10
 
 /*
  * This should be large enough to hold all pcm data between
  * tsleeps in chn_{read,write} at the highest sample rate.
  * (which is usually 48kHz * 16bit * stereo = 192000 bytes/sec)
  */
 #define CHN_2NDBUFBLKSIZE	(2 * 1024)
 /* The total number of blocks per secondary bufhard. */
 #define CHN_2NDBUFBLKNUM	(32)
 /* The size of a whole secondary bufhard. */
 #define CHN_2NDBUFMAXSIZE	(131072)
 
 #define CHANNEL_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj))
diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c
index cce05f4ecf37..15f437b8627c 100644
--- a/sys/dev/sound/pcm/dsp.c
+++ b/sys/dev/sound/pcm/dsp.c
@@ -1,3378 +1,3407 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  *
  * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
  * Portions Copyright (c) Ryan Beasley <ryan.beasley@gmail.com> - GSoC 2006
  * Copyright (c) 1999 Cameron Grant <cg@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 #ifdef HAVE_KERNEL_OPTION_HEADERS
 #include "opt_snd.h"
 #endif
 
 #include <dev/sound/pcm/sound.h>
 #include <sys/ctype.h>
 #include <sys/lock.h>
 #include <sys/rwlock.h>
 #include <sys/sysent.h>
 
 #include <vm/vm.h>
 #include <vm/vm_object.h>
 #include <vm/vm_page.h>
 #include <vm/vm_pager.h>
 
 SND_DECLARE_FILE("$FreeBSD$");
 
 static int dsp_mmap_allow_prot_exec = 0;
 SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RWTUN,
     &dsp_mmap_allow_prot_exec, 0,
     "linux mmap compatibility (-1=force disable 0=auto 1=force enable)");
 
 static int dsp_basename_clone = 1;
 SYSCTL_INT(_hw_snd, OID_AUTO, basename_clone, CTLFLAG_RWTUN,
     &dsp_basename_clone, 0,
     "DSP basename cloning (0: Disable; 1: Enabled)");
 
 struct dsp_cdevinfo {
 	struct pcm_channel *rdch, *wrch;
 	struct pcm_channel *volch;
 	int busy, simplex;
 	TAILQ_ENTRY(dsp_cdevinfo) link;
 };
 
 #define PCM_RDCH(x)		(((struct dsp_cdevinfo *)(x)->si_drv1)->rdch)
 #define PCM_WRCH(x)		(((struct dsp_cdevinfo *)(x)->si_drv1)->wrch)
 #define PCM_VOLCH(x)		(((struct dsp_cdevinfo *)(x)->si_drv1)->volch)
 #define PCM_SIMPLEX(x)		(((struct dsp_cdevinfo *)(x)->si_drv1)->simplex)
 
 #define DSP_CDEVINFO_CACHESIZE	8
 
 #define DSP_REGISTERED(x, y)	(PCM_REGISTERED(x) &&			\
 				 (y) != NULL && (y)->si_drv1 != NULL)
 
 #define OLDPCM_IOCTL
 
 static d_open_t dsp_open;
 static d_close_t dsp_close;
 static d_read_t dsp_read;
 static d_write_t dsp_write;
 static d_ioctl_t dsp_ioctl;
 static d_poll_t dsp_poll;
 static d_mmap_t dsp_mmap;
 static d_mmap_single_t dsp_mmap_single;
 
 struct cdevsw dsp_cdevsw = {
 	.d_version =	D_VERSION,
 	.d_open =	dsp_open,
 	.d_close =	dsp_close,
 	.d_read =	dsp_read,
 	.d_write =	dsp_write,
 	.d_ioctl =	dsp_ioctl,
 	.d_poll =	dsp_poll,
 	.d_mmap =	dsp_mmap,
 	.d_mmap_single = dsp_mmap_single,
 	.d_name =	"dsp",
 };
 
 static eventhandler_tag dsp_ehtag = NULL;
 static int dsp_umax = -1;
 static int dsp_cmax = -1;
 
 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);
 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_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask);
 #ifdef OSSV4_EXPERIMENT
 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)
 {
 	return (devclass_get_softc(pcm_devclass, PCMUNIT(dev)));
 }
 
 static uint32_t
 dsp_get_flags(struct cdev *dev)
 {
 	device_t bdev;
 
 	bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev));
 
 	return ((bdev != NULL) ? pcm_getflags(bdev) : 0xffffffff);
 }
 
 static void
 dsp_set_flags(struct cdev *dev, uint32_t flags)
 {
 	device_t bdev;
 
 	bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev));
 
 	if (bdev != NULL)
 		pcm_setflags(bdev, flags);
 }
 
 /*
  * return the channels associated with an open device instance.
  * lock channels specified.
  */
 static int
 getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch,
     uint32_t prio)
 {
 	struct snddev_info *d;
 	struct pcm_channel *ch;
 	uint32_t flags;
 
 	if (PCM_SIMPLEX(dev) != 0) {
 		d = dsp_get_info(dev);
 		if (!PCM_REGISTERED(d))
 			return (ENXIO);
 		PCM_LOCK(d);
 		PCM_WAIT(d);
 		PCM_ACQUIRE(d);
 		/*
 		 * Note: order is important -
 		 *       pcm flags -> prio query flags -> wild guess
 		 */
 		ch = NULL;
 		flags = dsp_get_flags(dev);
 		if (flags & SD_F_PRIO_WR) {
 			ch = PCM_RDCH(dev);
 			PCM_RDCH(dev) = NULL;
 		} else if (flags & SD_F_PRIO_RD) {
 			ch = PCM_WRCH(dev);
 			PCM_WRCH(dev) = NULL;
 		} else if (prio & SD_F_PRIO_WR) {
 			ch = PCM_RDCH(dev);
 			PCM_RDCH(dev) = NULL;
 			flags |= SD_F_PRIO_WR;
 		} else if (prio & SD_F_PRIO_RD) {
 			ch = PCM_WRCH(dev);
 			PCM_WRCH(dev) = NULL;
 			flags |= SD_F_PRIO_RD;
 		} else if (PCM_WRCH(dev) != NULL) {
 			ch = PCM_RDCH(dev);
 			PCM_RDCH(dev) = NULL;
 			flags |= SD_F_PRIO_WR;
 		} else if (PCM_RDCH(dev) != NULL) {
 			ch = PCM_WRCH(dev);
 			PCM_WRCH(dev) = NULL;
 			flags |= SD_F_PRIO_RD;
 		}
 		PCM_SIMPLEX(dev) = 0;
 		dsp_set_flags(dev, flags);
 		if (ch != NULL) {
 			CHN_LOCK(ch);
 			pcm_chnref(ch, -1);
 			pcm_chnrelease(ch);
 		}
 		PCM_RELEASE(d);
 		PCM_UNLOCK(d);
 	}
 
 	*rdch = PCM_RDCH(dev);
 	*wrch = PCM_WRCH(dev);
 
 	if (*rdch != NULL && (prio & SD_F_PRIO_RD))
 		CHN_LOCK(*rdch);
 	if (*wrch != NULL && (prio & SD_F_PRIO_WR))
 		CHN_LOCK(*wrch);
 
 	return (0);
 }
 
 /* unlock specified channels */
 static void
 relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch,
     uint32_t prio)
 {
 	if (wrch != NULL && (prio & SD_F_PRIO_WR))
 		CHN_UNLOCK(wrch);
 	if (rdch != NULL && (prio & SD_F_PRIO_RD))
 		CHN_UNLOCK(rdch);
 }
 
 static void
 dsp_cdevinfo_alloc(struct cdev *dev,
     struct pcm_channel *rdch, struct pcm_channel *wrch,
     struct pcm_channel *volch)
 {
 	struct snddev_info *d;
 	struct dsp_cdevinfo *cdi;
 	int simplex;
 
 	d = dsp_get_info(dev);
 
 	KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 == NULL &&
 	    ((rdch == NULL && wrch == NULL) || rdch != wrch),
 	    ("bogus %s(), what are you trying to accomplish here?", __func__));
 	PCM_BUSYASSERT(d);
 	PCM_LOCKASSERT(d);
 
 	simplex = (dsp_get_flags(dev) & SD_F_SIMPLEX) ? 1 : 0;
 
 	/*
 	 * Scan for free instance entry and put it into the end of list.
 	 * Create new one if necessary.
 	 */
 	TAILQ_FOREACH(cdi, &d->dsp_cdevinfo_pool, link) {
 		if (cdi->busy != 0)
 			break;
 		cdi->rdch = rdch;
 		cdi->wrch = wrch;
 		cdi->volch = volch;
 		cdi->simplex = simplex;
 		cdi->busy = 1;
 		TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link);
 		TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link);
 		dev->si_drv1 = cdi;
 		return;
 	}
 	PCM_UNLOCK(d);
 	cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO);
 	PCM_LOCK(d);
 	cdi->rdch = rdch;
 	cdi->wrch = wrch;
 	cdi->volch = volch;
 	cdi->simplex = simplex;
 	cdi->busy = 1;
 	TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link);
 	dev->si_drv1 = cdi;
 }
 
 static void
 dsp_cdevinfo_free(struct cdev *dev)
 {
 	struct snddev_info *d;
 	struct dsp_cdevinfo *cdi, *tmp;
 	uint32_t flags;
 	int i;
 
 	d = dsp_get_info(dev);
 
 	KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 != NULL &&
 	    PCM_RDCH(dev) == NULL && PCM_WRCH(dev) == NULL &&
 	    PCM_VOLCH(dev) == NULL,
 	    ("bogus %s(), what are you trying to accomplish here?", __func__));
 	PCM_BUSYASSERT(d);
 	PCM_LOCKASSERT(d);
 
 	cdi = dev->si_drv1;
 	dev->si_drv1 = NULL;
 	cdi->rdch = NULL;
 	cdi->wrch = NULL;
 	cdi->volch = NULL;
 	cdi->simplex = 0;
 	cdi->busy = 0;
 
 	/*
 	 * Once it is free, move it back to the beginning of list for
 	 * faster new entry allocation.
 	 */
 	TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link);
 	TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link);
 
 	/*
 	 * Scan the list, cache free entries up to DSP_CDEVINFO_CACHESIZE.
 	 * Reset simplex flags.
 	 */
 	flags = dsp_get_flags(dev) & ~SD_F_PRIO_SET;
 	i = DSP_CDEVINFO_CACHESIZE;
 	TAILQ_FOREACH_SAFE(cdi, &d->dsp_cdevinfo_pool, link, tmp) {
 		if (cdi->busy != 0) {
 			if (cdi->simplex == 0) {
 				if (cdi->rdch != NULL)
 					flags |= SD_F_PRIO_RD;
 				if (cdi->wrch != NULL)
 					flags |= SD_F_PRIO_WR;
 			}
 		} else {
 			if (i == 0) {
 				TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link);
 				free(cdi, M_DEVBUF);
 			} else
 				i--;
 		}
 	}
 	dsp_set_flags(dev, flags);
 }
 
 void
 dsp_cdevinfo_init(struct snddev_info *d)
 {
 	struct dsp_cdevinfo *cdi;
 	int i;
 
 	KASSERT(d != NULL, ("NULL snddev_info"));
 	PCM_BUSYASSERT(d);
 	PCM_UNLOCKASSERT(d);
 
 	TAILQ_INIT(&d->dsp_cdevinfo_pool);
 	for (i = 0; i < DSP_CDEVINFO_CACHESIZE; i++) {
 		cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO);
 		TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link);
 	}
 }
 
 void
 dsp_cdevinfo_flush(struct snddev_info *d)
 {
 	struct dsp_cdevinfo *cdi, *tmp;
 
 	KASSERT(d != NULL, ("NULL snddev_info"));
 	PCM_BUSYASSERT(d);
 	PCM_UNLOCKASSERT(d);
 
 	cdi = TAILQ_FIRST(&d->dsp_cdevinfo_pool);
 	while (cdi != NULL) {
 		tmp = TAILQ_NEXT(cdi, link);
 		free(cdi, M_DEVBUF);
 		cdi = tmp;
 	}
 	TAILQ_INIT(&d->dsp_cdevinfo_pool);
 }
 
 /* duplex / simplex cdev type */
 enum {
 	DSP_CDEV_TYPE_RDONLY,		/* simplex read-only (record)   */
 	DSP_CDEV_TYPE_WRONLY,		/* simplex write-only (play)    */
 	DSP_CDEV_TYPE_RDWR		/* duplex read, write, or both  */
 };
 
 enum {
 	DSP_CDEV_VOLCTL_NONE,
 	DSP_CDEV_VOLCTL_READ,
 	DSP_CDEV_VOLCTL_WRITE
 };
 
 #define DSP_F_VALID(x)		((x) & (FREAD | FWRITE))
 #define DSP_F_DUPLEX(x)		(((x) & (FREAD | FWRITE)) == (FREAD | FWRITE))
 #define DSP_F_SIMPLEX(x)	(!DSP_F_DUPLEX(x))
 #define DSP_F_READ(x)		((x) & FREAD)
 #define DSP_F_WRITE(x)		((x) & FWRITE)
 
 static const struct {
 	int type;
 	char *name;
 	char *sep;
 	char *alias;
 	int use_sep;
 	int hw;
 	int max;
 	int volctl;
 	uint32_t fmt, spd;
 	int query;
 } dsp_cdevs[] = {
 	{ SND_DEV_DSP,         "dsp",    ".", NULL, 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_U8, 1, 0),     DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 	{ SND_DEV_AUDIO,       "audio",  ".", NULL, 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_MU_LAW, 1, 0), DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 	{ SND_DEV_DSP16,       "dspW",   ".", NULL, 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_S16_LE, 1, 0), DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 	{ SND_DEV_DSPHW_PLAY,  "dsp",   ".p", NULL, 1, 1, SND_MAXHWCHAN, 1,
 	  SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY },
 	{ SND_DEV_DSPHW_VPLAY, "dsp",  ".vp", NULL, 1, 1, SND_MAXVCHANS, 1,
 	  SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY },
 	{ SND_DEV_DSPHW_REC,   "dsp",   ".r", NULL, 1, 1, SND_MAXHWCHAN, 1,
 	  SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY },
 	{ SND_DEV_DSPHW_VREC,  "dsp",  ".vr", NULL, 1, 1, SND_MAXVCHANS, 1,
 	  SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY },
 	{ SND_DEV_DSPHW_CD,    "dspcd",  ".", NULL, 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_S16_LE, 2, 0), 44100, DSP_CDEV_TYPE_RDWR   },
 	/* Low priority, OSSv4 aliases. */
 	{ SND_DEV_DSP,      "dsp_ac3",   ".", "dsp", 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_U8, 1, 0),     DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 	{ SND_DEV_DSP,     "dsp_mmap",   ".", "dsp", 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_U8, 1, 0),     DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 	{ SND_DEV_DSP,  "dsp_multich",   ".", "dsp", 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_U8, 1, 0),     DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 	{ SND_DEV_DSP, "dsp_spdifout",   ".", "dsp", 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_U8, 1, 0),     DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 	{ SND_DEV_DSP,  "dsp_spdifin",   ".", "dsp", 0, 0, 0, 0,
 	  SND_FORMAT(AFMT_U8, 1, 0),     DSP_DEFAULT_SPEED,
 	  DSP_CDEV_TYPE_RDWR },
 };
 
 #define DSP_FIXUP_ERROR()		do {				\
 	prio = dsp_get_flags(i_dev);					\
 	if (!DSP_F_VALID(flags))					\
 		error = EINVAL;						\
 	if (!DSP_F_DUPLEX(flags) &&					\
 	    ((DSP_F_READ(flags) && d->reccount == 0) ||			\
 	    (DSP_F_WRITE(flags) && d->playcount == 0)))			\
 		error = ENOTSUP;					\
 	else if (!DSP_F_DUPLEX(flags) && (prio & SD_F_SIMPLEX) &&	\
 	    ((DSP_F_READ(flags) && (prio & SD_F_PRIO_WR)) ||		\
 	    (DSP_F_WRITE(flags) && (prio & SD_F_PRIO_RD))))		\
 		error = EBUSY;						\
 	else if (DSP_REGISTERED(d, i_dev))				\
 		error = EBUSY;						\
 } while (0)
 
 static int
 dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
 {
 	struct pcm_channel *rdch, *wrch;
 	struct snddev_info *d;
 	uint32_t fmt, spd, prio, volctl;
 	int i, error, rderror, wrerror, devtype, wdevunit, rdevunit;
 
 	/* Kind of impossible.. */
 	if (i_dev == NULL || td == NULL)
 		return (ENODEV);
 
 	d = dsp_get_info(i_dev);
 	if (PCM_DETACHING(d) || !PCM_REGISTERED(d))
 		return (EBADF);
 
 	PCM_GIANT_ENTER(d);
 
 	/* Lock snddev so nobody else can monkey with it. */
 	PCM_LOCK(d);
 	PCM_WAIT(d);
 
 	/*
 	 * Try to acquire cloned device before someone else pick it.
 	 * ENODEV means this is not a cloned droids.
 	 */
 	error = snd_clone_acquire(i_dev);
 	if (!(error == 0 || error == ENODEV)) {
 		DSP_FIXUP_ERROR();
 		PCM_UNLOCK(d);
 		PCM_GIANT_EXIT(d);
 		return (error);
 	}
 
 	error = 0;
 	DSP_FIXUP_ERROR();
 
 	if (error != 0) {
 		(void)snd_clone_release(i_dev);
 		PCM_UNLOCK(d);
 		PCM_GIANT_EXIT(d);
 		return (error);
 	}
 
 	/*
 	 * That is just enough. Acquire and unlock pcm lock so
 	 * the other will just have to wait until we finish doing
 	 * everything.
 	 */
 	PCM_ACQUIRE(d);
 	PCM_UNLOCK(d);
 
 	devtype = PCMDEV(i_dev);
 	wdevunit = -1;
 	rdevunit = -1;
 	fmt = 0;
 	spd = 0;
 	volctl = DSP_CDEV_VOLCTL_NONE;
 
 	for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) {
 		if (devtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL)
 			continue;
 		/*
 		 * Volume control only valid for DSPHW devices,
 		 * and it must be opened in opposite direction be it
 		 * simplex or duplex. Anything else will be handled
 		 * as usual.
 		 */
 		if (dsp_cdevs[i].query == DSP_CDEV_TYPE_WRONLY) {
 			if (dsp_cdevs[i].volctl != 0 &&
 			    DSP_F_READ(flags)) {
 				volctl = DSP_CDEV_VOLCTL_WRITE;
 				flags &= ~FREAD;
 				flags |= FWRITE;
 			}
 			if (DSP_F_READ(flags)) {
 				(void)snd_clone_release(i_dev);
 				PCM_RELEASE_QUICK(d);
 				PCM_GIANT_EXIT(d);
 				return (ENOTSUP);
 			}
 			wdevunit = dev2unit(i_dev);
 		} else if (dsp_cdevs[i].query == DSP_CDEV_TYPE_RDONLY) {
 			if (dsp_cdevs[i].volctl != 0 &&
 			    DSP_F_WRITE(flags)) {
 				volctl = DSP_CDEV_VOLCTL_READ;
 				flags &= ~FWRITE;
 				flags |= FREAD;
 			}
 			if (DSP_F_WRITE(flags)) {
 				(void)snd_clone_release(i_dev);
 				PCM_RELEASE_QUICK(d);
 				PCM_GIANT_EXIT(d);
 				return (ENOTSUP);
 			}
 			rdevunit = dev2unit(i_dev);
 		}
 		fmt = dsp_cdevs[i].fmt;
 		spd = dsp_cdevs[i].spd;
 		break;
 	}
 
 	/* No matching devtype? */
 	if (fmt == 0 || spd == 0)
 		panic("impossible devtype %d", devtype);
 
 	rdch = NULL;
 	wrch = NULL;
 	rderror = 0;
 	wrerror = 0;
 
 	/*
 	 * if we get here, the open request is valid- either:
 	 *   * we were previously not open
 	 *   * we were open for play xor record and the opener wants
 	 *     the non-open direction
 	 */
 	if (DSP_F_READ(flags)) {
 		/* open for read */
 		rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC,
 		    td->td_proc->p_pid, td->td_proc->p_comm, rdevunit);
 
 		if (rderror == 0 && chn_reset(rdch, fmt, spd) != 0)
 			rderror = ENXIO;
 
 		if (volctl == DSP_CDEV_VOLCTL_READ)
 			rderror = 0;
 
 		if (rderror != 0) {
 			if (rdch != NULL)
 				pcm_chnrelease(rdch);
 			if (!DSP_F_DUPLEX(flags)) {
 				(void)snd_clone_release(i_dev);
 				PCM_RELEASE_QUICK(d);
 				PCM_GIANT_EXIT(d);
 				return (rderror);
 			}
 			rdch = NULL;
 		} else if (volctl == DSP_CDEV_VOLCTL_READ) {
 			if (rdch != NULL) {
 				pcm_chnref(rdch, 1);
 				pcm_chnrelease(rdch);
 			}
 		} else {
 			if (flags & O_NONBLOCK)
 				rdch->flags |= CHN_F_NBIO;
 			if (flags & O_EXCL)
 				rdch->flags |= CHN_F_EXCLUSIVE;
 			pcm_chnref(rdch, 1);
 			if (volctl == DSP_CDEV_VOLCTL_NONE)
 				chn_vpc_reset(rdch, SND_VOL_C_PCM, 0);
 		 	CHN_UNLOCK(rdch);
 		}
 	}
 
 	if (DSP_F_WRITE(flags)) {
 		/* open for write */
 		wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY,
 		    td->td_proc->p_pid, td->td_proc->p_comm, wdevunit);
 
 		if (wrerror == 0 && chn_reset(wrch, fmt, spd) != 0)
 			wrerror = ENXIO;
 
 		if (volctl == DSP_CDEV_VOLCTL_WRITE)
 			wrerror = 0;
 
 		if (wrerror != 0) {
 			if (wrch != NULL)
 				pcm_chnrelease(wrch);
 			if (!DSP_F_DUPLEX(flags)) {
 				if (rdch != NULL) {
 					/*
 					 * Lock, deref and release previously
 					 * created record channel
 					 */
 					CHN_LOCK(rdch);
 					pcm_chnref(rdch, -1);
 					pcm_chnrelease(rdch);
 				}
 				(void)snd_clone_release(i_dev);
 				PCM_RELEASE_QUICK(d);
 				PCM_GIANT_EXIT(d);
 				return (wrerror);
 			}
 			wrch = NULL;
 		} else if (volctl == DSP_CDEV_VOLCTL_WRITE) {
 			if (wrch != NULL) {
 				pcm_chnref(wrch, 1);
 				pcm_chnrelease(wrch);
 			}
 		} else {
 			if (flags & O_NONBLOCK)
 				wrch->flags |= CHN_F_NBIO;
 			if (flags & O_EXCL)
 				wrch->flags |= CHN_F_EXCLUSIVE;
 			pcm_chnref(wrch, 1);
 			if (volctl == DSP_CDEV_VOLCTL_NONE)
 				chn_vpc_reset(wrch, SND_VOL_C_PCM, 0);
 			CHN_UNLOCK(wrch);
 		}
 	}
 
 	PCM_LOCK(d);
 
 	/*
 	 * We're done. Allocate channels information for this cdev.
 	 */
 	switch (volctl) {
 	case DSP_CDEV_VOLCTL_READ:
 		KASSERT(wrch == NULL, ("wrch=%p not null!", wrch));
 		dsp_cdevinfo_alloc(i_dev, NULL, NULL, rdch);
 		break;
 	case DSP_CDEV_VOLCTL_WRITE:
 		KASSERT(rdch == NULL, ("rdch=%p not null!", rdch));
 		dsp_cdevinfo_alloc(i_dev, NULL, NULL, wrch);
 		break;
 	case DSP_CDEV_VOLCTL_NONE:
 	default:
 		if (wrch == NULL && rdch == NULL) {
 			(void)snd_clone_release(i_dev);
 			PCM_RELEASE(d);
 			PCM_UNLOCK(d);
 			PCM_GIANT_EXIT(d);
 			if (wrerror != 0)
 				return (wrerror);
 			if (rderror != 0)
 				return (rderror);
 			return (EINVAL);
 		}
 		dsp_cdevinfo_alloc(i_dev, rdch, wrch, NULL);
 		if (rdch != NULL)
 			CHN_INSERT_HEAD(d, rdch, channels.pcm.opened);
 		if (wrch != NULL)
 			CHN_INSERT_HEAD(d, wrch, channels.pcm.opened);
 		break;
 	}
 
 	/*
 	 * Increase clone refcount for its automatic garbage collector.
 	 */
 	(void)snd_clone_ref(i_dev);
 
 	PCM_RELEASE(d);
 	PCM_UNLOCK(d);
 
 	PCM_GIANT_LEAVE(d);
 
 	return (0);
 }
 
 static int
 dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
 {
 	struct pcm_channel *rdch, *wrch, *volch;
 	struct snddev_info *d;
 	int sg_ids, rdref, wdref;
 
 	d = dsp_get_info(i_dev);
 	if (!DSP_REGISTERED(d, i_dev))
 		return (EBADF);
 
 	PCM_GIANT_ENTER(d);
 
 	PCM_LOCK(d);
 	PCM_WAIT(d);
 	PCM_ACQUIRE(d);
 
 	rdch = PCM_RDCH(i_dev);
 	wrch = PCM_WRCH(i_dev);
 	volch = PCM_VOLCH(i_dev);
 
 	PCM_RDCH(i_dev) = NULL;
 	PCM_WRCH(i_dev) = NULL;
 	PCM_VOLCH(i_dev) = NULL;
 
 	rdref = -1;
 	wdref = -1;
 
 	if (volch != NULL) {
 		if (volch == rdch)
 			rdref--;
 		else if (volch == wrch)
 			wdref--;
 		else {
 			CHN_LOCK(volch);
 			pcm_chnref(volch, -1);
 			CHN_UNLOCK(volch);
 		}
 	}
 
 	if (rdch != NULL)
 		CHN_REMOVE(d, rdch, channels.pcm.opened);
 	if (wrch != NULL)
 		CHN_REMOVE(d, wrch, channels.pcm.opened);
 
 	if (rdch != NULL || wrch != NULL) {
 		PCM_UNLOCK(d);
 		if (rdch != NULL) {
 			/*
 			 * The channel itself need not be locked because:
 			 *   a)  Adding a channel to a syncgroup happens only
 			 *       in dsp_ioctl(), which cannot run concurrently
 			 *       to dsp_close().
 			 *   b)  The syncmember pointer (sm) is protected by
 			 *       the global syncgroup list lock.
 			 *   c)  A channel can't just disappear, invalidating
 			 *       pointers, unless it's closed/dereferenced
 			 *       first.
 			 */
 			PCM_SG_LOCK();
 			sg_ids = chn_syncdestroy(rdch);
 			PCM_SG_UNLOCK();
 			if (sg_ids != 0)
 				free_unr(pcmsg_unrhdr, sg_ids);
 
 			CHN_LOCK(rdch);
 			pcm_chnref(rdch, rdref);
 			chn_abort(rdch); /* won't sleep */
 			rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
 			    CHN_F_DEAD | CHN_F_EXCLUSIVE);
 			chn_reset(rdch, 0, 0);
 			pcm_chnrelease(rdch);
 		}
 		if (wrch != NULL) {
 			/*
 			 * Please see block above.
 			 */
 			PCM_SG_LOCK();
 			sg_ids = chn_syncdestroy(wrch);
 			PCM_SG_UNLOCK();
 			if (sg_ids != 0)
 				free_unr(pcmsg_unrhdr, sg_ids);
 
 			CHN_LOCK(wrch);
 			pcm_chnref(wrch, wdref);
 			chn_flush(wrch); /* may sleep */
 			wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
 			    CHN_F_DEAD | CHN_F_EXCLUSIVE);
 			chn_reset(wrch, 0, 0);
 			pcm_chnrelease(wrch);
 		}
 		PCM_LOCK(d);
 	}
 
 	dsp_cdevinfo_free(i_dev);
 	/*
 	 * Release clone busy state and unref it so the automatic
 	 * garbage collector will get the hint and do the remaining
 	 * cleanup process.
 	 */
 	(void)snd_clone_release(i_dev);
 
 	/*
 	 * destroy_dev() might sleep, so release pcm lock
 	 * here and rely on pcm cv serialization.
 	 */
 	PCM_UNLOCK(d);
 	(void)snd_clone_unref(i_dev);
 	PCM_LOCK(d);
 
 	PCM_RELEASE(d);
 	PCM_UNLOCK(d);
 
 	PCM_GIANT_LEAVE(d);
 
 	return (0);
 }
 
 static __inline int
 dsp_io_ops(struct cdev *i_dev, struct uio *buf)
 {
 	struct snddev_info *d;
 	struct pcm_channel **ch, *rdch, *wrch;
 	int (*chn_io)(struct pcm_channel *, struct uio *);
 	int prio, ret;
 	pid_t runpid;
 
 	KASSERT(i_dev != NULL && buf != NULL &&
 	    (buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE),
 	    ("%s(): io train wreck!", __func__));
 
 	d = dsp_get_info(i_dev);
 	if (PCM_DETACHING(d) || !DSP_REGISTERED(d, i_dev))
 		return (EBADF);
 
 	PCM_GIANT_ENTER(d);
 
 	switch (buf->uio_rw) {
 	case UIO_READ:
 		prio = SD_F_PRIO_RD;
 		ch = &rdch;
 		chn_io = chn_read;
 		break;
 	case UIO_WRITE:
 		prio = SD_F_PRIO_WR;
 		ch = &wrch;
 		chn_io = chn_write;
 		break;
 	default:
 		panic("invalid/corrupted uio direction: %d", buf->uio_rw);
 		break;
 	}
 
 	rdch = NULL;
 	wrch = NULL;
 	runpid = buf->uio_td->td_proc->p_pid;
 
 	getchns(i_dev, &rdch, &wrch, prio);
 
 	if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) {
 		if (rdch != NULL || wrch != NULL)
 			relchns(i_dev, rdch, wrch, prio);
 		PCM_GIANT_EXIT(d);
 		return (EBADF);
 	}
 
 	if (((*ch)->flags & (CHN_F_MMAP | CHN_F_DEAD)) ||
 	    (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) {
 		relchns(i_dev, rdch, wrch, prio);
 		PCM_GIANT_EXIT(d);
 		return (EINVAL);
 	} else if (!((*ch)->flags & CHN_F_RUNNING)) {
 		(*ch)->flags |= CHN_F_RUNNING;
 		(*ch)->pid = runpid;
 	}
 
 	/*
 	 * chn_read/write must give up channel lock in order to copy bytes
 	 * from/to userland, so up the "in progress" counter to make sure
 	 * someone else doesn't come along and muss up the buffer.
 	 */
 	++(*ch)->inprog;
 	ret = chn_io(*ch, buf);
 	--(*ch)->inprog;
 
 	CHN_BROADCAST(&(*ch)->cv);
 
 	relchns(i_dev, rdch, wrch, prio);
 
 	PCM_GIANT_LEAVE(d);
 
 	return (ret);
 }
 
 static int
 dsp_read(struct cdev *i_dev, struct uio *buf, int flag)
 {
 	return (dsp_io_ops(i_dev, buf));
 }
 
 static int
 dsp_write(struct cdev *i_dev, struct uio *buf, int flag)
 {
 	return (dsp_io_ops(i_dev, buf));
 }
 
 static int
 dsp_get_volume_channel(struct cdev *dev, struct pcm_channel **volch)
 {
 	struct snddev_info *d;
 	struct pcm_channel *c;
 	int unit;
 
 	KASSERT(dev != NULL && volch != NULL,
 	    ("%s(): NULL query dev=%p volch=%p", __func__, dev, volch));
 
 	d = dsp_get_info(dev);
 	if (!PCM_REGISTERED(d)) {
 		*volch = NULL;
 		return (EINVAL);
 	}
 
 	PCM_UNLOCKASSERT(d);
 
 	*volch = NULL;
 
 	c = PCM_VOLCH(dev);
 	if (c != NULL) {
 		if (!(c->feederflags & (1 << FEEDER_VOLUME)))
 			return (-1);
 		*volch = c;
 		return (0);
 	}
 
 	PCM_LOCK(d);
 	PCM_WAIT(d);
 	PCM_ACQUIRE(d);
 
 	unit = dev2unit(dev);
 
 	CHN_FOREACH(c, d, channels.pcm) {
 		CHN_LOCK(c);
 		if (c->unit != unit) {
 			CHN_UNLOCK(c);
 			continue;
 		}
 		*volch = c;
 		pcm_chnref(c, 1);
 		PCM_VOLCH(dev) = c;
 		CHN_UNLOCK(c);
 		PCM_RELEASE(d);
 		PCM_UNLOCK(d);
 		return ((c->feederflags & (1 << FEEDER_VOLUME)) ? 0 : -1);
 	}
 
 	PCM_RELEASE(d);
 	PCM_UNLOCK(d);
 
 	return (EINVAL);
 }
 
 static int
 dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd,
     caddr_t arg)
 {
 	struct snddev_info *d;
 	struct pcm_channel *rdch, *wrch;
 	int j, devtype, ret;
+	int left, right, center, mute;
 
 	d = dsp_get_info(dev);
 	if (!PCM_REGISTERED(d) || !(dsp_get_flags(dev) & SD_F_VPC))
 		return (-1);
 
 	PCM_UNLOCKASSERT(d);
 
 	j = cmd & 0xff;
 
 	rdch = PCM_RDCH(dev);
 	wrch = PCM_WRCH(dev);
 
 	/* No specific channel, look into cache */
 	if (volch == NULL)
 		volch = PCM_VOLCH(dev);
 
 	/* Look harder */
 	if (volch == NULL) {
 		if (j == SOUND_MIXER_RECLEV && rdch != NULL)
 			volch = rdch;
 		else if (j == SOUND_MIXER_PCM && wrch != NULL)
 			volch = wrch;
 	}
 
 	devtype = PCMDEV(dev);
 
 	/* Look super harder */
 	if (volch == NULL &&
 	    (devtype == SND_DEV_DSPHW_PLAY || devtype == SND_DEV_DSPHW_VPLAY ||
 	    devtype == SND_DEV_DSPHW_REC || devtype == SND_DEV_DSPHW_VREC)) {
 		ret = dsp_get_volume_channel(dev, &volch);
 		if (ret != 0)
 			return (ret);
 		if (volch == NULL)
 			return (EINVAL);
 	}
 
 	/* Final validation */
-	if (volch != NULL) {
-		CHN_LOCK(volch);
-		if (!(volch->feederflags & (1 << FEEDER_VOLUME))) {
-			CHN_UNLOCK(volch);
-			return (-1);
-		}
-		if (volch->direction == PCMDIR_PLAY)
-			wrch = volch;
-		else
-			rdch = volch;
-	}
-
-	ret = EINVAL;
+	if (volch == NULL)
+		return (EINVAL);
 
-	if (volch != NULL &&
-	    ((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) ||
-	    (j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) {
-		if ((cmd & ~0xff) == MIXER_WRITE(0)) {
-			int left, right, center;
+	CHN_LOCK(volch);
+	if (!(volch->feederflags & (1 << FEEDER_VOLUME))) {
+		CHN_UNLOCK(volch);
+		return (EINVAL);
+	}
 
+	switch (cmd & ~0xff) {
+	case MIXER_WRITE(0):
+		switch (j) {
+		case SOUND_MIXER_MUTE:
+			if (volch->direction == PCMDIR_REC) {
+				chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0);
+			} else {
+				chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0);
+			}
+			break;
+		case SOUND_MIXER_PCM:
+			if (volch->direction != PCMDIR_PLAY)
+				break;
 			left = *(int *)arg & 0x7f;
 			right = ((*(int *)arg) >> 8) & 0x7f;
 			center = (left + right) >> 1;
-			chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right,
-			    center);
-		} else if ((cmd & ~0xff) == MIXER_READ(0)) {
-			*(int *)arg = CHN_GETVOLUME(volch,
-				SND_VOL_C_PCM, SND_CHN_T_FL);
-			*(int *)arg |= CHN_GETVOLUME(volch,
-				SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
+			chn_setvolume_multi(volch, SND_VOL_C_PCM,
+			    left, right, center);
+			break;
+		case SOUND_MIXER_RECLEV:
+			if (volch->direction != PCMDIR_REC)
+				break;
+			left = *(int *)arg & 0x7f;
+			right = ((*(int *)arg) >> 8) & 0x7f;
+			center = (left + right) >> 1;
+			chn_setvolume_multi(volch, SND_VOL_C_PCM,
+			    left, right, center);
+			break;
+		default:
+			/* ignore all other mixer writes */
+			break;
 		}
-		ret = 0;
-	} else if (rdch != NULL || wrch != NULL) {
+		break;
+
+	case MIXER_READ(0):
 		switch (j) {
+		case SOUND_MIXER_MUTE:
+			mute = CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FL) ||
+			    CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FR);
+			if (volch->direction == PCMDIR_REC) {
+				*(int *)arg = mute << SOUND_MIXER_RECLEV;
+			} else {
+				*(int *)arg = mute << SOUND_MIXER_PCM;
+			}
+			break;
+		case SOUND_MIXER_PCM:
+			if (volch->direction != PCMDIR_PLAY)
+				break;
+			*(int *)arg = CHN_GETVOLUME(volch,
+			    SND_VOL_C_PCM, SND_CHN_T_FL);
+			*(int *)arg |= CHN_GETVOLUME(volch,
+			    SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
+			break;
+		case SOUND_MIXER_RECLEV:
+			if (volch->direction != PCMDIR_REC)
+				break;
+			*(int *)arg = CHN_GETVOLUME(volch,
+			    SND_VOL_C_PCM, SND_CHN_T_FL);
+			*(int *)arg |= CHN_GETVOLUME(volch,
+			    SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
+			break;
 		case SOUND_MIXER_DEVMASK:
 		case SOUND_MIXER_CAPS:
 		case SOUND_MIXER_STEREODEVS:
-			if ((cmd & ~0xff) == MIXER_READ(0)) {
-				*(int *)arg = 0;
-				if (rdch != NULL)
-					*(int *)arg |= SOUND_MASK_RECLEV;
-				if (wrch != NULL)
-					*(int *)arg |= SOUND_MASK_PCM;
-			}
-			ret = 0;
-			break;
-		case SOUND_MIXER_RECMASK:
-		case SOUND_MIXER_RECSRC:
-			if ((cmd & ~0xff) == MIXER_READ(0))
-				*(int *)arg = 0;
-			ret = 0;
+			if (volch->direction == PCMDIR_REC)
+				*(int *)arg = SOUND_MASK_RECLEV;
+			else
+				*(int *)arg = SOUND_MASK_PCM;
 			break;
 		default:
+			*(int *)arg = 0;
 			break;
 		}
-	}
-
-	if (volch != NULL)
-		CHN_UNLOCK(volch);
+		break;
 
-	return (ret);
+	default:
+		break;
+	}
+	CHN_UNLOCK(volch);
+	return (0);
 }
 
 static int
 dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
     struct thread *td)
 {
     	struct pcm_channel *chn, *rdch, *wrch;
 	struct snddev_info *d;
 	u_long xcmd;
 	int *arg_i, ret, tmp;
 
 	d = dsp_get_info(i_dev);
 	if (PCM_DETACHING(d) || !DSP_REGISTERED(d, i_dev))
 		return (EBADF);
 
 	PCM_GIANT_ENTER(d);
 
 	arg_i = (int *)arg;
 	ret = 0;
 	xcmd = 0;
 	chn = NULL;
 
 	if (IOCGROUP(cmd) == 'M') {
 		if (cmd == OSS_GETVERSION) {
 			*arg_i = SOUND_VERSION;
 			PCM_GIANT_EXIT(d);
 			return (0);
 		}
 		ret = dsp_ioctl_channel(i_dev, PCM_VOLCH(i_dev), cmd, arg);
 		if (ret != -1) {
 			PCM_GIANT_EXIT(d);
 			return (ret);
 		}
 
 		if (d->mixer_dev != NULL) {
 			PCM_ACQUIRE_QUICK(d);
 			ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td,
 			    MIXER_CMD_DIRECT);
 			PCM_RELEASE_QUICK(d);
 		} else
 			ret = EBADF;
 
 		PCM_GIANT_EXIT(d);
 
 		return (ret);
 	}
 
 	/*
 	 * Certain ioctls may be made on any type of device (audio, mixer,
 	 * and MIDI).  Handle those special cases here.
 	 */
 	if (IOCGROUP(cmd) == 'X') {
 		PCM_ACQUIRE_QUICK(d);
 		switch(cmd) {
 		case SNDCTL_SYSINFO:
 			sound_oss_sysinfo((oss_sysinfo *)arg);
 			break;
 		case SNDCTL_CARDINFO:
 			ret = sound_oss_card_info((oss_card_info *)arg);
 			break;
 		case SNDCTL_AUDIOINFO:
 		case SNDCTL_AUDIOINFO_EX:
 		case SNDCTL_ENGINEINFO:
 			ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg);
 			break;
 		case SNDCTL_MIXERINFO:
 			ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg);
 			break;
 		default:
 			ret = EINVAL;
 		}
 		PCM_RELEASE_QUICK(d);
 		PCM_GIANT_EXIT(d);
 		return (ret);
 	}
 
 	getchns(i_dev, &rdch, &wrch, 0);
 
 	if (wrch != NULL && (wrch->flags & CHN_F_DEAD))
 		wrch = NULL;
 	if (rdch != NULL && (rdch->flags & CHN_F_DEAD))
 		rdch = NULL;
 
 	if (wrch == NULL && rdch == NULL) {
 		PCM_GIANT_EXIT(d);
 		return (EINVAL);
 	}
 
     	switch(cmd) {
 #ifdef OLDPCM_IOCTL
     	/*
      	 * we start with the new ioctl interface.
      	 */
     	case AIONWRITE:	/* how many bytes can write ? */
 		if (wrch) {
 			CHN_LOCK(wrch);
 /*
 		if (wrch && wrch->bufhard.dl)
 			while (chn_wrfeed(wrch) == 0);
 */
 			*arg_i = sndbuf_getfree(wrch->bufsoft);
 			CHN_UNLOCK(wrch);
 		} else {
 			*arg_i = 0;
 			ret = EINVAL;
 		}
 		break;
 
     	case AIOSSIZE:     /* set the current blocksize */
 		{
 	    		struct snd_size *p = (struct snd_size *)arg;
 
 			p->play_size = 0;
 			p->rec_size = 0;
 			PCM_ACQUIRE_QUICK(d);
 	    		if (wrch) {
 				CHN_LOCK(wrch);
 				chn_setblocksize(wrch, 2, p->play_size);
 				p->play_size = sndbuf_getblksz(wrch->bufsoft);
 				CHN_UNLOCK(wrch);
 			}
 	    		if (rdch) {
 				CHN_LOCK(rdch);
 				chn_setblocksize(rdch, 2, p->rec_size);
 				p->rec_size = sndbuf_getblksz(rdch->bufsoft);
 				CHN_UNLOCK(rdch);
 			}
 			PCM_RELEASE_QUICK(d);
 		}
 		break;
     	case AIOGSIZE:	/* get the current blocksize */
 		{
 	    		struct snd_size *p = (struct snd_size *)arg;
 
 	    		if (wrch) {
 				CHN_LOCK(wrch);
 				p->play_size = sndbuf_getblksz(wrch->bufsoft);
 				CHN_UNLOCK(wrch);
 			}
 	    		if (rdch) {
 				CHN_LOCK(rdch);
 				p->rec_size = sndbuf_getblksz(rdch->bufsoft);
 				CHN_UNLOCK(rdch);
 			}
 		}
 		break;
 
     	case AIOSFMT:
     	case AIOGFMT:
 		{
 	    		snd_chan_param *p = (snd_chan_param *)arg;
 
 			if (cmd == AIOSFMT &&
 			    ((p->play_format != 0 && p->play_rate == 0) ||
 			    (p->rec_format != 0 && p->rec_rate == 0))) {
 				ret = EINVAL;
 				break;
 			}
 			PCM_ACQUIRE_QUICK(d);
 	    		if (wrch) {
 				CHN_LOCK(wrch);
 				if (cmd == AIOSFMT && p->play_format != 0) {
 					chn_setformat(wrch,
 					    SND_FORMAT(p->play_format,
 					    AFMT_CHANNEL(wrch->format),
 					    AFMT_EXTCHANNEL(wrch->format)));
 					chn_setspeed(wrch, p->play_rate);
 				}
 	    			p->play_rate = wrch->speed;
 	    			p->play_format = AFMT_ENCODING(wrch->format);
 				CHN_UNLOCK(wrch);
 			} else {
 	    			p->play_rate = 0;
 	    			p->play_format = 0;
 	    		}
 	    		if (rdch) {
 				CHN_LOCK(rdch);
 				if (cmd == AIOSFMT && p->rec_format != 0) {
 					chn_setformat(rdch,
 					    SND_FORMAT(p->rec_format,
 					    AFMT_CHANNEL(rdch->format),
 					    AFMT_EXTCHANNEL(rdch->format)));
 					chn_setspeed(rdch, p->rec_rate);
 				}
 				p->rec_rate = rdch->speed;
 				p->rec_format = AFMT_ENCODING(rdch->format);
 				CHN_UNLOCK(rdch);
 			} else {
 	    			p->rec_rate = 0;
 	    			p->rec_format = 0;
 	    		}
 			PCM_RELEASE_QUICK(d);
 		}
 		break;
 
     	case AIOGCAP:     /* get capabilities */
 		{
 	    		snd_capabilities *p = (snd_capabilities *)arg;
 			struct pcmchan_caps *pcaps = NULL, *rcaps = NULL;
 			struct cdev *pdev;
 
 			PCM_LOCK(d);
 			if (rdch) {
 				CHN_LOCK(rdch);
 				rcaps = chn_getcaps(rdch);
 			}
 			if (wrch) {
 				CHN_LOCK(wrch);
 				pcaps = chn_getcaps(wrch);
 			}
 	    		p->rate_min = max(rcaps? rcaps->minspeed : 0,
 	                      		  pcaps? pcaps->minspeed : 0);
 	    		p->rate_max = min(rcaps? rcaps->maxspeed : 1000000,
 	                      		  pcaps? pcaps->maxspeed : 1000000);
 	    		p->bufsize = min(rdch? sndbuf_getsize(rdch->bufsoft) : 1000000,
 	                     		 wrch? sndbuf_getsize(wrch->bufsoft) : 1000000);
 			/* XXX bad on sb16 */
 	    		p->formats = (rdch? chn_getformats(rdch) : 0xffffffff) &
 			 	     (wrch? chn_getformats(wrch) : 0xffffffff);
 			if (rdch && wrch)
 				p->formats |= (dsp_get_flags(i_dev) & SD_F_SIMPLEX)? 0 : AFMT_FULLDUPLEX;
 			pdev = d->mixer_dev;
 	    		p->mixers = 1; /* default: one mixer */
 	    		p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0;
 	    		p->left = p->right = 100;
 			if (wrch)
 				CHN_UNLOCK(wrch);
 			if (rdch)
 				CHN_UNLOCK(rdch);
 			PCM_UNLOCK(d);
 		}
 		break;
 
     	case AIOSTOP:
 		if (*arg_i == AIOSYNC_PLAY && wrch) {
 			CHN_LOCK(wrch);
 			*arg_i = chn_abort(wrch);
 			CHN_UNLOCK(wrch);
 		} else if (*arg_i == AIOSYNC_CAPTURE && rdch) {
 			CHN_LOCK(rdch);
 			*arg_i = chn_abort(rdch);
 			CHN_UNLOCK(rdch);
 		} else {
 	   	 	printf("AIOSTOP: bad channel 0x%x\n", *arg_i);
 	    		*arg_i = 0;
 		}
 		break;
 
     	case AIOSYNC:
 		printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n",
 	    		((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos);
 		break;
 #endif
 	/*
 	 * here follow the standard ioctls (filio.h etc.)
 	 */
     	case FIONREAD: /* get # bytes to read */
 		if (rdch) {
 			CHN_LOCK(rdch);
 /*			if (rdch && rdch->bufhard.dl)
 				while (chn_rdfeed(rdch) == 0);
 */
 			*arg_i = sndbuf_getready(rdch->bufsoft);
 			CHN_UNLOCK(rdch);
 		} else {
 			*arg_i = 0;
 			ret = EINVAL;
 		}
 		break;
 
     	case FIOASYNC: /*set/clear async i/o */
 		DEB( printf("FIOASYNC\n") ; )
 		break;
 
     	case SNDCTL_DSP_NONBLOCK: /* set non-blocking i/o */
     	case FIONBIO: /* set/clear non-blocking i/o */
 		if (rdch) {
 			CHN_LOCK(rdch);
 			if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i)
 				rdch->flags |= CHN_F_NBIO;
 			else
 				rdch->flags &= ~CHN_F_NBIO;
 			CHN_UNLOCK(rdch);
 		}
 		if (wrch) {
 			CHN_LOCK(wrch);
 			if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i)
 				wrch->flags |= CHN_F_NBIO;
 			else
 				wrch->flags &= ~CHN_F_NBIO;
 			CHN_UNLOCK(wrch);
 		}
 		break;
 
     	/*
 	 * Finally, here is the linux-compatible ioctl interface
 	 */
 #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int)
     	case THE_REAL_SNDCTL_DSP_GETBLKSIZE:
     	case SNDCTL_DSP_GETBLKSIZE:
 		chn = wrch ? wrch : rdch;
 		if (chn) {
 			CHN_LOCK(chn);
 			*arg_i = sndbuf_getblksz(chn->bufsoft);
 			CHN_UNLOCK(chn);
 		} else {
 			*arg_i = 0;
 			ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_SETBLKSIZE:
 		RANGE(*arg_i, 16, 65536);
 		PCM_ACQUIRE_QUICK(d);
 		if (wrch) {
 			CHN_LOCK(wrch);
 			chn_setblocksize(wrch, 2, *arg_i);
 			CHN_UNLOCK(wrch);
 		}
 		if (rdch) {
 			CHN_LOCK(rdch);
 			chn_setblocksize(rdch, 2, *arg_i);
 			CHN_UNLOCK(rdch);
 		}
 		PCM_RELEASE_QUICK(d);
 		break;
 
     	case SNDCTL_DSP_RESET:
 		DEB(printf("dsp reset\n"));
 		if (wrch) {
 			CHN_LOCK(wrch);
 			chn_abort(wrch);
 			chn_resetbuf(wrch);
 			CHN_UNLOCK(wrch);
 		}
 		if (rdch) {
 			CHN_LOCK(rdch);
 			chn_abort(rdch);
 			chn_resetbuf(rdch);
 			CHN_UNLOCK(rdch);
 		}
 		break;
 
     	case SNDCTL_DSP_SYNC:
 		DEB(printf("dsp sync\n"));
 		/* chn_sync may sleep */
 		if (wrch) {
 			CHN_LOCK(wrch);
 			chn_sync(wrch, 0);
 			CHN_UNLOCK(wrch);
 		}
 		break;
 
     	case SNDCTL_DSP_SPEED:
 		/* chn_setspeed may sleep */
 		tmp = 0;
 		PCM_ACQUIRE_QUICK(d);
 		if (wrch) {
 			CHN_LOCK(wrch);
 			ret = chn_setspeed(wrch, *arg_i);
 			tmp = wrch->speed;
 			CHN_UNLOCK(wrch);
 		}
 		if (rdch && ret == 0) {
 			CHN_LOCK(rdch);
 			ret = chn_setspeed(rdch, *arg_i);
 			if (tmp == 0)
 				tmp = rdch->speed;
 			CHN_UNLOCK(rdch);
 		}
 		PCM_RELEASE_QUICK(d);
 		*arg_i = tmp;
 		break;
 
     	case SOUND_PCM_READ_RATE:
 		chn = wrch ? wrch : rdch;
 		if (chn) {
 			CHN_LOCK(chn);
 			*arg_i = chn->speed;
 			CHN_UNLOCK(chn);
 		} else {
 			*arg_i = 0;
 			ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_STEREO:
 		tmp = -1;
 		*arg_i = (*arg_i)? 2 : 1;
 		PCM_ACQUIRE_QUICK(d);
 		if (wrch) {
 			CHN_LOCK(wrch);
 			ret = chn_setformat(wrch,
 			    SND_FORMAT(wrch->format, *arg_i, 0));
 			tmp = (AFMT_CHANNEL(wrch->format) > 1)? 1 : 0;
 			CHN_UNLOCK(wrch);
 		}
 		if (rdch && ret == 0) {
 			CHN_LOCK(rdch);
 			ret = chn_setformat(rdch,
 			    SND_FORMAT(rdch->format, *arg_i, 0));
 			if (tmp == -1)
 				tmp = (AFMT_CHANNEL(rdch->format) > 1)? 1 : 0;
 			CHN_UNLOCK(rdch);
 		}
 		PCM_RELEASE_QUICK(d);
 		*arg_i = tmp;
 		break;
 
     	case SOUND_PCM_WRITE_CHANNELS:
 /*	case SNDCTL_DSP_CHANNELS: ( == SOUND_PCM_WRITE_CHANNELS) */
 		if (*arg_i < 0) {
 			*arg_i = 0;
 			ret = EINVAL;
 			break;
 		}
 		if (*arg_i != 0) {
 			struct pcmchan_matrix *m;
 			uint32_t ext;
 
 			tmp = 0;
 			if (*arg_i > SND_CHN_MAX)
 				*arg_i = SND_CHN_MAX;
 
 			m = feeder_matrix_default_channel_map(*arg_i);
 			if (m != NULL)
 				ext = m->ext;
 			else
 				ext = 0;
 
 			PCM_ACQUIRE_QUICK(d);
 	  		if (wrch) {
 				CHN_LOCK(wrch);
 				ret = chn_setformat(wrch,
 				    SND_FORMAT(wrch->format, *arg_i, ext));
 				tmp = AFMT_CHANNEL(wrch->format);
 				CHN_UNLOCK(wrch);
 			}
 			if (rdch && ret == 0) {
 				CHN_LOCK(rdch);
 				ret = chn_setformat(rdch,
 				    SND_FORMAT(rdch->format, *arg_i, ext));
 				if (tmp == 0)
 					tmp = AFMT_CHANNEL(rdch->format);
 				CHN_UNLOCK(rdch);
 			}
 			PCM_RELEASE_QUICK(d);
 			*arg_i = tmp;
 		} else {
 			chn = wrch ? wrch : rdch;
 			CHN_LOCK(chn);
 			*arg_i = AFMT_CHANNEL(chn->format);
 			CHN_UNLOCK(chn);
 		}
 		break;
 
     	case SOUND_PCM_READ_CHANNELS:
 		chn = wrch ? wrch : rdch;
 		if (chn) {
 			CHN_LOCK(chn);
 			*arg_i = AFMT_CHANNEL(chn->format);
 			CHN_UNLOCK(chn);
 		} else {
 			*arg_i = 0;
 			ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_GETFMTS:	/* returns a mask of supported fmts */
 		chn = wrch ? wrch : rdch;
 		if (chn) {
 			CHN_LOCK(chn);
 			*arg_i = chn_getformats(chn);
 			CHN_UNLOCK(chn);
 		} else {
 			*arg_i = 0;
 			ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_SETFMT:	/* sets _one_ format */
 		if (*arg_i != AFMT_QUERY) {
 			tmp = 0;
 			PCM_ACQUIRE_QUICK(d);
 			if (wrch) {
 				CHN_LOCK(wrch);
 				ret = chn_setformat(wrch, SND_FORMAT(*arg_i,
 				    AFMT_CHANNEL(wrch->format),
 				    AFMT_EXTCHANNEL(wrch->format)));
 				tmp = wrch->format;
 				CHN_UNLOCK(wrch);
 			}
 			if (rdch && ret == 0) {
 				CHN_LOCK(rdch);
 				ret = chn_setformat(rdch, SND_FORMAT(*arg_i,
 				    AFMT_CHANNEL(rdch->format),
 				    AFMT_EXTCHANNEL(rdch->format)));
 				if (tmp == 0)
 					tmp = rdch->format;
 				CHN_UNLOCK(rdch);
 			}
 			PCM_RELEASE_QUICK(d);
 			*arg_i = AFMT_ENCODING(tmp);
 		} else {
 			chn = wrch ? wrch : rdch;
 			CHN_LOCK(chn);
 			*arg_i = AFMT_ENCODING(chn->format);
 			CHN_UNLOCK(chn);
 		}
 		break;
 
     	case SNDCTL_DSP_SETFRAGMENT:
 		DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg));
 		{
 			uint32_t fragln = (*arg_i) & 0x0000ffff;
 			uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16;
 			uint32_t fragsz;
 			uint32_t r_maxfrags, r_fragsz;
 
 			RANGE(fragln, 4, 16);
 			fragsz = 1 << fragln;
 
 			if (maxfrags == 0)
 				maxfrags = CHN_2NDBUFMAXSIZE / fragsz;
 			if (maxfrags < 2)
 				maxfrags = 2;
 			if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE)
 				maxfrags = CHN_2NDBUFMAXSIZE / fragsz;
 
 			DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz));
 			PCM_ACQUIRE_QUICK(d);
 		    	if (rdch) {
 				CHN_LOCK(rdch);
 				ret = chn_setblocksize(rdch, maxfrags, fragsz);
 				r_maxfrags = sndbuf_getblkcnt(rdch->bufsoft);
 				r_fragsz = sndbuf_getblksz(rdch->bufsoft);
 				CHN_UNLOCK(rdch);
 			} else {
 				r_maxfrags = maxfrags;
 				r_fragsz = fragsz;
 			}
 		    	if (wrch && ret == 0) {
 				CHN_LOCK(wrch);
 				ret = chn_setblocksize(wrch, maxfrags, fragsz);
  				maxfrags = sndbuf_getblkcnt(wrch->bufsoft);
 				fragsz = sndbuf_getblksz(wrch->bufsoft);
 				CHN_UNLOCK(wrch);
 			} else { /* use whatever came from the read channel */
 				maxfrags = r_maxfrags;
 				fragsz = r_fragsz;
 			}
 			PCM_RELEASE_QUICK(d);
 
 			fragln = 0;
 			while (fragsz > 1) {
 				fragln++;
 				fragsz >>= 1;
 			}
 	    		*arg_i = (maxfrags << 16) | fragln;
 		}
 		break;
 
     	case SNDCTL_DSP_GETISPACE:
 		/* return the size of data available in the input queue */
 		{
 	    		audio_buf_info *a = (audio_buf_info *)arg;
 	    		if (rdch) {
 	        		struct snd_dbuf *bs = rdch->bufsoft;
 
 				CHN_LOCK(rdch);
 				a->bytes = sndbuf_getready(bs);
 	        		a->fragments = a->bytes / sndbuf_getblksz(bs);
 	        		a->fragstotal = sndbuf_getblkcnt(bs);
 	        		a->fragsize = sndbuf_getblksz(bs);
 				CHN_UNLOCK(rdch);
 	    		} else
 				ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_GETOSPACE:
 		/* return space available in the output queue */
 		{
 	    		audio_buf_info *a = (audio_buf_info *)arg;
 	    		if (wrch) {
 	        		struct snd_dbuf *bs = wrch->bufsoft;
 
 				CHN_LOCK(wrch);
 				/* XXX abusive DMA update: chn_wrupdate(wrch); */
 				a->bytes = sndbuf_getfree(bs);
 	        		a->fragments = a->bytes / sndbuf_getblksz(bs);
 	        		a->fragstotal = sndbuf_getblkcnt(bs);
 	        		a->fragsize = sndbuf_getblksz(bs);
 				CHN_UNLOCK(wrch);
 	    		} else
 				ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_GETIPTR:
 		{
 	    		count_info *a = (count_info *)arg;
 	    		if (rdch) {
 	        		struct snd_dbuf *bs = rdch->bufsoft;
 
 				CHN_LOCK(rdch);
 				/* XXX abusive DMA update: chn_rdupdate(rdch); */
 	        		a->bytes = sndbuf_gettotal(bs);
 	        		a->blocks = sndbuf_getblocks(bs) - rdch->blocks;
 	        		a->ptr = sndbuf_getfreeptr(bs);
 				rdch->blocks = sndbuf_getblocks(bs);
 				CHN_UNLOCK(rdch);
 	    		} else
 				ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_GETOPTR:
 		{
 	    		count_info *a = (count_info *)arg;
 	    		if (wrch) {
 	        		struct snd_dbuf *bs = wrch->bufsoft;
 
 				CHN_LOCK(wrch);
 				/* XXX abusive DMA update: chn_wrupdate(wrch); */
 	        		a->bytes = sndbuf_gettotal(bs);
 	        		a->blocks = sndbuf_getblocks(bs) - wrch->blocks;
 	        		a->ptr = sndbuf_getreadyptr(bs);
 				wrch->blocks = sndbuf_getblocks(bs);
 				CHN_UNLOCK(wrch);
 	    		} else
 				ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_GETCAPS:
 		PCM_LOCK(d);
 		*arg_i = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER;
 		if (rdch && wrch && !(dsp_get_flags(i_dev) & SD_F_SIMPLEX))
 			*arg_i |= PCM_CAP_DUPLEX;
 		if (rdch && (rdch->flags & CHN_F_VIRTUAL) != 0)
 			*arg_i |= PCM_CAP_VIRTUAL;
 		if (wrch && (wrch->flags & CHN_F_VIRTUAL) != 0)
 			*arg_i |= PCM_CAP_VIRTUAL;
 		PCM_UNLOCK(d);
 		break;
 
     	case SOUND_PCM_READ_BITS:
 		chn = wrch ? wrch : rdch;
 		if (chn) {
 			CHN_LOCK(chn);
 			if (chn->format & AFMT_8BIT)
 				*arg_i = 8;
 			else if (chn->format & AFMT_16BIT)
 				*arg_i = 16;
 			else if (chn->format & AFMT_24BIT)
 				*arg_i = 24;
 			else if (chn->format & AFMT_32BIT)
 				*arg_i = 32;
 			else
 				ret = EINVAL;
 			CHN_UNLOCK(chn);
 		} else {
 			*arg_i = 0;
 			ret = EINVAL;
 		}
 		break;
 
     	case SNDCTL_DSP_SETTRIGGER:
 		if (rdch) {
 			CHN_LOCK(rdch);
 			rdch->flags &= ~CHN_F_NOTRIGGER;
 		    	if (*arg_i & PCM_ENABLE_INPUT)
 				chn_start(rdch, 1);
 			else {
 				chn_abort(rdch);
 				chn_resetbuf(rdch);
 				rdch->flags |= CHN_F_NOTRIGGER;
 			}
 			CHN_UNLOCK(rdch);
 		}
 		if (wrch) {
 			CHN_LOCK(wrch);
 			wrch->flags &= ~CHN_F_NOTRIGGER;
 		    	if (*arg_i & PCM_ENABLE_OUTPUT)
 				chn_start(wrch, 1);
 			else {
 				chn_abort(wrch);
 				chn_resetbuf(wrch);
 				wrch->flags |= CHN_F_NOTRIGGER;
 			}
 			CHN_UNLOCK(wrch);
 		}
 		break;
 
     	case SNDCTL_DSP_GETTRIGGER:
 		*arg_i = 0;
 		if (wrch) {
 			CHN_LOCK(wrch);
 			if (wrch->flags & CHN_F_TRIGGERED)
 				*arg_i |= PCM_ENABLE_OUTPUT;
 			CHN_UNLOCK(wrch);
 		}
 		if (rdch) {
 			CHN_LOCK(rdch);
 			if (rdch->flags & CHN_F_TRIGGERED)
 				*arg_i |= PCM_ENABLE_INPUT;
 			CHN_UNLOCK(rdch);
 		}
 		break;
 
 	case SNDCTL_DSP_GETODELAY:
 		if (wrch) {
 	        	struct snd_dbuf *bs = wrch->bufsoft;
 
 			CHN_LOCK(wrch);
 			/* XXX abusive DMA update: chn_wrupdate(wrch); */
 			*arg_i = sndbuf_getready(bs);
 			CHN_UNLOCK(wrch);
 		} else
 			ret = EINVAL;
 		break;
 
     	case SNDCTL_DSP_POST:
 		if (wrch) {
 			CHN_LOCK(wrch);
 			wrch->flags &= ~CHN_F_NOTRIGGER;
 			chn_start(wrch, 1);
 			CHN_UNLOCK(wrch);
 		}
 		break;
 
 	case SNDCTL_DSP_SETDUPLEX:
 		/*
 		 * switch to full-duplex mode if card is in half-duplex
 		 * mode and is able to work in full-duplex mode
 		 */
 		PCM_LOCK(d);
 		if (rdch && wrch && (dsp_get_flags(i_dev) & SD_F_SIMPLEX))
 			dsp_set_flags(i_dev, dsp_get_flags(i_dev)^SD_F_SIMPLEX);
 		PCM_UNLOCK(d);
 		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;
 			chn = rdch;
 		}
 		/* FALLTHROUGH */
 	case SNDCTL_DSP_SETRECVOL:
 		if (xcmd == 0) {
 			xcmd = SOUND_MIXER_WRITE_RECLEV;
 			chn = rdch;
 		}
 		/* FALLTHROUGH */
 	case SNDCTL_DSP_GETPLAYVOL:
 		if (xcmd == 0) {
 			xcmd = SOUND_MIXER_READ_PCM;
 			chn = wrch;
 		}
 		/* FALLTHROUGH */
 	case SNDCTL_DSP_SETPLAYVOL:
 		if (xcmd == 0) {
 			xcmd = SOUND_MIXER_WRITE_PCM;
 			chn = wrch;
 		}
 
 		ret = dsp_ioctl_channel(i_dev, chn, xcmd, arg);
 		if (ret != -1) {
 			PCM_GIANT_EXIT(d);
 			return (ret);
 		}
 
 		if (d->mixer_dev != NULL) {
 			PCM_ACQUIRE_QUICK(d);
 			ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td,
 			    MIXER_CMD_DIRECT);
 			PCM_RELEASE_QUICK(d);
 		} 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) {
 			PCM_ACQUIRE_QUICK(d);
 			ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td,
 			    MIXER_CMD_DIRECT);
 			PCM_RELEASE_QUICK(d);
 		} 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_getalign(b);
 			oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getalign(b);
 #else
 			oc->samples = sndbuf_gettotal(bs) / sndbuf_getalign(bs);
 			oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getalign(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 useful 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:
 		PCM_ACQUIRE_QUICK(d);
 		ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg);
 		PCM_RELEASE_QUICK(d);
 		break;
 
 	case SNDCTL_DSP_SYNCSTART:
 		PCM_ACQUIRE_QUICK(d);
 		ret = dsp_oss_syncstart(*arg_i);
 		PCM_RELEASE_QUICK(d);
 		break;
 
 	case SNDCTL_DSP_POLICY:
 		PCM_ACQUIRE_QUICK(d);
 		ret = dsp_oss_policy(wrch, rdch, *arg_i);
 		PCM_RELEASE_QUICK(d);
 		break;
 
 	case SNDCTL_DSP_COOKEDMODE:
 		PCM_ACQUIRE_QUICK(d);
 		if (!(dsp_get_flags(i_dev) & SD_F_BITPERFECT))
 			ret = dsp_oss_cookedmode(wrch, rdch, *arg_i);
 		PCM_RELEASE_QUICK(d);
 		break;
 	case SNDCTL_DSP_GET_CHNORDER:
 		PCM_ACQUIRE_QUICK(d);
 		ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg);
 		PCM_RELEASE_QUICK(d);
 		break;
 	case SNDCTL_DSP_SET_CHNORDER:
 		PCM_ACQUIRE_QUICK(d);
 		ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg);
 		PCM_RELEASE_QUICK(d);
 		break;
 	case SNDCTL_DSP_GETCHANNELMASK:		/* XXX vlc */
 		PCM_ACQUIRE_QUICK(d);
 		ret = dsp_oss_getchannelmask(wrch, rdch, (int *)arg);
 		PCM_RELEASE_QUICK(d);
 		break;
 	case SNDCTL_DSP_BIND_CHANNEL:		/* XXX what?!? */
 		ret = EINVAL;
 		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;
 
 	/*
 	 * XXX Once implemented, revisit this for proper cv protection
 	 *     (if necessary).
 	 */
 	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 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:
 		/* undocumented */
 
     	case SNDCTL_DSP_SUBDIVIDE:
     	case SOUND_PCM_WRITE_FILTER:
     	case SOUND_PCM_READ_FILTER:
 		/* dunno what these do, don't sound important */
 
     	default:
 		DEB(printf("default ioctl fn 0x%08lx fail\n", cmd));
 		ret = EINVAL;
 		break;
     	}
 
 	PCM_GIANT_LEAVE(d);
 
     	return (ret);
 }
 
 static int
 dsp_poll(struct cdev *i_dev, int events, struct thread *td)
 {
 	struct snddev_info *d;
 	struct pcm_channel *wrch, *rdch;
 	int ret, e;
 
 	d = dsp_get_info(i_dev);
 	if (PCM_DETACHING(d) || !DSP_REGISTERED(d, i_dev)) {
 		/* XXX many clients don't understand POLLNVAL */
 		return (events & (POLLHUP | POLLPRI | POLLIN |
 		    POLLRDNORM | POLLOUT | POLLWRNORM));
 	}
 	PCM_GIANT_ENTER(d);
 
 	wrch = NULL;
 	rdch = NULL;
 	ret = 0;
 
 	getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
 
 	if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) {
 		e = (events & (POLLOUT | POLLWRNORM));
 		if (e)
 			ret |= chn_poll(wrch, e, td);
 	}
 
 	if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) {
 		e = (events & (POLLIN | POLLRDNORM));
 		if (e)
 			ret |= chn_poll(rdch, e, td);
 	}
 
 	relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
 
 	PCM_GIANT_LEAVE(d);
 
 	return (ret);
 }
 
 static int
 dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr,
     int nprot, vm_memattr_t *memattr)
 {
 
 	/*
 	 * offset is in range due to checks in dsp_mmap_single().
 	 * XXX memattr is not honored.
 	 */
 	*paddr = vtophys(offset);
 	return (0);
 }
 
 static int
 dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset,
     vm_size_t size, struct vm_object **object, int nprot)
 {
 	struct snddev_info *d;
 	struct pcm_channel *wrch, *rdch, *c;
 
 	/*
 	 * Reject PROT_EXEC by default. It just doesn't makes sense.
 	 * Unfortunately, we have to give up this one due to linux_mmap
 	 * changes.
 	 *
 	 * https://lists.freebsd.org/pipermail/freebsd-emulation/2007-June/003698.html
 	 *
 	 */
 #ifdef SV_ABI_LINUX
 	if ((nprot & PROT_EXEC) && (dsp_mmap_allow_prot_exec < 0 ||
 	    (dsp_mmap_allow_prot_exec == 0 &&
 	    SV_CURPROC_ABI() != SV_ABI_LINUX)))
 #else
 	if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec < 1)
 #endif
 		return (EINVAL);
 
 	/*
 	 * PROT_READ (alone) selects the input buffer.
 	 * PROT_WRITE (alone) selects the output buffer.
 	 * PROT_WRITE|PROT_READ together select the output buffer.
 	 */
 	if ((nprot & (PROT_READ | PROT_WRITE)) == 0)
 		return (EINVAL);
 
 	d = dsp_get_info(i_dev);
 	if (PCM_DETACHING(d) || !DSP_REGISTERED(d, i_dev))
 		return (EINVAL);
 
 	PCM_GIANT_ENTER(d);
 
 	getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
 
 	c = ((nprot & PROT_WRITE) != 0) ? wrch : rdch;
 	if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) ||
 	    (*offset  + size) > sndbuf_getsize(c->bufsoft) ||
 	    (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) ||
 	    (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) {
 		relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
 		PCM_GIANT_EXIT(d);
 		return (EINVAL);
 	}
 
 	if (wrch != NULL)
 		wrch->flags |= CHN_F_MMAP;
 	if (rdch != NULL)
 		rdch->flags |= CHN_F_MMAP;
 
 	*offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset);
 	relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
 	*object = vm_pager_allocate(OBJT_DEVICE, i_dev,
 	    size, nprot, *offset, curthread->td_ucred);
 
 	PCM_GIANT_LEAVE(d);
 
 	if (*object == NULL)
 		 return (EINVAL);
 	return (0);
 }
 
 /* So much for dev_stdclone() */
 static int
 dsp_stdclone(char *name, char *namep, char *sep, int use_sep, int *u, int *c)
 {
 	size_t len;
 
 	len = strlen(namep);
 	if (strncmp(name, namep, len) != 0)
 		return (ENODEV);
 
 	name += len;
 
 	if (isdigit(*name) == 0)
 		return (ENODEV);
 
 	len = strlen(sep);
 
 	if (*name == '0' && !(name[1] == '\0' || bcmp(name + 1, sep, len) == 0))
 		return (ENODEV);
 
 	for (*u = 0; isdigit(*name) != 0; name++) {
 		*u *= 10;
 		*u += *name - '0';
 		if (*u > dsp_umax)
 			return (ENODEV);
 	}
 
 	if (*name == '\0')
 		return ((use_sep == 0) ? 0 : ENODEV);
 
 	if (bcmp(name, sep, len) != 0 || isdigit(name[len]) == 0)
 		return (ENODEV);
 
 	name += len;
 
 	if (*name == '0' && name[1] != '\0')
 		return (ENODEV);
 
 	for (*c = 0; isdigit(*name) != 0; name++) {
 		*c *= 10;
 		*c += *name - '0';
 		if (*c > dsp_cmax)
 			return (ENODEV);
 	}
 
 	if (*name != '\0')
 		return (ENODEV);
 
 	return (0);
 }
 
 static void
 dsp_clone(void *arg,
     struct ucred *cred,
     char *name, int namelen, struct cdev **dev)
 {
 	struct snddev_info *d;
 	struct snd_clone_entry *ce;
 	struct pcm_channel *c;
 	int i, unit, udcmask, cunit, devtype, devhw, devcmax, tumax;
 	char *devname, *devcmp, *devsep;
 
 	KASSERT(dsp_umax >= 0 && dsp_cmax >= 0, ("Uninitialized unit!"));
 
 	if (*dev != NULL)
 		return;
 
 	unit = -1;
 	cunit = -1;
 	devtype = -1;
 	devhw = 0;
 	devcmax = -1;
 	tumax = -1;
 	devname = NULL;
 	devsep = NULL;
 
 	for (i = 0; unit == -1 &&
 	    i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) {
 		devtype = dsp_cdevs[i].type;
 		devcmp = dsp_cdevs[i].name;
 		devsep = dsp_cdevs[i].sep;
 		devname = dsp_cdevs[i].alias;
 		if (devname == NULL)
 			devname = devcmp;
 		devhw = dsp_cdevs[i].hw;
 		devcmax = dsp_cdevs[i].max - 1;
 		if (strcmp(name, devcmp) == 0) {
 			if (dsp_basename_clone != 0)
 				unit = snd_unit;
 		} else if (dsp_stdclone(name, devcmp, devsep,
 		    dsp_cdevs[i].use_sep, &unit, &cunit) != 0) {
 			unit = -1;
 			cunit = -1;
 		}
 	}
 
 	d = devclass_get_softc(pcm_devclass, unit);
 	if (!PCM_REGISTERED(d) || d->clones == NULL)
 		return;
 
 	/* XXX Need Giant magic entry ??? */
 
 	PCM_LOCK(d);
 	if (snd_clone_disabled(d->clones)) {
 		PCM_UNLOCK(d);
 		return;
 	}
 
 	PCM_WAIT(d);
 	PCM_ACQUIRE(d);
 	PCM_UNLOCK(d);
 
 	udcmask = snd_u2unit(unit) | snd_d2unit(devtype);
 
 	if (devhw != 0) {
 		KASSERT(devcmax <= dsp_cmax,
 		    ("overflow: devcmax=%d, dsp_cmax=%d", devcmax, dsp_cmax));
 		if (cunit > devcmax) {
 			PCM_RELEASE_QUICK(d);
 			return;
 		}
 		udcmask |= snd_c2unit(cunit);
 		CHN_FOREACH(c, d, channels.pcm) {
 			CHN_LOCK(c);
 			if (c->unit != udcmask) {
 				CHN_UNLOCK(c);
 				continue;
 			}
 			CHN_UNLOCK(c);
 			udcmask &= ~snd_c2unit(cunit);
 			/*
 			 * Temporarily increase clone maxunit to overcome
 			 * vchan flexibility.
 			 *
 			 * # sysctl dev.pcm.0.play.vchans=256
 			 * dev.pcm.0.play.vchans: 1 -> 256
 			 * # cat /dev/zero > /dev/dsp0.vp255 &
 			 * [1] 17296
 			 * # sysctl dev.pcm.0.play.vchans=0
 			 * dev.pcm.0.play.vchans: 256 -> 1
 			 * # fg
 			 * [1]  + running    cat /dev/zero > /dev/dsp0.vp255
 			 * ^C
 			 * # cat /dev/zero > /dev/dsp0.vp255
 			 * zsh: operation not supported: /dev/dsp0.vp255
 			 */
 			tumax = snd_clone_getmaxunit(d->clones);
 			if (cunit > tumax)
 				snd_clone_setmaxunit(d->clones, cunit);
 			else
 				tumax = -1;
 			goto dsp_clone_alloc;
 		}
 		/*
 		 * Ok, so we're requesting unallocated vchan, but still
 		 * within maximum vchan limit.
 		 */
 		if (((devtype == SND_DEV_DSPHW_VPLAY && d->pvchancount > 0) ||
 		    (devtype == SND_DEV_DSPHW_VREC && d->rvchancount > 0)) &&
 		    cunit < snd_maxautovchans) {
 			udcmask &= ~snd_c2unit(cunit);
 			tumax = snd_clone_getmaxunit(d->clones);
 			if (cunit > tumax)
 				snd_clone_setmaxunit(d->clones, cunit);
 			else
 				tumax = -1;
 			goto dsp_clone_alloc;
 		}
 		PCM_RELEASE_QUICK(d);
 		return;
 	}
 
 dsp_clone_alloc:
 	ce = snd_clone_alloc(d->clones, dev, &cunit, udcmask);
 	if (tumax != -1)
 		snd_clone_setmaxunit(d->clones, tumax);
 	if (ce != NULL) {
 		udcmask |= snd_c2unit(cunit);
 		*dev = make_dev(&dsp_cdevsw, PCMMINOR(udcmask),
 		    UID_ROOT, GID_WHEEL, 0666, "%s%d%s%d",
 		    devname, unit, devsep, cunit);
 		snd_clone_register(ce, *dev);
 	}
 
 	PCM_RELEASE_QUICK(d);
 
 	if (*dev != NULL)
 		dev_ref(*dev);
 }
 
 static void
 dsp_sysinit(void *p)
 {
 	if (dsp_ehtag != NULL)
 		return;
 	/* initialize unit numbering */
 	snd_unit_init();
 	dsp_umax = PCMMAXUNIT;
 	dsp_cmax = PCMMAXCHAN;
 	dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000);
 }
 
 static void
 dsp_sysuninit(void *p)
 {
 	if (dsp_ehtag == NULL)
 		return;
 	EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag);
 	dsp_ehtag = NULL;
 }
 
 SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL);
 SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL);
 
 char *
 dsp_unit2name(char *buf, size_t len, int unit)
 {
 	int i, dtype;
 
 	KASSERT(buf != NULL && len != 0,
 	    ("bogus buf=%p len=%ju", buf, (uintmax_t)len));
 
 	dtype = snd_unit2d(unit);
 
 	for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) {
 		if (dtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL)
 			continue;
 		snprintf(buf, len, "%s%d%s%d", dsp_cdevs[i].name,
 		    snd_unit2u(unit), dsp_cdevs[i].sep, snd_unit2c(unit));
 		return (buf);
 	}
 
 	return (NULL);
 }
 
 /**
  * @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 pcmchan_caps *caps;
 	struct pcm_channel *ch;
 	struct snddev_info *d;
 	uint32_t fmts;
 	int i, nchan, *rates, minch, maxch;
 	char *devname, buf[CHN_NAMELEN];
 
 	/*
 	 * 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;
 	devname = NULL;
 	nchan = 0;
 	bzero(buf, sizeof(buf));
 
 	/*
 	 * Search for the requested audio device (channel).  Start by
 	 * iterating over pcm devices.
 	 */ 
 	for (i = 0; pcm_devclass != NULL &&
 	    i < devclass_get_maxunit(pcm_devclass); i++) {
 		d = devclass_get_softc(pcm_devclass, i);
 		if (!PCM_REGISTERED(d))
 			continue;
 
 		/* XXX Need Giant magic entry ??? */
 
 		/* See the note in function docblock */
 		PCM_UNLOCKASSERT(d);
 		PCM_LOCK(d);
 
 		CHN_FOREACH(ch, d, channels.pcm) {
 			CHN_UNLOCKASSERT(ch);
 			CHN_LOCK(ch);
 			if (ai->dev == -1) {
 				if (DSP_REGISTERED(d, i_dev) &&
 				    (ch == PCM_RDCH(i_dev) ||	/* record ch */
 				    ch == PCM_WRCH(i_dev))) {	/* playback ch */
 					devname = dsp_unit2name(buf,
 					    sizeof(buf), ch->unit);
 				}
 			} else if (ai->dev == nchan) {
 				devname = dsp_unit2name(buf, sizeof(buf),
 				    ch->unit);
 			}
 			if (devname != NULL)
 				break;
 			CHN_UNLOCK(ch);
 			++nchan;
 		}
 
 		if (devname != NULL) {
 			/*
 			 * At this point, the following synchronization stuff
 			 * has happened:
 			 * - a specific PCM device is locked.
 			 * - 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 PCM_CAP_DUPLEX is out.
 			 */
 			/**
 			 * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep
 			 *       these in pcmchan::caps?
 			 */
 			ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER |
 			    ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) |
 			    ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT);
 
 			/*
 			 * 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 (AFMT_CHANNEL(caps->fmtlist[i]) > 1) {
 					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, "/dev/", sizeof(ai->devnode));
 			strlcat(ai->devnode, devname, 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 > OSS_MAX_SAMPLE_RATES)
 				ai->nrates = OSS_MAX_SAMPLE_RATES;
 
 			for (i = 0; i < ai->nrates; i++)
 				ai->rates[i] = rates[i];
 			
 			ai->next_play_engine = 0;
 			ai->next_rec_engine = 0;
 
 			CHN_UNLOCK(ch);
 		}
 
 		PCM_UNLOCK(d);
 
 		if (devname != NULL)
 			return (0);
 	}
 
 	/* Exhausted the search -- nothing is locked, so return. */
 	return (EINVAL);
 }
 
 /**
  * @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, sg_ids[3];
 
 	smrd = NULL;
 	smwr = NULL;
 	sg = NULL;
 	ret = 0;
 
 	/*
 	 * Free_unr() may sleep, so store released syncgroup IDs until after
 	 * all locks are released.
 	 */
 	sg_ids[0] = sg_ids[1] = sg_ids[2] = 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);
 		sg_ids[0] = chn_syncdestroy(wrch);
 	}
 
 	if (rdch) {
 		CHN_LOCK(rdch);
 		sg_ids[1] = 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 out;
 	}
 
 	/*
 	 * 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 = alloc_unr(pcmsg_unrhdr);
 
 			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 out;
 
 	/*
 	 * 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 out;
 		}
 
 		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 out;
 		}
 
 		SLIST_INSERT_HEAD(&sg->members, smwr, link);
 		smwr->parent = sg;
 		smwr->ch = wrch;
 
 		chn_abort(wrch);
 		wrch->flags |= CHN_F_NOTRIGGER;
 		wrch->sm = smwr;
 	}
 
 out:
 	if (ret != 0) {
 		if (smrd != NULL)
 			free(smrd, M_DEVBUF);
 		if ((sg != NULL) && SLIST_EMPTY(&sg->members)) {
 			sg_ids[2] = sg->id;
 			SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
 			free(sg, M_DEVBUF);
 		}
 
 		if (wrch)
 			wrch->sm = NULL;
 		if (rdch)
 			rdch->sm = NULL;
 	}
 
 	if (wrch)
 		CHN_UNLOCK(wrch);
 	if (rdch)
 		CHN_UNLOCK(rdch);
 
 	PCM_SG_UNLOCK();
 
 	if (sg_ids[0])
 		free_unr(pcmsg_unrhdr, sg_ids[0]);
 	if (sg_ids[1])
 		free_unr(pcmsg_unrhdr, sg_ids[1]);
 	if (sg_ids[2])
 		free_unr(pcmsg_unrhdr, sg_ids[2]);
 
 	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, "pcmsg", timo);
 				if (ret == EINTR || ret == ERESTART)
 					break;
 
 				needlocks = 1;
 				ret = 0; /* Assumes ret == EAGAIN... */
 			}
 		}
 	} 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();
 
 	/*
 	 * Free_unr() may sleep, so be sure to give up the syncgroup lock
 	 * first.
 	 */
 	if (ret == 0)
 		free_unr(pcmsg_unrhdr, sg_id);
 
 	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 ret;
 
 	if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX)
 		return (EIO);
 
 	/* Default: success */
 	ret = 0;
 
 	if (rdch) {
 		CHN_LOCK(rdch);
 		ret = chn_setlatency(rdch, policy);
 		CHN_UNLOCK(rdch);
 	}
 
 	if (wrch && ret == 0) {
 		CHN_LOCK(wrch);
 		ret = chn_setlatency(wrch, policy);
 		CHN_UNLOCK(wrch);
 	}
 
 	if (ret)
 		ret = EIO;
 
 	return (ret);
 }
 
 /**
  * @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.
  *
  * @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)
 {
 
 	/*
 	 * XXX I just don't get it. Why don't they call it
 	 * "BITPERFECT" ~ SNDCTL_DSP_BITPERFECT !?!?.
 	 * This is just plain so confusing, incoherent,
 	 * <insert any non-printable characters here>.
 	 */
 	if (!(enabled == 1 || enabled == 0))
 		return (EINVAL);
 
 	/*
 	 * I won't give in. I'm inverting its logic here and now.
 	 * Brag all you want, but "BITPERFECT" should be the better
 	 * term here.
 	 */
 	enabled ^= 0x00000001;
 
 	if (wrch != NULL) {
 		CHN_LOCK(wrch);
 		wrch->flags &= ~CHN_F_BITPERFECT;
 		wrch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000;
 		CHN_UNLOCK(wrch);
 	}
 
 	if (rdch != NULL) {
 		CHN_LOCK(rdch);
 		rdch->flags &= ~CHN_F_BITPERFECT;
 		rdch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000;
 		CHN_UNLOCK(rdch);
 	}
 
 	return (0);
 }
 
 /**
  * @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)
 {
 	struct pcm_channel *ch;
 	int ret;
 
 	ch = (wrch != NULL) ? wrch : rdch;
 	if (ch != NULL) {
 		CHN_LOCK(ch);
 		ret = chn_oss_getorder(ch, map);
 		CHN_UNLOCK(ch);
 	} else
 		ret = EINVAL;
 
 	return (ret);
 }
 
 /**
  * @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)
 {
 	int ret;
 
 	ret = 0;
 
 	if (wrch != NULL) {
 		CHN_LOCK(wrch);
 		ret = chn_oss_setorder(wrch, map);
 		CHN_UNLOCK(wrch);
 	}
 
 	if (ret == 0 && rdch != NULL) {
 		CHN_LOCK(rdch);
 		ret = chn_oss_setorder(rdch, map);
 		CHN_UNLOCK(rdch);
 	}
 
 	return (ret);
 }
 
 static int
 dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch,
     int *mask)
 {
 	struct pcm_channel *ch;
 	uint32_t chnmask;
 	int ret;
 
 	chnmask = 0;
 	ch = (wrch != NULL) ? wrch : rdch;
 
 	if (ch != NULL) {
 		CHN_LOCK(ch);
 		ret = chn_oss_getmask(ch, &chnmask);
 		CHN_UNLOCK(ch);
 	} else
 		ret = EINVAL;
 
 	if (ret == 0)
 		*mask = chnmask;
 
 	return (ret);
 }
 
 #ifdef OSSV4_EXPERIMENT
 /**
  * @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 */
diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c
index 322d7f6b2c84..2312bd89c9d4 100644
--- a/sys/dev/sound/pcm/feeder_volume.c
+++ b/sys/dev/sound/pcm/feeder_volume.c
@@ -1,343 +1,352 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  *
  * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 /* feeder_volume, a long 'Lost Technology' rather than a new feature. */
 
 #ifdef _KERNEL
 #ifdef HAVE_KERNEL_OPTION_HEADERS
 #include "opt_snd.h"
 #endif
 #include <dev/sound/pcm/sound.h>
 #include <dev/sound/pcm/pcm.h>
 #include "feeder_if.h"
 
 #define SND_USE_FXDIV
 #include "snd_fxdiv_gen.h"
 
 SND_DECLARE_FILE("$FreeBSD$");
 #endif
 
 typedef void (*feed_volume_t)(int *, int *, uint32_t, uint8_t *, uint32_t);
 
 #define FEEDVOLUME_CALC8(s, v)	(SND_VOL_CALC_SAMPLE((intpcm_t)		\
 				 (s) << 8, v) >> 8)
 #define FEEDVOLUME_CALC16(s, v)	SND_VOL_CALC_SAMPLE((intpcm_t)(s), v)
 #define FEEDVOLUME_CALC24(s, v)	SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v)
 #define FEEDVOLUME_CALC32(s, v)	SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v)
 
 #define FEEDVOLUME_DECLARE(SIGN, BIT, ENDIAN)				\
 static void								\
 feed_volume_##SIGN##BIT##ENDIAN(int *vol, int *matrix,			\
     uint32_t channels, uint8_t *dst, uint32_t count)			\
 {									\
 	intpcm##BIT##_t v;						\
 	intpcm_t x;							\
 	uint32_t i;							\
 									\
 	dst += count * PCM_##BIT##_BPS * channels;			\
 	do {								\
 		i = channels;						\
 		do {							\
 			dst -= PCM_##BIT##_BPS;				\
 			i--;						\
 			x = PCM_READ_##SIGN##BIT##_##ENDIAN(dst);	\
 			v = FEEDVOLUME_CALC##BIT(x, vol[matrix[i]]);	\
 			x = PCM_CLAMP_##SIGN##BIT(v);			\
 			_PCM_WRITE_##SIGN##BIT##_##ENDIAN(dst, x);	\
 		} while (i != 0);					\
 	} while (--count != 0);						\
 }
 
 #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
 FEEDVOLUME_DECLARE(S, 16, LE)
 FEEDVOLUME_DECLARE(S, 32, LE)
 #endif
 #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
 FEEDVOLUME_DECLARE(S, 16, BE)
 FEEDVOLUME_DECLARE(S, 32, BE)
 #endif
 #ifdef SND_FEEDER_MULTIFORMAT
 FEEDVOLUME_DECLARE(S,  8, NE)
 FEEDVOLUME_DECLARE(S, 24, LE)
 FEEDVOLUME_DECLARE(S, 24, BE)
 FEEDVOLUME_DECLARE(U,  8, NE)
 FEEDVOLUME_DECLARE(U, 16, LE)
 FEEDVOLUME_DECLARE(U, 24, LE)
 FEEDVOLUME_DECLARE(U, 32, LE)
 FEEDVOLUME_DECLARE(U, 16, BE)
 FEEDVOLUME_DECLARE(U, 24, BE)
 FEEDVOLUME_DECLARE(U, 32, BE)
 #endif
 
 struct feed_volume_info {
 	uint32_t bps, channels;
 	feed_volume_t apply;
 	int volume_class;
 	int state;
 	int matrix[SND_CHN_MAX];
 };
 
 #define FEEDVOLUME_ENTRY(SIGN, BIT, ENDIAN)				\
 	{								\
 		AFMT_##SIGN##BIT##_##ENDIAN,				\
 		feed_volume_##SIGN##BIT##ENDIAN				\
 	}
 
 static const struct {
 	uint32_t format;
 	feed_volume_t apply;
 } feed_volume_info_tab[] = {
 #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
 	FEEDVOLUME_ENTRY(S, 16, LE),
 	FEEDVOLUME_ENTRY(S, 32, LE),
 #endif
 #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT)
 	FEEDVOLUME_ENTRY(S, 16, BE),
 	FEEDVOLUME_ENTRY(S, 32, BE),
 #endif
 #ifdef SND_FEEDER_MULTIFORMAT
 	FEEDVOLUME_ENTRY(S,  8, NE),
 	FEEDVOLUME_ENTRY(S, 24, LE),
 	FEEDVOLUME_ENTRY(S, 24, BE),
 	FEEDVOLUME_ENTRY(U,  8, NE),
 	FEEDVOLUME_ENTRY(U, 16, LE),
 	FEEDVOLUME_ENTRY(U, 24, LE),
 	FEEDVOLUME_ENTRY(U, 32, LE),
 	FEEDVOLUME_ENTRY(U, 16, BE),
 	FEEDVOLUME_ENTRY(U, 24, BE),
 	FEEDVOLUME_ENTRY(U, 32, BE)
 #endif
 };
 
 #define FEEDVOLUME_TAB_SIZE	((int32_t)				\
 				 (sizeof(feed_volume_info_tab) /	\
 				  sizeof(feed_volume_info_tab[0])))
 
 static int
 feed_volume_init(struct pcm_feeder *f)
 {
 	struct feed_volume_info *info;
 	struct pcmchan_matrix *m;
 	uint32_t i;
 	int ret;
 
 	if (f->desc->in != f->desc->out ||
 	    AFMT_CHANNEL(f->desc->in) > SND_CHN_MAX)
 		return (EINVAL);
 
 	for (i = 0; i < FEEDVOLUME_TAB_SIZE; i++) {
 		if (AFMT_ENCODING(f->desc->in) ==
 		    feed_volume_info_tab[i].format) {
 			info = malloc(sizeof(*info), M_DEVBUF,
 			    M_NOWAIT | M_ZERO);
 			if (info == NULL)
 				return (ENOMEM);
 
 			info->bps = AFMT_BPS(f->desc->in);
 			info->channels = AFMT_CHANNEL(f->desc->in);
 			info->apply = feed_volume_info_tab[i].apply;
 			info->volume_class = SND_VOL_C_PCM;
 			info->state = FEEDVOLUME_ENABLE;
 
 			f->data = info;
 			m = feeder_matrix_default_channel_map(info->channels);
 			if (m == NULL) {
 				free(info, M_DEVBUF);
 				return (EINVAL);
 			}
 
 			ret = feeder_volume_apply_matrix(f, m);
 			if (ret != 0)
 				free(info, M_DEVBUF);
 
 			return (ret);
 		}
 	}
 
 	return (EINVAL);
 }
 
 static int
 feed_volume_free(struct pcm_feeder *f)
 {
 	struct feed_volume_info *info;
 
 	info = f->data;
 	if (info != NULL)
 		free(info, M_DEVBUF);
 
 	f->data = NULL;
 
 	return (0);
 }
 
 static int
 feed_volume_set(struct pcm_feeder *f, int what, int value)
 {
 	struct feed_volume_info *info;
 	struct pcmchan_matrix *m;
 	int ret;
 
 	info = f->data;
 	ret = 0;
 
 	switch (what) {
 	case FEEDVOLUME_CLASS:
 		if (value < SND_VOL_C_BEGIN || value > SND_VOL_C_END)
 			return (EINVAL);
 		info->volume_class = value;
 		break;
 	case FEEDVOLUME_CHANNELS:
 		if (value < SND_CHN_MIN || value > SND_CHN_MAX)
 			return (EINVAL);
 		m = feeder_matrix_default_channel_map(value);
 		if (m == NULL)
 			return (EINVAL);
 		ret = feeder_volume_apply_matrix(f, m);
 		break;
 	case FEEDVOLUME_STATE:
 		if (!(value == FEEDVOLUME_ENABLE || value == FEEDVOLUME_BYPASS))
 			return (EINVAL);
 		info->state = value;
 		break;
 	default:
 		return (EINVAL);
 		break;
 	}
 
 	return (ret);
 }
 
 static int
 feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
     uint32_t count, void *source)
 {
+	int temp_vol[SND_CHN_T_VOL_MAX];
 	struct feed_volume_info *info;
 	uint32_t j, align;
-	int i, *vol, *matrix;
+	int i, *matrix;
 	uint8_t *dst;
+	const int16_t *vol;
+	const int8_t *muted;
 
 	/*
 	 * Fetch filter data operation.
 	 */
 	info = f->data;
 
 	if (info->state == FEEDVOLUME_BYPASS)
 		return (FEEDER_FEED(f->source, c, b, count, source));
 
 	vol = c->volume[SND_VOL_C_VAL(info->volume_class)];
+	muted = c->muted[SND_VOL_C_VAL(info->volume_class)];
 	matrix = info->matrix;
 
 	/*
 	 * First, let see if we really need to apply gain at all.
 	 */
 	j = 0;
 	i = info->channels;
-	do {
-		if (vol[matrix[--i]] != SND_VOL_FLAT) {
+	while (i--) {
+		if (vol[matrix[i]] != SND_VOL_FLAT ||
+		    muted[matrix[i]] != 0) {
 			j = 1;
 			break;
 		}
-	} while (i != 0);
+	}
 
 	/* Nope, just bypass entirely. */
 	if (j == 0)
 		return (FEEDER_FEED(f->source, c, b, count, source));
 
+	/* Check if any controls are muted. */
+	for (j = 0; j != SND_CHN_T_VOL_MAX; j++)
+		temp_vol[j] = muted[j] ? 0 : vol[j];
+
 	dst = b;
 	align = info->bps * info->channels;
 
 	do {
 		if (count < align)
 			break;
 
 		j = SND_FXDIV(FEEDER_FEED(f->source, c, dst, count, source),
 		    align);
 		if (j == 0)
 			break;
 
-		info->apply(vol, matrix, info->channels, dst, j);
+		info->apply(temp_vol, matrix, info->channels, dst, j);
 
 		j *= align;
 		dst += j;
 		count -= j;
 
 	} while (count != 0);
 
 	return (dst - b);
 }
 
 static struct pcm_feederdesc feeder_volume_desc[] = {
 	{ FEEDER_VOLUME, 0, 0, 0, 0 },
 	{ 0, 0, 0, 0, 0 }
 };
 
 static kobj_method_t feeder_volume_methods[] = {
 	KOBJMETHOD(feeder_init,		feed_volume_init),
 	KOBJMETHOD(feeder_free,		feed_volume_free),
 	KOBJMETHOD(feeder_set,		feed_volume_set),
 	KOBJMETHOD(feeder_feed,		feed_volume_feed),
 	KOBJMETHOD_END
 };
 
 FEEDER_DECLARE(feeder_volume, NULL);
 
 /* Extern */
 
 /*
  * feeder_volume_apply_matrix(): For given matrix map, apply its configuration
  *                               to feeder_volume matrix structure. There are
  *                               possibilites that feeder_volume be inserted
  *                               before or after feeder_matrix, which in this
  *                               case feeder_volume must be in a good terms
  *                               with _current_ matrix.
  */
 int
 feeder_volume_apply_matrix(struct pcm_feeder *f, struct pcmchan_matrix *m)
 {
 	struct feed_volume_info *info;
 	uint32_t i;
 
 	if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_VOLUME ||
 	    f->data == NULL || m == NULL || m->channels < SND_CHN_MIN ||
 	    m->channels > SND_CHN_MAX)
 		return (EINVAL);
 
 	info = f->data;
 
 	for (i = 0; i < (sizeof(info->matrix) / sizeof(info->matrix[0])); i++) {
 		if (i < m->channels)
 			info->matrix[i] = m->map[i].type;
 		else
 			info->matrix[i] = SND_CHN_T_FL;
 	}
 
 	info->channels = m->channels;
 
 	return (0);
 }