Index: stable/10/sys/dev/sound/pcm/channel.c =================================================================== --- stable/10/sys/dev/sound/pcm/channel.c (revision 283949) +++ stable/10/sys/dev/sound/pcm/channel.c (revision 283950) @@ -1,2547 +1,2522 @@ /*- * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * Portions Copyright (c) Luigi Rizzo - 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 #include #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, 1, "report software-emulated formats"); int report_soft_matrix = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_matrix, CTLFLAG_RW, &report_soft_matrix, 1, "report software-emulated channel matrixing"); int chn_latency = CHN_LATENCY_DEFAULT; TUNABLE_INT("hw.snd.latency", &chn_latency); 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_RW, 0, sizeof(int), sysctl_hw_snd_latency, "I", "buffering latency (0=low ... 10=high)"); int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; TUNABLE_INT("hw.snd.latency_profile", &chn_latency_profile); 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_RW, 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", "buffering latency profile (0=aggresive 1=safe)"); static int chn_timeout = CHN_TIMEOUT; TUNABLE_INT("hw.snd.timeout", &chn_timeout); #ifdef SND_DEBUG 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_RW, 0, sizeof(int), sysctl_hw_snd_timeout, "I", "interrupt timeout (1 - 10) seconds"); #endif static int chn_vpc_autoreset = 1; TUNABLE_INT("hw.snd.vpc_autoreset", &chn_vpc_autoreset); SYSCTL_INT(_hw_snd, OID_AUTO, vpc_autoreset, CTLFLAG_RW, &chn_vpc_autoreset, 0, "automatically reset channels volume to 0db"); static int chn_vol_0db_pcm = SND_VOL_0DB_PCM; TUNABLE_INT("hw.snd.vpc_0db", &chn_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_RW, 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, 0, sizeof(int), sysctl_hw_snd_vpc_reset, "I", "reset volume on all channels"); static int chn_usefrags = 0; TUNABLE_INT("hw.snd.usefrags", &chn_usefrags); static int chn_syncdelay = -1; TUNABLE_INT("hw.snd.syncdelay", &chn_syncdelay); #ifdef SND_DEBUG SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RW, &chn_usefrags, 1, "prefer setfragments() over setblocksize()"); SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RW, &chn_syncdelay, 1, "append (0-1000) millisecond trailing buffer delay on each sync"); #endif /** * @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 } }; -static const struct { - char *name, *alias1, *alias2; - int matrix_id; -} matrix_id_tab[] = { - { "1.0", "1", "mono", SND_CHN_MATRIX_1_0 }, - { "2.0", "2", "stereo", SND_CHN_MATRIX_2_0 }, - { "2.1", NULL, NULL, SND_CHN_MATRIX_2_1 }, - { "3.0", "3", NULL, SND_CHN_MATRIX_3_0 }, - { "3.1", NULL, NULL, SND_CHN_MATRIX_3_1 }, - { "4.0", "4", "quad", SND_CHN_MATRIX_4_0 }, - { "4.1", NULL, NULL, SND_CHN_MATRIX_4_1 }, - { "5.0", "5", NULL, SND_CHN_MATRIX_5_0 }, - { "5.1", "6", NULL, SND_CHN_MATRIX_5_1 }, - { "6.0", NULL, NULL, SND_CHN_MATRIX_6_0 }, - { "6.1", "7", NULL, SND_CHN_MATRIX_6_1 }, - { "7.0", NULL, NULL, SND_CHN_MATRIX_7_0 }, - { "7.1", "8", NULL, SND_CHN_MATRIX_7_1 }, - { NULL, NULL, NULL, SND_CHN_MATRIX_UNKNOWN } -}; - uint32_t snd_str2afmt(const char *req) { - uint32_t i, afmt; - int matrix_id; - char b1[8], b2[8]; + 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); - afmt = 0; - matrix_id = SND_CHN_MATRIX_UNKNOWN; + i = sscanf(b2, "%d.%d", &ch, &ext); - for (i = 0; afmt == 0 && afmt_tab[i].name != NULL; i++) { - if (strcasecmp(afmt_tab[i].name, b1) == 0 || - (afmt_tab[i].alias1 != NULL && - strcasecmp(afmt_tab[i].alias1, b1) == 0) || - (afmt_tab[i].alias2 != NULL && - strcasecmp(afmt_tab[i].alias2, b1) == 0)) { - afmt = afmt_tab[i].afmt; - strlcpy(b1, afmt_tab[i].name, sizeof(b1)); - } - } - - if (afmt == 0) + 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; matrix_id == SND_CHN_MATRIX_UNKNOWN && - matrix_id_tab[i].name != NULL; i++) { - if (strcmp(matrix_id_tab[i].name, b2) == 0 || - (matrix_id_tab[i].alias1 != NULL && - strcmp(matrix_id_tab[i].alias1, b2) == 0) || - (matrix_id_tab[i].alias2 != NULL && - strcasecmp(matrix_id_tab[i].alias2, b2) == 0)) { - matrix_id = matrix_id_tab[i].matrix_id; - strlcpy(b2, matrix_id_tab[i].name, sizeof(b2)); + 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)); } - - if (matrix_id == SND_CHN_MATRIX_UNKNOWN) - return (0); - -#ifndef _KERNEL - printf("Parse OK: '%s' -> '%s:%s' %d\n", req, b1, b2, - (int)(b2[0]) - '0' + (int)(b2[2]) - '0'); -#endif - - return (SND_FORMAT(afmt, b2[0] - '0' + b2[2] - '0', b2[2] - '0')); + /* not a valid format */ + return (0); } uint32_t snd_afmt2str(uint32_t afmt, char *buf, size_t len) { - uint32_t i, enc, ch, ext; - char tmp[AFMTSTR_LEN]; + uint32_t enc; + uint32_t ext; + uint32_t ch; + int i; if (buf == NULL || len < AFMTSTR_LEN) return (0); - - bzero(tmp, sizeof(tmp)); + 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) { - strlcpy(tmp, afmt_tab[i].name, sizeof(tmp)); - strlcat(tmp, ":", sizeof(tmp)); - break; - } + 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)); } - - if (strlen(tmp) == 0) - return (0); - - for (i = 0; matrix_id_tab[i].name != NULL; i++) { - if (ch == (matrix_id_tab[i].name[0] - '0' + - matrix_id_tab[i].name[2] - '0') && - ext == (matrix_id_tab[i].name[2] - '0')) { - strlcat(tmp, matrix_id_tab[i].name, sizeof(tmp)); - break; - } - } - - if (strlen(tmp) == 0) - return (0); - - strlcpy(buf, tmp, len); - - return (snd_str2afmt(buf)); + 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; 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]); } 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 Index: stable/10/sys/dev/sound/pcm/channel.h =================================================================== --- stable/10/sys/dev/sound/pcm/channel.h (revision 283949) +++ stable/10/sys/dev/sound/pcm/channel.h (revision 283950) @@ -1,444 +1,445 @@ /*- * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * 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 "" #define CHN_COMM_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]; 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); 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 #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 5 #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)) Index: stable/10/sys/dev/sound/pcm/feeder_chain.c =================================================================== --- stable/10/sys/dev/sound/pcm/feeder_chain.c (revision 283949) +++ stable/10/sys/dev/sound/pcm/feeder_chain.c (revision 283950) @@ -1,843 +1,858 @@ /*- * Copyright (c) 2008-2009 Ariff Abdullah * 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 #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* chain state */ struct feeder_chain_state { uint32_t afmt; /* audio format */ uint32_t rate; /* sampling rate */ struct pcmchan_matrix *matrix; /* matrix map */ }; /* * chain descriptor that will be passed around from the beginning until the * end of chain process. */ struct feeder_chain_desc { struct feeder_chain_state origin; /* original state */ struct feeder_chain_state current; /* current state */ struct feeder_chain_state target; /* target state */ struct pcm_feederdesc desc; /* feeder descriptor */ uint32_t afmt_ne; /* prefered native endian */ int mode; /* chain mode */ int use_eq; /* need EQ? */ int use_matrix; /* need channel matrixing? */ int use_volume; /* need softpcmvol? */ int dummy; /* dummy passthrough */ int expensive; /* possibly expensive */ }; #define FEEDER_CHAIN_LEAN 0 #define FEEDER_CHAIN_16 1 #define FEEDER_CHAIN_32 2 #define FEEDER_CHAIN_MULTI 3 #define FEEDER_CHAIN_FULLMULTI 4 #define FEEDER_CHAIN_LAST 5 #if defined(SND_FEEDER_FULL_MULTIFORMAT) #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_FULLMULTI #elif defined(SND_FEEDER_MULTIFORMAT) #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_MULTI #else #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_LEAN #endif /* * List of prefered formats that might be required during * processing. It will be decided through snd_fmtbest(). */ /* 'Lean' mode, signed 16 or 32 bit native endian. */ static uint32_t feeder_chain_formats_lean[] = { AFMT_S16_NE, AFMT_S32_NE, 0 }; /* Force everything to signed 16 bit native endian. */ static uint32_t feeder_chain_formats_16[] = { AFMT_S16_NE, 0 }; /* Force everything to signed 32 bit native endian. */ static uint32_t feeder_chain_formats_32[] = { AFMT_S32_NE, 0 }; /* Multiple choices, all except 8 bit. */ static uint32_t feeder_chain_formats_multi[] = { AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE, AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE, AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE, 0 }; /* Everything that is convertible. */ static uint32_t feeder_chain_formats_fullmulti[] = { AFMT_S8, AFMT_U8, AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE, AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE, AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE, 0 }; static uint32_t *feeder_chain_formats[FEEDER_CHAIN_LAST] = { [FEEDER_CHAIN_LEAN] = feeder_chain_formats_lean, [FEEDER_CHAIN_16] = feeder_chain_formats_16, [FEEDER_CHAIN_32] = feeder_chain_formats_32, [FEEDER_CHAIN_MULTI] = feeder_chain_formats_multi, [FEEDER_CHAIN_FULLMULTI] = feeder_chain_formats_fullmulti }; static int feeder_chain_mode = FEEDER_CHAIN_DEFAULT; #if defined(_KERNEL) && defined(SND_DEBUG) && defined(SND_FEEDER_FULL_MULTIFORMAT) TUNABLE_INT("hw.snd.feeder_chain_mode", &feeder_chain_mode); SYSCTL_INT(_hw_snd, OID_AUTO, feeder_chain_mode, CTLFLAG_RW, &feeder_chain_mode, 0, "feeder chain mode " "(0=lean, 1=16bit, 2=32bit, 3=multiformat, 4=fullmultiformat)"); #endif /* * feeder_build_format(): Chain any format converter. */ static int feeder_build_format(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feederdesc *desc; int ret; desc = &(cdesc->desc); desc->type = FEEDER_FORMAT; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_format\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = cdesc->target.afmt; ret = chn_addfeeder(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_format\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_FORMAT; cdesc->current.afmt = cdesc->target.afmt; return (0); } /* * feeder_build_formatne(): Chain format converter that suite best for native * endian format. */ static int feeder_build_formatne(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_chain_state otarget; int ret; if (cdesc->afmt_ne == 0 || AFMT_ENCODING(cdesc->current.afmt) == cdesc->afmt_ne) return (0); otarget = cdesc->target; cdesc->target = cdesc->current; cdesc->target.afmt = SND_FORMAT(cdesc->afmt_ne, cdesc->current.matrix->channels, cdesc->current.matrix->ext); ret = feeder_build_format(c, cdesc); if (ret != 0) return (ret); cdesc->target = otarget; return (0); } /* * feeder_build_rate(): Chain sample rate converter. */ static int feeder_build_rate(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_RATE; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_rate\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = chn_addfeeder(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_rate\n", __func__); return (ret); } f = c->feeder; /* * If in 'dummy' mode (possibly due to passthrough mode), set the * conversion quality to the lowest possible (should be fastest) since * listener won't be hearing anything. Theoretically we can just * disable it, but that will cause weird runtime behaviour: * application appear to play something that is either too fast or too * slow. */ if (cdesc->dummy != 0) { ret = FEEDER_SET(f, FEEDRATE_QUALITY, 0); if (ret != 0) { device_printf(c->dev, "%s(): can't set resampling quality\n", __func__); return (ret); } } ret = FEEDER_SET(f, FEEDRATE_SRC, cdesc->current.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set source rate\n", __func__); return (ret); } ret = FEEDER_SET(f, FEEDRATE_DST, cdesc->target.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set destination rate\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_RATE; cdesc->current.rate = cdesc->target.rate; return (0); } /* * feeder_build_matrix(): Chain channel matrixing converter. */ static int feeder_build_matrix(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_MATRIX; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_matrix\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = SND_FORMAT(cdesc->current.afmt, cdesc->target.matrix->channels, cdesc->target.matrix->ext); ret = chn_addfeeder(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_matrix\n", __func__); return (ret); } f = c->feeder; ret = feeder_matrix_setup(f, cdesc->current.matrix, cdesc->target.matrix); if (ret != 0) { device_printf(c->dev, "%s(): feeder_matrix_setup() failed\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_MATRIX; cdesc->current.afmt = desc->out; cdesc->current.matrix = cdesc->target.matrix; cdesc->use_matrix = 0; return (0); } /* * feeder_build_volume(): Chain soft volume. */ static int feeder_build_volume(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_VOLUME; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_volume\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = chn_addfeeder(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_volume\n", __func__); return (ret); } f = c->feeder; /* * If in 'dummy' mode (possibly due to passthrough mode), set BYPASS * mode since listener won't be hearing anything. Theoretically we can * just disable it, but that will confuse volume per channel mixer. */ if (cdesc->dummy != 0) { ret = FEEDER_SET(f, FEEDVOLUME_STATE, FEEDVOLUME_BYPASS); if (ret != 0) { device_printf(c->dev, "%s(): can't set volume bypass\n", __func__); return (ret); } } ret = feeder_volume_apply_matrix(f, cdesc->current.matrix); if (ret != 0) { device_printf(c->dev, "%s(): feeder_volume_apply_matrix() failed\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_VOLUME; cdesc->use_volume = 0; return (0); } /* * feeder_build_eq(): Chain parametric software equalizer. */ static int feeder_build_eq(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_EQ; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_eq\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = chn_addfeeder(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_eq\n", __func__); return (ret); } f = c->feeder; ret = FEEDER_SET(f, FEEDEQ_RATE, cdesc->current.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set rate on feeder_eq\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_EQ; cdesc->use_eq = 0; return (0); } /* * feeder_build_root(): Chain root feeder, the top, father of all. */ static int feeder_build_root(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; int ret; fc = feeder_getclass(NULL); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_root\n", __func__); return (ENOTSUP); } ret = chn_addfeeder(c, fc, NULL); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_root\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_ROOT; c->feeder->desc->in = cdesc->current.afmt; c->feeder->desc->out = cdesc->current.afmt; return (0); } /* * feeder_build_mixer(): Chain software mixer for virtual channels. */ static int feeder_build_mixer(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feederdesc *desc; int ret; desc = &(cdesc->desc); desc->type = FEEDER_MIXER; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_mixer\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = chn_addfeeder(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_mixer\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_MIXER; return (0); } /* Macrosses to ease our job doing stuffs later. */ #define FEEDER_BW(c, t) ((c)->t.matrix->channels * (c)->t.rate) #define FEEDRATE_UP(c) ((c)->target.rate > (c)->current.rate) #define FEEDRATE_DOWN(c) ((c)->target.rate < (c)->current.rate) #define FEEDRATE_REQUIRED(c) (FEEDRATE_UP(c) || FEEDRATE_DOWN(c)) #define FEEDMATRIX_UP(c) ((c)->target.matrix->channels > \ (c)->current.matrix->channels) #define FEEDMATRIX_DOWN(c) ((c)->target.matrix->channels < \ (c)->current.matrix->channels) #define FEEDMATRIX_REQUIRED(c) (FEEDMATRIX_UP(c) || \ FEEDMATRIX_DOWN(c) || (c)->use_matrix != 0) #define FEEDFORMAT_REQUIRED(c) (AFMT_ENCODING((c)->current.afmt) != \ AFMT_ENCODING((c)->target.afmt)) #define FEEDVOLUME_REQUIRED(c) ((c)->use_volume != 0) #define FEEDEQ_VALIDRATE(c, t) (feeder_eq_validrate((c)->t.rate) != 0) #define FEEDEQ_ECONOMY(c) (FEEDER_BW(c, current) < FEEDER_BW(c, target)) #define FEEDEQ_REQUIRED(c) ((c)->use_eq != 0 && \ FEEDEQ_VALIDRATE(c, current)) #define FEEDFORMAT_NE_REQUIRED(c) \ ((c)->afmt_ne != AFMT_S32_NE && \ (((c)->mode == FEEDER_CHAIN_16 && \ AFMT_ENCODING((c)->current.afmt) != AFMT_S16_NE) || \ ((c)->mode == FEEDER_CHAIN_32 && \ AFMT_ENCODING((c)->current.afmt) != AFMT_S32_NE) || \ (c)->mode == FEEDER_CHAIN_FULLMULTI || \ ((c)->mode == FEEDER_CHAIN_MULTI && \ ((c)->current.afmt & AFMT_8BIT)) || \ ((c)->mode == FEEDER_CHAIN_LEAN && \ !((c)->current.afmt & (AFMT_S16_NE | AFMT_S32_NE))))) +static void +feeder_default_matrix(struct pcmchan_matrix *m, uint32_t fmt, int id) +{ + int x; + + memset(m, 0, sizeof(*m)); + + m->id = id; + m->channels = AFMT_CHANNEL(fmt); + m->ext = AFMT_EXTCHANNEL(fmt); + for (x = 0; x != SND_CHN_T_MAX; x++) + m->offset[x] = -1; +} + int feeder_chain(struct pcm_channel *c) { struct snddev_info *d; struct pcmchan_caps *caps; struct feeder_chain_desc cdesc; struct pcmchan_matrix *hwmatrix, *softmatrix; uint32_t hwfmt, softfmt; int ret; CHN_LOCKASSERT(c); /* Remove everything first. */ while (chn_removefeeder(c) == 0) ; KASSERT(c->feeder == NULL, ("feeder chain not empty")); /* clear and populate chain descriptor. */ bzero(&cdesc, sizeof(cdesc)); switch (feeder_chain_mode) { case FEEDER_CHAIN_LEAN: case FEEDER_CHAIN_16: case FEEDER_CHAIN_32: #if defined(SND_FEEDER_MULTIFORMAT) || defined(SND_FEEDER_FULL_MULTIFORMAT) case FEEDER_CHAIN_MULTI: #endif #if defined(SND_FEEDER_FULL_MULTIFORMAT) case FEEDER_CHAIN_FULLMULTI: #endif break; default: feeder_chain_mode = FEEDER_CHAIN_DEFAULT; break; } cdesc.mode = feeder_chain_mode; cdesc.expensive = 1; /* XXX faster.. */ #define VCHAN_PASSTHROUGH(c) (((c)->flags & (CHN_F_VIRTUAL | \ CHN_F_PASSTHROUGH)) == \ (CHN_F_VIRTUAL | CHN_F_PASSTHROUGH)) /* Get the best possible hardware format. */ if (VCHAN_PASSTHROUGH(c)) hwfmt = c->parentchannel->format; else { caps = chn_getcaps(c); if (caps == NULL || caps->fmtlist == NULL) { device_printf(c->dev, "%s(): failed to get channel caps\n", __func__); return (ENODEV); } if ((c->format & AFMT_PASSTHROUGH) && !snd_fmtvalid(c->format, caps->fmtlist)) return (ENODEV); hwfmt = snd_fmtbest(c->format, caps->fmtlist); if (hwfmt == 0 || !snd_fmtvalid(hwfmt, caps->fmtlist)) { device_printf(c->dev, "%s(): invalid hardware format 0x%08x\n", __func__, hwfmt); { int i; for (i = 0; caps->fmtlist[i] != 0; i++) printf("0x%08x\n", caps->fmtlist[i]); printf("Req: 0x%08x\n", c->format); } return (ENODEV); } } /* * The 'hardware' possibly have different intepretation of channel * matrixing, so get it first ..... */ hwmatrix = CHANNEL_GETMATRIX(c->methods, c->devinfo, hwfmt); if (hwmatrix == NULL) { - device_printf(c->dev, - "%s(): failed to acquire hw matrix [0x%08x]\n", - __func__, hwfmt); - return (ENODEV); + /* setup a default matrix */ + hwmatrix = &c->matrix_scratch; + feeder_default_matrix(hwmatrix, hwfmt, + SND_CHN_MATRIX_UNKNOWN); } /* ..... and rebuild hwfmt. */ hwfmt = SND_FORMAT(hwfmt, hwmatrix->channels, hwmatrix->ext); /* Reset and rebuild default channel format/matrix map. */ softfmt = c->format; softmatrix = &c->matrix; if (softmatrix->channels != AFMT_CHANNEL(softfmt) || softmatrix->ext != AFMT_EXTCHANNEL(softfmt)) { softmatrix = feeder_matrix_format_map(softfmt); if (softmatrix == NULL) { - device_printf(c->dev, - "%s(): failed to acquire soft matrix [0x%08x]\n", - __func__, softfmt); - return (ENODEV); + /* setup a default matrix */ + softmatrix = &c->matrix; + feeder_default_matrix(softmatrix, softfmt, + SND_CHN_MATRIX_PCMCHANNEL); + } else { + c->matrix = *softmatrix; + c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; } - c->matrix = *softmatrix; - c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; } softfmt = SND_FORMAT(softfmt, softmatrix->channels, softmatrix->ext); if (softfmt != c->format) device_printf(c->dev, "%s(): WARNING: %s Soft format 0x%08x -> 0x%08x\n", __func__, CHN_DIRSTR(c), c->format, softfmt); /* * PLAY and REC are opposite. */ if (c->direction == PCMDIR_PLAY) { cdesc.origin.afmt = softfmt; cdesc.origin.matrix = softmatrix; cdesc.origin.rate = c->speed; cdesc.target.afmt = hwfmt; cdesc.target.matrix = hwmatrix; cdesc.target.rate = sndbuf_getspd(c->bufhard); } else { cdesc.origin.afmt = hwfmt; cdesc.origin.matrix = hwmatrix; cdesc.origin.rate = sndbuf_getspd(c->bufhard); cdesc.target.afmt = softfmt; cdesc.target.matrix = softmatrix; cdesc.target.rate = c->speed; } d = c->parentsnddev; /* * If channel is in bitperfect or passthrough mode, make it appear * that 'origin' and 'target' identical, skipping mostly chain * procedures. */ if (CHN_BITPERFECT(c) || (c->format & AFMT_PASSTHROUGH)) { if (c->direction == PCMDIR_PLAY) cdesc.origin = cdesc.target; else cdesc.target = cdesc.origin; c->format = cdesc.target.afmt; c->speed = cdesc.target.rate; } else { /* hwfmt is not convertible, so 'dummy' it. */ if (hwfmt & AFMT_PASSTHROUGH) cdesc.dummy = 1; if ((softfmt & AFMT_CONVERTIBLE) && (((d->flags & SD_F_VPC) && !(c->flags & CHN_F_HAS_VCHAN)) || (!(d->flags & SD_F_VPC) && (d->flags & SD_F_SOFTPCMVOL) && !(c->flags & CHN_F_VIRTUAL)))) cdesc.use_volume = 1; if (feeder_matrix_compare(cdesc.origin.matrix, cdesc.target.matrix) != 0) cdesc.use_matrix = 1; /* Soft EQ only applicable for PLAY. */ if (cdesc.dummy == 0 && c->direction == PCMDIR_PLAY && (d->flags & SD_F_EQ) && (((d->flags & SD_F_EQ_PC) && !(c->flags & CHN_F_HAS_VCHAN)) || (!(d->flags & SD_F_EQ_PC) && !(c->flags & CHN_F_VIRTUAL)))) cdesc.use_eq = 1; if (FEEDFORMAT_NE_REQUIRED(&cdesc)) { cdesc.afmt_ne = (cdesc.dummy != 0) ? snd_fmtbest(AFMT_ENCODING(softfmt), feeder_chain_formats[cdesc.mode]) : snd_fmtbest(AFMT_ENCODING(cdesc.target.afmt), feeder_chain_formats[cdesc.mode]); if (cdesc.afmt_ne == 0) { device_printf(c->dev, "%s(): snd_fmtbest failed!\n", __func__); cdesc.afmt_ne = (((cdesc.dummy != 0) ? softfmt : cdesc.target.afmt) & (AFMT_24BIT | AFMT_32BIT)) ? AFMT_S32_NE : AFMT_S16_NE; } } } cdesc.current = cdesc.origin; /* Build everything. */ c->feederflags = 0; #define FEEDER_BUILD(t) do { \ ret = feeder_build_##t(c, &cdesc); \ if (ret != 0) \ return (ret); \ } while (0) if (!(c->flags & CHN_F_HAS_VCHAN) || c->direction == PCMDIR_REC) FEEDER_BUILD(root); else if (c->direction == PCMDIR_PLAY && (c->flags & CHN_F_HAS_VCHAN)) FEEDER_BUILD(mixer); else return (ENOTSUP); /* * The basic idea is: The smaller the bandwidth, the cheaper the * conversion process, with following constraints:- * * 1) Almost all feeders work best in 16/32 native endian. * 2) Try to avoid 8bit feeders due to poor dynamic range. * 3) Avoid volume, format, matrix and rate in BITPERFECT or * PASSTHROUGH mode. * 4) Try putting volume before EQ or rate. Should help to * avoid/reduce possible clipping. * 5) EQ require specific, valid rate, unless it allow sloppy * conversion. */ if (FEEDMATRIX_UP(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && (!FEEDEQ_VALIDRATE(&cdesc, target) || (cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc)))) FEEDER_BUILD(eq); if (FEEDRATE_REQUIRED(&cdesc)) FEEDER_BUILD(rate); FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } else if (FEEDMATRIX_DOWN(&cdesc)) { FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDEQ_REQUIRED(&cdesc) && (!FEEDEQ_VALIDRATE(&cdesc, target) || FEEDEQ_ECONOMY(&cdesc))) FEEDER_BUILD(eq); if (FEEDRATE_REQUIRED(&cdesc)) FEEDER_BUILD(rate); if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } else { if (FEEDRATE_DOWN(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && !FEEDEQ_VALIDRATE(&cdesc, target)) { if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); FEEDER_BUILD(eq); } FEEDER_BUILD(rate); } if (FEEDMATRIX_REQUIRED(&cdesc)) FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDRATE_UP(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && !FEEDEQ_VALIDRATE(&cdesc, target)) FEEDER_BUILD(eq); FEEDER_BUILD(rate); } if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } if (FEEDFORMAT_REQUIRED(&cdesc)) FEEDER_BUILD(format); if (c->direction == PCMDIR_REC && (c->flags & CHN_F_HAS_VCHAN)) FEEDER_BUILD(mixer); sndbuf_setfmt(c->bufsoft, c->format); sndbuf_setspd(c->bufsoft, c->speed); sndbuf_setfmt(c->bufhard, hwfmt); chn_syncstate(c); return (0); } Index: stable/10/sys/dev/sound/pcm/sound.h =================================================================== --- stable/10/sys/dev/sound/pcm/sound.h (revision 283949) +++ stable/10/sys/dev/sound/pcm/sound.h (revision 283950) @@ -1,629 +1,631 @@ /*- * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * Copyright (c) 1995 Hannu Savolainen * 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$ */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #if __FreeBSD_version < 500000 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #include #include #include #include #include #define PCM_SOFTC_SIZE (sizeof(struct snddev_info)) #define SND_STATUSLEN 64 #define SOUND_MODVER 5 #define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER SOUND_MODVER /* * We're abusing the fact that MAXMINOR still have enough room * for our bit twiddling and nobody ever need 512 unique soundcards, * 32 unique device types and 1024 unique cloneable devices for the * next 100 years... */ #define PCMMAXUNIT (snd_max_u()) #define PCMMAXDEV (snd_max_d()) #define PCMMAXCHAN (snd_max_c()) #define PCMMAXCLONE PCMMAXCHAN #define PCMUNIT(x) (snd_unit2u(dev2unit(x))) #define PCMDEV(x) (snd_unit2d(dev2unit(x))) #define PCMCHAN(x) (snd_unit2c(dev2unit(x))) /* XXX unit2minor compat */ #if __FreeBSD_version >= 800062 #define PCMMINOR(x) (x) #else #define PCMMINOR(x) unit2minor(x) #endif /* * By design, limit possible channels for each direction. */ #define SND_MAXHWCHAN 256 #define SND_MAXVCHANS SND_MAXHWCHAN #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 /* * Obsolete due to better matrixing */ #if 0 #define SD_F_PSWAPLR 0x00000008 #define SD_F_RSWAPLR 0x00000010 #endif #define SD_F_DYING 0x00000008 #define SD_F_SUICIDE 0x00000010 #define SD_F_BUSY 0x00000020 #define SD_F_MPSAFE 0x00000040 #define SD_F_REGISTERED 0x00000080 #define SD_F_BITPERFECT 0x00000100 #define SD_F_VPC 0x00000200 /* volume-per-channel */ #define SD_F_EQ 0x00000400 /* EQ */ #define SD_F_EQ_ENABLED 0x00000800 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00001000 /* EQ bypassed */ #define SD_F_EQ_PC 0x00002000 /* EQ per-channel */ #define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED) #define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \ SD_F_EQ_BYPASSED | SD_F_EQ_PC) #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 #define SD_F_BITS "\020" \ "\001SIMPLEX" \ "\002AUTOVCHAN" \ "\003SOFTPCMVOL" \ "\004DYING" \ "\005SUICIDE" \ "\006BUSY" \ "\007MPSAFE" \ "\010REGISTERED" \ "\011BITPERFECT" \ "\012VPC" \ "\013EQ" \ "\014EQ_ENABLED" \ "\015EQ_BYPASSED" \ "\016EQ_PC" \ "\035PRIO_RD" \ "\036PRIO_WR" \ "\037DIR_SET" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL && \ !((x)->flags & SD_F_DYING)) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && \ ((x)->flags & SD_F_REGISTERED)) /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) #define DSP_BUFFSIZE (8192) /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_G711 (AFMT_MU_LAW | AFMT_A_LAW) #define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8) #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ AFMT_S16_BE | AFMT_U16_BE) #define AFMT_CONVERTIBLE (AFMT_8BIT | AFMT_16BIT | AFMT_24BIT | \ AFMT_32BIT) /* Supported vchan mixing formats */ #define AFMT_VCHAN (AFMT_CONVERTIBLE & ~AFMT_G711) #define AFMT_PASSTHROUGH AFMT_AC3 #define AFMT_PASSTHROUGH_RATE 48000 #define AFMT_PASSTHROUGH_CHANNEL 2 #define AFMT_PASSTHROUGH_EXTCHANNEL 0 /* * We're simply using unused, contiguous bits from various AFMT_ definitions. * ~(0xb00ff7ff) */ #define AFMT_ENCODING_MASK 0xf00fffff -#define AFMT_CHANNEL_MASK 0x01f00000 +#define AFMT_CHANNEL_MASK 0x07f00000 #define AFMT_CHANNEL_SHIFT 20 -#define AFMT_EXTCHANNEL_MASK 0x0e000000 -#define AFMT_EXTCHANNEL_SHIFT 25 +#define AFMT_CHANNEL_MAX 0x7f +#define AFMT_EXTCHANNEL_MASK 0x08000000 +#define AFMT_EXTCHANNEL_SHIFT 27 +#define AFMT_EXTCHANNEL_MAX 1 #define AFMT_ENCODING(v) ((v) & AFMT_ENCODING_MASK) #define AFMT_EXTCHANNEL(v) (((v) & AFMT_EXTCHANNEL_MASK) >> \ AFMT_EXTCHANNEL_SHIFT) #define AFMT_CHANNEL(v) (((v) & AFMT_CHANNEL_MASK) >> \ AFMT_CHANNEL_SHIFT) #define AFMT_BIT(v) (((v) & AFMT_32BIT) ? 32 : \ (((v) & AFMT_24BIT) ? 24 : \ ((((v) & AFMT_16BIT) || \ ((v) & AFMT_PASSTHROUGH)) ? 16 : 8))) #define AFMT_BPS(v) (AFMT_BIT(v) >> 3) #define AFMT_ALIGN(v) (AFMT_BPS(v) * AFMT_CHANNEL(v)) #define SND_FORMAT(f, c, e) (AFMT_ENCODING(f) | \ (((c) << AFMT_CHANNEL_SHIFT) & \ AFMT_CHANNEL_MASK) | \ (((e) << AFMT_EXTCHANNEL_SHIFT) & \ AFMT_EXTCHANNEL_MASK)) #define AFMT_U8_NE AFMT_U8 #define AFMT_S8_NE AFMT_S8 #define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) /* * Minor numbers for the sound driver. * * Unfortunately Creative called the codec chip of SB as a DSP. For this * reason the /dev/dsp is reserved for digitized audio use. There is a * device for true DSP processors but it will be called something else. * In v3.0 it's /dev/sndproc but this could be a temporary solution. */ #define SND_DEV_CTL 0 /* Control port /dev/mixer */ #define SND_DEV_SEQ 1 /* Sequencer /dev/sequencer */ #define SND_DEV_MIDIN 2 /* Raw midi access */ #define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ #define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ #define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ #define SND_DEV_STATUS 6 /* /dev/sndstat */ /* #7 not in use now. */ #define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 #define SND_DEV_DSPHW_PLAY 11 /* specific playback channel */ #define SND_DEV_DSPHW_VPLAY 12 /* specific virtual playback channel */ #define SND_DEV_DSPHW_REC 13 /* specific record channel */ #define SND_DEV_DSPHW_VREC 14 /* specific virtual record channel */ #define SND_DEV_DSPHW_CD 15 /* s16le/stereo 44100Hz CD */ /* * OSSv4 compatible device. For now, it serve no purpose and * the cloning itself will forward the request to ordinary /dev/dsp * instead. */ #define SND_DEV_DSP_MMAP 16 /* /dev/dsp_mmap */ #define SND_DEV_DSP_AC3 17 /* /dev/dsp_ac3 */ #define SND_DEV_DSP_MULTICH 18 /* /dev/dsp_multich */ #define SND_DEV_DSP_SPDIFOUT 19 /* /dev/dsp_spdifout */ #define SND_DEV_DSP_SPDIFIN 20 /* /dev/dsp_spdifin */ #define SND_DEV_LAST SND_DEV_DSP_SPDIFIN #define SND_DEV_MAX PCMMAXDEV #define DSP_DEFAULT_SPEED 8000 #define ON 1 #define OFF 0 extern int pcm_veto_load; extern int snd_unit; extern int snd_maxautovchans; extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; /* * some macros for debugging purposes * DDB/DEB to enable/disable debugging stuff * BVDDB to enable debugging when bootverbose */ #define BVDDB(x) if (bootverbose) x #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); int pcm_setvchans(struct snddev_info *d, int direction, int newcnt, int num); int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, char *comm, int devunit); int pcm_chnrelease(struct pcm_channel *c); int pcm_chnref(struct pcm_channel *c, int ref); int pcm_inprog(struct snddev_info *d, int delta); struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo); int pcm_chn_destroy(struct pcm_channel *ch); int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch); int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch); int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); int pcm_unregister(device_t dev); int pcm_setstatus(device_t dev, char *str); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep); void *snd_mtxcreate(const char *desc, const char *type); void snd_mtxfree(void *m); void snd_mtxassert(void *m); #define snd_mtxlock(m) mtx_lock(m) #define snd_mtxunlock(m) mtx_unlock(m) typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); int sndstat_acquire(struct thread *td); int sndstat_release(struct thread *td); int sndstat_register(device_t dev, char *str, sndstat_handler handler); int sndstat_registerfile(char *str); int sndstat_unregister(device_t dev); int sndstat_unregisterfile(char *str); #define SND_DECLARE_FILE(version) \ _SND_DECLARE_FILE(__LINE__, version) #define _SND_DECLARE_FILE(uniq, version) \ __SND_DECLARE_FILE(uniq, version) #define __SND_DECLARE_FILE(uniq, version) \ static char sndstat_vinfo[] = version; \ SYSINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_registerfile, sndstat_vinfo); \ SYSUNINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_unregisterfile, sndstat_vinfo); /* usage of flags in device config entry (config file) */ #define DV_F_DRQ_MASK 0x00000007 /* mask for secondary drq */ #define DV_F_DUAL_DMA 0x00000010 /* set to use secondary dma channel */ /* ought to be made obsolete but still used by mss */ #define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ #define DV_F_DEV_SHIFT 8 /* force device type/class */ /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * we also have to do this now makedev() has gone away. */ struct snddev_info { struct { struct { SLIST_HEAD(, pcm_channel) head; struct { SLIST_HEAD(, pcm_channel) head; } busy; struct { SLIST_HEAD(, pcm_channel) head; } opened; } pcm; } channels; TAILQ_HEAD(dsp_cdevinfo_linkhead, dsp_cdevinfo) dsp_cdevinfo_pool; struct snd_clone *clones; unsigned devcount, playcount, reccount, pvchancount, rvchancount ; unsigned flags; int inprog; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; uint32_t pvchanrate, pvchanformat; uint32_t rvchanrate, rvchanformat; int32_t eqpreamp; struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; struct cv cv; }; void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) #define PCM_TRYLOCK(d) mtx_trylock((d)->lock) #define PCM_LOCKASSERT(d) mtx_assert((d)->lock, MA_OWNED) #define PCM_UNLOCKASSERT(d) mtx_assert((d)->lock, MA_NOTOWNED) /* * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these * with PCM_LOCK/UNLOCK() sequence, or I'll come to gnaw upon you! */ #ifdef SND_DIAGNOSTIC #define PCM_WAIT(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM WAIT] Mutex not owned!", \ __func__, __LINE__); \ while ((x)->flags & SD_F_BUSY) { \ if (snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM WAIT] calling cv_wait().\n", \ __func__, __LINE__); \ cv_wait(&(x)->cv, (x)->lock); \ } \ } while (0) #define PCM_ACQUIRE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) \ panic("%s(%d): [PCM ACQUIRE] " \ "Trying to acquire BUSY cv!", __func__, __LINE__); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) { \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) { \ if ((x)->cv.cv_waiters > 1 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM RELEASE] " \ "cv_waiters=%d > 1!\n", \ __func__, __LINE__, \ (x)->cv.cv_waiters); \ cv_broadcast(&(x)->cv); \ } \ } else \ panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) do { \ if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ panic("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", __func__, __LINE__, x); \ } while (0) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT ENTER] Giant owned!\n", \ __func__, __LINE__); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ __func__, __LINE__); \ if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_MPSAFE) { \ if (_pcm_giant == 1) \ panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT EXIT] Giant owned!\n", \ __func__, __LINE__); \ } \ if (_pcm_giant != 0) { \ if (mtx_owned(&Giant) == 0) \ panic("%s(%d): [GIANT EXIT] Giant not owned!", \ __func__, __LINE__); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #else /* SND_DIAGNOSTIC */ #define PCM_WAIT(x) do { \ PCM_LOCKASSERT(x); \ while ((x)->flags & SD_F_BUSY) \ cv_wait(&(x)->cv, (x)->lock); \ } while (0) #define PCM_ACQUIRE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT(!((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ __func__, __LINE__)); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT((x)->flags & SD_F_BUSY, \ ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__)); \ (x)->flags &= ~SD_F_BUSY; \ if ((x)->cv.cv_waiters != 0) \ cv_broadcast(&(x)->cv); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ ((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", \ __func__, __LINE__, x)) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ PCM_UNLOCKASSERT(x); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ PCM_UNLOCKASSERT(x); \ KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__)); \ KASSERT(!((x)->flags & SD_F_MPSAFE) || \ (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__)); \ if (_pcm_giant != 0) { \ mtx_assert(&Giant, MA_OWNED); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #endif /* !SND_DIAGNOSTIC */ #define PCM_GIANT_LEAVE(x) \ PCM_GIANT_EXIT(x); \ } while (0) #ifdef KLD_MODULE #define PCM_KLDSTRING(a) ("kld " # a) #else #define PCM_KLDSTRING(a) "" #endif #endif /* _KERNEL */ #endif /* _OS_H_ */ Index: stable/10/sys/dev/sound/usb/uaudio.c =================================================================== --- stable/10/sys/dev/sound/usb/uaudio.c (revision 283949) +++ stable/10/sys/dev/sound/usb/uaudio.c (revision 283950) @@ -1,6113 +1,6126 @@ /* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 __FBSDID("$FreeBSD$"); /* * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf * http://www.usb.org/developers/devclass_docs/frmts10.pdf * http://www.usb.org/developers/devclass_docs/termt10.pdf */ /* * Also merged: * $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $ * $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $ * $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $ * $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #include #include #define USB_DEBUG_VAR uaudio_debug #include #include #include /* for bootverbose */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include "feeder_if.h" static int uaudio_default_rate = 0; /* use rate list */ static int uaudio_default_bits = 32; static int uaudio_default_channels = 0; /* use default */ #ifdef USB_DEBUG static int uaudio_debug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio"); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RW, &uaudio_debug, 0, "uaudio debug level"); TUNABLE_INT("hw.usb.uaudio.default_rate", &uaudio_default_rate); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RW, &uaudio_default_rate, 0, "uaudio default sample rate"); TUNABLE_INT("hw.usb.uaudio.default_bits", &uaudio_default_bits); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RW, &uaudio_default_bits, 0, "uaudio default sample bits"); TUNABLE_INT("hw.usb.uaudio.default_channels", &uaudio_default_channels); SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RW, &uaudio_default_channels, 0, "uaudio default sample channels"); #endif #define UAUDIO_IRQS (8000 / UAUDIO_NFRAMES) /* interrupts per second */ #define UAUDIO_NFRAMES 64 /* must be factor of 8 due HS-USB */ #define UAUDIO_NCHANBUFS 2 /* number of outstanding request */ #define UAUDIO_RECURSE_LIMIT 255 /* rounds */ +#define UAUDIO_CHANNELS_MAX MIN(64, AFMT_CHANNEL_MAX) +#define UAUDIO_MATRIX_MAX 8 /* channels */ #define MAKE_WORD(h,l) (((h) << 8) | (l)) #define BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1) #define UAUDIO_MAX_CHAN(x) (x) #define MIX(sc) ((sc)->sc_mixer_node) union uaudio_asid { const struct usb_audio_streaming_interface_descriptor *v1; const struct usb_audio20_streaming_interface_descriptor *v2; }; union uaudio_asf1d { const struct usb_audio_streaming_type1_descriptor *v1; const struct usb_audio20_streaming_type1_descriptor *v2; }; union uaudio_sed { const struct usb_audio_streaming_endpoint_descriptor *v1; const struct usb_audio20_streaming_endpoint_descriptor *v2; }; struct uaudio_mixer_node { const char *name; int32_t minval; int32_t maxval; #define MIX_MAX_CHAN 16 int32_t wValue[MIX_MAX_CHAN]; /* using nchan */ uint32_t mul; uint32_t ctl; int wData[MIX_MAX_CHAN]; /* using nchan */ uint16_t wIndex; uint8_t update[(MIX_MAX_CHAN + 7) / 8]; uint8_t nchan; uint8_t type; #define MIX_ON_OFF 1 #define MIX_SIGNED_16 2 #define MIX_UNSIGNED_16 3 #define MIX_SIGNED_8 4 #define MIX_SELECTOR 5 #define MIX_UNKNOWN 6 #define MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \ ((n) == MIX_UNSIGNED_16)) ? 2 : 1) #define MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16) #define MAX_SELECTOR_INPUT_PIN 256 uint8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; uint8_t class; uint8_t val_default; uint8_t desc[64]; struct uaudio_mixer_node *next; }; struct uaudio_configure_msg { struct usb_proc_msg hdr; struct uaudio_softc *sc; }; #define CHAN_MAX_ALT 24 struct uaudio_chan_alt { union uaudio_asf1d p_asf1d; union uaudio_sed p_sed; const usb_endpoint_descriptor_audio_t *p_ed1; const struct uaudio_format *p_fmt; const struct usb_config *usb_cfg; uint32_t sample_rate; /* in Hz */ uint16_t sample_size; uint8_t iface_index; uint8_t iface_alt_index; uint8_t channels; }; struct uaudio_chan { struct pcmchan_caps pcm_cap; /* capabilities */ struct uaudio_chan_alt usb_alt[CHAN_MAX_ALT]; struct snd_dbuf *pcm_buf; struct mtx *pcm_mtx; /* lock protecting this structure */ struct uaudio_softc *priv_sc; struct pcm_channel *pcm_ch; struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1]; uint8_t *buf; /* pointer to buffer */ uint8_t *start; /* upper layer buffer start */ uint8_t *end; /* upper layer buffer end */ uint8_t *cur; /* current position in upper layer * buffer */ uint32_t intr_frames; /* in units */ uint32_t frames_per_second; uint32_t sample_rem; uint32_t sample_curr; uint32_t max_buf; int32_t jitter_rem; int32_t jitter_curr; int feedback_rate; uint32_t pcm_format[2]; uint16_t bytes_per_frame[2]; uint32_t intr_counter; uint32_t running; uint32_t num_alt; uint32_t cur_alt; uint32_t set_alt; uint32_t operation; #define CHAN_OP_NONE 0 #define CHAN_OP_START 1 #define CHAN_OP_STOP 2 #define CHAN_OP_DRAIN 3 }; #define UMIDI_EMB_JACK_MAX 16 /* units */ #define UMIDI_TX_FRAMES 256 /* units */ #define UMIDI_TX_BUFFER (UMIDI_TX_FRAMES * 4) /* bytes */ enum { UMIDI_TX_TRANSFER, UMIDI_RX_TRANSFER, UMIDI_N_TRANSFER, }; struct umidi_sub_chan { struct usb_fifo_sc fifo; uint8_t *temp_cmd; uint8_t temp_0[4]; uint8_t temp_1[4]; uint8_t state; #define UMIDI_ST_UNKNOWN 0 /* scan for command */ #define UMIDI_ST_1PARAM 1 #define UMIDI_ST_2PARAM_1 2 #define UMIDI_ST_2PARAM_2 3 #define UMIDI_ST_SYSEX_0 4 #define UMIDI_ST_SYSEX_1 5 #define UMIDI_ST_SYSEX_2 6 uint8_t read_open:1; uint8_t write_open:1; uint8_t unused:6; }; struct umidi_chan { struct umidi_sub_chan sub[UMIDI_EMB_JACK_MAX]; struct mtx mtx; struct usb_xfer *xfer[UMIDI_N_TRANSFER]; uint8_t iface_index; uint8_t iface_alt_index; uint8_t read_open_refcount; uint8_t write_open_refcount; uint8_t curr_cable; uint8_t max_emb_jack; uint8_t valid; uint8_t single_command; }; struct uaudio_search_result { uint8_t bit_input[(256 + 7) / 8]; uint8_t bit_output[(256 + 7) / 8]; uint8_t recurse_level; uint8_t id_max; uint8_t is_input; }; enum { UAUDIO_HID_RX_TRANSFER, UAUDIO_HID_N_TRANSFER, }; struct uaudio_hid { struct usb_xfer *xfer[UAUDIO_HID_N_TRANSFER]; struct hid_location volume_up_loc; struct hid_location volume_down_loc; struct hid_location mute_loc; uint32_t flags; #define UAUDIO_HID_VALID 0x0001 #define UAUDIO_HID_HAS_ID 0x0002 #define UAUDIO_HID_HAS_VOLUME_UP 0x0004 #define UAUDIO_HID_HAS_VOLUME_DOWN 0x0008 #define UAUDIO_HID_HAS_MUTE 0x0010 uint8_t iface_index; uint8_t volume_up_id; uint8_t volume_down_id; uint8_t mute_id; }; struct uaudio_softc { struct sbuf sc_sndstat; struct sndcard_func sc_sndcard_func; struct uaudio_chan sc_rec_chan; struct uaudio_chan sc_play_chan; struct umidi_chan sc_midi_chan; struct uaudio_hid sc_hid; struct uaudio_search_result sc_mixer_clocks; struct uaudio_mixer_node sc_mixer_node; struct uaudio_configure_msg sc_config_msg[2]; struct mtx *sc_mixer_lock; struct snd_mixer *sc_mixer_dev; struct usb_device *sc_udev; struct usb_xfer *sc_mixer_xfer[1]; struct uaudio_mixer_node *sc_mixer_root; struct uaudio_mixer_node *sc_mixer_curr; uint32_t sc_mix_info; uint32_t sc_recsrc_info; uint16_t sc_audio_rev; uint16_t sc_mixer_count; uint8_t sc_sndstat_valid; uint8_t sc_mixer_iface_index; uint8_t sc_mixer_iface_no; uint8_t sc_mixer_chan; uint8_t sc_pcm_registered:1; uint8_t sc_mixer_init:1; uint8_t sc_uq_audio_swap_lr:1; uint8_t sc_uq_au_inp_async:1; uint8_t sc_uq_au_no_xu:1; uint8_t sc_uq_bad_adc:1; uint8_t sc_uq_au_vendor_class:1; + uint8_t sc_pcm_bitperfect:1; }; struct uaudio_terminal_node { union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it_v1; const struct usb_audio_output_terminal *ot_v1; const struct usb_audio_mixer_unit_0 *mu_v1; const struct usb_audio_selector_unit *su_v1; const struct usb_audio_feature_unit *fu_v1; const struct usb_audio_processing_unit_0 *pu_v1; const struct usb_audio_extension_unit_0 *eu_v1; const struct usb_audio20_clock_source_unit *csrc_v2; const struct usb_audio20_clock_selector_unit_0 *csel_v2; const struct usb_audio20_clock_multiplier_unit *cmul_v2; const struct usb_audio20_input_terminal *it_v2; const struct usb_audio20_output_terminal *ot_v2; const struct usb_audio20_mixer_unit_0 *mu_v2; const struct usb_audio20_selector_unit *su_v2; const struct usb_audio20_feature_unit *fu_v2; const struct usb_audio20_sample_rate_unit *ru_v2; const struct usb_audio20_processing_unit_0 *pu_v2; const struct usb_audio20_extension_unit_0 *eu_v2; const struct usb_audio20_effect_unit *ef_v2; } u; struct uaudio_search_result usr; struct uaudio_terminal_node *root; }; struct uaudio_format { uint16_t wFormat; uint8_t bPrecision; uint32_t freebsd_fmt; const char *description; }; static const struct uaudio_format uaudio10_formats[] = { {UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; static const struct uaudio_format uaudio20_formats[] = { {UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"}, {UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"}, {UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"}, {UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"}, {UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"}, {UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"}, {UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"}, {UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"}, {UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"}, {UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"}, {0, 0, 0, NULL} }; #define UAC_OUTPUT 0 #define UAC_INPUT 1 #define UAC_EQUAL 2 #define UAC_RECORD 3 #define UAC_NCLASSES 4 #ifdef USB_DEBUG static const char *uac_names[] = { "outputs", "inputs", "equalization", "record" }; #endif /* prototypes */ static device_probe_t uaudio_probe; static device_attach_t uaudio_attach; static device_detach_t uaudio_detach; static usb_callback_t uaudio_chan_play_callback; static usb_callback_t uaudio_chan_play_sync_callback; static usb_callback_t uaudio_chan_record_callback; static usb_callback_t uaudio_chan_record_sync_callback; static usb_callback_t uaudio_mixer_write_cfg_callback; static usb_callback_t umidi_bulk_read_callback; static usb_callback_t umidi_bulk_write_callback; static usb_callback_t uaudio_hid_rx_callback; static usb_proc_callback_t uaudio_configure_msg; /* ==== USB mixer ==== */ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS); static void uaudio_mixer_ctl_free(struct uaudio_softc *); static void uaudio_mixer_register_sysctl(struct uaudio_softc *, device_t); static void uaudio_mixer_reload_all(struct uaudio_softc *); static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *); /* ==== USB audio v1.0 ==== */ static void uaudio_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static uint32_t uaudio_mixer_feature_get_bmaControls( const struct usb_audio_feature_unit *, uint8_t); static void uaudio_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing_updown(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_processing(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio_mixer_add_extension(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static uint16_t uaudio_mixer_feature_name(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t); static int uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t, struct uaudio_mixer_node *); /* ==== USB audio v2.0 ==== */ static void uaudio20_mixer_add_mixer(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_selector(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static void uaudio20_mixer_add_feature(struct uaudio_softc *, const struct uaudio_terminal_node *, int); static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t, const struct uaudio_terminal_node *); static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static uint16_t uaudio20_mixer_feature_name(const struct uaudio_terminal_node *, struct uaudio_mixer_node *); static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *, const uint8_t *, uint8_t, struct uaudio_search_result *); static const void *uaudio20_mixer_verify_desc(const void *, uint32_t); static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t, uint8_t, uint32_t); /* USB audio v1.0 and v2.0 */ static void uaudio_chan_fill_info_sub(struct uaudio_softc *, struct usb_device *, uint32_t, uint8_t, uint8_t); static void uaudio_chan_fill_info(struct uaudio_softc *, struct usb_device *); static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct uaudio_mixer_node *); static void uaudio_mixer_fill_info(struct uaudio_softc *, struct usb_device *, void *); static void uaudio_mixer_ctl_set(struct uaudio_softc *, struct uaudio_mixer_node *, uint8_t, int32_t val); static int uaudio_mixer_signext(uint8_t, int); static int uaudio_mixer_bsd2value(struct uaudio_mixer_node *, int32_t val); static void uaudio_mixer_init(struct uaudio_softc *); static const struct uaudio_terminal_node *uaudio_mixer_get_input( const struct uaudio_terminal_node *, uint8_t); static const struct uaudio_terminal_node *uaudio_mixer_get_output( const struct uaudio_terminal_node *, uint8_t); static void uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *, uint8_t, uint8_t, struct uaudio_search_result *); static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t); static struct umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *); static void umidi_start_read(struct usb_fifo *); static void umidi_stop_read(struct usb_fifo *); static void umidi_start_write(struct usb_fifo *); static void umidi_stop_write(struct usb_fifo *); static int umidi_open(struct usb_fifo *, int); static int umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int); static void umidi_close(struct usb_fifo *, int); static void umidi_init(device_t dev); static int umidi_probe(device_t dev); static int umidi_detach(device_t dev); static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa); static void uaudio_hid_detach(struct uaudio_softc *sc); #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc( const usb_endpoint_descriptor_audio_t *); #endif static const struct usb_config uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_record_sync_callback, }, }; static const struct usb_config uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = { [0] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [1] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = UAUDIO_NFRAMES, .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_callback, }, [2] = { .type = UE_ISOCHRONOUS, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use "wMaxPacketSize * frames" */ .frames = 1, .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,}, .callback = &uaudio_chan_play_sync_callback, }, }; static const struct usb_config uaudio_mixer_config[1] = { [0] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = (sizeof(struct usb_device_request) + 4), .callback = &uaudio_mixer_write_cfg_callback, .timeout = 1000, /* 1 second */ }, }; static const uint8_t umidi_cmd_to_len[16] = { [0x0] = 0, /* reserved */ [0x1] = 0, /* reserved */ [0x2] = 2, /* bytes */ [0x3] = 3, /* bytes */ [0x4] = 3, /* bytes */ [0x5] = 1, /* bytes */ [0x6] = 2, /* bytes */ [0x7] = 3, /* bytes */ [0x8] = 3, /* bytes */ [0x9] = 3, /* bytes */ [0xA] = 3, /* bytes */ [0xB] = 3, /* bytes */ [0xC] = 2, /* bytes */ [0xD] = 2, /* bytes */ [0xE] = 3, /* bytes */ [0xF] = 1, /* bytes */ }; static const struct usb_config umidi_config[UMIDI_N_TRANSFER] = { [UMIDI_TX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UMIDI_TX_BUFFER, .callback = &umidi_bulk_write_callback, }, [UMIDI_RX_TRANSFER] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 4, /* bytes */ .flags = {.short_xfer_ok = 1,.proxy_buffer = 1,}, .callback = &umidi_bulk_read_callback, }, }; static const struct usb_config uaudio_hid_config[UAUDIO_HID_N_TRANSFER] = { [UAUDIO_HID_RX_TRANSFER] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.short_xfer_ok = 1,}, .callback = &uaudio_hid_rx_callback, }, }; static devclass_t uaudio_devclass; static device_method_t uaudio_methods[] = { DEVMETHOD(device_probe, uaudio_probe), DEVMETHOD(device_attach, uaudio_attach), DEVMETHOD(device_detach, uaudio_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t uaudio_driver = { .name = "uaudio", .methods = uaudio_methods, .size = sizeof(struct uaudio_softc), }; /* The following table is derived from Linux's quirks-table.h */ static const STRUCT_USB_HOST_ID uaudio_vendor_midi[] = { { USB_VPI(USB_VENDOR_YAMAHA, 0x1000, 0) }, /* UX256 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1001, 0) }, /* MU1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1002, 0) }, /* MU2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1003, 0) }, /* MU500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1004, 3) }, /* UW500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1005, 0) }, /* MOTIF6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1006, 0) }, /* MOTIF7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1007, 0) }, /* MOTIF8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1008, 0) }, /* UX96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1009, 0) }, /* UX16 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100a, 3) }, /* EOS BX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100c, 0) }, /* UC-MX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100d, 0) }, /* UC-KX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100e, 0) }, /* S08 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x100f, 0) }, /* CLP-150 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1010, 0) }, /* CLP-170 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1011, 0) }, /* P-250 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1012, 0) }, /* TYROS */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1013, 0) }, /* PF-500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1014, 0) }, /* S90 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1015, 0) }, /* MOTIF-R */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1016, 0) }, /* MDP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1017, 0) }, /* CVP-204 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1018, 0) }, /* CVP-206 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1019, 0) }, /* CVP-208 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101a, 0) }, /* CVP-210 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101b, 0) }, /* PSR-1100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101c, 0) }, /* PSR-2100 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101d, 0) }, /* CLP-175 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101e, 0) }, /* PSR-K1 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x101f, 0) }, /* EZ-J24 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1020, 0) }, /* EZ-250i */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1021, 0) }, /* MOTIF ES 6 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1022, 0) }, /* MOTIF ES 7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1023, 0) }, /* MOTIF ES 8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1024, 0) }, /* CVP-301 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1025, 0) }, /* CVP-303 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1026, 0) }, /* CVP-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1027, 0) }, /* CVP-307 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1028, 0) }, /* CVP-309 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1029, 0) }, /* CVP-309GP */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102a, 0) }, /* PSR-1500 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102b, 0) }, /* PSR-3000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x102e, 0) }, /* ELS-01/01C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1030, 0) }, /* PSR-295/293 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1031, 0) }, /* DGX-205/203 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1032, 0) }, /* DGX-305 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1033, 0) }, /* DGX-505 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1034, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1035, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1036, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1037, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1038, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1039, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x103f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1040, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1041, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1042, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1043, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1044, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1045, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x104f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1050, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1051, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1052, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1053, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1054, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1055, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1056, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1057, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1058, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1059, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105a, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105b, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105c, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x105d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x1503, 3) }, /* MOX6/MOX8 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2000, 0) }, /* DGP-7 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2001, 0) }, /* DGP-5 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2002, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x2003, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5000, 0) }, /* CS1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5001, 0) }, /* DSP1D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5002, 0) }, /* DME32 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5003, 0) }, /* DM2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5004, 0) }, /* 02R96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5005, 0) }, /* ACU16-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5006, 0) }, /* NHB32-C */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5007, 0) }, /* DM1000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5008, 0) }, /* 01V96 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x5009, 0) }, /* SPX2000 */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500a, 0) }, /* PM5D */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500b, 0) }, /* DME64N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500c, 0) }, /* DME24N */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500d, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500e, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x500f, 0) }, /* NULL */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7000, 0) }, /* DTX */ { USB_VPI(USB_VENDOR_YAMAHA, 0x7010, 0) }, /* UB99 */ }; static const STRUCT_USB_HOST_ID __used uaudio_devs[] = { /* Generic USB audio class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),}, /* Generic USB MIDI class match */ {USB_IFACE_CLASS(UICLASS_AUDIO), USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),}, }; static int uaudio_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); /* lookup non-standard device(s) */ if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { return (BUS_PROBE_SPECIFIC); } if (uaa->info.bInterfaceClass != UICLASS_AUDIO) { if (uaa->info.bInterfaceClass != UICLASS_VENDOR || usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0) return (ENXIO); } /* check for AUDIO control interface */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) { if (usb_test_quirk(uaa, UQ_BAD_AUDIO)) return (ENXIO); else return (BUS_PROBE_GENERIC); } /* check for MIDI stream */ if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) { if (usb_test_quirk(uaa, UQ_BAD_MIDI)) return (ENXIO); else return (BUS_PROBE_GENERIC); } return (ENXIO); } static int uaudio_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uaudio_softc *sc = device_get_softc(dev); struct usb_interface_descriptor *id; usb_error_t err; device_t child; sc->sc_play_chan.priv_sc = sc; sc->sc_rec_chan.priv_sc = sc; sc->sc_udev = uaa->device; sc->sc_mixer_iface_index = uaa->info.bIfaceIndex; sc->sc_mixer_iface_no = uaa->info.bIfaceNum; sc->sc_config_msg[0].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[0].sc = sc; sc->sc_config_msg[1].hdr.pm_callback = &uaudio_configure_msg; sc->sc_config_msg[1].sc = sc; if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR)) sc->sc_uq_audio_swap_lr = 1; if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC)) sc->sc_uq_au_inp_async = 1; if (usb_test_quirk(uaa, UQ_AU_NO_XU)) sc->sc_uq_au_no_xu = 1; if (usb_test_quirk(uaa, UQ_BAD_ADC)) sc->sc_uq_bad_adc = 1; if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS)) sc->sc_uq_au_vendor_class = 1; umidi_init(dev); device_set_usb_desc(dev); id = usbd_get_interface_descriptor(uaa->iface); /* must fill mixer info before channel info */ uaudio_mixer_fill_info(sc, uaa->device, id); /* fill channel info */ uaudio_chan_fill_info(sc, uaa->device); DPRINTF("audio rev %d.%02x\n", sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); if (sc->sc_mixer_count == 0) { if (uaa->info.idVendor == USB_VENDOR_MAUDIO && (uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA || uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA8R)) { DPRINTF("Generating mixer descriptors\n"); uaudio_mixer_controls_create_ftu(sc); } } DPRINTF("%d mixer controls\n", sc->sc_mixer_count); if (sc->sc_play_chan.num_alt > 0) { uint8_t x; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinte * re-enumeration loop: */ err = usbd_set_alt_interface_index(sc->sc_udev, sc->sc_play_chan.usb_alt[0].iface_index, sc->sc_play_chan.usb_alt[0].iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_play_chan.num_alt; x++) { device_printf(dev, "Play: %d Hz, %d ch, %s format, " "2x8ms buffer.\n", sc->sc_play_chan.usb_alt[x].sample_rate, sc->sc_play_chan.usb_alt[x].channels, sc->sc_play_chan.usb_alt[x].p_fmt->description); } } else { device_printf(dev, "No playback.\n"); } if (sc->sc_rec_chan.num_alt > 0) { uint8_t x; /* * Need to set a default alternate interface, else * some USB audio devices might go into an infinte * re-enumeration loop: */ err = usbd_set_alt_interface_index(sc->sc_udev, sc->sc_rec_chan.usb_alt[0].iface_index, sc->sc_rec_chan.usb_alt[0].iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); } for (x = 0; x != sc->sc_rec_chan.num_alt; x++) { device_printf(dev, "Record: %d Hz, %d ch, %s format, " "2x8ms buffer.\n", sc->sc_rec_chan.usb_alt[x].sample_rate, sc->sc_rec_chan.usb_alt[x].channels, sc->sc_rec_chan.usb_alt[x].p_fmt->description); } } else { device_printf(dev, "No recording.\n"); } if (sc->sc_midi_chan.valid == 0) { if (usbd_lookup_id_by_uaa(uaudio_vendor_midi, sizeof(uaudio_vendor_midi), uaa) == 0) { sc->sc_midi_chan.iface_index = (uint8_t)uaa->driver_info; sc->sc_midi_chan.iface_alt_index = 0; sc->sc_midi_chan.valid = 1; } } if (sc->sc_midi_chan.valid) { if (umidi_probe(dev)) { goto detach; } device_printf(dev, "MIDI sequencer.\n"); } else { device_printf(dev, "No MIDI sequencer.\n"); } DPRINTF("doing child attach\n"); /* attach the children */ sc->sc_sndcard_func.func = SCF_PCM; /* * Only attach a PCM device if we have a playback, recording * or mixer device present: */ if (sc->sc_play_chan.num_alt > 0 || sc->sc_rec_chan.num_alt > 0 || sc->sc_mix_info) { child = device_add_child(dev, "pcm", -1); if (child == NULL) { DPRINTF("out of memory\n"); goto detach; } device_set_ivars(child, &sc->sc_sndcard_func); } if (bus_generic_attach(dev)) { DPRINTF("child attach failed\n"); goto detach; } if (uaudio_hid_probe(sc, uaa) == 0) { device_printf(dev, "HID volume keys found.\n"); } else { device_printf(dev, "No HID volume keys found.\n"); } /* reload all mixer settings */ uaudio_mixer_reload_all(sc); return (0); /* success */ detach: uaudio_detach(dev); return (ENXIO); } static void uaudio_pcm_setflags(device_t dev, uint32_t flags) { pcm_setflags(dev, pcm_getflags(dev) | flags); } int uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); char status[SND_STATUSLEN]; uaudio_mixer_init(sc); if (sc->sc_uq_audio_swap_lr) { DPRINTF("hardware has swapped left and right\n"); /* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */ } if (!(sc->sc_mix_info & SOUND_MASK_PCM)) { DPRINTF("emulating master volume\n"); /* * Emulate missing pcm mixer controller * through FEEDER_VOLUME */ uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL); } + if (sc->sc_pcm_bitperfect) { + DPRINTF("device needs bitperfect by default\n"); + uaudio_pcm_setflags(dev, SD_F_BITPERFECT); + } if (mixer_init(dev, mixer_class, sc)) goto detach; sc->sc_mixer_init = 1; mixer_hwvol_init(dev); snprintf(status, sizeof(status), "at ? %s", PCM_KLDSTRING(snd_uaudio)); if (pcm_register(dev, sc, (sc->sc_play_chan.num_alt > 0) ? 1 : 0, (sc->sc_rec_chan.num_alt > 0) ? 1 : 0)) { goto detach; } uaudio_pcm_setflags(dev, SD_F_MPSAFE); sc->sc_pcm_registered = 1; if (sc->sc_play_chan.num_alt > 0) { pcm_addchan(dev, PCMDIR_PLAY, chan_class, sc); } if (sc->sc_rec_chan.num_alt > 0) { pcm_addchan(dev, PCMDIR_REC, chan_class, sc); } pcm_setstatus(dev, status); uaudio_mixer_register_sysctl(sc, dev); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan.feedback_rate, 0, "Feedback sample rate in Hz"); return (0); /* success */ detach: uaudio_detach_sub(dev); return (ENXIO); } int uaudio_detach_sub(device_t dev) { struct uaudio_softc *sc = device_get_softc(device_get_parent(dev)); int error = 0; repeat: if (sc->sc_pcm_registered) { error = pcm_unregister(dev); } else { if (sc->sc_mixer_init) { error = mixer_uninit(dev); } } if (error) { device_printf(dev, "Waiting for sound application to exit!\n"); usb_pause_mtx(NULL, 2 * hz); goto repeat; /* try again */ } return (0); /* success */ } static int uaudio_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); /* * Stop USB transfers early so that any audio applications * will time out and close opened /dev/dspX.Y device(s), if * any. */ usb_proc_explore_lock(sc->sc_udev); sc->sc_play_chan.operation = CHAN_OP_DRAIN; sc->sc_rec_chan.operation = CHAN_OP_DRAIN; usb_proc_explore_mwait(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); usb_proc_explore_unlock(sc->sc_udev); usbd_transfer_unsetup(sc->sc_play_chan.xfer, UAUDIO_NCHANBUFS + 1); usbd_transfer_unsetup(sc->sc_rec_chan.xfer, UAUDIO_NCHANBUFS + 1); uaudio_hid_detach(sc); if (bus_generic_detach(dev) != 0) { DPRINTF("detach failed!\n"); } sbuf_delete(&sc->sc_sndstat); sc->sc_sndstat_valid = 0; umidi_detach(dev); /* free mixer data */ uaudio_mixer_ctl_free(sc); return (0); } static uint32_t uaudio_get_buffer_size(struct uaudio_chan *ch, uint8_t alt) { struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt]; /* We use 2 times 8ms of buffer */ uint32_t buf_size = (((chan_alt->sample_rate * (UAUDIO_NFRAMES / 8)) + 1000 - 1) / 1000) * chan_alt->sample_size; return (buf_size); } static void uaudio_configure_msg_sub(struct uaudio_softc *sc, struct uaudio_chan *chan, int dir) { struct uaudio_chan_alt *chan_alt; uint32_t frames; uint32_t buf_size; uint16_t fps; uint8_t set_alt; uint8_t fps_shift; uint8_t operation; usb_error_t err; if (chan->num_alt <= 0) return; DPRINTF("\n"); usb_proc_explore_lock(sc->sc_udev); operation = chan->operation; chan->operation = CHAN_OP_NONE; usb_proc_explore_unlock(sc->sc_udev); mtx_lock(chan->pcm_mtx); if (chan->cur_alt != chan->set_alt) set_alt = chan->set_alt; else set_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); if (set_alt >= chan->num_alt) goto done; chan_alt = chan->usb_alt + set_alt; usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); err = usbd_set_alt_interface_index(sc->sc_udev, chan_alt->iface_index, chan_alt->iface_alt_index); if (err) { DPRINTF("setting of alternate index failed: %s!\n", usbd_errstr(err)); goto error; } /* * Only set the sample rate if the channel reports that it * supports the frequency control. */ if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { unsigned int x; for (x = 0; x != 256; x++) { if (dir == PCMDIR_PLAY) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } if (uaudio20_set_speed(sc->sc_udev, sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting * the speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } } else if (chan_alt->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) { if (uaudio_set_speed(sc->sc_udev, chan_alt->p_ed1->bEndpointAddress, chan_alt->sample_rate)) { /* * If the endpoint is adaptive setting the * speed may fail. */ DPRINTF("setting of sample rate failed! " "(continuing anyway)\n"); } } if (usbd_transfer_setup(sc->sc_udev, &chan_alt->iface_index, chan->xfer, chan_alt->usb_cfg, UAUDIO_NCHANBUFS + 1, chan, chan->pcm_mtx)) { DPRINTF("could not allocate USB transfers!\n"); goto error; } fps = usbd_get_isoc_fps(sc->sc_udev); if (fps < 8000) { /* FULL speed USB */ frames = 8; } else { /* HIGH speed USB */ frames = UAUDIO_NFRAMES; } fps_shift = usbd_xfer_get_fps_shift(chan->xfer[0]); /* down shift number of frames per second, if any */ fps >>= fps_shift; frames >>= fps_shift; /* bytes per frame should not be zero */ chan->bytes_per_frame[0] = ((chan_alt->sample_rate / fps) * chan_alt->sample_size); chan->bytes_per_frame[1] = (((chan_alt->sample_rate + fps - 1) / fps) * chan_alt->sample_size); /* setup data rate dithering, if any */ chan->frames_per_second = fps; chan->sample_rem = chan_alt->sample_rate % fps; chan->sample_curr = 0; /* compute required buffer size */ buf_size = (chan->bytes_per_frame[1] * frames); if (buf_size > (chan->end - chan->start)) { DPRINTF("buffer size is too big\n"); goto error; } chan->intr_frames = frames; DPRINTF("fps=%d sample_rem=%d\n", (int)fps, (int)chan->sample_rem); if (chan->intr_frames == 0) { DPRINTF("frame shift is too high!\n"); goto error; } mtx_lock(chan->pcm_mtx); chan->cur_alt = set_alt; mtx_unlock(chan->pcm_mtx); done: #if (UAUDIO_NCHANBUFS != 2) #error "please update code" #endif switch (operation) { case CHAN_OP_START: mtx_lock(chan->pcm_mtx); usbd_transfer_start(chan->xfer[0]); usbd_transfer_start(chan->xfer[1]); mtx_unlock(chan->pcm_mtx); break; case CHAN_OP_STOP: mtx_lock(chan->pcm_mtx); usbd_transfer_stop(chan->xfer[0]); usbd_transfer_stop(chan->xfer[1]); mtx_unlock(chan->pcm_mtx); break; default: break; } return; error: usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1); mtx_lock(chan->pcm_mtx); chan->cur_alt = CHAN_MAX_ALT; mtx_unlock(chan->pcm_mtx); } static void uaudio_configure_msg(struct usb_proc_msg *pm) { struct uaudio_softc *sc = ((struct uaudio_configure_msg *)pm)->sc; usb_proc_explore_unlock(sc->sc_udev); uaudio_configure_msg_sub(sc, &sc->sc_play_chan, PCMDIR_PLAY); uaudio_configure_msg_sub(sc, &sc->sc_rec_chan, PCMDIR_REC); usb_proc_explore_lock(sc->sc_udev); } /*========================================================================* * AS - Audio Stream - routines *========================================================================*/ #ifdef USB_DEBUG static void uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed) { if (ed) { DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n" "bEndpointAddress=%d bmAttributes=0x%x \n" "wMaxPacketSize=%d bInterval=%d \n" "bRefresh=%d bSynchAddress=%d\n", ed, ed->bLength, ed->bDescriptorType, ed->bEndpointAddress, ed->bmAttributes, UGETW(ed->wMaxPacketSize), ed->bInterval, UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0, UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0); } } #endif /* * The following is a workaround for broken no-name USB audio devices * sold by dealextreme called "3D sound". The problem is that the * manufacturer computed wMaxPacketSize is too small to hold the * actual data sent. In other words the device sometimes sends more * data than it actually reports it can send in a single isochronous * packet. */ static void uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep, uint32_t xps, uint32_t add) { uint32_t mps; mps = UGETW(ep->wMaxPacketSize); /* * If the device indicates it can send more data than what the * sample rate indicates, we apply the workaround. */ if (mps > xps) { /* allow additional data */ xps += add; /* check against the maximum USB 1.x length */ if (xps > 1023) xps = 1023; /* check if we should do an update */ if (mps < xps) { /* simply update the wMaxPacketSize field */ USETW(ep->wMaxPacketSize, xps); DPRINTF("Workaround: Updated wMaxPacketSize " "from %d to %d bytes.\n", (int)mps, (int)xps); } } } static usb_error_t uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t rate) { struct usb_device_request req; usb_error_t error; uint8_t data[255]; uint16_t actlen; uint16_t rates; uint16_t x; DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n", iface_no, clockid, rate); req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UA20_CS_RANGE; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); USETW(req.wLength, 255); error = usbd_do_request_flags(udev, NULL, &req, data, USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT); if (error != 0 || actlen < 2) return (USB_ERR_INVAL); rates = data[0] | (data[1] << 8); actlen = (actlen - 2) / 12; if (rates > actlen) { DPRINTF("Too many rates\n"); rates = actlen; } for (x = 0; x != rates; x++) { uint32_t min = UGETDW(data + 2 + (12 * x)); uint32_t max = UGETDW(data + 6 + (12 * x)); uint32_t res = UGETDW(data + 10 + (12 * x)); if (res == 0) { DPRINTF("Zero residue\n"); res = 1; } if (min > max) { DPRINTF("Swapped max and min\n"); uint32_t temp; temp = min; min = max; max = temp; } if (rate >= min && rate <= max && (((rate - min) % res) == 0)) { return (0); } } return (USB_ERR_INVAL); } static void uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev, uint32_t rate, uint8_t channels, uint8_t bit_resolution) { struct usb_descriptor *desc = NULL; union uaudio_asid asid = { NULL }; union uaudio_asf1d asf1d = { NULL }; union uaudio_sed sed = { NULL }; struct usb_midi_streaming_endpoint_descriptor *msid = NULL; usb_endpoint_descriptor_audio_t *ed1 = NULL; const struct usb_audio_control_descriptor *acdp = NULL; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); struct usb_interface_descriptor *id; const struct uaudio_format *p_fmt = NULL; struct uaudio_chan *chan; struct uaudio_chan_alt *chan_alt; uint32_t format; uint16_t curidx = 0xFFFF; uint16_t lastidx = 0xFFFF; uint16_t alt_index = 0; uint16_t audio_rev = 0; uint16_t x; uint8_t ep_dir; uint8_t bChannels; uint8_t bBitResolution; uint8_t audio_if = 0; uint8_t midi_if = 0; uint8_t uma_if_class; while ((desc = usb_desc_foreach(cd, desc))) { if ((desc->bDescriptorType == UDESC_INTERFACE) && (desc->bLength >= sizeof(*id))) { id = (void *)desc; if (id->bInterfaceNumber != lastidx) { lastidx = id->bInterfaceNumber; curidx++; alt_index = 0; } else { alt_index++; } if ((!(sc->sc_hid.flags & UAUDIO_HID_VALID)) && (id->bInterfaceClass == UICLASS_HID) && (id->bInterfaceSubClass == 0) && (id->bInterfaceProtocol == 0) && (alt_index == 0) && usbd_get_iface(udev, curidx) != NULL) { DPRINTF("Found HID interface at %d\n", curidx); sc->sc_hid.flags |= UAUDIO_HID_VALID; sc->sc_hid.iface_index = curidx; } uma_if_class = ((id->bInterfaceClass == UICLASS_AUDIO) || ((id->bInterfaceClass == UICLASS_VENDOR) && (sc->sc_uq_au_vendor_class != 0))); if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) { audio_if = 1; } else { audio_if = 0; } if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) { /* * XXX could allow multiple MIDI interfaces */ midi_if = 1; if ((sc->sc_midi_chan.valid == 0) && (usbd_get_iface(udev, curidx) != NULL)) { sc->sc_midi_chan.iface_index = curidx; sc->sc_midi_chan.iface_alt_index = alt_index; sc->sc_midi_chan.valid = 1; } } else { midi_if = 0; } asid.v1 = NULL; asf1d.v1 = NULL; ed1 = NULL; sed.v1 = NULL; /* * There can only be one USB audio instance * per USB device. Grab all USB audio * interfaces on this USB device so that we * don't attach USB audio twice: */ if (alt_index == 0 && curidx != sc->sc_mixer_iface_index && (id->bInterfaceClass == UICLASS_AUDIO || audio_if != 0 || midi_if != 0)) { usbd_set_parent_iface(sc->sc_udev, curidx, sc->sc_mixer_iface_index); } } if (audio_if == 0) { if (midi_if == 0) { if ((acdp == NULL) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) && (desc->bLength >= sizeof(*acdp))) { acdp = (void *)desc; audio_rev = UGETW(acdp->bcdADC); } } else { msid = (void *)desc; /* get the maximum number of embedded jacks in use, if any */ if (msid->bLength >= sizeof(*msid) && msid->bDescriptorType == UDESC_CS_ENDPOINT && msid->bDescriptorSubtype == MS_GENERAL && msid->bNumEmbMIDIJack > sc->sc_midi_chan.max_emb_jack) { sc->sc_midi_chan.max_emb_jack = msid->bNumEmbMIDIJack; } } /* * Don't collect any USB audio descriptors if * this is not an USB audio stream interface. */ continue; } if ((acdp != NULL) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == AS_GENERAL) && (asid.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asid.v2)) { asid.v2 = (void *)desc; } } else { if (desc->bLength >= sizeof(*asid.v1)) { asid.v1 = (void *)desc; } } } if ((acdp != NULL) && (desc->bDescriptorType == UDESC_CS_INTERFACE) && (desc->bDescriptorSubtype == FORMAT_TYPE) && (asf1d.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*asf1d.v2)) asf1d.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*asf1d.v1)) { asf1d.v1 = (void *)desc; if (asf1d.v1->bFormatType != FORMAT_TYPE_I) { DPRINTFN(11, "ignored bFormatType = %d\n", asf1d.v1->bFormatType); asf1d.v1 = NULL; continue; } if (desc->bLength < (sizeof(*asf1d.v1) + ((asf1d.v1->bSamFreqType == 0) ? 6 : (asf1d.v1->bSamFreqType * 3)))) { DPRINTFN(11, "invalid descriptor, " "too short\n"); asf1d.v1 = NULL; continue; } } } } if ((desc->bDescriptorType == UDESC_ENDPOINT) && (desc->bLength >= UEP_MINSIZE) && (ed1 == NULL)) { ed1 = (void *)desc; if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) { ed1 = NULL; continue; } } if ((acdp != NULL) && (desc->bDescriptorType == UDESC_CS_ENDPOINT) && (desc->bDescriptorSubtype == AS_GENERAL) && (sed.v1 == NULL)) { if (audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (audio_rev >= UAUDIO_VERSION_20) { if (desc->bLength >= sizeof(*sed.v2)) sed.v2 = (void *)desc; } else { if (desc->bLength >= sizeof(*sed.v1)) sed.v1 = (void *)desc; } } if (asid.v1 == NULL || asf1d.v1 == NULL || ed1 == NULL || sed.v1 == NULL) { /* need more descriptors */ continue; } ep_dir = UE_GET_DIR(ed1->bEndpointAddress); /* We ignore sync endpoint information until further. */ if (audio_rev >= UAUDIO_VERSION_30) { goto next_ep; } else if (audio_rev >= UAUDIO_VERSION_20) { uint32_t dwFormat; dwFormat = UGETDW(asid.v2->bmFormats); bChannels = asid.v2->bNrChannels; bBitResolution = asf1d.v2->bSubslotSize * 8; if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } for (p_fmt = uaudio20_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat & dwFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } for (x = 0; x != 256; x++) { if (ep_dir == UE_DIR_OUT) { if (!(sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8)))) { continue; } } else { if (!(sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8)))) { continue; } } DPRINTF("Checking clock ID=%d\n", x); if (uaudio20_check_rate(udev, sc->sc_mixer_iface_no, x, rate)) { DPRINTF("Unsupported sampling " "rate, id=%d\n", x); goto next_ep; } } } else { uint16_t wFormat; wFormat = UGETW(asid.v1->wFormatTag); bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels); bBitResolution = asf1d.v1->bSubFrameSize * 8; if (asf1d.v1->bSamFreqType == 0) { DPRINTFN(16, "Sample rate: %d-%dHz\n", UA_SAMP_LO(asf1d.v1), UA_SAMP_HI(asf1d.v1)); if ((rate >= UA_SAMP_LO(asf1d.v1)) && (rate <= UA_SAMP_HI(asf1d.v1))) goto found_rate; } else { for (x = 0; x < asf1d.v1->bSamFreqType; x++) { DPRINTFN(16, "Sample rate = %dHz\n", UA_GETSAMP(asf1d.v1, x)); if (rate == UA_GETSAMP(asf1d.v1, x)) goto found_rate; } } goto next_ep; found_rate: for (p_fmt = uaudio10_formats; p_fmt->wFormat != 0; p_fmt++) { if ((p_fmt->wFormat == wFormat) && (p_fmt->bPrecision == bBitResolution)) break; } if (p_fmt->wFormat == 0) { DPRINTF("Unsupported audio format\n"); goto next_ep; } if ((bChannels != channels) || (bBitResolution != bit_resolution)) { DPRINTF("Wrong number of channels\n"); goto next_ep; } } chan = (ep_dir == UE_DIR_IN) ? &sc->sc_rec_chan : &sc->sc_play_chan; if (usbd_get_iface(udev, curidx) == NULL) { DPRINTF("Interface is not valid\n"); goto next_ep; } if (chan->num_alt == CHAN_MAX_ALT) { DPRINTF("Too many alternate settings\n"); goto next_ep; } chan->set_alt = 0; chan->cur_alt = CHAN_MAX_ALT; chan_alt = &chan->usb_alt[chan->num_alt++]; #ifdef USB_DEBUG uaudio_chan_dump_ep_desc(ed1); #endif DPRINTF("Sample rate = %dHz, channels = %d, " "bits = %d, format = %s\n", rate, channels, bit_resolution, p_fmt->description); chan_alt->sample_rate = rate; chan_alt->p_asf1d = asf1d; chan_alt->p_ed1 = ed1; chan_alt->p_fmt = p_fmt; chan_alt->p_sed = sed; chan_alt->iface_index = curidx; chan_alt->iface_alt_index = alt_index; if (ep_dir == UE_DIR_IN) chan_alt->usb_cfg = uaudio_cfg_record; else chan_alt->usb_cfg = uaudio_cfg_play; chan_alt->sample_size = (UAUDIO_MAX_CHAN(channels) * p_fmt->bPrecision) / 8; chan_alt->channels = channels; if (ep_dir == UE_DIR_IN && usbd_get_speed(udev) == USB_SPEED_FULL) { uaudio_record_fix_fs(ed1, chan_alt->sample_size * (rate / 1000), chan_alt->sample_size * (rate / 4000)); } /* setup play/record format */ format = chan_alt->p_fmt->freebsd_fmt; + /* get default SND_FORMAT() */ + format = SND_FORMAT(format, chan_alt->channels, 0); + switch (chan_alt->channels) { + uint32_t temp_fmt; + case 1: case 2: - /* stereo */ - format = SND_FORMAT(format, 2, 0); + /* mono and stereo */ break; - case 1: - /* mono */ - format = SND_FORMAT(format, 1, 0); - break; default: /* surround and more */ - format = feeder_matrix_default_format( - SND_FORMAT(format, chan_alt->channels, 0)); + temp_fmt = feeder_matrix_default_format(format); + /* if multichannel, then format can be zero */ + if (temp_fmt != 0) + format = temp_fmt; break; } /* check if format is not supported */ if (format == 0) { DPRINTF("The selected audio format is not supported\n"); chan->num_alt--; goto next_ep; } if (chan->num_alt > 1) { /* we only accumulate one format at different sample rates */ if (chan->pcm_format[0] != format) { DPRINTF("Multiple formats is not supported\n"); chan->num_alt--; goto next_ep; } /* ignore if duplicate sample rate entry */ if (rate == chan->usb_alt[chan->num_alt - 2].sample_rate) { DPRINTF("Duplicate sample rate detected\n"); chan->num_alt--; goto next_ep; } } chan->pcm_cap.fmtlist = chan->pcm_format; chan->pcm_cap.fmtlist[0] = format; + /* check if device needs bitperfect */ + if (chan_alt->channels > UAUDIO_MATRIX_MAX) + sc->sc_pcm_bitperfect = 1; + if (rate < chan->pcm_cap.minspeed || chan->pcm_cap.minspeed == 0) chan->pcm_cap.minspeed = rate; if (rate > chan->pcm_cap.maxspeed || chan->pcm_cap.maxspeed == 0) chan->pcm_cap.maxspeed = rate; if (sc->sc_sndstat_valid != 0) { sbuf_printf(&sc->sc_sndstat, "\n\t" "mode %d.%d:(%s) %dch, %dbit, %s, %dHz", curidx, alt_index, (ep_dir == UE_DIR_IN) ? "input" : "output", channels, p_fmt->bPrecision, p_fmt->description, rate); } next_ep: sed.v1 = NULL; ed1 = NULL; } } /* This structure defines all the supported rates. */ static const uint32_t uaudio_rate_list[CHAN_MAX_ALT] = { 384000, 352800, 192000, 176400, 96000, 88200, 88000, 80000, 72000, 64000, 56000, 48000, 44100, 40000, 32000, 24000, 22050, 16000, 11025, 8000, 0 }; static void uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev) { uint32_t rate = uaudio_default_rate; uint8_t z; uint8_t bits = uaudio_default_bits; uint8_t y; uint8_t channels = uaudio_default_channels; uint8_t x; bits -= (bits % 8); if ((bits == 0) || (bits > 32)) { /* set a valid value */ bits = 32; } if (channels == 0) { switch (usbd_get_speed(udev)) { case USB_SPEED_LOW: case USB_SPEED_FULL: /* * Due to high bandwidth usage and problems * with HIGH-speed split transactions we * disable surround setups on FULL-speed USB * by default */ channels = 4; break; default: - channels = 16; + channels = UAUDIO_CHANNELS_MAX; break; } - } else if (channels > 16) { - channels = 16; - } - if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) { + } else if (channels > UAUDIO_CHANNELS_MAX) + channels = UAUDIO_CHANNELS_MAX; + + if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) sc->sc_sndstat_valid = 1; - } + /* try to search for a valid config */ for (x = channels; x; x--) { for (y = bits; y; y -= 8) { /* try user defined rate, if any */ if (rate != 0) uaudio_chan_fill_info_sub(sc, udev, rate, x, y); /* try find a matching rate, if any */ for (z = 0; uaudio_rate_list[z]; z++) uaudio_chan_fill_info_sub(sc, udev, uaudio_rate_list[z], x, y); } } if (sc->sc_sndstat_valid) sbuf_finish(&sc->sc_sndstat); } static void uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint64_t sample_rate; uint8_t buf[4]; uint64_t temp; int len; int actlen; int nframes; usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(6, "transferred %d bytes\n", actlen); if (nframes == 0) break; len = usbd_xfer_frame_len(xfer, 0); if (len == 0) break; if (len > sizeof(buf)) len = sizeof(buf); memset(buf, 0, sizeof(buf)); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, len); temp = UGETDW(buf); DPRINTF("Value = 0x%08x\n", (int)temp); /* auto-detect SYNC format */ if (len == 4) temp &= 0x0fffffff; /* check for no data */ if (temp == 0) break; temp *= 125ULL; sample_rate = ch->usb_alt[ch->cur_alt].sample_rate; /* auto adjust */ while (temp < (sample_rate - (sample_rate / 4))) temp *= 2; while (temp > (sample_rate + (sample_rate / 2))) temp /= 2; DPRINTF("Comparing %d Hz :: %d Hz\n", (int)temp, (int)sample_rate); /* * Use feedback value as fallback when there is no * recording channel: */ if (ch->priv_sc->sc_rec_chan.num_alt == 0) ch->jitter_curr = temp - sample_rate; ch->feedback_rate = temp; break; case USB_ST_SETUP: usbd_xfer_set_frames(xfer, 1); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ break; } } static int uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt) { uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes; return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC); } static void uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct uaudio_chan *ch_rec; struct usb_page_cache *pc; uint32_t mfl; uint32_t total; uint32_t blockcount; uint32_t n; uint32_t offset; int sample_size; int actlen; int sumlen; if (ch->running == 0 || ch->start == ch->end) { DPRINTF("not running or no buffer!\n"); return; } /* check if there is a record channel */ if (ch->priv_sc->sc_rec_chan.num_alt > 0) ch_rec = &ch->priv_sc->sc_rec_chan; else ch_rec = NULL; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: tr_setup: if (ch_rec != NULL) { /* reset receive jitter counters */ mtx_lock(ch_rec->pcm_mtx); ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; mtx_unlock(ch_rec->pcm_mtx); } /* reset transmit jitter counters */ ch->jitter_curr = 0; ch->jitter_rem = 0; /* FALLTHROUGH */ case USB_ST_TRANSFERRED: if (actlen < sumlen) { DPRINTF("short transfer, " "%d of %d bytes\n", actlen, sumlen); } chn_intr(ch->pcm_ch); /* * Check for asynchronous playback endpoint and that * the playback endpoint is properly configured: */ if (ch_rec != NULL && uaudio_chan_is_async(ch, ch->cur_alt) != 0) { mtx_lock(ch_rec->pcm_mtx); if (ch_rec->cur_alt < ch_rec->num_alt) { int64_t tx_jitter; int64_t rx_rate; /* translate receive jitter into transmit jitter */ tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate; tx_jitter = (tx_jitter * ch_rec->jitter_curr) + ch->jitter_rem; /* reset receive jitter counters */ ch_rec->jitter_curr = 0; ch_rec->jitter_rem = 0; /* compute exact number of transmit jitter samples */ rx_rate = ch_rec->usb_alt[ch_rec->cur_alt].sample_rate; ch->jitter_curr += tx_jitter / rx_rate; ch->jitter_rem = tx_jitter % rx_rate; } mtx_unlock(ch_rec->pcm_mtx); } /* start the SYNC transfer one time per second, if any */ if (++(ch->intr_counter) >= UAUDIO_IRQS) { ch->intr_counter = 0; usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]); } mfl = usbd_xfer_max_framelen(xfer); if (ch->bytes_per_frame[1] > mfl) { DPRINTF("bytes per transfer, %d, " "exceeds maximum, %d!\n", ch->bytes_per_frame[1], mfl); break; } blockcount = ch->intr_frames; /* setup number of frames */ usbd_xfer_set_frames(xfer, blockcount); /* get sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; /* reset total length */ total = 0; /* setup frame lengths */ for (n = 0; n != blockcount; n++) { uint32_t frame_len; ch->sample_curr += ch->sample_rem; if (ch->sample_curr >= ch->frames_per_second) { ch->sample_curr -= ch->frames_per_second; frame_len = ch->bytes_per_frame[1]; } else { frame_len = ch->bytes_per_frame[0]; } /* handle free running clock case */ if (ch->jitter_curr > 0 && (frame_len + sample_size) <= mfl) { DPRINTFN(6, "sending one sample more\n"); ch->jitter_curr--; frame_len += sample_size; } else if (ch->jitter_curr < 0 && frame_len >= sample_size) { DPRINTFN(6, "sending one sample less\n"); ch->jitter_curr++; frame_len -= sample_size; } usbd_xfer_set_frame_len(xfer, n, frame_len); total += frame_len; } DPRINTFN(6, "transferring %d bytes\n", total); offset = 0; pc = usbd_xfer_get_frame(xfer, 0); while (total > 0) { n = (ch->end - ch->cur); if (n > total) n = total; usbd_copy_in(pc, offset, ch->cur, n); total -= n; ch->cur += n; offset += n; if (ch->cur >= ch->end) ch->cur = ch->start; } usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } static void uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error) { /* TODO */ } static void uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_chan *ch = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t offset0; uint32_t mfl; int m; int n; int len; int actlen; int nframes; int expected_bytes; int sample_size; if (ch->start == ch->end) { DPRINTF("no buffer!\n"); return; } usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes); mfl = usbd_xfer_max_framelen(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: offset0 = 0; pc = usbd_xfer_get_frame(xfer, 0); /* try to compute the number of expected bytes */ ch->sample_curr += (ch->sample_rem * ch->intr_frames); /* compute number of expected bytes */ expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) + ((ch->sample_curr / ch->frames_per_second) * (ch->bytes_per_frame[1] - ch->bytes_per_frame[0])); /* keep remainder */ ch->sample_curr %= ch->frames_per_second; /* get current sample size */ sample_size = ch->usb_alt[ch->cur_alt].sample_size; for (n = 0; n != nframes; n++) { uint32_t offset1 = offset0; len = usbd_xfer_frame_len(xfer, n); /* make sure we only receive complete samples */ len = len - (len % sample_size); /* subtract bytes received from expected payload */ expected_bytes -= len; /* don't receive data when not ready */ if (ch->running == 0 || ch->cur_alt != ch->set_alt) continue; /* fill ring buffer with samples, if any */ while (len > 0) { m = (ch->end - ch->cur); if (m > len) m = len; usbd_copy_out(pc, offset1, ch->cur, m); len -= m; offset1 += m; ch->cur += m; if (ch->cur >= ch->end) ch->cur = ch->start; } offset0 += mfl; } /* update current jitter */ ch->jitter_curr -= (expected_bytes / sample_size); /* don't allow a huge amount of jitter to accumulate */ nframes = 2 * ch->intr_frames; /* range check current jitter */ if (ch->jitter_curr < -nframes) ch->jitter_curr = -nframes; else if (ch->jitter_curr > nframes) ch->jitter_curr = nframes; DPRINTFN(6, "transferred %d bytes, jitter %d samples\n", actlen, ch->jitter_curr); if (ch->running != 0) chn_intr(ch->pcm_ch); case USB_ST_SETUP: tr_setup: nframes = ch->intr_frames; usbd_xfer_set_frames(xfer, nframes); for (n = 0; n != nframes; n++) usbd_xfer_set_frame_len(xfer, n, mfl); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) goto tr_setup; break; } } void * uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct uaudio_chan *ch = ((dir == PCMDIR_PLAY) ? &sc->sc_play_chan : &sc->sc_rec_chan); uint32_t buf_size; uint8_t x; /* store mutex and PCM channel */ ch->pcm_ch = c; ch->pcm_mtx = c->lock; /* compute worst case buffer */ buf_size = 0; for (x = 0; x != ch->num_alt; x++) { uint32_t temp = uaudio_get_buffer_size(ch, x); if (temp > buf_size) buf_size = temp; } /* allow double buffering */ buf_size *= 2; DPRINTF("Worst case buffer is %d bytes\n", (int)buf_size); ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO); if (ch->buf == NULL) goto error; if (sndbuf_setup(b, ch->buf, buf_size) != 0) goto error; ch->start = ch->buf; ch->end = ch->buf + buf_size; ch->cur = ch->buf; ch->pcm_buf = b; ch->max_buf = buf_size; if (ch->pcm_mtx == NULL) { DPRINTF("ERROR: PCM channels does not have a mutex!\n"); goto error; } return (ch); error: uaudio_chan_free(ch); return (NULL); } int uaudio_chan_free(struct uaudio_chan *ch) { if (ch->buf != NULL) { free(ch->buf, M_DEVBUF); ch->buf = NULL; } usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1); ch->num_alt = 0; return (0); } int uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize) { uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); sndbuf_setup(ch->pcm_buf, ch->buf, temp); return (temp / 2); } int uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize, uint32_t blockcount) { return (1); } int uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed) { struct uaudio_softc *sc; uint8_t x; sc = ch->priv_sc; for (x = 0; x < ch->num_alt; x++) { if (ch->usb_alt[x].sample_rate < speed) { /* sample rate is too low */ break; } } if (x != 0) x--; usb_proc_explore_lock(sc->sc_udev); ch->set_alt = x; usb_proc_explore_unlock(sc->sc_udev); DPRINTF("Selecting alt %d\n", (int)x); return (ch->usb_alt[x].sample_rate); } int uaudio_chan_getptr(struct uaudio_chan *ch) { return (ch->cur - ch->start); } struct pcmchan_caps * uaudio_chan_getcaps(struct uaudio_chan *ch) { return (&ch->pcm_cap); } static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = { .id = SND_CHN_MATRIX_DRV, .channels = 2, .ext = 0, .map = { /* Right */ [0] = { .type = SND_CHN_T_FR, .members = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR }, /* Left */ [1] = { .type = SND_CHN_T_FL, .members = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL }, [2] = { .type = SND_CHN_T_MAX, .members = 0 } }, .mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL, .offset = { 1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } }; struct pcmchan_matrix * uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format) { struct uaudio_softc *sc; sc = ch->priv_sc; if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 && AFMT_CHANNEL(format) == 2) return (&uaudio_chan_matrix_swap_2_0); return (feeder_matrix_format_map(format)); } int uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format) { DPRINTF("Selecting format 0x%08x\n", (unsigned int)format); return (0); } static void uaudio_chan_start_sub(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; int do_start = 0; if (ch->operation != CHAN_OP_DRAIN) { if (ch->cur_alt == ch->set_alt && ch->operation == CHAN_OP_NONE && mtx_owned(ch->pcm_mtx) != 0) { /* save doing the explore task */ do_start = 1; } else { ch->operation = CHAN_OP_START; (void)usb_proc_explore_msignal(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } } if (do_start) { usbd_transfer_start(ch->xfer[0]); usbd_transfer_start(ch->xfer[1]); } } static int uaudio_chan_need_both(struct uaudio_softc *sc) { return (sc->sc_play_chan.num_alt > 0 && sc->sc_play_chan.running != 0 && uaudio_chan_is_async(&sc->sc_play_chan, sc->sc_play_chan.set_alt) != 0 && sc->sc_rec_chan.num_alt > 0 && sc->sc_rec_chan.running == 0); } static int uaudio_chan_need_none(struct uaudio_softc *sc) { return (sc->sc_play_chan.num_alt > 0 && sc->sc_play_chan.running == 0 && sc->sc_rec_chan.num_alt > 0 && sc->sc_rec_chan.running == 0); } void uaudio_chan_start(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if not running */ if (ch->running == 0) { uint32_t temp; /* get current buffer size */ temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt); /* set running flag */ ch->running = 1; /* ensure the hardware buffer is reset */ ch->start = ch->buf; ch->end = ch->buf + temp; ch->cur = ch->buf; if (uaudio_chan_need_both(sc)) { /* * Start both endpoints because of need for * jitter information: */ uaudio_chan_start_sub(&sc->sc_rec_chan); uaudio_chan_start_sub(&sc->sc_play_chan); } else { uaudio_chan_start_sub(ch); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } static void uaudio_chan_stop_sub(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; int do_stop = 0; if (ch->operation != CHAN_OP_DRAIN) { if (ch->cur_alt == ch->set_alt && ch->operation == CHAN_OP_NONE && mtx_owned(ch->pcm_mtx) != 0) { /* save doing the explore task */ do_stop = 1; } else { ch->operation = CHAN_OP_STOP; (void)usb_proc_explore_msignal(sc->sc_udev, &sc->sc_config_msg[0], &sc->sc_config_msg[1]); } } if (do_stop) { usbd_transfer_stop(ch->xfer[0]); usbd_transfer_stop(ch->xfer[1]); } } void uaudio_chan_stop(struct uaudio_chan *ch) { struct uaudio_softc *sc = ch->priv_sc; /* make operation atomic */ usb_proc_explore_lock(sc->sc_udev); /* check if running */ if (ch->running != 0) { /* clear running flag */ ch->running = 0; if (uaudio_chan_need_both(sc)) { /* * Leave the endpoints running because we need * information about jitter! */ } else if (uaudio_chan_need_none(sc)) { /* * Stop both endpoints in case the one was used for * jitter information: */ uaudio_chan_stop_sub(&sc->sc_rec_chan); uaudio_chan_stop_sub(&sc->sc_play_chan); } else { uaudio_chan_stop_sub(ch); } } /* exit atomic operation */ usb_proc_explore_unlock(sc->sc_udev); } /*========================================================================* * AC - Audio Controller - routines *========================================================================*/ static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct uaudio_softc *sc; struct uaudio_mixer_node *pmc; int hint; int error; int temp = 0; int chan = 0; sc = (struct uaudio_softc *)oidp->oid_arg1; hint = oidp->oid_arg2; if (sc->sc_mixer_lock == NULL) return (ENXIO); /* lookup mixer node */ mtx_lock(sc->sc_mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { for (chan = 0; chan != (int)pmc->nchan; chan++) { if (pmc->wValue[chan] != -1 && pmc->wValue[chan] == hint) { temp = pmc->wData[chan]; goto found; } } } found: mtx_unlock(sc->sc_mixer_lock); error = sysctl_handle_int(oidp, &temp, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* update mixer value */ mtx_lock(sc->sc_mixer_lock); if (pmc != NULL && temp >= pmc->minval && temp <= pmc->maxval) { pmc->wData[chan] = temp; pmc->update[(chan / 8)] |= (1 << (chan % 8)); /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } mtx_unlock(sc->sc_mixer_lock); return (0); } static void uaudio_mixer_ctl_free(struct uaudio_softc *sc) { struct uaudio_mixer_node *p_mc; while ((p_mc = sc->sc_mixer_root) != NULL) { sc->sc_mixer_root = p_mc->next; free(p_mc, M_USBDEV); } } static void uaudio_mixer_register_sysctl(struct uaudio_softc *sc, device_t dev) { struct uaudio_mixer_node *pmc; struct sysctl_oid *mixer_tree; struct sysctl_oid *control_tree; char buf[32]; int chan; int n; mixer_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mixer", CTLFLAG_RD, NULL, ""); if (mixer_tree == NULL) return; for (n = 0, pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next, n++) { for (chan = 0; chan < pmc->nchan; chan++) { if (pmc->nchan > 1) { snprintf(buf, sizeof(buf), "%s_%d_%d", pmc->name, n, chan); } else { snprintf(buf, sizeof(buf), "%s_%d", pmc->name, n); } control_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(mixer_tree), OID_AUTO, buf, CTLFLAG_RD, NULL, "Mixer control nodes"); if (control_tree == NULL) continue; SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "val", CTLTYPE_INT | CTLFLAG_RW, sc, pmc->wValue[chan], uaudio_mixer_sysctl_handler, "I", "Current value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "min", CTLFLAG_RD, 0, pmc->minval, "Minimum value"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "max", CTLFLAG_RD, 0, pmc->maxval, "Maximum value"); SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(control_tree), OID_AUTO, "desc", CTLFLAG_RD, pmc->desc, 0, "Description"); } } } /* M-Audio FastTrack Ultra Mixer Description */ /* Origin: Linux USB Audio driver */ static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *sc) { int chx; int chy; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(8, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect"; MIX(sc).minval = 0; MIX(sc).maxval = 7; MIX(sc).mul = 7; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Room1,2,3,Hall1,2,Plate,Delay,Echo", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chx = 0; chx != 8; chx++) { for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_rec"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "AIn%d - Out%d Record Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mix_play"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).val_default = (chx == chy) ? 2 : 0; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "DIn%d - Out%d Playback Volume", chy + 1, chx + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(2, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_vol"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(3, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_dur"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f00; MIX(sc).mul = 0x7f00; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Duration", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(4, 0); MIX(sc).class = UAC_OUTPUT; MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_fb"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; MIX(sc).minval = 0; MIX(sc).maxval = 0x7f; MIX(sc).mul = 0x7f; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; strlcpy(MIX(sc).desc, "Effect Feedback Volume", sizeof(MIX(sc).desc)); uaudio_mixer_add_ctl_sub(sc, &MIX(sc)); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(7, sc->sc_mixer_iface_no); for (chy = 0; chy != 4; chy++) { MIX(sc).wValue[0] = MAKE_WORD(7, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_ret"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Return %d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no); for (chy = 0; chy != 8; chy++) { MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send AIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1 + 8); MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "effect_send"; MIX(sc).nchan = 1; MIX(sc).update[0] = 1; snprintf(MIX(sc).desc, sizeof(MIX(sc).desc), "Effect Send DIn%d Volume", chy + 1); uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_reload_all(struct uaudio_softc *sc) { struct uaudio_mixer_node *pmc; int chan; if (sc->sc_mixer_lock == NULL) return; mtx_lock(sc->sc_mixer_lock); for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) { /* use reset defaults for non-oss controlled settings */ if (pmc->ctl == SOUND_MIXER_NRDEVICES) continue; for (chan = 0; chan < pmc->nchan; chan++) pmc->update[chan / 8] |= (1 << (chan % 8)); } usbd_transfer_start(sc->sc_mixer_xfer[0]); /* start HID volume keys, if any */ usbd_transfer_start(sc->sc_hid.xfer[0]); mtx_unlock(sc->sc_mixer_lock); } static void uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { struct uaudio_mixer_node *p_mc_new = malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK); int ch; if (p_mc_new != NULL) { memcpy(p_mc_new, mc, sizeof(*p_mc_new)); p_mc_new->next = sc->sc_mixer_root; sc->sc_mixer_root = p_mc_new; sc->sc_mixer_count++; /* set default value for all channels */ for (ch = 0; ch < p_mc_new->nchan; ch++) { switch (p_mc_new->val_default) { case 1: /* 50% */ p_mc_new->wData[ch] = (p_mc_new->maxval + p_mc_new->minval) / 2; break; case 2: /* 100% */ p_mc_new->wData[ch] = p_mc_new->maxval; break; default: /* 0% */ p_mc_new->wData[ch] = p_mc_new->minval; break; } } } else { DPRINTF("out of memory\n"); } } static void uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc) { int32_t res; if (mc->class < UAC_NCLASSES) { DPRINTF("adding %s.%d\n", uac_names[mc->class], mc->ctl); } else { DPRINTF("adding %d\n", mc->ctl); } if (mc->type == MIX_ON_OFF) { mc->minval = 0; mc->maxval = 1; } else if (mc->type == MIX_SELECTOR) { } else { /* determine min and max values */ mc->minval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MIN, mc); mc->maxval = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_MAX, mc); /* check if max and min was swapped */ if (mc->maxval < mc->minval) { res = mc->maxval; mc->maxval = mc->minval; mc->minval = res; } /* compute value range */ mc->mul = mc->maxval - mc->minval; if (mc->mul == 0) mc->mul = 1; /* compute value alignment */ res = uaudio_mixer_get(sc->sc_udev, sc->sc_audio_rev, GET_RES, mc); DPRINTF("Resolution = %d\n", (int)res); } uaudio_mixer_add_ctl_sub(sc, mc); #ifdef USB_DEBUG if (uaudio_debug > 2) { uint8_t i; for (i = 0; i < mc->nchan; i++) { DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]); } DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' " "min=%d max=%d\n", mc->wIndex, mc->type, mc->ctl, mc->minval, mc->maxval); } #endif } static void uaudio_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1; const struct usb_audio_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_SIGNED_16; if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio20_mixer_add_mixer(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2; const struct usb_audio20_mixer_unit_1 *d1; uint32_t bno; /* bit number */ uint32_t p; /* bit number accumulator */ uint32_t mo; /* matching outputs */ uint32_t mc; /* matching channels */ uint32_t ichs; /* input channels */ uint32_t ochs; /* output channels */ uint32_t c; uint32_t chs; /* channels */ uint32_t i; uint32_t o; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); /* compute the number of input channels */ ichs = 0; for (i = 0; i < d0->bNrInPins; i++) { ichs += uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; } d1 = (const void *)(d0->baSourceId + d0->bNrInPins); /* and the number of output channels */ ochs = d1->bNrChannels; DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs); memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); uaudio20_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_SIGNED_16; if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL) return; for (p = i = 0; i < d0->bNrInPins; i++) { chs = uaudio20_mixer_get_cluster( d0->baSourceId[i], iot).bNrChannels; mc = 0; for (c = 0; c < chs; c++) { mo = 0; for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) mo++; } if (mo == 1) mc++; } if ((mc == chs) && (chs <= MIX_MAX_CHAN)) { /* repeat bit-scan */ mc = 0; for (c = 0; c < chs; c++) { for (o = 0; o < ochs; o++) { bno = ((p + c) * ochs) + o; if (BIT_TEST(d1->bmControls, bno)) MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1); } } MIX(sc).nchan = chs; uaudio_mixer_add_ctl(sc, &MIX(sc)); } p += chs; } } static void uaudio_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_selector_unit *d = iot[id].u.su_v1; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) { MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; } MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval); for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) { MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; } for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio_mixer_feature_name( &iot[d->baSourceId[i]], &MIX(sc)); } MIX(sc).class = 0; /* not used */ uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio20_mixer_add_selector(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_selector_unit *d = iot[id].u.su_v2; uint16_t i; DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins); if (d->bNrInPins == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); MIX(sc).wValue[0] = MAKE_WORD(0, 0); uaudio20_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).nchan = 1; MIX(sc).type = MIX_SELECTOR; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).minval = 1; MIX(sc).maxval = d->bNrInPins; MIX(sc).name = "selector"; i = d->baSourceId[d->bNrInPins]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN; MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval); for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES; for (i = 0; i < MIX(sc).maxval; i++) { MIX(sc).slctrtype[i] = uaudio20_mixer_feature_name( &iot[d->baSourceId[i]], &MIX(sc)); } MIX(sc).class = 0; /* not used */ uaudio_mixer_add_ctl(sc, &MIX(sc)); } static uint32_t uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d, uint8_t i) { uint32_t temp = 0; uint32_t offset = (i * d->bControlSize); if (d->bControlSize > 0) { temp |= d->bmaControls[offset]; if (d->bControlSize > 1) { temp |= d->bmaControls[offset + 1] << 8; if (d->bControlSize > 2) { temp |= d->bmaControls[offset + 2] << 16; if (d->bControlSize > 3) { temp |= d->bmaControls[offset + 3] << 24; } } } } return (temp); } static void uaudio_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_feature_unit *d = iot[id].u.fu_v1; uint32_t fumask; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t ctl; uint8_t i; if (d->bControlSize == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 7) / d->bControlSize; mmask = uaudio_mixer_feature_get_bmaControls(d, 0); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) { DPRINTFN(10, "chan=%d mask=%x\n", chan, uaudio_mixer_feature_get_bmaControls(d, chan)); cmask |= uaudio_mixer_feature_get_bmaControls(d, chan); } if (nchan > MIX_MAX_CHAN) { nchan = MIX_MAX_CHAN; } MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[d->bControlSize]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) { fumask = FU_MASK(ctl); DPRINTFN(5, "ctl=%d fumask=0x%04x\n", ctl, fumask); if (mmask & fumask) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(ctl, 0); } else if (cmask & fumask) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask) MIX(sc).wValue[i - 1] = MAKE_WORD(ctl, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } mixernumber = uaudio_mixer_feature_name(&iot[id], &MIX(sc)); switch (ctl) { case MUTE_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mute"; break; case VOLUME_CONTROL: MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; break; case BASS_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; break; case MID_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; break; case TREBLE_CONTROL: MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; break; case GRAPHIC_EQUALIZER_CONTROL: continue; /* XXX don't add anything */ case AGC_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; break; case DELAY_CONTROL: MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; break; case BASS_BOOST_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; break; case LOUDNESS_CONTROL: MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; break; default: MIX(sc).type = MIX_UNKNOWN; break; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio20_mixer_add_feature(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2; uint32_t ctl; uint32_t mmask; uint32_t cmask; uint16_t mixernumber; uint8_t nchan; uint8_t chan; uint8_t i; uint8_t what; if (UGETDW(d->bmaControls[0]) == 0) return; memset(&MIX(sc), 0, sizeof(MIX(sc))); nchan = (d->bLength - 6) / 4; mmask = UGETDW(d->bmaControls[0]); cmask = 0; if (nchan == 0) return; /* figure out what we can control */ for (chan = 1; chan < nchan; chan++) cmask |= UGETDW(d->bmaControls[chan]); if (nchan > MIX_MAX_CHAN) nchan = MIX_MAX_CHAN; MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no); i = d->bmaControls[nchan][0]; if (i == 0 || usbd_req_get_string_any(sc->sc_udev, NULL, MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) { MIX(sc).desc[0] = 0; } for (ctl = 3; ctl != 0; ctl <<= 2) { mixernumber = uaudio20_mixer_feature_name(&iot[id], &MIX(sc)); switch (ctl) { case (3 << 0): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; MIX(sc).name = "mute"; what = MUTE_CONTROL; break; case (3 << 2): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "vol"; what = VOLUME_CONTROL; break; case (3 << 4): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_BASS; MIX(sc).name = "bass"; what = BASS_CONTROL; break; case (3 << 6): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "mid"; what = MID_CONTROL; break; case (3 << 8): MIX(sc).type = MIX_SIGNED_8; MIX(sc).ctl = SOUND_MIXER_TREBLE; MIX(sc).name = "treble"; what = TREBLE_CONTROL; break; case (3 << 12): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "agc"; what = AGC_CONTROL; break; case (3 << 14): MIX(sc).type = MIX_UNSIGNED_16; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "delay"; what = DELAY_CONTROL; break; case (3 << 16): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_NRDEVICES; /* XXXXX */ MIX(sc).name = "boost"; what = BASS_BOOST_CONTROL; break; case (3 << 18): MIX(sc).type = MIX_ON_OFF; MIX(sc).ctl = SOUND_MIXER_LOUD; /* Is this correct ? */ MIX(sc).name = "loudness"; what = LOUDNESS_CONTROL; break; case (3 << 20): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igain"; what = INPUT_GAIN_CONTROL; break; case (3 << 22): MIX(sc).type = MIX_SIGNED_16; MIX(sc).ctl = mixernumber; MIX(sc).name = "igainpad"; what = INPUT_GAIN_PAD_CONTROL; break; default: continue; } if ((mmask & ctl) == ctl) { MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(what, 0); } else if ((cmask & ctl) == ctl) { MIX(sc).nchan = nchan - 1; for (i = 1; i < nchan; i++) { if ((UGETDW(d->bmaControls[i]) & ctl) == ctl) MIX(sc).wValue[i - 1] = MAKE_WORD(what, i); else MIX(sc).wValue[i - 1] = -1; } } else { continue; } if (MIX(sc).type != MIX_UNKNOWN) uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static void uaudio_mixer_add_processing_updown(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); const struct usb_audio_processing_unit_updown *ud = (const void *)(d1->bmControls + d1->bControlSize); uint8_t i; if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) { return; } if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes)) == NULL) { return; } DPRINTFN(3, "bUnitId=%d bNrModes=%d\n", d0->bUnitId, ud->bNrModes); if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) { DPRINTF("no mode select\n"); return; } memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_ON_OFF; /* XXX */ for (i = 0; i < ud->bNrModes; i++) { DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i])); /* XXX */ } uaudio_mixer_add_ctl(sc, &MIX(sc)); } static void uaudio_mixer_add_processing(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1; const struct usb_audio_processing_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); uint16_t ptype; memset(&MIX(sc), 0, sizeof(MIX(sc))); ptype = UGETW(d0->wProcessType); DPRINTFN(3, "wProcessType=%d bUnitId=%d " "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins); if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) { MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } switch (ptype) { case UPDOWNMIX_PROCESS: uaudio_mixer_add_processing_updown(sc, iot, id); break; case DOLBY_PROLOGIC_PROCESS: case P3D_STEREO_EXTENDER_PROCESS: case REVERBATION_PROCESS: case CHORUS_PROCESS: case DYN_RANGE_COMP_PROCESS: default: DPRINTF("unit %d, type=%d is not implemented\n", d0->bUnitId, ptype); break; } } static void uaudio_mixer_add_extension(struct uaudio_softc *sc, const struct uaudio_terminal_node *iot, int id) { const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1; const struct usb_audio_extension_unit_1 *d1 = (const void *)(d0->baSourceId + d0->bNrInPins); DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n", d0->bUnitId, d0->bNrInPins); if (sc->sc_uq_au_no_xu) { return; } if (d1->bControlSize == 0) { return; } if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) { memset(&MIX(sc), 0, sizeof(MIX(sc))); MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no); MIX(sc).nchan = 1; MIX(sc).wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0); uaudio_mixer_determine_class(&iot[id], &MIX(sc)); MIX(sc).type = MIX_ON_OFF; uaudio_mixer_add_ctl(sc, &MIX(sc)); } } static const void * uaudio_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio_mixer_unit_1 *d1; const struct usb_audio_extension_unit_1 *e1; const struct usb_audio_processing_unit_1 *u1; union { const struct usb_descriptor *desc; const struct usb_audio_input_terminal *it; const struct usb_audio_output_terminal *ot; const struct usb_audio_mixer_unit_0 *mu; const struct usb_audio_selector_unit *su; const struct usb_audio_feature_unit *fu; const struct usb_audio_processing_unit_0 *pu; const struct usb_audio_extension_unit_0 *eu; } u; u.desc = arg; if (u.desc == NULL) { goto error; } if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) { goto error; } switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) { goto error; } len += u.mu->bNrInPins; if (u.desc->bLength < len) { goto error; } d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1); break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) { goto error; } len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; if (u.desc->bLength < len) goto error; len += u.fu->bControlSize; break; case UDESCSUB_AC_PROCESSING: len += sizeof(*u.pu); if (u.desc->bLength < len) { goto error; } len += u.pu->bNrInPins; if (u.desc->bLength < len) { goto error; } u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); if (u.desc->bLength < len) { goto error; } len += u1->bControlSize; break; case UDESCSUB_AC_EXTENSION: len += sizeof(*u.eu); if (u.desc->bLength < len) { goto error; } len += u.eu->bNrInPins; if (u.desc->bLength < len) { goto error; } e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); if (u.desc->bLength < len) { goto error; } len += e1->bControlSize; break; default: goto error; } if (u.desc->bLength < len) { goto error; } return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static const void * uaudio20_mixer_verify_desc(const void *arg, uint32_t len) { const struct usb_audio20_mixer_unit_1 *d1; const struct usb_audio20_extension_unit_1 *e1; const struct usb_audio20_processing_unit_1 *u1; const struct usb_audio20_clock_selector_unit_1 *c1; union { const struct usb_descriptor *desc; const struct usb_audio20_clock_source_unit *csrc; const struct usb_audio20_clock_selector_unit_0 *csel; const struct usb_audio20_clock_multiplier_unit *cmul; const struct usb_audio20_input_terminal *it; const struct usb_audio20_output_terminal *ot; const struct usb_audio20_mixer_unit_0 *mu; const struct usb_audio20_selector_unit *su; const struct usb_audio20_feature_unit *fu; const struct usb_audio20_sample_rate_unit *ru; const struct usb_audio20_processing_unit_0 *pu; const struct usb_audio20_extension_unit_0 *eu; const struct usb_audio20_effect_unit *ef; } u; u.desc = arg; if (u.desc == NULL) goto error; if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) goto error; switch (u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: len += sizeof(*u.it); break; case UDESCSUB_AC_OUTPUT: len += sizeof(*u.ot); break; case UDESCSUB_AC_MIXER: len += sizeof(*u.mu); if (u.desc->bLength < len) goto error; len += u.mu->bNrInPins; if (u.desc->bLength < len) goto error; d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins); len += sizeof(*d1) + d1->bNrChannels; break; case UDESCSUB_AC_SELECTOR: len += sizeof(*u.su); if (u.desc->bLength < len) goto error; len += u.su->bNrInPins + 1; break; case UDESCSUB_AC_FEATURE: len += sizeof(*u.fu) + 1; break; case UDESCSUB_AC_EFFECT: len += sizeof(*u.ef) + 4; break; case UDESCSUB_AC_PROCESSING_V2: len += sizeof(*u.pu); if (u.desc->bLength < len) goto error; len += u.pu->bNrInPins; if (u.desc->bLength < len) goto error; u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins); len += sizeof(*u1); break; case UDESCSUB_AC_EXTENSION_V2: len += sizeof(*u.eu); if (u.desc->bLength < len) goto error; len += u.eu->bNrInPins; if (u.desc->bLength < len) goto error; e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins); len += sizeof(*e1); break; case UDESCSUB_AC_CLOCK_SRC: len += sizeof(*u.csrc); break; case UDESCSUB_AC_CLOCK_SEL: len += sizeof(*u.csel); if (u.desc->bLength < len) goto error; len += u.csel->bNrInPins; if (u.desc->bLength < len) goto error; c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins); len += sizeof(*c1); break; case UDESCSUB_AC_CLOCK_MUL: len += sizeof(*u.cmul); break; case UDESCSUB_AC_SAMPLE_RT: len += sizeof(*u.ru); break; default: goto error; } if (u.desc->bLength < len) goto error; return (u.desc); error: if (u.desc) { DPRINTF("invalid descriptor, type=%d, " "sub_type=%d, len=%d of %d bytes\n", u.desc->bDescriptorType, u.desc->bDescriptorSubtype, u.desc->bLength, len); } return (NULL); } static struct usb_audio_cluster uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) { goto error; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v1->bNrChannels; r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0]; r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1]; r.iChannelNames = iot[id].u.it_v1->iChannelNames; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v1->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio_cluster *) &iot[id].u.mu_v1->baSourceId[ iot[id].u.mu_v1->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v1->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v1->baSourceId[0]; } break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v1->bSourceId; break; case UDESCSUB_AC_PROCESSING: r = *((const struct usb_audio_cluster *) &iot[id].u.pu_v1->baSourceId[ iot[id].u.pu_v1->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION: r = *((const struct usb_audio_cluster *) &iot[id].u.eu_v1->baSourceId[ iot[id].u.eu_v1->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("bad data\n"); memset(&r, 0, sizeof(r)); done: return (r); } static struct usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot) { struct usb_audio20_cluster r; const struct usb_descriptor *dp; uint8_t i; for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) { /* avoid infinite loops */ dp = iot[id].u.desc; if (dp == NULL) goto error; switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: r.bNrChannels = iot[id].u.it_v2->bNrChannels; r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0]; r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1]; r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2]; r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3]; r.iChannelNames = iot[id].u.it_v2->iTerminal; goto done; case UDESCSUB_AC_OUTPUT: id = iot[id].u.ot_v2->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio20_cluster *) &iot[id].u.mu_v2->baSourceId[ iot[id].u.mu_v2->bNrInPins]; goto done; case UDESCSUB_AC_SELECTOR: if (iot[id].u.su_v2->bNrInPins > 0) { /* XXX This is not really right */ id = iot[id].u.su_v2->baSourceId[0]; } break; case UDESCSUB_AC_SAMPLE_RT: id = iot[id].u.ru_v2->bSourceId; break; case UDESCSUB_AC_EFFECT: id = iot[id].u.ef_v2->bSourceId; break; case UDESCSUB_AC_FEATURE: id = iot[id].u.fu_v2->bSourceId; break; case UDESCSUB_AC_PROCESSING_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.pu_v2->baSourceId[ iot[id].u.pu_v2->bNrInPins]); goto done; case UDESCSUB_AC_EXTENSION_V2: r = *((const struct usb_audio20_cluster *) &iot[id].u.eu_v2->baSourceId[ iot[id].u.eu_v2->bNrInPins]); goto done; default: goto error; } } error: DPRINTF("Bad data!\n"); memset(&r, 0, sizeof(r)); done: return (r); } static uint16_t uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { uint16_t terminal_type = 0x0000; const struct uaudio_terminal_node *input[2]; const struct uaudio_terminal_node *output[2]; input[0] = uaudio_mixer_get_input(iot, 0); input[1] = uaudio_mixer_get_input(iot, 1); output[0] = uaudio_mixer_get_output(iot, 0); output[1] = uaudio_mixer_get_output(iot, 1); /* * check if there is only * one output terminal: */ if (output[0] && (!output[1])) { terminal_type = UGETW(output[0]->u.ot_v1->wTerminalType); } /* * If the only output terminal is USB, * the class is UAC_RECORD. */ if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) { mix->class = UAC_RECORD; if (input[0] && (!input[1])) { terminal_type = UGETW(input[0]->u.it_v1->wTerminalType); } else { terminal_type = 0; } goto done; } /* * if the unit is connected to just * one input terminal, the * class is UAC_INPUT: */ if (input[0] && (!input[1])) { mix->class = UAC_INPUT; terminal_type = UGETW(input[0]->u.it_v1->wTerminalType); goto done; } /* * Otherwise, the class is UAC_OUTPUT. */ mix->class = UAC_OUTPUT; done: return (terminal_type); } static uint16_t uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { uint16_t terminal_type = 0x0000; const struct uaudio_terminal_node *input[2]; const struct uaudio_terminal_node *output[2]; input[0] = uaudio_mixer_get_input(iot, 0); input[1] = uaudio_mixer_get_input(iot, 1); output[0] = uaudio_mixer_get_output(iot, 0); output[1] = uaudio_mixer_get_output(iot, 1); /* * check if there is only * one output terminal: */ if (output[0] && (!output[1])) terminal_type = UGETW(output[0]->u.ot_v2->wTerminalType); /* * If the only output terminal is USB, * the class is UAC_RECORD. */ if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) { mix->class = UAC_RECORD; if (input[0] && (!input[1])) { terminal_type = UGETW(input[0]->u.it_v2->wTerminalType); } else { terminal_type = 0; } goto done; } /* * if the unit is connected to just * one input terminal, the * class is UAC_INPUT: */ if (input[0] && (!input[1])) { mix->class = UAC_INPUT; terminal_type = UGETW(input[0]->u.it_v2->wTerminalType); goto done; } /* * Otherwise, the class is UAC_OUTPUT. */ mix->class = UAC_OUTPUT; done: return (terminal_type); } struct uaudio_tt_to_feature { uint16_t terminal_type; uint16_t feature; }; static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = { {UAT_STREAM, SOUND_MIXER_PCM}, {UATI_MICROPHONE, SOUND_MIXER_MIC}, {UATI_DESKMICROPHONE, SOUND_MIXER_MIC}, {UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC}, {UATI_OMNIMICROPHONE, SOUND_MIXER_MIC}, {UATI_MICROPHONEARRAY, SOUND_MIXER_MIC}, {UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC}, {UATO_SPEAKER, SOUND_MIXER_SPEAKER}, {UATO_DESKTOPSPEAKER, SOUND_MIXER_SPEAKER}, {UATO_ROOMSPEAKER, SOUND_MIXER_SPEAKER}, {UATO_COMMSPEAKER, SOUND_MIXER_SPEAKER}, {UATE_ANALOGCONN, SOUND_MIXER_LINE}, {UATE_LINECONN, SOUND_MIXER_LINE}, {UATE_LEGACYCONN, SOUND_MIXER_LINE}, {UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM}, {UATE_SPDIF, SOUND_MIXER_ALTPCM}, {UATE_1394DA, SOUND_MIXER_ALTPCM}, {UATE_1394DV, SOUND_MIXER_ALTPCM}, {UATF_CDPLAYER, SOUND_MIXER_CD}, {UATF_SYNTHESIZER, SOUND_MIXER_SYNTH}, {UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO}, {UATF_DVDAUDIO, SOUND_MIXER_VIDEO}, {UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO}, /* telephony terminal types */ {UATT_UNDEFINED, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATT_PHONELINE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATT_TELEPHONE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATT_DOWNLINEPHONE, SOUND_MIXER_PHONEIN}, /* SOUND_MIXER_PHONEOUT */ {UATF_RADIORECV, SOUND_MIXER_RADIO}, {UATF_RADIOXMIT, SOUND_MIXER_RADIO}, {UAT_UNDEFINED, SOUND_MIXER_VOLUME}, {UAT_VENDOR, SOUND_MIXER_VOLUME}, {UATI_UNDEFINED, SOUND_MIXER_VOLUME}, /* output terminal types */ {UATO_UNDEFINED, SOUND_MIXER_VOLUME}, {UATO_DISPLAYAUDIO, SOUND_MIXER_VOLUME}, {UATO_SUBWOOFER, SOUND_MIXER_VOLUME}, {UATO_HEADPHONES, SOUND_MIXER_VOLUME}, /* bidir terminal types */ {UATB_UNDEFINED, SOUND_MIXER_VOLUME}, {UATB_HANDSET, SOUND_MIXER_VOLUME}, {UATB_HEADSET, SOUND_MIXER_VOLUME}, {UATB_SPEAKERPHONE, SOUND_MIXER_VOLUME}, {UATB_SPEAKERPHONEESUP, SOUND_MIXER_VOLUME}, {UATB_SPEAKERPHONEECANC, SOUND_MIXER_VOLUME}, /* external terminal types */ {UATE_UNDEFINED, SOUND_MIXER_VOLUME}, /* embedded function terminal types */ {UATF_UNDEFINED, SOUND_MIXER_VOLUME}, {UATF_CALIBNOISE, SOUND_MIXER_VOLUME}, {UATF_EQUNOISE, SOUND_MIXER_VOLUME}, {UATF_DAT, SOUND_MIXER_VOLUME}, {UATF_DCC, SOUND_MIXER_VOLUME}, {UATF_MINIDISK, SOUND_MIXER_VOLUME}, {UATF_ANALOGTAPE, SOUND_MIXER_VOLUME}, {UATF_PHONOGRAPH, SOUND_MIXER_VOLUME}, {UATF_VCRAUDIO, SOUND_MIXER_VOLUME}, {UATF_SATELLITE, SOUND_MIXER_VOLUME}, {UATF_CABLETUNER, SOUND_MIXER_VOLUME}, {UATF_DSS, SOUND_MIXER_VOLUME}, {UATF_MULTITRACK, SOUND_MIXER_VOLUME}, {0xffff, SOUND_MIXER_VOLUME}, /* default */ {0x0000, SOUND_MIXER_VOLUME}, }; static uint16_t uaudio_mixer_feature_name(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature; uint16_t terminal_type = uaudio_mixer_determine_class(iot, mix); if ((mix->class == UAC_RECORD) && (terminal_type == 0)) { return (SOUND_MIXER_IMIX); } while (uat->terminal_type) { if (uat->terminal_type == terminal_type) { break; } uat++; } DPRINTF("terminal_type=0x%04x -> %d\n", terminal_type, uat->feature); return (uat->feature); } static uint16_t uaudio20_mixer_feature_name(const struct uaudio_terminal_node *iot, struct uaudio_mixer_node *mix) { const struct uaudio_tt_to_feature *uat; uint16_t terminal_type = uaudio20_mixer_determine_class(iot, mix); if ((mix->class == UAC_RECORD) && (terminal_type == 0)) return (SOUND_MIXER_IMIX); for (uat = uaudio_tt_to_feature; uat->terminal_type != 0; uat++) { if (uat->terminal_type == terminal_type) break; } DPRINTF("terminal_type=0x%04x -> %d\n", terminal_type, uat->feature); return (uat->feature); } static const struct uaudio_terminal_node * uaudio_mixer_get_input(const struct uaudio_terminal_node *iot, uint8_t i) { struct uaudio_terminal_node *root = iot->root; uint8_t n; n = iot->usr.id_max; do { if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) { if (!i--) return (root + n); } } while (n--); return (NULL); } static const struct uaudio_terminal_node * uaudio_mixer_get_output(const struct uaudio_terminal_node *iot, uint8_t i) { struct uaudio_terminal_node *root = iot->root; uint8_t n; n = iot->usr.id_max; do { if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) { if (!i--) return (root + n); } } while (n--); return (NULL); } static void uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_FEATURE: if (is_last) { p_id = &iot->u.fu_v1->bSourceId; n_id = 1; goto top; } uaudio_mixer_find_inputs_sub( root, &iot->u.fu_v1->bSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: if (is_last) { p_id = &iot->u.ot_v1->bSourceId; n_id = 1; goto top; } uaudio_mixer_find_inputs_sub( root, &iot->u.ot_v1->bSourceId, 1, info); break; case UDESCSUB_AC_MIXER: if (is_last) { p_id = iot->u.mu_v1->baSourceId; n_id = iot->u.mu_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.mu_v1->baSourceId, iot->u.mu_v1->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: if (is_last) { p_id = iot->u.su_v1->baSourceId; n_id = iot->u.su_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.su_v1->baSourceId, iot->u.su_v1->bNrInPins, info); break; case UDESCSUB_AC_PROCESSING: if (is_last) { p_id = iot->u.pu_v1->baSourceId; n_id = iot->u.pu_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.pu_v1->baSourceId, iot->u.pu_v1->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION: if (is_last) { p_id = iot->u.eu_v1->baSourceId; n_id = iot->u.eu_v1->bNrInPins; goto top; } uaudio_mixer_find_inputs_sub( root, iot->u.eu_v1->baSourceId, iot->u.eu_v1->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->bit_input[i / 8] |= (1 << (i % 8)); break; case UDESCSUB_AC_OUTPUT: if (is_last) { p_id = &iot->u.ot_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.ot_v2->bSourceId, 1, info); break; case UDESCSUB_AC_MIXER: if (is_last) { p_id = iot->u.mu_v2->baSourceId; n_id = iot->u.mu_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.mu_v2->baSourceId, iot->u.mu_v2->bNrInPins, info); break; case UDESCSUB_AC_SELECTOR: if (is_last) { p_id = iot->u.su_v2->baSourceId; n_id = iot->u.su_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.su_v2->baSourceId, iot->u.su_v2->bNrInPins, info); break; case UDESCSUB_AC_SAMPLE_RT: if (is_last) { p_id = &iot->u.ru_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.ru_v2->bSourceId, 1, info); break; case UDESCSUB_AC_EFFECT: if (is_last) { p_id = &iot->u.ef_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.ef_v2->bSourceId, 1, info); break; case UDESCSUB_AC_FEATURE: if (is_last) { p_id = &iot->u.fu_v2->bSourceId; n_id = 1; goto top; } uaudio20_mixer_find_inputs_sub( root, &iot->u.fu_v2->bSourceId, 1, info); break; case UDESCSUB_AC_PROCESSING_V2: if (is_last) { p_id = iot->u.pu_v2->baSourceId; n_id = iot->u.pu_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.pu_v2->baSourceId, iot->u.pu_v2->bNrInPins, info); break; case UDESCSUB_AC_EXTENSION_V2: if (is_last) { p_id = iot->u.eu_v2->baSourceId; n_id = iot->u.eu_v2->bNrInPins; goto top; } uaudio20_mixer_find_inputs_sub( root, iot->u.eu_v2->baSourceId, iot->u.eu_v2->bNrInPins, info); break; default: break; } } } static void uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root, const uint8_t *p_id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot; uint8_t n; uint8_t i; uint8_t is_last; uint8_t id; top: for (n = 0; n < n_id; n++) { i = p_id[n]; if (info->recurse_level == UAUDIO_RECURSE_LIMIT) { DPRINTF("avoided going into a circle at id=%d!\n", i); return; } info->recurse_level++; iot = (root + i); if (iot->u.desc == NULL) continue; is_last = ((n + 1) == n_id); switch (iot->u.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: info->is_input = 1; if (is_last) { p_id = &iot->u.it_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.it_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_OUTPUT: info->is_input = 0; if (is_last) { p_id = &iot->u.ot_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.ot_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SEL: if (is_last) { p_id = iot->u.csel_v2->baCSourceId; n_id = iot->u.csel_v2->bNrInPins; goto top; } uaudio20_mixer_find_clocks_sub(root, iot->u.csel_v2->baCSourceId, iot->u.csel_v2->bNrInPins, info); break; case UDESCSUB_AC_CLOCK_MUL: if (is_last) { p_id = &iot->u.cmul_v2->bCSourceId; n_id = 1; goto top; } uaudio20_mixer_find_clocks_sub(root, &iot->u.cmul_v2->bCSourceId, 1, info); break; case UDESCSUB_AC_CLOCK_SRC: id = iot->u.csrc_v2->bClockId; switch (info->is_input) { case 0: info->bit_output[id / 8] |= (1 << (id % 8)); break; case 1: info->bit_input[id / 8] |= (1 << (id % 8)); break; default: break; } break; default: break; } } } static void uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *root, uint8_t id, uint8_t n_id, struct uaudio_search_result *info) { struct uaudio_terminal_node *iot = (root + id); uint8_t j; j = n_id; do { if ((j != id) && ((root + j)->u.desc) && ((root + j)->u.desc->bDescriptorSubtype == UDESCSUB_AC_OUTPUT)) { /* * "j" (output) <--- virtual wire <--- "id" (input) * * if "j" has "id" on the input, then "id" have "j" on * the output, because they are connected: */ if ((root + j)->usr.bit_input[id / 8] & (1 << (id % 8))) { iot->usr.bit_output[j / 8] |= (1 << (j % 8)); } } } while (j--); } static void uaudio_mixer_fill_info(struct uaudio_softc *sc, struct usb_device *udev, void *desc) { const struct usb_audio_control_descriptor *acdp; struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); const struct usb_descriptor *dp; const struct usb_audio_unit *au; struct uaudio_terminal_node *iot = NULL; uint16_t wTotalLen; uint8_t ID_max = 0; /* inclusive */ uint8_t i; desc = usb_desc_foreach(cd, desc); if (desc == NULL) { DPRINTF("no Audio Control header\n"); goto done; } acdp = desc; if ((acdp->bLength < sizeof(*acdp)) || (acdp->bDescriptorType != UDESC_CS_INTERFACE) || (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) { DPRINTF("invalid Audio Control header\n"); goto done; } /* "wTotalLen" is allowed to be corrupt */ wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength; /* get USB audio revision */ sc->sc_audio_rev = UGETW(acdp->bcdADC); DPRINTFN(3, "found AC header, vers=%03x, len=%d\n", sc->sc_audio_rev, wTotalLen); iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP, M_WAITOK | M_ZERO); if (iot == NULL) { DPRINTF("no memory!\n"); goto done; } while ((desc = usb_desc_foreach(cd, desc))) { dp = desc; if (dp->bLength > wTotalLen) { break; } else { wTotalLen -= dp->bLength; } if (sc->sc_audio_rev >= UAUDIO_VERSION_30) au = NULL; else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) au = uaudio20_mixer_verify_desc(dp, 0); else au = uaudio_mixer_verify_desc(dp, 0); if (au) { iot[au->bUnitId].u.desc = (const void *)au; if (au->bUnitId > ID_max) ID_max = au->bUnitId; } } DPRINTF("Maximum ID=%d\n", ID_max); /* * determine sourcing inputs for * all nodes in the tree: */ i = ID_max; do { if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { /* FALLTHROUGH */ } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { uaudio20_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); sc->sc_mixer_clocks.is_input = 255; sc->sc_mixer_clocks.recurse_level = 0; uaudio20_mixer_find_clocks_sub(iot, &i, 1, &sc->sc_mixer_clocks); } else { uaudio_mixer_find_inputs_sub(iot, &i, 1, &((iot + i)->usr)); } } while (i--); /* * determine outputs for * all nodes in the tree: */ i = ID_max; do { uaudio_mixer_find_outputs_sub(iot, i, ID_max, &((iot + i)->usr)); } while (i--); /* set "id_max" and "root" */ i = ID_max; do { (iot + i)->usr.id_max = ID_max; (iot + i)->root = iot; } while (i--); /* * Scan the config to create a linked list of "mixer" nodes: */ i = ID_max; do { dp = iot[i].u.desc; if (dp == NULL) continue; DPRINTFN(11, "id=%d subtype=%d\n", i, dp->bDescriptorSubtype); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { continue; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: case UDESCSUB_AC_PROCESSING_V2: case UDESCSUB_AC_EXTENSION_V2: case UDESCSUB_AC_EFFECT: case UDESCSUB_AC_CLOCK_SRC: case UDESCSUB_AC_CLOCK_SEL: case UDESCSUB_AC_CLOCK_MUL: case UDESCSUB_AC_SAMPLE_RT: break; case UDESCSUB_AC_MIXER: uaudio20_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio20_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio20_mixer_add_feature(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } continue; } switch (dp->bDescriptorSubtype) { case UDESCSUB_AC_HEADER: DPRINTF("unexpected AC header\n"); break; case UDESCSUB_AC_INPUT: case UDESCSUB_AC_OUTPUT: break; case UDESCSUB_AC_MIXER: uaudio_mixer_add_mixer(sc, iot, i); break; case UDESCSUB_AC_SELECTOR: uaudio_mixer_add_selector(sc, iot, i); break; case UDESCSUB_AC_FEATURE: uaudio_mixer_add_feature(sc, iot, i); break; case UDESCSUB_AC_PROCESSING: uaudio_mixer_add_processing(sc, iot, i); break; case UDESCSUB_AC_EXTENSION: uaudio_mixer_add_extension(sc, iot, i); break; default: DPRINTF("bad AC desc subtype=0x%02x\n", dp->bDescriptorSubtype); break; } } while (i--); done: free(iot, M_TEMP); } static int uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev, uint8_t what, struct uaudio_mixer_node *mc) { struct usb_device_request req; int val; uint8_t data[2 + (2 * 3)]; usb_error_t err; if (mc->wValue[0] == -1) return (0); if (audio_rev >= UAUDIO_VERSION_30) return (0); else if (audio_rev >= UAUDIO_VERSION_20) { if (what == GET_CUR) { req.bRequest = UA20_CS_CUR; USETW(req.wLength, 2); } else { req.bRequest = UA20_CS_RANGE; USETW(req.wLength, 8); } } else { uint16_t len = MIX_SIZE(mc->type); req.bRequest = what; USETW(req.wLength, len); } req.bmRequestType = UT_READ_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[0]); USETW(req.wIndex, mc->wIndex); memset(data, 0, sizeof(data)); err = usbd_do_request(udev, NULL, &req, data); if (err) { DPRINTF("err=%s\n", usbd_errstr(err)); return (0); } if (audio_rev >= UAUDIO_VERSION_30) { val = 0; } else if (audio_rev >= UAUDIO_VERSION_20) { switch (what) { case GET_CUR: val = (data[0] | (data[1] << 8)); break; case GET_MIN: val = (data[2] | (data[3] << 8)); break; case GET_MAX: val = (data[4] | (data[5] << 8)); break; case GET_RES: val = (data[6] | (data[7] << 8)); break; default: val = 0; break; } } else { val = (data[0] | (data[1] << 8)); } if (what == GET_CUR || what == GET_MIN || what == GET_MAX) val = uaudio_mixer_signext(mc->type, val); DPRINTFN(3, "val=%d\n", val); return (val); } static void uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_device_request req; struct uaudio_softc *sc = usbd_xfer_softc(xfer); struct uaudio_mixer_node *mc = sc->sc_mixer_curr; struct usb_page_cache *pc; uint16_t len; uint8_t repeat = 1; uint8_t update; uint8_t chan; uint8_t buf[2]; DPRINTF("\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: tr_transferred: case USB_ST_SETUP: tr_setup: if (mc == NULL) { mc = sc->sc_mixer_root; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; repeat = 0; } while (mc) { while (sc->sc_mixer_chan < mc->nchan) { chan = sc->sc_mixer_chan; sc->sc_mixer_chan++; update = ((mc->update[chan / 8] & (1 << (chan % 8))) && (mc->wValue[chan] != -1)); mc->update[chan / 8] &= ~(1 << (chan % 8)); if (update) { req.bmRequestType = UT_WRITE_CLASS_INTERFACE; USETW(req.wValue, mc->wValue[chan]); USETW(req.wIndex, mc->wIndex); if (sc->sc_audio_rev >= UAUDIO_VERSION_30) { return; } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) { len = 2; req.bRequest = UA20_CS_CUR; USETW(req.wLength, len); } else { len = MIX_SIZE(mc->type); req.bRequest = SET_CUR; USETW(req.wLength, len); } buf[0] = (mc->wData[chan] & 0xFF); buf[1] = (mc->wData[chan] >> 8) & 0xFF; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, buf, len); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, len); usbd_xfer_set_frames(xfer, len ? 2 : 1); usbd_transfer_submit(xfer); return; } } mc = mc->next; sc->sc_mixer_curr = mc; sc->sc_mixer_chan = 0; } if (repeat) { goto tr_setup; } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error == USB_ERR_CANCELLED) { /* do nothing - we are detaching */ break; } goto tr_transferred; } } static usb_error_t uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed) { struct usb_device_request req; uint8_t data[3]; DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed); req.bmRequestType = UT_WRITE_CLASS_ENDPOINT; req.bRequest = SET_CUR; USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0); USETW(req.wIndex, endpt); USETW(req.wLength, 3); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; return (usbd_do_request(udev, NULL, &req, data)); } static usb_error_t uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no, uint8_t clockid, uint32_t speed) { struct usb_device_request req; uint8_t data[4]; DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n", iface_no, clockid, speed); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UA20_CS_CUR; USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0); USETW2(req.wIndex, clockid, iface_no); USETW(req.wLength, 4); data[0] = speed; data[1] = speed >> 8; data[2] = speed >> 16; data[3] = speed >> 24; return (usbd_do_request(udev, NULL, &req, data)); } static int uaudio_mixer_signext(uint8_t type, int val) { if (!MIX_UNSIGNED(type)) { if (MIX_SIZE(type) == 2) { val = (int16_t)val; } else { val = (int8_t)val; } } return (val); } static int uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int32_t val) { if (mc->type == MIX_ON_OFF) { val = (val != 0); } else if (mc->type == MIX_SELECTOR) { if ((val < mc->minval) || (val > mc->maxval)) { val = mc->minval; } } else { /* compute actual volume */ val = (val * mc->mul) / 255; /* add lower offset */ val = val + mc->minval; /* make sure we don't write a value out of range */ if (val > mc->maxval) val = mc->maxval; else if (val < mc->minval) val = mc->minval; } DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n", mc->type, val, mc->minval, mc->maxval, val); return (val); } static void uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc, uint8_t chan, int32_t val) { val = uaudio_mixer_bsd2value(mc, val); mc->update[chan / 8] |= (1 << (chan % 8)); mc->wData[chan] = val; /* start the transfer, if not already started */ usbd_transfer_start(sc->sc_mixer_xfer[0]); } static void uaudio_mixer_init(struct uaudio_softc *sc) { struct uaudio_mixer_node *mc; int32_t i; for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if (mc->ctl != SOUND_MIXER_NRDEVICES) { /* * Set device mask bits. See * /usr/include/machine/soundcard.h */ sc->sc_mix_info |= (1 << mc->ctl); } if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) { continue; } sc->sc_recsrc_info |= 1 << mc->slctrtype[i - 1]; } } } } int uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m) { DPRINTF("\n"); sc->sc_mixer_lock = mixer_get_lock(m); sc->sc_mixer_dev = m; if (usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index, sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc, sc->sc_mixer_lock)) { DPRINTFN(0, "could not allocate USB " "transfer for audio mixer!\n"); return (ENOMEM); } if (!(sc->sc_mix_info & SOUND_MASK_VOLUME)) { mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); } mix_setdevs(m, sc->sc_mix_info); mix_setrecdevs(m, sc->sc_recsrc_info); return (0); } int uaudio_mixer_uninit_sub(struct uaudio_softc *sc) { DPRINTF("\n"); usbd_transfer_unsetup(sc->sc_mixer_xfer, 1); sc->sc_mixer_lock = NULL; return (0); } void uaudio_mixer_set(struct uaudio_softc *sc, unsigned type, unsigned left, unsigned right) { struct uaudio_mixer_node *mc; int chan; for (mc = sc->sc_mixer_root; mc != NULL; mc = mc->next) { if (mc->ctl == type) { for (chan = 0; chan < mc->nchan; chan++) { uaudio_mixer_ctl_set(sc, mc, chan, (int)((chan == 0 ? left : right) * 255) / 100); } } } } uint32_t uaudio_mixer_setrecsrc(struct uaudio_softc *sc, uint32_t src) { struct uaudio_mixer_node *mc; uint32_t mask; uint32_t temp; int32_t i; for (mc = sc->sc_mixer_root; mc; mc = mc->next) { if ((mc->ctl == SOUND_MIXER_NRDEVICES) && (mc->type == MIX_SELECTOR)) { /* compute selector mask */ mask = 0; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { mask |= (1 << mc->slctrtype[i - 1]); } temp = mask & src; if (temp == 0) { continue; } /* find the first set bit */ temp = (-temp) & temp; /* update "src" */ src &= ~mask; src |= temp; for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) { if (temp != (1 << mc->slctrtype[i - 1])) { continue; } uaudio_mixer_ctl_set(sc, mc, 0, i); break; } } } return (src); } /*========================================================================* * MIDI support routines *========================================================================*/ static void umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint8_t buf[4]; uint8_t cmd_len; uint8_t cn; uint16_t pos; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", actlen); pos = 0; pc = usbd_xfer_get_frame(xfer, 0); while (actlen >= 4) { /* copy out the MIDI data */ usbd_copy_out(pc, pos, buf, 4); /* command length */ cmd_len = umidi_cmd_to_len[buf[0] & 0xF]; /* cable number */ cn = buf[0] >> 4; /* * Lookup sub-channel. The index is range * checked below. */ sub = &chan->sub[cn]; if ((cmd_len != 0) && (cn < chan->max_emb_jack) && (sub->read_open != 0)) { /* Send data to the application */ usb_fifo_put_data_linear( sub->fifo.fp[USB_FIFO_RX], buf + 1, cmd_len, 1); } actlen -= 4; pos += 4; } case USB_ST_SETUP: DPRINTF("start\n"); tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } /* * The following statemachine, that converts MIDI commands to * USB MIDI packets, derives from Linux's usbmidi.c, which * was written by "Clemens Ladisch": * * Returns: * 0: No command * Else: Command is complete */ static uint8_t umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b) { uint8_t p0 = (cn << 4); if (b >= 0xf8) { sub->temp_0[0] = p0 | 0x0f; sub->temp_0[1] = b; sub->temp_0[2] = 0; sub->temp_0[3] = 0; sub->temp_cmd = sub->temp_0; return (1); } else if (b >= 0xf0) { switch (b) { case 0xf0: /* system exclusive begin */ sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case 0xf1: /* MIDI time code */ case 0xf3: /* song select */ sub->temp_1[1] = b; sub->state = UMIDI_ST_1PARAM; break; case 0xf2: /* song position pointer */ sub->temp_1[1] = b; sub->state = UMIDI_ST_2PARAM_1; break; case 0xf4: /* unknown */ case 0xf5: /* unknown */ sub->state = UMIDI_ST_UNKNOWN; break; case 0xf6: /* tune request */ sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf6; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case 0xf7: /* system exclusive end */ switch (sub->state) { case UMIDI_ST_SYSEX_0: sub->temp_1[0] = p0 | 0x05; sub->temp_1[1] = 0xf7; sub->temp_1[2] = 0; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_1: sub->temp_1[0] = p0 | 0x06; sub->temp_1[2] = 0xf7; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x07; sub->temp_1[3] = 0xf7; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_UNKNOWN; return (1); } sub->state = UMIDI_ST_UNKNOWN; break; } } else if (b >= 0x80) { sub->temp_1[1] = b; if ((b >= 0xc0) && (b <= 0xdf)) { sub->state = UMIDI_ST_1PARAM; } else { sub->state = UMIDI_ST_2PARAM_1; } } else { /* b < 0x80 */ switch (sub->state) { case UMIDI_ST_1PARAM: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; } else { p0 |= 0x02; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[2] = b; sub->temp_1[3] = 0; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_2PARAM_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_2PARAM_2; break; case UMIDI_ST_2PARAM_2: if (sub->temp_1[1] < 0xf0) { p0 |= sub->temp_1[1] >> 4; sub->state = UMIDI_ST_2PARAM_1; } else { p0 |= 0x03; sub->state = UMIDI_ST_UNKNOWN; } sub->temp_1[0] = p0; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; return (1); case UMIDI_ST_SYSEX_0: sub->temp_1[1] = b; sub->state = UMIDI_ST_SYSEX_1; break; case UMIDI_ST_SYSEX_1: sub->temp_1[2] = b; sub->state = UMIDI_ST_SYSEX_2; break; case UMIDI_ST_SYSEX_2: sub->temp_1[0] = p0 | 0x04; sub->temp_1[3] = b; sub->temp_cmd = sub->temp_1; sub->state = UMIDI_ST_SYSEX_0; return (1); default: break; } } return (0); } static void umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umidi_chan *chan = usbd_xfer_softc(xfer); struct umidi_sub_chan *sub; struct usb_page_cache *pc; uint32_t actlen; uint16_t nframes; uint8_t buf; uint8_t start_cable; uint8_t tr_any; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); /* * NOTE: Some MIDI devices only accept 4 bytes of data per * short terminated USB transfer. */ switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d bytes\n", len); case USB_ST_SETUP: tr_setup: DPRINTF("start\n"); nframes = 0; /* reset */ start_cable = chan->curr_cable; tr_any = 0; pc = usbd_xfer_get_frame(xfer, 0); while (1) { /* round robin de-queueing */ sub = &chan->sub[chan->curr_cable]; if (sub->write_open) { usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX], &buf, 1, &actlen, 0); } else { actlen = 0; } if (actlen) { tr_any = 1; DPRINTF("byte=0x%02x from FIFO %u\n", buf, (unsigned int)chan->curr_cable); if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) { DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n", sub->temp_cmd[0], sub->temp_cmd[1], sub->temp_cmd[2], sub->temp_cmd[3]); usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4); nframes++; if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0)) break; } else { continue; } } chan->curr_cable++; if (chan->curr_cable >= chan->max_emb_jack) chan->curr_cable = 0; if (chan->curr_cable == start_cable) { if (tr_any == 0) break; tr_any = 0; } } if (nframes != 0) { DPRINTF("Transferring %d frames\n", (int)nframes); usbd_xfer_set_frame_len(xfer, 0, 4 * nframes); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static struct umidi_sub_chan * umidi_sub_by_fifo(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) { sub = &chan->sub[n]; if ((sub->fifo.fp[USB_FIFO_RX] == fifo) || (sub->fifo.fp[USB_FIFO_TX] == fifo)) { return (sub); } } panic("%s:%d cannot find usb_fifo!\n", __FILE__, __LINE__); return (NULL); } static void umidi_start_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); } static void umidi_stop_read(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->read_open = 0; if (--(chan->read_open_refcount) == 0) { /* * XXX don't stop the read transfer here, hence that causes * problems with some MIDI adapters */ DPRINTF("(stopping read transfer)\n"); } } static void umidi_start_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]); } static void umidi_stop_write(struct usb_fifo *fifo) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); DPRINTF("\n"); sub->write_open = 0; if (--(chan->write_open_refcount) == 0) { DPRINTF("(stopping write transfer)\n"); usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]); } } static int umidi_open(struct usb_fifo *fifo, int fflags) { struct umidi_chan *chan = usb_fifo_softc(fifo); struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo); if (fflags & FREAD) { if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) { return (ENOMEM); } mtx_lock(&chan->mtx); chan->read_open_refcount++; sub->read_open = 1; mtx_unlock(&chan->mtx); } if (fflags & FWRITE) { if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) { return (ENOMEM); } /* clear stall first */ mtx_lock(&chan->mtx); chan->write_open_refcount++; sub->write_open = 1; /* reset */ sub->state = UMIDI_ST_UNKNOWN; mtx_unlock(&chan->mtx); } return (0); /* success */ } static void umidi_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { usb_fifo_free_buffer(fifo); } if (fflags & FWRITE) { usb_fifo_free_buffer(fifo); } } static int umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, int fflags) { return (ENODEV); } static void umidi_init(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE); } static struct usb_fifo_methods umidi_fifo_methods = { .f_start_read = &umidi_start_read, .f_start_write = &umidi_start_write, .f_stop_read = &umidi_stop_read, .f_stop_write = &umidi_stop_write, .f_open = &umidi_open, .f_close = &umidi_close, .f_ioctl = &umidi_ioctl, .basename[0] = "umidi", }; static int umidi_probe(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); struct umidi_chan *chan = &sc->sc_midi_chan; struct umidi_sub_chan *sub; int unit = device_get_unit(dev); int error; uint32_t n; if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI)) chan->single_command = 1; if (usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index, chan->iface_alt_index)) { DPRINTF("setting of alternate index failed!\n"); goto detach; } usbd_set_parent_iface(sc->sc_udev, chan->iface_index, sc->sc_mixer_iface_index); error = usbd_transfer_setup(uaa->device, &chan->iface_index, chan->xfer, umidi_config, UMIDI_N_TRANSFER, chan, &chan->mtx); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); goto detach; } /* * Some USB MIDI device makers couldn't resist using * wMaxPacketSize = 4 for RX and TX BULK endpoints, although * that size is an unsupported value for FULL speed BULK * endpoints. The same applies to some HIGH speed MIDI devices * which are using a wMaxPacketSize different from 512 bytes. * * Refer to section 5.8.3 in USB 2.0 PDF: Cite: "All Host * Controllers are required to have support for 8-, 16-, 32-, * and 64-byte maximum packet sizes for full-speed bulk * endpoints and 512 bytes for high-speed bulk endpoints." */ if (usbd_xfer_maxp_was_clamped(chan->xfer[UMIDI_TX_TRANSFER])) chan->single_command = 1; if (chan->single_command != 0) device_printf(dev, "Single command MIDI quirk enabled\n"); if ((chan->max_emb_jack == 0) || (chan->max_emb_jack > UMIDI_EMB_JACK_MAX)) { chan->max_emb_jack = UMIDI_EMB_JACK_MAX; } for (n = 0; n < chan->max_emb_jack; n++) { sub = &chan->sub[n]; error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx, &umidi_fifo_methods, &sub->fifo, unit, n, chan->iface_index, UID_ROOT, GID_OPERATOR, 0644); if (error) { goto detach; } } mtx_lock(&chan->mtx); /* * NOTE: At least one device will not work properly unless the * BULK IN pipe is open all the time. This might have to do * about that the internal queues of the device overflow if we * don't read them regularly. */ usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); return (0); /* success */ detach: return (ENXIO); /* failure */ } static int umidi_detach(device_t dev) { struct uaudio_softc *sc = device_get_softc(dev); struct umidi_chan *chan = &sc->sc_midi_chan; uint32_t n; for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) usb_fifo_detach(&chan->sub[n].fifo); mtx_lock(&chan->mtx); usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]); mtx_unlock(&chan->mtx); usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER); mtx_destroy(&chan->mtx); return (0); } static void uaudio_hid_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct uaudio_softc *sc = usbd_xfer_softc(xfer); const uint8_t *buffer = usbd_xfer_get_frame_buffer(xfer, 0); struct snd_mixer *m; uint8_t id; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d\n", actlen); if (actlen != 0 && (sc->sc_hid.flags & UAUDIO_HID_HAS_ID)) { id = *buffer; buffer++; actlen--; } else { id = 0; } m = sc->sc_mixer_dev; if ((sc->sc_hid.flags & UAUDIO_HID_HAS_MUTE) && (sc->sc_hid.mute_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.mute_loc)) { DPRINTF("Mute toggle\n"); mixer_hwvol_mute_locked(m); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_UP) && (sc->sc_hid.volume_up_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_up_loc)) { DPRINTF("Volume Up\n"); mixer_hwvol_step_locked(m, 1, 1); } if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_DOWN) && (sc->sc_hid.volume_down_id == id) && hid_get_data(buffer, actlen, &sc->sc_hid.volume_down_loc)) { DPRINTF("Volume Down\n"); mixer_hwvol_step_locked(m, -1, -1); } case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ DPRINTF("error=%s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static int uaudio_hid_probe(struct uaudio_softc *sc, struct usb_attach_arg *uaa) { void *d_ptr; uint32_t flags; uint16_t d_len; uint8_t id; int error; if (!(sc->sc_hid.flags & UAUDIO_HID_VALID)) return (-1); if (sc->sc_mixer_lock == NULL) return (-1); /* Get HID descriptor */ error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, &d_len, M_TEMP, sc->sc_hid.iface_index); if (error) { DPRINTF("error reading report description\n"); return (-1); } /* check if there is an ID byte */ hid_report_size(d_ptr, d_len, hid_input, &id); if (id != 0) sc->sc_hid.flags |= UAUDIO_HID_HAS_ID; if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE9 /* Volume Increment */), hid_input, 0, &sc->sc_hid.volume_up_loc, &flags, &sc->sc_hid.volume_up_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_UP; DPRINTFN(1, "Found Volume Up key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xEA /* Volume Decrement */), hid_input, 0, &sc->sc_hid.volume_down_loc, &flags, &sc->sc_hid.volume_down_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_DOWN; DPRINTFN(1, "Found Volume Down key\n"); } if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_CONSUMER, 0xE2 /* Mute */), hid_input, 0, &sc->sc_hid.mute_loc, &flags, &sc->sc_hid.mute_id)) { if (flags & HIO_VARIABLE) sc->sc_hid.flags |= UAUDIO_HID_HAS_MUTE; DPRINTFN(1, "Found Mute key\n"); } free(d_ptr, M_TEMP); if (!(sc->sc_hid.flags & (UAUDIO_HID_HAS_VOLUME_UP | UAUDIO_HID_HAS_VOLUME_DOWN | UAUDIO_HID_HAS_MUTE))) { DPRINTFN(1, "Did not find any volume related keys\n"); return (-1); } /* prevent the uhid driver from attaching */ usbd_set_parent_iface(uaa->device, sc->sc_hid.iface_index, sc->sc_mixer_iface_index); /* allocate USB transfers */ error = usbd_transfer_setup(uaa->device, &sc->sc_hid.iface_index, sc->sc_hid.xfer, uaudio_hid_config, UAUDIO_HID_N_TRANSFER, sc, sc->sc_mixer_lock); if (error) { DPRINTF("error=%s\n", usbd_errstr(error)); return (-1); } return (0); } static void uaudio_hid_detach(struct uaudio_softc *sc) { usbd_transfer_unsetup(sc->sc_hid.xfer, UAUDIO_HID_N_TRANSFER); } DRIVER_MODULE_ORDERED(uaudio, uhub, uaudio_driver, uaudio_devclass, NULL, 0, SI_ORDER_ANY); MODULE_DEPEND(uaudio, usb, 1, 1, 1); MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(uaudio, 1); Index: stable/10/sys/sys/param.h =================================================================== --- stable/10/sys/sys/param.h (revision 283949) +++ stable/10/sys/sys/param.h (revision 283950) @@ -1,361 +1,361 @@ /*- * Copyright (c) 1982, 1986, 1989, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)param.h 8.3 (Berkeley) 4/4/95 * $FreeBSD$ */ #ifndef _SYS_PARAM_H_ #define _SYS_PARAM_H_ #include #define BSD 199506 /* System version (year & month). */ #define BSD4_3 1 #define BSD4_4 1 /* * __FreeBSD_version numbers are documented in the Porter's Handbook. * If you bump the version for any reason, you should update the documentation * there. * Currently this lives here in the doc/ repository: * * head/en_US.ISO8859-1/books/porters-handbook/book.xml * * scheme is: Rxx * 'R' is in the range 0 to 4 if this is a release branch or * x.0-CURRENT before RELENG_*_0 is created, otherwise 'R' is * in the range 5 to 9. */ #undef __FreeBSD_version -#define __FreeBSD_version 1001516 /* Master, propagated to newvers */ +#define __FreeBSD_version 1001517 /* Master, propagated to newvers */ /* * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD, * which by definition is always true on FreeBSD. This macro is also defined * on other systems that use the kernel of FreeBSD, such as GNU/kFreeBSD. * * It is tempting to use this macro in userland code when we want to enable * kernel-specific routines, and in fact it's fine to do this in code that * is part of FreeBSD itself. However, be aware that as presence of this * macro is still not widespread (e.g. older FreeBSD versions, 3rd party * compilers, etc), it is STRONGLY DISCOURAGED to check for this macro in * external applications without also checking for __FreeBSD__ as an * alternative. */ #undef __FreeBSD_kernel__ #define __FreeBSD_kernel__ #ifdef _KERNEL #define P_OSREL_SIGWAIT 700000 #define P_OSREL_SIGSEGV 700004 #define P_OSREL_MAP_ANON 800104 #define P_OSREL_MAJOR(x) ((x) / 100000) #endif #ifndef LOCORE #include #endif /* * Machine-independent constants (some used in following include files). * Redefined constants are from POSIX 1003.1 limits file. * * MAXCOMLEN should be >= sizeof(ac_comm) (see ) */ #include #define MAXCOMLEN 19 /* max command name remembered */ #define MAXINTERP PATH_MAX /* max interpreter file name length */ #define MAXLOGNAME 33 /* max login name length (incl. NUL) */ #define MAXUPRC CHILD_MAX /* max simultaneous processes */ #define NCARGS ARG_MAX /* max bytes for an exec function */ #define NGROUPS (NGROUPS_MAX+1) /* max number groups */ #define NOFILE OPEN_MAX /* max open files per process */ #define NOGROUP 65535 /* marker for empty group set member */ #define MAXHOSTNAMELEN 256 /* max hostname size */ #define SPECNAMELEN 63 /* max length of devicename */ /* More types and definitions used throughout the kernel. */ #ifdef _KERNEL #include #include #ifndef LOCORE #include #include #endif #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #endif #ifndef _KERNEL /* Signals. */ #include #endif /* Machine type dependent parameters. */ #include #ifndef _KERNEL #include #endif #ifndef DEV_BSHIFT #define DEV_BSHIFT 9 /* log2(DEV_BSIZE) */ #endif #define DEV_BSIZE (1<>PAGE_SHIFT) #endif /* * btodb() is messy and perhaps slow because `bytes' may be an off_t. We * want to shift an unsigned type to avoid sign extension and we don't * want to widen `bytes' unnecessarily. Assume that the result fits in * a daddr_t. */ #ifndef btodb #define btodb(bytes) /* calculates (bytes / DEV_BSIZE) */ \ (sizeof (bytes) > sizeof(long) \ ? (daddr_t)((unsigned long long)(bytes) >> DEV_BSHIFT) \ : (daddr_t)((unsigned long)(bytes) >> DEV_BSHIFT)) #endif #ifndef dbtob #define dbtob(db) /* calculates (db * DEV_BSIZE) */ \ ((off_t)(db) << DEV_BSHIFT) #endif #define PRIMASK 0x0ff #define PCATCH 0x100 /* OR'd with pri for tsleep to check signals */ #define PDROP 0x200 /* OR'd with pri to stop re-entry of interlock mutex */ #define NZERO 0 /* default "nice" */ #define NBBY 8 /* number of bits in a byte */ #define NBPW sizeof(int) /* number of bytes per word (integer) */ #define CMASK 022 /* default file mask: S_IWGRP|S_IWOTH */ #define NODEV (dev_t)(-1) /* non-existent device */ /* * File system parameters and macros. * * MAXBSIZE - Filesystems are made out of blocks of at most MAXBSIZE bytes * per block. MAXBSIZE may be made larger without effecting * any existing filesystems as long as it does not exceed MAXPHYS, * and may be made smaller at the risk of not being able to use * filesystems which require a block size exceeding MAXBSIZE. * * MAXBCACHEBUF - Maximum size of a buffer in the buffer cache. This must * be >= MAXBSIZE and can be set differently for different * architectures by defining it in . * Making this larger allows NFS to do larger reads/writes. * * BKVASIZE - Nominal buffer space per buffer, in bytes. BKVASIZE is the * minimum KVM memory reservation the kernel is willing to make. * Filesystems can of course request smaller chunks. Actual * backing memory uses a chunk size of a page (PAGE_SIZE). * The default value here can be overridden on a per-architecture * basis by defining it in . This should * probably be done to increase its value, when MAXBCACHEBUF is * defined as a larger value in . * * If you make BKVASIZE too small you risk seriously fragmenting * the buffer KVM map which may slow things down a bit. If you * make it too big the kernel will not be able to optimally use * the KVM memory reserved for the buffer cache and will wind * up with too-few buffers. * * The default is 16384, roughly 2x the block size used by a * normal UFS filesystem. */ #define MAXBSIZE 65536 /* must be power of 2 */ #ifndef MAXBCACHEBUF #define MAXBCACHEBUF MAXBSIZE /* must be a power of 2 >= MAXBSIZE */ #endif #ifndef BKVASIZE #define BKVASIZE 16384 /* must be power of 2 */ #endif #define BKVAMASK (BKVASIZE-1) /* * MAXPATHLEN defines the longest permissible path length after expanding * symbolic links. It is used to allocate a temporary buffer from the buffer * pool in which to do the name expansion, hence should be a power of two, * and must be less than or equal to MAXBSIZE. MAXSYMLINKS defines the * maximum number of symbolic links that may be expanded in a path name. * It should be set high enough to allow all legitimate uses, but halt * infinite loops reasonably quickly. */ #define MAXPATHLEN PATH_MAX #define MAXSYMLINKS 32 /* Bit map related macros. */ #define setbit(a,i) (((unsigned char *)(a))[(i)/NBBY] |= 1<<((i)%NBBY)) #define clrbit(a,i) (((unsigned char *)(a))[(i)/NBBY] &= ~(1<<((i)%NBBY))) #define isset(a,i) \ (((const unsigned char *)(a))[(i)/NBBY] & (1<<((i)%NBBY))) #define isclr(a,i) \ ((((const unsigned char *)(a))[(i)/NBBY] & (1<<((i)%NBBY))) == 0) /* Macros for counting and rounding. */ #ifndef howmany #define howmany(x, y) (((x)+((y)-1))/(y)) #endif #define nitems(x) (sizeof((x)) / sizeof((x)[0])) #define rounddown(x, y) (((x)/(y))*(y)) #define rounddown2(x, y) ((x)&(~((y)-1))) /* if y is power of two */ #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* to any y */ #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ #define powerof2(x) ((((x)-1)&(x))==0) /* Macros for min/max. */ #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) #ifdef _KERNEL /* * Basic byte order function prototypes for non-inline functions. */ #ifndef LOCORE #ifndef _BYTEORDER_PROTOTYPED #define _BYTEORDER_PROTOTYPED __BEGIN_DECLS __uint32_t htonl(__uint32_t); __uint16_t htons(__uint16_t); __uint32_t ntohl(__uint32_t); __uint16_t ntohs(__uint16_t); __END_DECLS #endif #endif #ifndef lint #ifndef _BYTEORDER_FUNC_DEFINED #define _BYTEORDER_FUNC_DEFINED #define htonl(x) __htonl(x) #define htons(x) __htons(x) #define ntohl(x) __ntohl(x) #define ntohs(x) __ntohs(x) #endif /* !_BYTEORDER_FUNC_DEFINED */ #endif /* lint */ #endif /* _KERNEL */ /* * Scale factor for scaled integers used to count %cpu time and load avgs. * * The number of CPU `tick's that map to a unique `%age' can be expressed * by the formula (1 / (2 ^ (FSHIFT - 11))). The maximum load average that * can be calculated (assuming 32 bits) can be closely approximated using * the formula (2 ^ (2 * (16 - FSHIFT))) for (FSHIFT < 15). * * For the scheduler to maintain a 1:1 mapping of CPU `tick' to `%age', * FSHIFT must be at least 11; this gives us a maximum load avg of ~1024. */ #define FSHIFT 11 /* bits to right of fixed binary point */ #define FSCALE (1<> (PAGE_SHIFT - DEV_BSHIFT)) #define ctodb(db) /* calculates pages to devblks */ \ ((db) << (PAGE_SHIFT - DEV_BSHIFT)) /* * Old spelling of __containerof(). */ #define member2struct(s, m, x) \ ((struct s *)(void *)((char *)(x) - offsetof(struct s, m))) /* * Access a variable length array that has been declared as a fixed * length array. */ #define __PAST_END(array, offset) (((__typeof__(*(array)) *)(array))[offset]) #endif /* _SYS_PARAM_H_ */ Index: stable/10 =================================================================== --- stable/10 (revision 283949) +++ stable/10 (revision 283950) Property changes on: stable/10 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r282650-282651