--- sys/dev/sound/pcm/buffer.c.orig Thu Apr 19 02:26:40 2007 +++ sys/dev/sound/pcm/buffer.c Sun May 20 17:15:56 2007 @@ -651,6 +651,51 @@ return 0; } +static void +sndbuf_feed_vrec(struct pcm_channel *c) +{ + struct pcm_channel *ch; + int rl, rp, sz, bps; + + if (c->direction != PCMDIR_REC || CHN_EMPTY(c, children) + || !(c->flags & CHN_F_HAS_VCHAN) || + sndbuf_getready(c->bufsoft) == 0) + return; + + if (sndbuf_getready(c->bufsoft) < sndbuf_getbps(c->bufsoft)) + goto sndbuf_feed_vrec_out; + + rp = c->bufsoft->rp; + rl = c->bufsoft->rl; + + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + bps = sndbuf_getbps(ch->bufsoft); + sz = sndbuf_getfree(ch->bufsoft); + sz -= sz % bps; + if (!(ch->flags & CHN_F_TRIGGERED) || sz < bps) { + CHN_UNLOCK(ch); + continue; + } + do { + sz = FEEDER_FEED(ch->feeder, ch, + ch->bufsoft->tmpbuf, sz, c->bufsoft); + if (sz > bps) { + sndbuf_acquire(ch->bufsoft, + ch->bufsoft->tmpbuf, sz); + sz = sndbuf_getfree(ch->bufsoft); + } + sz -= sz % bps; + } while (sz > 0); + CHN_UNLOCK(ch); + c->bufsoft->rp = rp; + c->bufsoft->rl = rl; + } + +sndbuf_feed_vrec_out: + sndbuf_dispose(c->bufsoft, NULL, sndbuf_getready(c->bufsoft)); +} + /* count is number of bytes we want added to destination buffer */ int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count) @@ -669,6 +714,8 @@ /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ count -= cnt; } while (count && cnt); + + sndbuf_feed_vrec(channel); return 0; } --- sys/dev/sound/pcm/channel.c.orig Mon Apr 2 11:03:06 2007 +++ sys/dev/sound/pcm/channel.c Sun May 20 13:27:51 2007 @@ -170,11 +170,14 @@ case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); break; + case PCMDIR_PLAY_VIRTUAL: + c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); + break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); break; - case PCMDIR_VIRTUAL: - c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); + case PCMDIR_REC_VIRTUAL: + c->lock = snd_mtxcreate(c->name, "pcm virtual record channel"); break; case 0: c->lock = snd_mtxcreate(c->name, "pcm fake channel"); @@ -234,20 +237,27 @@ chn_wakeup(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; - struct pcmchan_children *pce; + struct pcm_channel *ch; CHN_LOCKASSERT(c); - if (SLIST_EMPTY(&c->children)) { + if (CHN_EMPTY(c, children)) { if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) selwakeuppri(sndbuf_getsel(bs), PRIBIO); - wakeup_one(bs); + } else if (CHN_EMPTY(c, children.busy)) { + CHN_FOREACH(ch, c, children) { + CHN_LOCK(ch); + chn_wakeup(ch); + CHN_UNLOCK(ch); + } } else { - SLIST_FOREACH(pce, &c->children, link) { - CHN_LOCK(pce->channel); - chn_wakeup(pce->channel); - CHN_UNLOCK(pce->channel); + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + chn_wakeup(ch); + CHN_UNLOCK(ch); } } + if (c->flags & CHN_F_SLEEPING) + wakeup_one(bs); } static int @@ -257,11 +267,14 @@ int ret; CHN_LOCKASSERT(c); + + c->flags |= CHN_F_SLEEPING; #ifdef USING_MUTEX ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout); #else ret = tsleep(bs, PRIBIO | PCATCH, str, timeout); #endif + c->flags &= ~CHN_F_SLEEPING; return ret; } @@ -500,12 +513,10 @@ ret = (amt > 0) ? sndbuf_feed(b, bs, c, c->feeder, amt) : 0; amt = sndbuf_getready(b); - if (amt > 0) { - c->xruns++; + if (amt > 0) sndbuf_dispose(b, NULL, amt); - } - if (sndbuf_getready(bs) > 0) + if (sndbuf_getready(bs) > 0 || !CHN_EMPTY(c, children)) chn_wakeup(c); return ret; @@ -519,7 +530,7 @@ CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel")); - if ((c->flags & CHN_F_MAPPED) || CHN_STOPPED(c)) + if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c)) return; chn_trigger(c, PCMTRIG_EMLDMARD); chn_dmaupdate(c); @@ -645,7 +656,7 @@ j = sndbuf_getbps(pb); } } - if (snd_verbose > 3 && SLIST_EMPTY(&c->children)) + if (snd_verbose > 3 && CHN_EMPTY(c, children)) printf("%s: %s (%s) threshold i=%d j=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", @@ -1094,6 +1105,8 @@ b = NULL; bs = NULL; + CHN_INIT(c, children); + CHN_INIT(c, children.busy); c->devinfo = NULL; c->feeder = NULL; c->latency = -1; @@ -1195,7 +1208,8 @@ if (CHN_STARTED(c)) chn_trigger(c, PCMTRIG_ABORT); - while (chn_removefeeder(c) == 0); + while (chn_removefeeder(c) == 0) + ; if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); c->flags |= CHN_F_DEAD; @@ -1528,7 +1542,7 @@ c->devinfo, hblksz)); CHN_LOCK(c); - if (!SLIST_EMPTY(&c->children)) { + if (!CHN_EMPTY(c, children)) { sblksz = round_blksz( sndbuf_xbytes(sndbuf_getsize(b) >> 1, b, bs), sndbuf_getbps(bs)); @@ -1750,6 +1764,7 @@ #ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; #endif + struct snddev_info *d = c->parentsnddev; int ret; CHN_LOCKASSERT(c); @@ -1757,8 +1772,27 @@ if (SND_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)) sndbuf_dmabounce(b); #endif + if ((go == PCMTRIG_START || go == PCMTRIG_STOP || + go == PCMTRIG_ABORT) && go == (int)((intptr_t)c->data1)) + return 0; + ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); + if (ret == 0) { + c->data1 = (void *)((intptr_t)go); + switch (go) { + case PCMTRIG_START: + CHN_INSERT_HEAD_SAFE(d, c, channels.pcm.busy); + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + CHN_REMOVE_SAFE(d, c, channels.pcm.busy); + break; + default: + break; + } + } + return ret; } @@ -1840,7 +1874,10 @@ c->align = sndbuf_getalign(c->bufsoft); - if (SLIST_EMPTY(&c->children)) { + if (CHN_EMPTY(c, children) || c->direction == PCMDIR_REC) { + /* + * Virtual rec need this instead of feed_vchan. + */ fc = feeder_getclass(NULL); KASSERT(fc != NULL, ("can't find root feeder")); @@ -1851,7 +1888,7 @@ return err; } c->feeder->desc->out = c->format; - } else { + } else if (c->direction == PCMDIR_PLAY) { if (c->flags & CHN_F_HAS_VCHAN) { desc.type = FEEDER_MIXER; desc.in = c->format; @@ -1874,7 +1911,9 @@ return err; } - } + } else + return EOPNOTSUPP; + c->feederflags &= ~(1 << FEEDER_VOLUME); if (c->direction == PCMDIR_PLAY && !(c->flags & CHN_F_VIRTUAL) && c->parentsnddev && @@ -2020,13 +2059,11 @@ int chn_notify(struct pcm_channel *c, u_int32_t flags) { - struct pcmchan_children *pce; - struct pcm_channel *child; int run; CHN_LOCK(c); - if (SLIST_EMPTY(&c->children)) { + if (CHN_EMPTY(c, children)) { CHN_UNLOCK(c); return ENODEV; } @@ -2064,20 +2101,8 @@ } if (flags & CHN_N_TRIGGER) { int nrun; - /* - * scan the children, and figure out if any are running - * if so, we need to be running, otherwise we need to be stopped - * if we aren't in our target sstate, move to it - */ - nrun = 0; - SLIST_FOREACH(pce, &c->children, link) { - child = pce->channel; - CHN_LOCK(child); - nrun = CHN_STARTED(child); - CHN_UNLOCK(child); - if (nrun) - break; - } + + nrun = CHN_EMPTY(c, children.busy) ? 0 : 1; if (nrun && !run) chn_start(c, 1); if (!nrun && run) --- sys/dev/sound/pcm/channel.h.orig Sun Nov 26 20:24:05 2006 +++ sys/dev/sound/pcm/channel.h Sat May 19 22:01:55 2007 @@ -26,11 +26,6 @@ * $FreeBSD: src/sys/dev/sound/pcm/channel.h,v 1.34 2006/11/26 12:24:05 ariff Exp $ */ -struct pcmchan_children { - SLIST_ENTRY(pcmchan_children) link; - struct pcm_channel *channel; -}; - struct pcmchan_caps { u_int32_t minspeed, maxspeed; u_int32_t *fmtlist; @@ -94,6 +89,8 @@ struct pcm_channel *parentchannel; void *devinfo; device_t dev; + struct cdev *devt; + int devminor; char name[CHN_NAMELEN]; struct mtx *lock; /** @@ -123,9 +120,82 @@ #ifdef OSSV4_EXPERIMENT u_int16_t lpeak, rpeak; /**< Peak value from 0-32767. */ #endif - SLIST_HEAD(, pcmchan_children) children; + + 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; + } pcm; + } channels; + + 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) + #include "channel_if.h" int chn_reinit(struct pcm_channel *c); @@ -208,9 +278,10 @@ extern int chn_latency_profile; extern int report_soft_formats; -#define PCMDIR_VIRTUAL 2 -#define PCMDIR_PLAY 1 -#define PCMDIR_REC -1 +#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 @@ -223,6 +294,7 @@ #define CHN_F_RUNNING 0x00000010 /* dma is running */ #define CHN_F_TRIGGERED 0x00000020 #define CHN_F_NOTRIGGER 0x00000040 +#define CHN_F_SLEEPING 0x00000080 #define CHN_F_BUSY 0x00001000 /* has been opened */ #define CHN_F_HAS_SIZE 0x00002000 /* user set block size */ --- sys/dev/sound/pcm/clone.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/clone.c Sun May 20 06:20:53 2007 @@ -0,0 +1,723 @@ +/*- + * Copyright (c) 2007 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. + */ + +#include +#include + +SND_DECLARE_FILE("$FreeBSD$"); + +/* + * So here we go again, another clonedevs manager. Unlike default clonedevs, + * this clone manager is designed to withstand various abusive behavior + * (such as 'while : ; do ls /dev/whatever ; done', etc.), reusable object + * after reaching certain expiration threshold, aggressive garbage collector, + * transparent device allocator and concurrency handling across multiple + * thread/proc. Due to limited information given by dev_clone EVENTHANDLER, + * we don't have much clues whether the caller wants a real open() or simply + * making fun of us with things like stat(), mtime() etc. Assuming that: + * 1) Time window between dev_clone EH <-> real open() should be small + * enough and 2) mtime()/stat() etc. always looks like a half way / stalled + * operation, we can decide whether a new cdev must be created, old + * (expired) cdev can be reused or an existing cdev can be shared. + * + * Most of the operations and logics are generic enough and can be applied + * on other places (such as if_tap, snp, etc). Perhaps this can be + * rearranged to complement clone_*(). However, due to this still being + * specific to the sound driver (and as a proof of concept on how it can be + * done), si_drv2 is used to keep the pointer of the clone list entry to + * avoid expensive lookup. + */ + +/* clone entry */ +struct snddev_clone_e { + TAILQ_ENTRY(snddev_clone_e) link; + struct snddev_clone *parent; + struct cdev *devt; + struct timespec tsp; + uint32_t flags; + pid_t pid; + int devminor; +}; + +/* clone manager */ +struct snddev_clone { + TAILQ_HEAD(link_head, snddev_clone_e) head; + struct mtx *lock; + struct timespec tsp; + int refcount; + int size; + int maxsize; + int deadline; + uint32_t flags; +}; + +#define SND_CLONE_LOCK(x) do { \ + if ((x)->lock != NULL) \ + mtx_lock((x)->lock); \ +} while(0) + +#define SND_CLONE_UNLOCK(x) do { \ + if ((x)->lock != NULL) \ + mtx_unlock((x)->lock); \ +} while(0) + +#define SND_CLONE_LOCKASSERT(x) do { \ + if ((x)->lock != NULL) \ + mtx_assert((x)->lock, MA_OWNED); \ +} while(0) + +/* + * Shamely ripped from vfs_subr.c + * We need at least 1/HZ precision as default timestamping. + */ +enum { SND_TSP_SEC, SND_TSP_HZ, SND_TSP_USEC, SND_TSP_NSEC }; + +static int snd_timestamp_precision = SND_TSP_HZ; +TUNABLE_INT("hw.snd.timestamp_precision", &snd_timestamp_precision); + +void +snd_timestamp(struct timespec *tsp) +{ + struct timeval tv; + + switch (snd_timestamp_precision) { + case SND_TSP_SEC: + tsp->tv_sec = time_second; + tsp->tv_nsec = 0; + break; + case SND_TSP_HZ: + getnanouptime(tsp); + break; + case SND_TSP_USEC: + microuptime(&tv); + TIMEVAL_TO_TIMESPEC(&tv, tsp); + break; + case SND_TSP_NSEC: + nanouptime(tsp); + break; + default: + snd_timestamp_precision = SND_TSP_HZ; + snd_timestamp(tsp); + break; + } +} + +#ifdef SND_DEBUG +static int +sysctl_hw_snd_timestamp_precision(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = snd_timestamp_precision; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (err == 0 && req->newptr != NULL) { + switch (val) { + case SND_TSP_SEC: + case SND_TSP_HZ: + case SND_TSP_USEC: + case SND_TSP_NSEC: + snd_timestamp_precision = val; + break; + default: + break; + } + } + + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, timestamp_precision, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_timestamp_precision, "I", + "timestamp precision (0=s 1=hz 2=us 3=ns)"); +#endif + +#define SND_CLONE_MAXSIZE_CLAMP(x) \ + (((x) > 0 && (x) < SND_CLONE_MAXSIZE) ? (x) : SND_CLONE_MAXSIZE) + +/* + * snd_clone_create() : Return opaque allocated clone manager. Mutex is not + * a mandatory requirement if the caller can guarantee safety against + * concurrent access. + */ +struct snddev_clone * +snd_clone_create(struct mtx *lock, int maxsize, int deadline, uint32_t flags) +{ + struct snddev_clone *c; + + c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); + c->lock = lock; + c->refcount = 0; + c->size = 0; + c->maxsize = SND_CLONE_MAXSIZE_CLAMP(maxsize); + c->deadline = deadline; + c->flags = flags & SND_CLONE_MASK; + snd_timestamp(&c->tsp); + TAILQ_INIT(&c->head); + + return (c); +} + +/* + * Getters / Setters. Not worth explaining :) + */ +int +snd_clone_getsize(struct snddev_clone *c) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + + return (c->size); +} + +int +snd_clone_getmaxsize(struct snddev_clone *c) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + + return (c->maxsize); +} + +int +snd_clone_setmaxsize(struct snddev_clone *c, int maxsize) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + SND_CLONE_LOCKASSERT(c); + + c->maxsize = SND_CLONE_MAXSIZE_CLAMP(maxsize); + + return (c->maxsize); +} + +int +snd_clone_getdeadline(struct snddev_clone *c) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + + return (c->deadline); +} + +int +snd_clone_setdeadline(struct snddev_clone *c, int deadline) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + SND_CLONE_LOCKASSERT(c); + + c->deadline = deadline; + + return (c->deadline); +} + +int +snd_clone_gettime(struct snddev_clone *c, struct timespec *tsp) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + KASSERT(tsp != NULL, ("NULL timespec")); + + *tsp = c->tsp; + + return (0); +} + +uint32_t +snd_clone_getflags(struct snddev_clone *c) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + + return (c->flags); +} + +uint32_t +snd_clone_setflags(struct snddev_clone *c, uint32_t flags) +{ + KASSERT(c != NULL, ("NULL snddev_clone")); + SND_CLONE_LOCKASSERT(c); + + c->flags = flags & SND_CLONE_MASK; + + return (c->flags); +} + +int +snd_clone_getdevtime(struct cdev *dev, struct timespec *tsp) +{ + struct snddev_clone_e *ce; + + KASSERT(dev != NULL, ("NULL dev")); + KASSERT(tsp != NULL, ("NULL timespec")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + *tsp = ce->tsp; + + return (0); +} + +uint32_t +snd_clone_getdevflags(struct cdev *dev) +{ + struct snddev_clone_e *ce; + + KASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0xffffffff); + + return (ce->flags); +} + +uint32_t +snd_clone_setdevflags(struct cdev *dev, uint32_t flags) +{ + struct snddev_clone_e *ce; + struct snddev_clone *c; + + KASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0xffffffff); + + c = ce->parent; + KASSERT(c != NULL, ("NULL parent")); + SND_CLONE_LOCKASSERT(c); + + ce->flags = flags & SND_CLONE_DEVMASK; + + return (ce->flags); +} + +/* Elapsed time conversion to ms */ +#define SND_CLONE_ELAPSED(x, y) \ + ((((x)->tv_sec - (y)->tv_sec) * 1000) + \ + (((y)->tv_nsec > (x)->tv_nsec) ? \ + (((1000000000L + (x)->tv_nsec - \ + (y)->tv_nsec) / 1000000) - 1000) : \ + (((x)->tv_nsec - (y)->tv_nsec) / 1000000))) + +#define SND_CLONE_EXPIRED(x, y, z) \ + ((x)->deadline < 1 || \ + ((y)->tv_sec - (z)->tv_sec) > ((x)->deadline / 1000) || \ + SND_CLONE_ELAPSED(y, z) > (x)->deadline) + +/* + * snd_clone_gc() : Garbage collector for stalled, expired objects. Refer to + * clone.h for explanations on GC settings. + */ +int +snd_clone_gc(struct snddev_clone *c) +{ + struct snddev_clone_e *ce, *tce; + struct timespec now; + int size, rsize, *counter; + + KASSERT(c != NULL, ("NULL snddev_clone")); + SND_CLONE_LOCKASSERT(c); + + if (c->size == 0) + return (0); + + snd_timestamp(&now); + + /* + * Bail out if the last clone handler was invoked below the deadline + * threshold. + */ + if ((c->flags & SND_CLONE_GC_EXPIRED) && + !SND_CLONE_EXPIRED(c, &now, &c->tsp)) + return (0); + + size = c->size; + rsize = c->size; + counter = (c->flags & SND_CLONE_GC_REVOKE) ? &rsize : &c->size; + + /* + * Visit each object in reverse order. If the object is still being + * referenced by a valid open(), skip it. Look for expired objects + * and either revoke its clone invocation status or mercilessly + * throw it away. + */ + TAILQ_FOREACH_REVERSE_SAFE(ce, &c->head, link_head, link, tce) { + if (!(ce->flags & SND_CLONE_BUSY) && + (!(ce->flags & SND_CLONE_INVOKE) || + SND_CLONE_EXPIRED(c, &now, &ce->tsp))) { + if (c->flags & SND_CLONE_GC_REVOKE) + ce->flags &= ~SND_CLONE_INVOKE; + else { + TAILQ_REMOVE(&c->head, ce, link); + if (ce->devt->si_drv1 != NULL) + free(ce->devt->si_drv1, M_DEVBUF); + destroy_dev(ce->devt); + free(ce, M_DEVBUF); + } + (*counter)--; + } + } + + /* return total pruned objects */ + return (size - *counter); +} + +void +snd_clone_destroy(struct snddev_clone *c) +{ + struct snddev_clone_e *ce; + + KASSERT(c != NULL, ("NULL snddev_clone")); + KASSERT(c->refcount == 0, ("refcount > 0")); + SND_CLONE_LOCKASSERT(c); + + while (!TAILQ_EMPTY(&c->head)) { + ce = TAILQ_FIRST(&c->head); + TAILQ_REMOVE(&c->head, ce, link); + if (ce->devt != NULL) { + if (ce->devt->si_drv1 != NULL) + free(ce->devt->si_drv1, M_DEVBUF); + destroy_dev(ce->devt); + } + free(ce, M_DEVBUF); + } + + free(c, M_DEVBUF); +} + +/* + * snd_clone_acquire() : The vital part of concurrency management. Must be + * called somewhere at the beginning of open() handler. ENODEV is not really + * fatal since it just tell the caller that this is not cloned stuff. + * EBUSY is *real*, don't forget that! + */ +int +snd_clone_acquire(struct cdev *dev) +{ + struct snddev_clone_e *ce; + struct snddev_clone *c; + + KASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + c = ce->parent; + KASSERT(c != NULL, ("NULL parent")); + SND_CLONE_LOCKASSERT(c); + + ce->flags &= ~SND_CLONE_INVOKE; + + if (ce->flags & SND_CLONE_BUSY) + return (EBUSY); + + ce->flags |= SND_CLONE_BUSY; + + return (0); +} + +/* + * snd_clone_release() : Release busy status. Must be called somewhere at + * the end of close() handler, or somewhere after fail open(). + */ +int +snd_clone_release(struct cdev *dev) +{ + struct snddev_clone_e *ce; + struct snddev_clone *c; + + KASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + c = ce->parent; + KASSERT(c != NULL, ("NULL parent")); + SND_CLONE_LOCKASSERT(c); + + ce->flags &= ~SND_CLONE_INVOKE; + + if (!(ce->flags & SND_CLONE_BUSY)) + return (EBADF); + + ce->flags &= ~SND_CLONE_BUSY; + + return (0); +} + +/* + * snd_clone_ref/unref() : Garbage collector reference counter. To make + * garbage collector run automatically, the sequence must be something like + * this (both in open() and close() handlers): + * + * open() - 1) snd_clone_acquire() + * 2) .... check check ... if failed, snd_clone_release() + * 3) Success. Call snd_clone_ref() + * + * close() - 1) .... check check check .... + * 2) Success. snd_clone_release() + * 3) snd_clone_unref() . Garbage collector will run at this point + * if this is the last referenced object. + */ +int +snd_clone_ref(struct cdev *dev) +{ + struct snddev_clone_e *ce; + struct snddev_clone *c; + + KASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0); + + c = ce->parent; + KASSERT(c != NULL, ("NULL parent")); + KASSERT(c->refcount >= 0, ("refcount < 0")); + SND_CLONE_LOCKASSERT(c); + + return (++c->refcount); +} + +int +snd_clone_unref(struct cdev *dev) +{ + struct snddev_clone_e *ce; + struct snddev_clone *c; + + KASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0); + + c = ce->parent; + KASSERT(c != NULL, ("NULL parent")); + KASSERT(c->refcount > 0, ("refcount <= 0")); + SND_CLONE_LOCKASSERT(c); + + c->refcount--; + + /* + * Run automatic garbage collector, if needed. + */ + if ((c->flags & SND_CLONE_GC_UNREF) && + (!(c->flags & SND_CLONE_GC_LASTREF) || + (c->refcount == 0 && (c->flags & SND_CLONE_GC_LASTREF)))) + (void)snd_clone_gc(c); + + return (c->refcount); +} + +struct cdev * +snd_clone_alloc(char *name) +{ + struct snddev_info *d; + struct snddev_clone *c; + struct snddev_clone_e *ce, *after, *bce, *cce, *nce, *tce; + struct snddev_chinfo *sdev; + struct cdev *ndev; + struct timespec now; + int i, umax, unit, cunit, allocunit, devtype; + pid_t curpid; + char *devname; + static int devtypes[] = PCM_DEV_CLONE_DEVS; + static char *devnames[] = PCM_DEV_CLONE_LABELS; + + KASSERT(name != NULL, ("NULL device name")); + + if (pcm_devclass == NULL) + return (NULL); + + devtype = -1; + devname = NULL; + ce = NULL; + after = NULL; + bce = NULL; /* "b"usy candidate */ + cce = NULL; /* "c"urthread/proc candidate */ + nce = NULL; /* "n"ull, totally unbusy candidate */ + tce = NULL; /* Last "t"ry candidate */ + unit = -1; + cunit = 0; + sdev = NULL; + allocunit = 0; + + for (i = 0; i < (sizeof(devtypes) / sizeof(devtypes[0])) && + unit == -1; i++) { + devtype = devtypes[i]; + devname = devnames[i]; + if (strcmp(name, devname) == 0) + unit = snd_unit; + else if (dev_stdclone(name, NULL, devname, &unit) != 1) + unit = -1; + } + + if (unit == -1 || unit >= devclass_get_maxunit(pcm_devclass)) + return (NULL); + + d = devclass_get_softc(pcm_devclass, unit); + if (d == NULL || (d->flags & SD_F_DYING) || d->clones == NULL) + return (NULL); + + c = d->clones; + SND_CLONE_LOCK(c); + snd_timestamp(&now); + + /* + * Sound driver maximum allowable cloned devices is 256 + * "by design". The real hard limit is 0xffffff as long as + * everything is unique. + */ + umax = d->playcount + d->reccount - 1; + if (d->pvchancount > 0) + umax += MAX(d->pvchancount, snd_maxautovchans) - 1; + if (d->rvchancount > 0) + umax += MAX(d->rvchancount, snd_maxautovchans) - 1; + if (umax > PCMMAXCHAN) + umax = PCMMAXCHAN; + + curpid = curthread->td_proc->p_pid; + + /* + * Sound driver has 3 types of clonable devices: dsp, audio and + * dspW. All of these are handled in a single list, and sorted + * incrementally. + */ + TAILQ_FOREACH(ce, &c->head, link) { + /* + * Sorting is done here, including collision checker. + */ + if (PCMMINOR_DEV(ce->devminor) < devtype) { + if (cunit == 0) + after = ce; + continue; + } else if (PCMMINOR_DEV(ce->devminor) > devtype) + break; + cunit++; + if (PCMMINOR_CHAN(ce->devminor) == allocunit) + allocunit++; + if (PCMMINOR_CHAN(ce->devminor) < allocunit) + after = ce; + + /* + * Generic part, not specific to sound driver. + * + * Clone logic: + * 1. Look for non busy, but keep track of the best + * possible busy cdev. + * 2. Look for the best (oldest referenced) entry that is + * in a same process / thread. + * 3. Look for the best (oldest referenced), absolute free + * entry. + * 4. Lastly, look for the best (oldest referenced) + * any entries that doesn't fit with anything above. + */ + if (ce->flags & SND_CLONE_BUSY) { + if (ce->devt != NULL && (bce == NULL || + timespeccmp(&ce->tsp, &bce->tsp, <))) + bce = ce; + continue; + } + if (ce->pid == curpid && + (cce == NULL || timespeccmp(&ce->tsp, &cce->tsp, <))) + cce = ce; + else if (!(ce->flags & SND_CLONE_INVOKE) && + (nce == NULL || timespeccmp(&ce->tsp, &nce->tsp, <))) + nce = ce; + else if (tce == NULL || timespeccmp(&ce->tsp, &tce->tsp, <)) + tce = ce; + } + if (cce != NULL) { + /* Same proc entry found, go for it */ + ce = cce; + goto snd_clone_alloc_out; + } else if (nce != NULL) { + /* Next, try absolute free entry */ + ce = nce; + goto snd_clone_alloc_out; + } else if (cunit > umax || allocunit > PCMMAXCHAN || + c->size >= c->maxsize) { + /* + * Maximum allowable unit reached. Try returning any + * available cdev and hope for the best. If the lookup is + * done for things like stat(), mtime() etc. , things should + * be ok. Otherwise, open() handler should do further checks + * and decide whether to return correct error code or not. + */ + if (tce != NULL) { + ce = tce; + goto snd_clone_alloc_out; + } else if (bce != NULL) { + ce = bce; + goto snd_clone_alloc_out; + } + SND_CLONE_UNLOCK(c); + return (NULL); + } + + /* + * No free entries found, and we still haven't reached maximum + * allowable units. Allocate, setup a minimal unique entry with busy + * status so nobody will monkey on this new entry since we had to + * give up locking for further setup. Minor magic is set right here + * to avoid collision with other contesting handler. + */ + ce = malloc(sizeof(*ce), M_DEVBUF, M_NOWAIT | M_ZERO); + if (ce == NULL) { + SND_CLONE_UNLOCK(c); + return (NULL); + } + ce->parent = c; + ce->devminor = PCMMKMINOR(unit, devtype, allocunit); + ce->flags |= SND_CLONE_ALLOC; + if (after != NULL) { + TAILQ_INSERT_AFTER(&c->head, after, ce, link); + } else { + TAILQ_INSERT_HEAD(&c->head, ce, link); + } + c->size++; + SND_CLONE_UNLOCK(c); + ndev = make_dev(&dsp_cdevsw, ce->devminor, + UID_ROOT, GID_WHEEL, 0666, "%s%d.%d", devname, unit, allocunit); + sdev = malloc(sizeof(*sdev), M_DEVBUF, M_WAITOK | M_ZERO); + ndev->si_drv1 = sdev; + ndev->si_drv2 = ce; + SND_CLONE_LOCK(c); + ce->devt = ndev; + ce->flags &= ~SND_CLONE_ALLOC; +snd_clone_alloc_out: + /* + * Set, mark, timestamp the entry if this is a truly free entry. + * Leave busy entry alone. + */ + if (!(ce->flags & SND_CLONE_BUSY)) { + ce->pid = curpid; + ce->tsp = now; + ce->flags |= SND_CLONE_INVOKE; + } + c->tsp = now; + SND_CLONE_UNLOCK(c); + + return (ce->devt); +} --- sys/dev/sound/pcm/clone.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/clone.h Sun May 20 00:04:32 2007 @@ -0,0 +1,101 @@ +/*- + * Copyright (c) 2007 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. + * + * $FreeBSD$ + */ + +#ifndef _SND_CLONE_H_ +#define _SND_CLONE_H_ + +struct snddev_clone; + +/* + * 750 milisecond default deadline. Short enough to not cause excessive + * garbage collection, long enough to indicate stalled VFS. + */ +#define SND_CLONE_DEADLINE 750 + +/* + * Creation flags, mostly related to the behaviour of garbage collector. + * + * SND_CLONE_GC_UNREF - Garbage collect during unref operation. + * SND_CLONE_GC_LASTREF - Garbage collect during last reference + * (refcount = 0) + * SND_CLONE_GC_EXPIRED - Don't garbage collect unless the global clone + * handler has been expired. + * SND_CLONE_GC_REVOKE - Revoke clone invocation status which has been + * expired instead of removing and freeing it. + */ +#define SND_CLONE_GC_UNREF 0x00000001 +#define SND_CLONE_GC_LASTREF 0x00000002 +#define SND_CLONE_GC_EXPIRED 0x00000004 +#define SND_CLONE_GC_REVOKE 0x00000008 + +#define SND_CLONE_GC_MASK (SND_CLONE_GC_UNREF | \ + SND_CLONE_GC_LASTREF | \ + SND_CLONE_GC_EXPIRED | \ + SND_CLONE_GC_REVOKE) + +#define SND_CLONE_MASK SND_CLONE_GC_MASK + +/* + * Runtime clone device flags + * + * These are mostly private to the clone manager operation: + * + * SND_CLONE_INVOKE - Cloning being invoked, waiting for next VFS operation. + * SND_CLONE_BUSY - In progress, being referenced by living thread/proc. + */ +#define SND_CLONE_INVOKE 0x00000001 +#define SND_CLONE_BUSY 0x00000002 + +#define SND_CLONE_ALLOC (SND_CLONE_INVOKE | SND_CLONE_BUSY) +#define SND_CLONE_DEVMASK (SND_CLONE_INVOKE | SND_CLONE_BUSY) + +#define SND_CLONE_MAXSIZE 0xffffff + +void snd_timestamp(struct timespec *); + +struct snddev_clone *snd_clone_create(struct mtx *, int, int, uint32_t); +int snd_clone_getsize(struct snddev_clone *); +int snd_clone_getmaxsize(struct snddev_clone *); +int snd_clone_setmaxsize(struct snddev_clone *, int); +int snd_clone_getdeadline(struct snddev_clone *); +int snd_clone_setdeadline(struct snddev_clone *, int); +int snd_clone_gettime(struct snddev_clone *, struct timespec *); +uint32_t snd_clone_getflags(struct snddev_clone *); +uint32_t snd_clone_setflags(struct snddev_clone *, uint32_t); +int snd_clone_getdevtime(struct cdev *, struct timespec *); +uint32_t snd_clone_getdevflags(struct cdev *); +uint32_t snd_clone_setdevflags(struct cdev *, uint32_t); +int snd_clone_gc(struct snddev_clone *); +void snd_clone_destroy(struct snddev_clone *); +int snd_clone_acquire(struct cdev *); +int snd_clone_release(struct cdev *); +int snd_clone_ref(struct cdev *); +int snd_clone_unref(struct cdev *); +struct cdev *snd_clone_alloc(char *); + +#endif /* !_SND_CLONE_H */ --- sys/dev/sound/pcm/dsp.c.orig Sat Mar 17 01:17:25 2007 +++ sys/dev/sound/pcm/dsp.c Sat May 19 09:42:27 2007 @@ -55,7 +55,7 @@ }; #ifdef USING_DEVFS -static eventhandler_tag dsp_ehtag; +static eventhandler_tag dsp_ehtag = NULL; #endif static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); @@ -128,6 +128,8 @@ flags = dsp_get_flags(dev); d = dsp_get_info(dev); + if (d == NULL) + return -1; pcm_inprog(d, 1); pcm_lock(d); KASSERT((flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \ @@ -138,15 +140,15 @@ dsp_set_flags(dev, flags); } - *rdch = dev->si_drv1; - *wrch = dev->si_drv2; + *rdch = PCM_RDCH(dev); + *wrch = PCM_WRCH(dev); if ((flags & SD_F_SIMPLEX) && (flags & SD_F_PRIO_SET)) { if (prio) { if (*rdch && flags & SD_F_PRIO_WR) { - dev->si_drv1 = NULL; + PCM_RDCH(dev) = NULL; *rdch = pcm_getfakechan(d); } else if (*wrch && flags & SD_F_PRIO_RD) { - dev->si_drv2 = NULL; + PCM_WRCH(dev) = NULL; *wrch = pcm_getfakechan(d); } } @@ -170,6 +172,8 @@ struct snddev_info *d; d = dsp_get_info(dev); + if (d == NULL) + return; if (wrch && wrch != pcm_getfakechan(d) && (prio & SD_F_PRIO_WR)) CHN_UNLOCK(wrch); if (rdch && rdch != pcm_getfakechan(d) && (prio & SD_F_PRIO_RD)) @@ -185,58 +189,65 @@ u_int32_t fmt; int devtype; int error; - int chnum; + int devminor; if (i_dev == NULL || td == NULL) return ENODEV; - if ((flags & (FREAD | FWRITE)) == 0) + if (!(flags & (FREAD | FWRITE))) return EINVAL; d = dsp_get_info(i_dev); + if (d == NULL) + return EBADF; + + /* lock snddev so nobody else can monkey with it */ + pcm_lock(d); + + error = snd_clone_acquire(i_dev); + if (!(error == 0 || error == ENODEV)) { + pcm_unlock(d); + return error; + } + devtype = PCMDEV(i_dev); - chnum = -1; + devminor = -1; /* decide default format */ switch (devtype) { case SND_DEV_DSP16: fmt = AFMT_S16_LE; break; - case SND_DEV_DSP: fmt = AFMT_U8; break; - case SND_DEV_AUDIO: fmt = AFMT_MU_LAW; break; - - case SND_DEV_NORESET: - fmt = 0; - break; - - case SND_DEV_DSPHW: + case SND_DEV_DSPHW_PLAY: + case SND_DEV_DSPHW_VPLAY: + case SND_DEV_DSPHW_REC: + case SND_DEV_DSPHW_VREC: /* * HW *specific* access without channel numbering confusion * caused by "first come first served" by dsp_clone(). */ fmt = AFMT_U8; - chnum = PCMCHAN(i_dev); + devminor = PCMMINOR(i_dev); break; - default: panic("impossible devtype %d", devtype); + break; } - /* lock snddev so nobody else can monkey with it */ - pcm_lock(d); + rdch = PCM_RDCH(i_dev); + wrch = PCM_WRCH(i_dev); - rdch = i_dev->si_drv1; - wrch = i_dev->si_drv2; - - if (rdch || wrch || ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && - (flags & (FREAD | FWRITE)) == (FREAD | FWRITE))) { + if (rdch != NULL || wrch != NULL || + ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && + (flags & (FREAD | FWRITE)) == (FREAD | FWRITE))) { /* simplex or not, better safe than sorry. */ + (void)snd_clone_release(i_dev); pcm_unlock(d); return EBUSY; } @@ -250,17 +261,23 @@ if (flags & FREAD) { /* open for read */ pcm_unlock(d); - error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, chnum); - if (error != 0 && error != EBUSY && chnum != -1 && (flags & FWRITE)) - error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, -1); + error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, + devminor); + if (error != 0 && error != EBUSY && devminor != -1 && + (flags & FWRITE)) + error = pcm_chnalloc(d, &rdch, PCMDIR_REC, + td->td_proc->p_pid, -1); - if (error == 0 && (chn_reset(rdch, fmt) || - (fmt && chn_setspeed(rdch, DSP_DEFAULT_SPEED)))) + if (error == 0 && (chn_reset(rdch, fmt) != 0 || + (chn_setspeed(rdch, DSP_DEFAULT_SPEED) != 0))) error = ENODEV; if (error != 0) { - if (rdch) + if (rdch != NULL) pcm_chnrelease(rdch); + pcm_lock(d); + (void)snd_clone_release(i_dev); + pcm_unlock(d); return error; } @@ -272,42 +289,53 @@ } if (flags & FWRITE) { - /* open for write */ - pcm_unlock(d); - error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, chnum); - if (error != 0 && error != EBUSY && chnum != -1 && (flags & FREAD)) - error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, -1); - - if (error == 0 && (chn_reset(wrch, fmt) || - (fmt && chn_setspeed(wrch, DSP_DEFAULT_SPEED)))) - error = ENODEV; + /* open for write */ + pcm_unlock(d); + error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, + devminor); + if (error != 0 && error != EBUSY && devminor != -1 && + (flags & FREAD)) + error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, + td->td_proc->p_pid, -1); - if (error != 0) { - if (wrch) - pcm_chnrelease(wrch); - if (rdch) { - /* - * Lock, deref and release previously created record channel - */ - CHN_LOCK(rdch); - pcm_chnref(rdch, -1); - pcm_chnrelease(rdch); - } + if (error == 0 && (chn_reset(wrch, fmt) != 0 || + (chn_setspeed(wrch, DSP_DEFAULT_SPEED)))) + error = ENODEV; - return error; - } + if (error != 0) { + if (wrch != NULL) + pcm_chnrelease(wrch); + if (rdch != NULL) { + /* + * Lock, deref and release previously + * created record channel + */ + CHN_LOCK(rdch); + pcm_chnref(rdch, -1); + pcm_chnrelease(rdch); + } + + pcm_lock(d); + (void)snd_clone_release(i_dev); + pcm_unlock(d); - if (flags & O_NONBLOCK) - wrch->flags |= CHN_F_NBIO; - pcm_chnref(wrch, 1); - CHN_UNLOCK(wrch); - pcm_lock(d); + return error; + } + + if (flags & O_NONBLOCK) + wrch->flags |= CHN_F_NBIO; + pcm_chnref(wrch, 1); + CHN_UNLOCK(wrch); + pcm_lock(d); } - i_dev->si_drv1 = rdch; - i_dev->si_drv2 = wrch; + PCM_RDCH(i_dev) = rdch; + PCM_WRCH(i_dev) = wrch; + + (void)snd_clone_ref(i_dev); pcm_unlock(d); + return 0; } @@ -319,10 +347,11 @@ int refs, sg_ids[2]; d = dsp_get_info(i_dev); + if (d == NULL) + return EBADF; pcm_lock(d); - rdch = i_dev->si_drv1; - wrch = i_dev->si_drv2; - pcm_unlock(d); + rdch = PCM_RDCH(i_dev); + wrch = PCM_WRCH(i_dev); /* * Free_unr() may sleep, so store released syncgroup IDs until after @@ -332,6 +361,7 @@ if (rdch || wrch) { refs = 0; + pcm_unlock(d); if (rdch) { /* * The channel itself need not be locked because: @@ -371,22 +401,24 @@ pcm_lock(d); if (rdch) - i_dev->si_drv1 = NULL; + PCM_RDCH(i_dev) = NULL; if (wrch) - i_dev->si_drv2 = NULL; + PCM_WRCH(i_dev) = NULL; /* * If there are no more references, release the channels. */ - if (refs == 0 && i_dev->si_drv1 == NULL && - i_dev->si_drv2 == NULL) { + if (refs == 0 && PCM_RDCH(i_dev) == NULL && + PCM_WRCH(i_dev) == NULL) { if (pcm_getfakechan(d)) pcm_getfakechan(d)->flags = 0; /* What is this?!? */ dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT); + (void)snd_clone_release(i_dev); + (void)snd_clone_unref(i_dev); } - pcm_unlock(d); } + pcm_unlock(d); if (sg_ids[0]) free_unr(pcmsg_unrhdr, sg_ids[0]); @@ -404,8 +436,8 @@ getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD); - KASSERT(rdch, ("dsp_read: nonexistant channel")); - KASSERT(rdch->flags & CHN_F_BUSY, ("dsp_read: nonbusy channel")); + if (rdch == NULL || !(rdch->flags & CHN_F_BUSY)) + return EBADF; if (rdch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); @@ -427,8 +459,8 @@ getchns(i_dev, &rdch, &wrch, SD_F_PRIO_WR); - KASSERT(wrch, ("dsp_write: nonexistant channel")); - KASSERT(wrch->flags & CHN_F_BUSY, ("dsp_write: nonbusy channel")); + if (wrch == NULL || !(wrch->flags & CHN_F_BUSY)) + return EBADF; if (wrch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); @@ -469,6 +501,8 @@ */ d = dsp_get_info(i_dev); + if (d == NULL) + return EBADF; if (IOCGROUP(cmd) == 'M') { /* * This is at least, a bug to bug compatible with OSS. @@ -516,7 +550,7 @@ wrch = NULL; if (kill & 2) rdch = NULL; - + switch(cmd) { #ifdef OLDPCM_IOCTL /* @@ -1512,75 +1546,35 @@ * if xN.i isn't busy, return its dev_t */ static void -dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, - struct cdev **dev) +dsp_clone(void *arg, +#if __FreeBSD_version >= 600034 + struct ucred *cred, +#endif + char *name, int namelen, struct cdev **dev) { - struct cdev *pdev; - struct snddev_info *pcm_dev; - struct snddev_channel *pcm_chan; - int i, unit, devtype; - static int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO}; - static char *devnames[3] = {"dsp", "dspW", "audio"}; - - if (*dev != NULL) - return; - if (pcm_devclass == NULL) + if (name == NULL || dev == NULL || *dev != NULL) return; - devtype = 0; - unit = -1; - for (i = 0; (i < 3) && (unit == -1); i++) { - devtype = devtypes[i]; - if (strcmp(name, devnames[i]) == 0) { - unit = snd_unit; - } else { - if (dev_stdclone(name, NULL, devnames[i], &unit) != 1) - unit = -1; - } - } - if (unit == -1 || unit >= devclass_get_maxunit(pcm_devclass)) - return; - - pcm_dev = devclass_get_softc(pcm_devclass, unit); - - if (pcm_dev == NULL) - return; - - SLIST_FOREACH(pcm_chan, &pcm_dev->channels, link) { - - switch(devtype) { - case SND_DEV_DSP: - pdev = pcm_chan->dsp_devt; - break; - case SND_DEV_DSP16: - pdev = pcm_chan->dspW_devt; - break; - case SND_DEV_AUDIO: - pdev = pcm_chan->audio_devt; - break; - default: - panic("Unknown devtype %d", devtype); - } - - if ((pdev != NULL) && (pdev->si_drv1 == NULL) && (pdev->si_drv2 == NULL)) { - *dev = pdev; - dev_ref(*dev); - return; - } - } + *dev = snd_clone_alloc(name); + if (*dev != NULL) + dev_ref(*dev); } static void dsp_sysinit(void *p) { + if (dsp_ehtag != NULL) + return; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { - if (dsp_ehtag != NULL) - EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); + if (dsp_ehtag == NULL) + return; + EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); + dsp_ehtag = NULL; } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); @@ -1622,7 +1616,6 @@ int dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) { - struct snddev_channel *sce; struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; @@ -1642,7 +1635,7 @@ t_cdev = NULL; nchan = 0; ret = 0; - + /* * Search for the requested audio device (channel). Start by * iterating over pcm devices. @@ -1657,18 +1650,17 @@ pcm_inprog(d, 1); pcm_lock(d); - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; + CHN_FOREACH(ch, d, channels.pcm) { mtx_assert(ch->lock, MA_NOTOWNED); CHN_LOCK(ch); if (ai->dev == -1) { - if ((ch == i_dev->si_drv1) || /* record ch */ - (ch == i_dev->si_drv2)) { /* playback ch */ + if ((ch == PCM_RDCH(i_dev)) || /* record ch */ + (ch == PCM_WRCH(i_dev))) { /* playback ch */ t_cdev = i_dev; goto dspfound; } } else if (ai->dev == nchan) { - t_cdev = sce->dsp_devt; + t_cdev = ch->devt; goto dspfound; } CHN_UNLOCK(ch); @@ -1721,7 +1713,7 @@ * table for later. Is there a risk of leaking information? */ ai->pid = ch->pid; - + /* * These flags stolen from SNDCTL_DSP_GETCAPS handler. Note, however, * that a single channel operates in only one direction, so @@ -2008,7 +2000,7 @@ struct pcmchan_syncgroup *sg; struct pcm_channel *c; int ret, needlocks; - + /* Get the synclists lock */ PCM_SG_LOCK(); --- sys/dev/sound/pcm/mixer.c.orig Mon Apr 2 18:24:15 2007 +++ sys/dev/sound/pcm/mixer.c Thu May 3 02:44:57 2007 @@ -130,26 +130,35 @@ mixer_set_softpcmvol(struct snd_mixer *mixer, struct snddev_info *d, unsigned left, unsigned right) { - struct snddev_channel *sce; - struct pcm_channel *ch; -#ifdef USING_MUTEX - int locked = (mixer->lock && mtx_owned((struct mtx *)(mixer->lock))) ? 1 : 0; + struct pcm_channel *c, *tmp; + int locked; + locked = (mixer->lock != NULL && + mtx_owned((struct mtx *)(mixer->lock))) ? 1 : 0; if (locked) snd_mtxunlock(mixer->lock); -#endif - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; - CHN_LOCK(ch); - if (ch->direction == PCMDIR_PLAY && - (ch->feederflags & (1 << FEEDER_VOLUME))) - chn_setvolume(ch, left, right); - CHN_UNLOCK(ch); + + if (CHN_EMPTY(d, channels.pcm.busy)) { + CHN_FOREACH_SAFE(c, d, tmp, channels.pcm) { + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + (c->feederflags & (1 << FEEDER_VOLUME))) + chn_setvolume(c, left, right); + CHN_UNLOCK(c); + } + } else { + CHN_FOREACH_SAFE(c, d, tmp, channels.pcm.busy) { + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + (c->feederflags & (1 << FEEDER_VOLUME))) + chn_setvolume(c, left, right); + CHN_UNLOCK(c); + } } -#ifdef USING_MUTEX + if (locked) snd_mtxlock(mixer->lock); -#endif + return 0; } @@ -845,8 +854,11 @@ #ifdef USING_DEVFS static void -mixer_clone(void *arg, struct ucred *cred, char *name, int namelen, - struct cdev **dev) +mixer_clone(void *arg, +#if __FreeBSD_version >= 600034 + struct ucred *cred, +#endif + char *name, int namelen, struct cdev **dev) { struct snddev_info *sd; --- sys/dev/sound/pcm/sound.c.orig Fri Mar 16 02:19:01 2007 +++ sys/dev/sound/pcm/sound.c Sun May 20 15:42:47 2007 @@ -155,112 +155,98 @@ } static int -pcm_setvchans(struct snddev_info *d, int newcnt) +pcm_setvchans(struct snddev_info *d, int direction, int newcnt) { - struct snddev_channel *sce = NULL; - struct pcm_channel *c = NULL; - int err = 0, vcnt, dcnt, i; + struct pcm_channel *c, *ch, *nch; + int err = 0, vcnt; pcm_inprog(d, 1); - if (d->playcount < 1) { + if ((direction == PCMDIR_PLAY && d->playcount < 1) || + (direction == PCMDIR_REC && d->reccount < 1)) { err = ENODEV; - goto setvchans_out; + goto pcm_setvchans_out; } if (!(d->flags & SD_F_AUTOVCHAN)) { err = EINVAL; - goto setvchans_out; + goto pcm_setvchans_out; } - vcnt = d->vchancount; - dcnt = d->playcount + d->reccount; - - if (newcnt < 0 || (dcnt + newcnt) > (PCMMAXCHAN + 1)) { + if (newcnt < 0 || newcnt > SND_MAXVCHANS) { err = E2BIG; - goto setvchans_out; + goto pcm_setvchans_out; } - dcnt += vcnt; + if (direction == PCMDIR_PLAY) + vcnt = d->pvchancount; + else if (direction == PCMDIR_REC) + vcnt = d->rvchancount; + else { + err = EINVAL; + goto pcm_setvchans_out; + } if (newcnt > vcnt) { /* add new vchans - find a parent channel first */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY && - ((c->flags & CHN_F_HAS_VCHAN) || - (vcnt == 0 && - !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) - goto addok; + if (c->direction == direction && + ((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 && + !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) + goto pcm_setvchans_addok; CHN_UNLOCK(c); } err = EBUSY; - goto setvchans_out; -addok: + goto pcm_setvchans_out; +pcm_setvchans_addok: c->flags |= CHN_F_BUSY; while (err == 0 && newcnt > vcnt) { - if (dcnt > PCMMAXCHAN) { - device_printf(d->dev, "%s: Maximum channel reached.\n", __func__); - break; - } err = vchan_create(c); - if (err == 0) { + if (err == 0) vcnt++; - dcnt++; - } else if (err == E2BIG && newcnt > vcnt) - device_printf(d->dev, "%s: err=%d Maximum channel reached.\n", __func__, err); + else if (err == E2BIG && newcnt > vcnt) + device_printf(d->dev, + "%s: err=%d Maximum channel reached.\n", + __func__, err); } if (vcnt == 0) c->flags &= ~CHN_F_BUSY; CHN_UNLOCK(c); } else if (newcnt < vcnt) { -#define ORPHAN_CDEVT(cdevt) \ - ((cdevt) == NULL || ((cdevt)->si_drv1 == NULL && \ - (cdevt)->si_drv2 == NULL)) - while (err == 0 && newcnt < vcnt) { - i = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY && - (c->flags & CHN_F_VIRTUAL) && - (i++ == newcnt)) { - if (!(c->flags & CHN_F_BUSY) && - ORPHAN_CDEVT(sce->dsp_devt) && - ORPHAN_CDEVT(sce->dspW_devt) && - ORPHAN_CDEVT(sce->audio_devt) && - ORPHAN_CDEVT(sce->dspHW_devt)) - goto remok; - /* - * Either we're busy, or our cdev - * has been stolen by dsp_clone(). - * Skip, and increase newcnt. - */ - if (!(c->flags & CHN_F_BUSY)) - device_printf(d->dev, - "%s: <%s> somebody steal my cdev!\n", - __func__, c->name); - newcnt++; - } + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction != direction || + CHN_EMPTY(c, children) || + !(c->flags & CHN_F_HAS_VCHAN)) { CHN_UNLOCK(c); + continue; + } + CHN_FOREACH_SAFE(ch, c, nch, children) { + CHN_LOCK(ch); + if (!(ch->flags & CHN_F_BUSY)) { + CHN_UNLOCK(ch); + CHN_UNLOCK(c); + err = vchan_destroy(ch); + CHN_LOCK(c); + if (err == 0) + vcnt--; + } else + CHN_UNLOCK(ch); + if (vcnt == newcnt) { + err = 0; + break; + } } - if (vcnt != newcnt) - err = EBUSY; - break; -remok: CHN_UNLOCK(c); - err = vchan_destroy(c); - if (err == 0) - vcnt--; - else - device_printf(d->dev, - "%s: WARNING: vchan_destroy() failed!", - __func__); + break; } + pcm_lock(d); + (void)snd_clone_gc(d->clones); + pcm_unlock(d); } -setvchans_out: +pcm_setvchans_out: pcm_inprog(d, -1); return err; } @@ -268,27 +254,25 @@ /* return error status and a locked channel */ int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, - pid_t pid, int chnum) + pid_t pid, int devminor) { struct pcm_channel *c; - struct snddev_channel *sce; - int err; + int err, vchancount; retry_chnalloc: err = ENODEV; /* scan for a free channel */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if (c->direction == direction && !(c->flags & CHN_F_BUSY)) { - if (chnum < 0 || sce->chan_num == chnum) { - c->flags |= CHN_F_BUSY; - c->pid = pid; - *ch = c; - return 0; - } - } - if (sce->chan_num == chnum) { + if (c->devt != NULL && c->direction == direction && + !(c->flags & CHN_F_BUSY) && + (devminor == -1 || devminor == -2 || + c->devminor == devminor)) { + c->flags |= CHN_F_BUSY; + c->pid = pid; + *ch = c; + return 0; + } else if (c->devminor == devminor) { if (c->direction != direction) err = EOPNOTSUPP; else if (c->flags & CHN_F_BUSY) @@ -303,12 +287,19 @@ } /* no channel available */ - if (chnum == -1 && direction == PCMDIR_PLAY && d->vchancount > 0 && - d->vchancount < snd_maxautovchans && - d->devcount <= PCMMAXCHAN) { - err = pcm_setvchans(d, d->vchancount + 1); + if (devminor == -1) { + if (direction == PCMDIR_PLAY) + vchancount = d->pvchancount; + else if (direction == PCMDIR_REC) + vchancount = d->rvchancount; + else + return err; + if (!(vchancount > 0 && vchancount < snd_maxautovchans && + vchancount < SND_MAXVCHANS)) + return err; + err = pcm_setvchans(d, direction, vchancount + 1); if (err == 0) { - chnum = -2; + devminor = -2; goto retry_chnalloc; } } @@ -357,10 +348,16 @@ static void pcm_setmaxautovchans(struct snddev_info *d, int num) { - if (num > 0 && d->vchancount == 0) - pcm_setvchans(d, 1); - else if (num == 0 && d->vchancount > 0) - pcm_setvchans(d, 0); + if (num > 0 && d->pvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_PLAY, 1); + else if (num >= 0 && d->pvchancount > num) + (void)pcm_setvchans(d, PCMDIR_PLAY, num); +#if 0 + if (num > 0 && d->rvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_REC, 1); + else if (num >= 0 && d->rvchancount > num) + (void)pcm_setvchans(d, PCMDIR_REC, num); +#endif } #ifdef USING_DEVFS @@ -376,7 +373,7 @@ if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass)) return EINVAL; d = devclass_get_softc(pcm_devclass, unit); - if (d == NULL || SLIST_EMPTY(&d->channels)) + if (d == NULL || CHN_EMPTY(d, channels.pcm)) return EINVAL; snd_unit = unit; } @@ -396,15 +393,16 @@ v = snd_maxautovchans; error = sysctl_handle_int(oidp, &v, sizeof(v), req); if (error == 0 && req->newptr != NULL) { - if (v < 0 || v > PCMMAXCHAN) - return E2BIG; - if (pcm_devclass != NULL && v != snd_maxautovchans) { - for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { - d = devclass_get_softc(pcm_devclass, i); - if (!d) - continue; - pcm_setmaxautovchans(d, v); - } + if (v < 0) + v = 0; + if (v > SND_MAXVCHANS) + v = SND_MAXVCHANS; + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (d == NULL) + continue; + pcm_setmaxautovchans(d, v); } snd_maxautovchans = v; } @@ -416,11 +414,10 @@ struct pcm_channel * pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) { - struct snddev_channel *sce; - struct pcm_channel *ch, *c; + struct pcm_channel *ch; char *dirs; uint32_t flsearch = 0; - int direction, err, rpnum, *pnum; + int direction, err, num, rpnum, *pnum; switch(dir) { case PCMDIR_PLAY: @@ -428,80 +425,80 @@ direction = PCMDIR_PLAY; pnum = &d->playcount; break; - + case PCMDIR_PLAY_VIRTUAL: + dirs = "virtual"; + direction = PCMDIR_PLAY; + pnum = &d->pvchancount; + flsearch = CHN_F_VIRTUAL; + break; case PCMDIR_REC: dirs = "record"; direction = PCMDIR_REC; pnum = &d->reccount; break; - - case PCMDIR_VIRTUAL: + case PCMDIR_REC_VIRTUAL: dirs = "virtual"; - direction = PCMDIR_PLAY; - pnum = &d->vchancount; + direction = PCMDIR_REC; + pnum = &d->rvchancount; flsearch = CHN_F_VIRTUAL; break; - default: return NULL; } - ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); - ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); - snd_mtxlock(d->lock); - ch->num = 0; - rpnum = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (direction != c->direction || - (c->flags & CHN_F_VIRTUAL) != flsearch) - continue; - if (ch->num == c->num) - ch->num++; - else { -#if 0 - device_printf(d->dev, - "%s: %s channel numbering screwed (Expect: %d, Got: %d)\n", - __func__, dirs, ch->num, c->num); -#endif - goto retry_num_search; - } - rpnum++; + + if (*pnum > PCMMAXCHAN) { + snd_mtxunlock(d->lock); + return NULL; } - goto retry_num_search_out; -retry_num_search: + + num = 0; rpnum = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (direction != c->direction || - (c->flags & CHN_F_VIRTUAL) != flsearch) + + CHN_FOREACH(ch, d, channels.pcm) { + if (direction != ch->direction || + (ch->flags & CHN_F_VIRTUAL) != flsearch) continue; - if (ch->num == c->num) { - ch->num++; - goto retry_num_search; + if (num == ch->num) { + num++; + if (num > PCMMAXCHAN) { + device_printf(d->dev, + "num=%d > %d\n", num, PCMMAXCHAN); + snd_mtxunlock(d->lock); + return NULL; + } } rpnum++; } -retry_num_search_out: + if (*pnum != rpnum) { device_printf(d->dev, - "%s: WARNING: pnum screwed : dirs=%s, pnum=%d, rpnum=%d\n", - __func__, dirs, *pnum, rpnum); - *pnum = rpnum; + "%s(): WARNING: pnum screwed : dirs=%s pnum=%d rpnum=%d\n", + __func__, dirs, *pnum, rpnum); + snd_mtxunlock(d->lock); + return NULL; } + (*pnum)++; snd_mtxunlock(d->lock); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); + ch->num = num; ch->pid = -1; ch->parentsnddev = d; ch->parentchannel = parent; ch->dev = d->dev; - snprintf(ch->name, CHN_NAMELEN, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num); + ch->data1 = (void *)((intptr_t)PCMTRIG_STOP); + ch->data2 = (void *)((intptr_t)PCMTRIG_STOP); + snprintf(ch->name, sizeof(ch->name), "%s:%s:%d", + device_get_nameunit(ch->dev), dirs, ch->num); err = chn_init(ch, devinfo, dir, direction); if (err) { - device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err); + device_printf(d->dev, "chn_init(%s) failed: err = %d\n", + ch->name, err); kobj_delete(ch->methods, M_DEVBUF); free(ch, M_DEVBUF); snd_mtxlock(d->lock); @@ -536,11 +533,11 @@ int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce, *tmp, *after; - unsigned rdevcount; - int device = device_get_unit(d->dev); - size_t namelen; - char dtype; + struct pcm_channel *tmp, *after; + struct snddev_chinfo *sdev; + struct cdev *ndev; + int num, unit, device; + char *dgrp, *dtype, dname[CHN_NAMELEN]; /* * Note it's confusing nomenclature. @@ -552,123 +549,82 @@ * unit -> pcm_device */ - sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO); + KASSERT(ch != NULL && (ch->direction == PCMDIR_PLAY || + ch->direction == PCMDIR_REC), ("Invalid pcm channel")); + + if (ch->direction == PCMDIR_PLAY) { + dgrp = "p"; + device = SND_DEV_DSPHW_PLAY; + } else if (ch->direction == PCMDIR_REC) { + dgrp = "r"; + device = SND_DEV_DSPHW_REC; + } else + return ENODEV; + + if (ch->flags & CHN_F_VIRTUAL) { + dtype = "v"; + if (ch->direction == PCMDIR_PLAY) + device = SND_DEV_DSPHW_VPLAY; + else if (ch->direction == PCMDIR_REC) + device = SND_DEV_DSPHW_VREC; + else + return ENODEV; + } else + dtype = ""; snd_mtxlock(d->lock); - sce->channel = ch; - sce->chan_num = 0; - rdevcount = 0; + after = NULL; - SLIST_FOREACH(tmp, &d->channels, link) { - if (sce->chan_num == tmp->chan_num) - sce->chan_num++; - else { -#if 0 - device_printf(d->dev, - "%s: cdev numbering screwed (Expect: %d, Got: %d)\n", - __func__, sce->chan_num, tmp->chan_num); -#endif - goto retry_chan_num_search; - } - after = tmp; - rdevcount++; - } - goto retry_chan_num_search_out; -retry_chan_num_search: + tmp = NULL; + num = 0; + unit = device_get_unit(d->dev); + ch->devminor = PCMMKMINOR(unit, device, ch->num); + snprintf(dname, sizeof(dname), "dsp%d.%s%s%d", unit, dtype, dgrp, + ch->num); + strlcat(ch->name, ":", sizeof(ch->name)); + strlcat(ch->name, dname, sizeof(ch->name)); + /* - * Look for possible channel numbering collision. This may not - * be optimized, but it will ensure that no collision occured. - * Can be considered cheap since none of the locking/unlocking - * operations involved. + * Look for possible device collision. */ - rdevcount = 0; - after = NULL; - SLIST_FOREACH(tmp, &d->channels, link) { - if (sce->chan_num == tmp->chan_num) { - sce->chan_num++; - goto retry_chan_num_search; - } - if (sce->chan_num > tmp->chan_num) + CHN_FOREACH(tmp, d, channels.pcm) { + if (tmp->devminor == ch->devminor) { + snd_mtxunlock(d->lock); + device_printf(d->dev, "%s(): Device collision " + "old=%p new=%p devminor=0x%08x\n", + __func__, tmp, ch, ch->devminor); + return ENODEV; + } + if (PCMMINOR_DEV(tmp->devminor) < device) { + if (num == 0) + after = tmp; + continue; + } else if (PCMMINOR_DEV(tmp->devminor) > device) + break; + num++; + if (tmp->num < ch->num) after = tmp; - rdevcount++; - } -retry_chan_num_search_out: - /* - * Don't overflow PCMMKMINOR / PCMMAXCHAN. - */ - if (sce->chan_num > PCMMAXCHAN) { - snd_mtxunlock(d->lock); - device_printf(d->dev, - "%s: WARNING: sce->chan_num overflow! (%d)\n", - __func__, sce->chan_num); - free(sce, M_DEVBUF); - return E2BIG; - } - if (d->devcount != rdevcount) { - device_printf(d->dev, - "%s: WARNING: devcount screwed! d->devcount=%u, rdevcount=%u\n", - __func__, d->devcount, rdevcount); - d->devcount = rdevcount; + else if (tmp->num > ch->num) + break; } - d->devcount++; - if (after == NULL) { - SLIST_INSERT_HEAD(&d->channels, sce, link); + + if (after != NULL) { + CHN_INSERT_AFTER(after, ch, channels.pcm); } else { - SLIST_INSERT_AFTER(after, sce, link); - } -#if 0 - if (1) { - int cnum = 0; - SLIST_FOREACH(tmp, &d->channels, link) { - if (cnum != tmp->chan_num) - device_printf(d->dev, - "%s: WARNING: inconsistent cdev numbering! (Expect: %d, Got: %d)\n", - __func__, cnum, tmp->chan_num); - cnum++; - } + CHN_INSERT_HEAD(d, ch, channels.pcm); } -#endif - if (ch->flags & CHN_F_VIRTUAL) - dtype = 'v'; - else if (ch->direction == PCMDIR_PLAY) - dtype = 'p'; - else if (ch->direction == PCMDIR_REC) - dtype = 'r'; - else - dtype = 'u'; /* we're screwed */ - - namelen = strlen(ch->name); - if ((CHN_NAMELEN - namelen) > 11) { /* ":dspXX.TYYY" */ - snprintf(ch->name + namelen, - CHN_NAMELEN - namelen, ":dsp%d.%c%d", - device, dtype, ch->num); - } + d->devcount++; snd_mtxunlock(d->lock); - /* - * I will revisit these someday, and nuke it mercilessly.. - */ - sce->dsp_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSP, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d", - device, sce->chan_num); - - sce->dspW_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSP16, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d", - device, sce->chan_num); - - sce->audio_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_AUDIO, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", - device, sce->chan_num); - - /* Except this. */ - sce->dspHW_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSPHW, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "dsp%d.%c%d", - device, dtype, ch->num); + ndev = make_dev(&dsp_cdevsw, ch->devminor, + UID_ROOT, GID_WHEEL, 0666, "%s", dname); + sdev = malloc(sizeof(*sdev), M_DEVBUF, M_WAITOK | M_ZERO); + ndev->si_drv1 = sdev; + + CHN_LOCK(ch); + ch->devt = ndev; + CHN_UNLOCK(ch); return 0; } @@ -676,41 +632,28 @@ int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce; -#if 0 - int ourlock; + struct pcm_channel *tmp; - ourlock = 0; - if (!mtx_owned(d->lock)) { - snd_mtxlock(d->lock); - ourlock = 1; - } -#endif + tmp = NULL; - SLIST_FOREACH(sce, &d->channels, link) { - if (sce->channel == ch) - goto gotit; + CHN_FOREACH(tmp, d, channels.pcm) { + if (tmp == ch) + break; } -#if 0 - if (ourlock) - snd_mtxunlock(d->lock); -#endif - return EINVAL; -gotit: - SLIST_REMOVE(&d->channels, sce, snddev_channel, link); - if (ch->flags & CHN_F_VIRTUAL) - d->vchancount--; + if (tmp != ch) + return EINVAL; + + CHN_REMOVE(d, ch, channels.pcm); + if (ch->flags & CHN_F_VIRTUAL) { + if (ch->direction == PCMDIR_PLAY) + d->pvchancount--; + else if (ch->direction == PCMDIR_REC) + d->rvchancount--; + } else if (ch->direction == PCMDIR_PLAY) + d->playcount--; else if (ch->direction == PCMDIR_REC) d->reccount--; - else - d->playcount--; - -#if 0 - if (ourlock) - snd_mtxunlock(d->lock); -#endif - free(sce, M_DEVBUF); return 0; } @@ -742,14 +685,12 @@ pcm_killchan(device_t dev) { struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; struct pcm_channel *ch; int error = 0; - sce = SLIST_FIRST(&d->channels); - ch = sce->channel; + ch = CHN_FIRST(d, channels.pcm); - error = pcm_chn_remove(d, sce->channel); + error = pcm_chn_remove(d, ch); if (error) return (error); return (pcm_chn_destroy(ch)); @@ -764,7 +705,7 @@ strlcpy(d->status, str, SND_STATUSLEN); snd_mtxunlock(d->lock); if (snd_maxautovchans > 0) - pcm_setvchans(d, 1); + (void)pcm_setvchans(d, PCMDIR_PLAY, 1); return 0; } @@ -822,10 +763,87 @@ return sz; } +#if defined(SND_DYNSYSCTL) && defined(SND_DEBUG) +static int +sysctl_dev_pcm_clone_flags(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + uint32_t flags; + int err; + + d = oidp->oid_arg1; + if (d == NULL || d->clones == NULL) + return (ENODEV); + + flags = snd_clone_getflags(d->clones); + err = sysctl_handle_int(oidp, (int *)(&flags), sizeof(flags), req); + + if (err == 0 && req->newptr != NULL) { + if ((flags & ~SND_CLONE_MASK)) + err = EINVAL; + else { + pcm_lock(d); + (void)snd_clone_setflags(d->clones, flags); + pcm_unlock(d); + } + } + + return (err); +} + +static int +sysctl_dev_pcm_clone_deadline(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int err, deadline; + + d = oidp->oid_arg1; + if (d == NULL || d->clones == NULL) + return (ENODEV); + + deadline = snd_clone_getdeadline(d->clones); + err = sysctl_handle_int(oidp, &deadline, sizeof(deadline), req); + + if (err == 0 && req->newptr != NULL) { + if (deadline < 0) + err = EINVAL; + else { + pcm_lock(d); + (void)snd_clone_setdeadline(d->clones, deadline); + pcm_unlock(d); + } + } + + return (err); +} + +static int +sysctl_dev_pcm_clone_gc(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int err, val; + + d = oidp->oid_arg1; + if (d == NULL || d->clones == NULL) + return (ENODEV); + + val = 0; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + + if (err == 0 && req->newptr != NULL && val != 0) { + pcm_lock(d); + (void)snd_clone_gc(d->clones); + pcm_unlock(d); + } + + return (err); +} +#endif + int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d; if (pcm_veto_load) { device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); @@ -833,6 +851,13 @@ return EINVAL; } + if (device_get_unit(dev) > PCMMAXUNIT) { + device_printf(dev, "PCMMAXUNIT reached : unit=%d > %d\n", + device_get_unit(dev), PCMMAXUNIT); + return ENODEV; + } + + d = device_get_softc(dev); d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); #if 0 @@ -848,10 +873,25 @@ d->devcount = 0; d->reccount = 0; d->playcount = 0; - d->vchancount = 0; + d->pvchancount = 0; + d->rvchancount = 0; + d->pvchanrate = 0; + d->pvchanformat = 0; + d->rvchanrate = 0; + d->rvchanformat = 0; d->inprog = 0; + d->clones = snd_clone_create(d->lock, PCMMAXCLONE, SND_CLONE_DEADLINE, + SND_CLONE_GC_UNREF | SND_CLONE_GC_LASTREF | SND_CLONE_GC_EXPIRED); + + if (bootverbose != 0 || snd_verbose > 3) + device_printf(dev, + "clone manager: maxsize=%d deadline=%dms flags=0x%08x\n", + snd_clone_getmaxsize(d->clones), + snd_clone_getdeadline(d->clones), + snd_clone_getflags(d->clones)); - SLIST_INIT(&d->channels); + CHN_INIT(d, channels.pcm); + CHN_INIT(d, channels.pcm.busy); if ((numplay == 0 || numrec == 0) && numplay != numrec) d->flags |= SD_F_SIMPLEX; @@ -865,6 +905,23 @@ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); +#ifdef SND_DEBUG + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_flags", CTLTYPE_UINT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_flags, "IU", + "clone flags"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_deadline", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_deadline, "I", + "clone expiration deadline (ms)"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_gc", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_gc, "I", + "clone garbage collector"); +#endif #endif if (numplay > 0) { d->flags |= SD_F_AUTOVCHAN; @@ -879,8 +936,6 @@ pcm_unregister(device_t dev) { struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; - struct pcmchan_children *pce; struct pcm_channel *ch; if (sndstat_acquire() != 0) { @@ -896,8 +951,7 @@ return EBUSY; } - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; + CHN_FOREACH(ch, d, channels.pcm) { if (ch->refcount > 0) { device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid); snd_mtxunlock(d->lock); @@ -913,38 +967,13 @@ return EBUSY; } - SLIST_FOREACH(sce, &d->channels, link) { - if (sce->dsp_devt) { - destroy_dev(sce->dsp_devt); - sce->dsp_devt = NULL; - } - if (sce->dspW_devt) { - destroy_dev(sce->dspW_devt); - sce->dspW_devt = NULL; - } - if (sce->audio_devt) { - destroy_dev(sce->audio_devt); - sce->audio_devt = NULL; - } - if (sce->dspHW_devt) { - destroy_dev(sce->dspHW_devt); - sce->dspHW_devt = NULL; + CHN_FOREACH(ch, d, channels.pcm) { + if (ch->devt != NULL) { + if (ch->devt->si_drv1 != NULL) + free(ch->devt->si_drv1, M_DEVBUF); + destroy_dev(ch->devt); } d->devcount--; - ch = sce->channel; - if (ch == NULL) - continue; - pce = SLIST_FIRST(&ch->children); - while (pce != NULL) { -#if 0 - device_printf(d->dev, "<%s> removing <%s>\n", - ch->name, (pce->channel != NULL) ? - pce->channel->name : "unknown"); -#endif - SLIST_REMOVE(&ch->children, pce, pcmchan_children, link); - free(pce, M_DEVBUF); - pce = SLIST_FIRST(&ch->children); - } } #ifdef SND_DYNSYSCTL @@ -954,28 +983,15 @@ #endif #endif -#if 0 - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; - if (ch == NULL) - continue; - if (!SLIST_EMPTY(&ch->children)) - device_printf(d->dev, "%s: WARNING: <%s> dangling child!\n", - __func__, ch->name); - } -#endif - while (!SLIST_EMPTY(&d->channels)) + while (!CHN_EMPTY(d, channels.pcm)) pcm_killchan(dev); chn_kill(d->fakechan); fkchan_kill(d->fakechan); -#if 0 - device_printf(d->dev, "%s: devcount=%u, playcount=%u, " - "reccount=%u, vchancount=%u\n", - __func__, d->devcount, d->playcount, d->reccount, - d->vchancount); -#endif + if (d->clones != NULL) + snd_clone_destroy(d->clones); + snd_mtxunlock(d->lock); snd_mtxfree(d->lock); sndstat_unregister(dev); @@ -989,10 +1005,8 @@ sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) { struct snddev_info *d; - struct snddev_channel *sce; struct pcm_channel *c; struct pcm_feeder *f; - int pc, rc, vc; if (verbose < 1) return 0; @@ -1002,19 +1016,10 @@ return ENXIO; snd_mtxlock(d->lock); - if (!SLIST_EMPTY(&d->channels)) { - pc = rc = vc = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (c->direction == PCMDIR_PLAY) { - if (c->flags & CHN_F_VIRTUAL) - vc++; - else - pc++; - } else - rc++; - } - sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", d->playcount, d->reccount, d->vchancount, + if (!CHN_EMPTY(d, channels.pcm)) { + sbuf_printf(s, " (%dp:%dv/%dr:%dv channels%s%s)", + d->playcount, d->pvchancount, + d->reccount, d->rvchancount, (d->flags & SD_F_SIMPLEX)? "" : " duplex", #ifdef USING_DEVFS (device_get_unit(dev) == snd_unit)? " default" : "" @@ -1028,8 +1033,7 @@ return 0; } - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { KASSERT(c->bufhard != NULL && c->bufsoft != NULL, ("hosed pcm channel setup")); @@ -1099,15 +1103,44 @@ sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; + int unit, direction, vchancount; int err, newcnt; - d = oidp->oid_arg1; + if (pcm_devclass == NULL) + return EINVAL; - newcnt = d->vchancount; + unit = VCHAN_SYSCTL_UNIT(oidp->oid_arg1); + if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass)) + return EINVAL; + + d = devclass_get_softc(pcm_devclass, unit); + if (d == NULL) + return EINVAL; + + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + break; + default: + return EINVAL; + break; + } + + newcnt = vchancount; err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); - if (err == 0 && req->newptr != NULL && d->vchancount != newcnt) - err = pcm_setvchans(d, newcnt); + if (err == 0 && req->newptr != NULL && vchancount != newcnt) { + if (newcnt < 0) + newcnt = 0; + if (newcnt > SND_MAXVCHANS) + newcnt = SND_MAXVCHANS; + err = pcm_setvchans(d, direction, newcnt); + } return err; } @@ -1139,7 +1172,6 @@ static int intnbits = sizeof(int) * 8; /* Better suited as macro? Must pester a C guru. */ - struct snddev_channel *sce; struct snddev_info *d; struct pcm_channel *c; int i, j, ncards; @@ -1174,8 +1206,7 @@ si->numaudios += d->devcount; ++ncards; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { mtx_assert(c->lock, MA_NOTOWNED); CHN_LOCK(c); if (c->flags & CHN_F_BUSY) --- sys/dev/sound/pcm/sound.h.orig Mon May 7 00:46:23 2007 +++ sys/dev/sound/pcm/sound.h Sun May 20 06:47:54 2007 @@ -94,6 +94,7 @@ #include #include #include +#include #include #define PCM_SOFTC_SIZE 512 @@ -119,21 +120,40 @@ * +-------- ((PCMMAXUNIT << 5) | PCMMAXDEV) << 16 */ +#ifndef MAXMINOR +#define MAXMINOR 0xffff00ffU +#endif + #define PCMMAXCHAN 0xff #define PCMMAXDEV 0x1f #define PCMMAXUNIT 0x7ff + +#define PCMCHAN_SHIFT 0 +#define PCMUNIT_SHIFT 21 +#define PCMDEV_SHIFT 16 + +#define PCMMINOR_CHAN(x) (((x) >> PCMCHAN_SHIFT) & PCMMAXCHAN) +#define PCMMINOR_UNIT(x) (((x) >> PCMUNIT_SHIFT) & PCMMAXUNIT) +#define PCMMINOR_DEV(x) (((x) >> PCMDEV_SHIFT) & PCMMAXDEV) + #define PCMMINOR(x) minor(x) -#define PCMCHAN(x) (PCMMINOR(x) & PCMMAXCHAN) -#define PCMUNIT(x) ((PCMMINOR(x) >> 21) & PCMMAXUNIT) -#define PCMDEV(x) ((PCMMINOR(x) >> 16) & PCMMAXDEV) -#define PCMMKMINOR(u, d, c) ((((u) & PCMMAXUNIT) << 21) | \ - (((d) & PCMMAXDEV) << 16) | ((c) & PCMMAXCHAN)) + +#define PCMCHAN(x) PCMMINOR_CHAN(PCMMINOR(x)) +#define PCMUNIT(x) PCMMINOR_UNIT(PCMMINOR(x)) +#define PCMDEV(x) PCMMINOR_DEV(PCMMINOR(x)) + +#define PCMMKMINOR(u, d, c) (((((u) & PCMMAXUNIT) << PCMUNIT_SHIFT) | \ + (((d) & PCMMAXDEV) << PCMDEV_SHIFT) | \ + (((c) & PCMMAXCHAN) << PCMCHAN_SHIFT)) & \ + MAXMINOR) #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 #define SD_F_PSWAPLR 0x00000008 #define SD_F_RSWAPLR 0x00000010 +#define SD_F_DYING 0x00000020 +#define SD_F_SUICIDE 0x00000040 #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) @@ -434,7 +454,7 @@ int fkchan_kill(struct pcm_channel *c); /* XXX Flawed definition. I'll fix it someday. */ -#define SND_MAXVCHANS PCMMAXCHAN +#define SND_MAXVCHANS (PCMMAXCHAN + 1) /* * Minor numbers for the sound driver. @@ -457,10 +477,35 @@ #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 11 /* specific channel request */ + +#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 DSP_DEFAULT_SPEED 8000 + +#define PCM_DEV_CLONE_DEVS { SND_DEV_DSP, SND_DEV_AUDIO, SND_DEV_DSP16 } +#define PCM_DEV_CLONE_LABELS { "dsp", "audio", "dspW" } + +#define PCMDEVTYPE_CLONE(x) ((x) == SND_DEV_DSP || \ + (x) == SND_DEV_AUDIO || \ + (x) == SND_DEV_DSP16) + +#define PCMDEVTYPE_CLONE_LABEL(x) (PCMDEVTYPE_CLONE(x) ? \ + (((x) == SND_DEV_DSP) ? "dsp" : \ + (((x) == SND_DEV_AUDIO) ? "audio" : \ + "dspW")) : "INVALID CLONE") + +#define PCMMINOR_CLONE(x) PCMDEVTYPE_CLONE(PCMMINOR_DEV(x)) +#define PCMMINOR_CLONE_LABEL(x) PCMDEVTYPE_CLONE_LABEL(PCMMINOR_DEV(x)) + +#define PCMDEV_CLONE(x) PCMMINOR_CLONE(PCMMINOR(x)) +#define PCMDEV_CLONE_LABEL(x) PCMMINOR_CLONE_LABEL(PCMMINOR(x)) + +#define PCMMAXCLONE ((PCMMAXCHAN + 1) * 3) + #define ON 1 #define OFF 0 @@ -485,7 +530,7 @@ SYSCTL_DECL(_hw_snd); struct pcm_channel *pcm_getfakechan(struct snddev_info *d); -int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, int chnum); +int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, int devminor); 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); @@ -551,20 +596,22 @@ * we also have to do this now makedev() has gone away. */ -struct snddev_channel { - SLIST_ENTRY(snddev_channel) link; - struct pcm_channel *channel; - int chan_num; - struct cdev *dsp_devt; - struct cdev *dspW_devt; - struct cdev *audio_devt; - struct cdev *dspHW_devt; +struct snddev_chinfo { + struct pcm_channel *rdch, *wrch; }; struct snddev_info { - SLIST_HEAD(, snddev_channel) channels; + struct { + struct { + SLIST_HEAD(, pcm_channel) head; + struct { + SLIST_HEAD(, pcm_channel) head; + } busy; + } pcm; + } channels; + struct snddev_clone *clones; struct pcm_channel *fakechan; - unsigned devcount, playcount, reccount, vchancount; + unsigned devcount, playcount, reccount, pvchancount, rvchancount ; unsigned flags; int inprog; unsigned int bufsz; @@ -573,8 +620,12 @@ char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; - + uint32_t pvchanrate, pvchanformat; + uint32_t rvchanrate, rvchanformat; }; + +#define PCM_RDCH(x) (((struct snddev_chinfo *)(x)->si_drv1)->rdch) +#define PCM_WRCH(x) (((struct snddev_chinfo *)(x)->si_drv1)->wrch) void sound_oss_sysinfo(oss_sysinfo *); --- sys/dev/sound/pcm/vchan.c.orig Mon Mar 19 02:26:40 2007 +++ sys/dev/sound/pcm/vchan.c Sun May 20 12:59:03 2007 @@ -36,13 +36,6 @@ MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); -/* - * Default speed / format - */ -#define VCHAN_DEFAULT_SPEED 48000 -#define VCHAN_DEFAULT_AFMT (AFMT_S16_LE | AFMT_STEREO) -#define VCHAN_DEFAULT_STRFMT "s16le" - typedef uint32_t (*feed_vchan_mixer)(uint8_t *, uint8_t *, uint32_t); struct vchinfo { @@ -194,7 +187,6 @@ { struct feed_vchan_info *info; struct snd_dbuf *src = source; - struct pcmchan_children *cce; struct pcm_channel *ch; uint32_t cnt, mcnt, rcnt, sz; uint8_t *tmp; @@ -219,8 +211,7 @@ rcnt = 0; mcnt = 0; - SLIST_FOREACH(cce, &c->children, link) { - ch = cce->channel; + CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (!(ch->flags & CHN_F_TRIGGERED)) { CHN_UNLOCK(ch); @@ -305,13 +296,15 @@ struct vchinfo *ch; struct pcm_channel *parent = devinfo; - KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction")); + KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, + ("vchan_init: bad direction")); ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); ch->parent = parent; ch->channel = c; ch->fmt = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; ch->blksz = 2048; + ch->run = PCMTRIG_STOP; c->flags |= CHN_F_VIRTUAL; @@ -403,16 +396,34 @@ vchan_trigger(kobj_t obj, void *data, int go) { struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; + struct pcm_channel *p, *c; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (go == ch->run || !(go == PCMTRIG_START || go == PCMTRIG_STOP || + go == PCMTRIG_ABORT)) return 0; - ch->run = (go == PCMTRIG_START)? 1 : 0; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_TRIGGER); - CHN_LOCK(channel); + p = ch->parent; + c = ch->channel; + ch->run = go; + + CHN_UNLOCK(c); + CHN_LOCK(p); + + switch (go) { + case PCMTRIG_START: + CHN_INSERT_HEAD_SAFE(p, c, children.busy); + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + CHN_REMOVE_SAFE(p, c, children.busy); + break; + default: + break; + } + + CHN_UNLOCK(p); + chn_notify(p, CHN_N_TRIGGER); + CHN_LOCK(c); return 0; } @@ -460,23 +471,49 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - struct snddev_channel *sce; - struct pcm_channel *c, *ch = NULL, *fake; + struct pcm_channel *c, *ch = NULL; struct pcmchan_caps *caps; + int vchancount, *vchanrate; + int unit, direction; int err = 0; int newspd = 0; - d = oidp->oid_arg1; - if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) + if (pcm_devclass == NULL) + return EINVAL; + + unit = VCHAN_SYSCTL_UNIT(oidp->oid_arg1); + if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass)) + return EINVAL; + + d = devclass_get_softc(pcm_devclass, unit); + if (d == NULL) + return EINVAL; + + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + vchanrate = &d->pvchanrate; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + vchanrate = &d->rvchanrate; + break; + default: + return EINVAL; + break; + } + + if (!(d->flags & SD_F_AUTOVCHAN) || vchancount < 1) return EINVAL; if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { pcm_inprog(d, -1); return EINPROGRESS; } - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY) { + if (c->direction == direction) { if (c->flags & CHN_F_VIRTUAL) { /* Sanity check */ if (ch != NULL && ch != c->parentchannel) { @@ -537,12 +574,9 @@ } CHN_UNLOCK(ch); if (err == 0) { - fake = pcm_getfakechan(d); - if (fake != NULL) { - CHN_LOCK(fake); - fake->speed = newspd; - CHN_UNLOCK(fake); - } + pcm_lock(d); + *vchanrate = newspd; + pcm_unlock(d); } } else CHN_UNLOCK(ch); @@ -555,23 +589,49 @@ sysctl_hw_snd_vchanformat(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - struct snddev_channel *sce; - struct pcm_channel *c, *ch = NULL, *fake; + struct pcm_channel *c, *ch = NULL; uint32_t newfmt, spd; - char fmtstr[AFMTSTR_MAXSZ]; + int vchancount, *vchanformat; + int unit, direction; int err = 0, i; + char fmtstr[AFMTSTR_MAXSZ]; + + if (pcm_devclass == NULL) + return EINVAL; + + unit = VCHAN_SYSCTL_UNIT(oidp->oid_arg1); + if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass)) + return EINVAL; + + d = devclass_get_softc(pcm_devclass, unit); + if (d == NULL) + return EINVAL; - d = oidp->oid_arg1; - if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + vchanformat = &d->pvchanformat; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + vchanformat = &d->rvchanformat; + break; + default: + return EINVAL; + break; + } + + if (!(d->flags & SD_F_AUTOVCHAN) || vchancount < 1) return EINVAL; if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { pcm_inprog(d, -1); return EINPROGRESS; } - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY) { + if (c->direction == direction) { if (c->flags & CHN_F_VIRTUAL) { /* Sanity check */ if (ch != NULL && ch != c->parentchannel) { @@ -628,12 +688,9 @@ err = chn_setspeed(ch, spd); CHN_UNLOCK(ch); if (err == 0) { - fake = pcm_getfakechan(d); - if (fake != NULL) { - CHN_LOCK(fake); - fake->format = newfmt; - CHN_UNLOCK(fake); - } + pcm_lock(d); + *vchanformat = newfmt; + pcm_unlock(d); } } else CHN_UNLOCK(ch); @@ -649,45 +706,56 @@ vchan_create(struct pcm_channel *parent) { struct snddev_info *d = parent->parentsnddev; - struct pcmchan_children *pce; - struct pcm_channel *child, *fake; + struct pcm_channel *ch, *tmp, *after; struct pcmchan_caps *parent_caps; uint32_t vchanfmt = 0; int err, first, speed = 0, r; + int direction; if (!(parent->flags & CHN_F_BUSY)) return EBUSY; - + if (parent->direction == PCMDIR_PLAY) + direction = PCMDIR_PLAY_VIRTUAL; + else if (parent->direction == PCMDIR_REC) + direction = PCMDIR_REC_VIRTUAL; + else + return EINVAL; CHN_UNLOCK(parent); - pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); - /* create a new playback channel */ - child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); - if (!child) { - free(pce, M_DEVBUF); + ch = pcm_chn_create(d, parent, &vchan_class, direction, parent); + if (ch == NULL) { CHN_LOCK(parent); return ENODEV; } - pce->channel = child; /* add us to our grandparent's channel list */ /* * XXX maybe we shouldn't always add the dev_t */ - err = pcm_chn_add(d, child); + err = pcm_chn_add(d, ch); if (err) { - pcm_chn_destroy(child); - free(pce, M_DEVBUF); + pcm_chn_destroy(ch); CHN_LOCK(parent); return err; } CHN_LOCK(parent); /* add us to our parent channel's children */ - first = SLIST_EMPTY(&parent->children); - SLIST_INSERT_HEAD(&parent->children, pce, link); + first = CHN_EMPTY(parent, children); + after = NULL; + CHN_FOREACH(tmp, parent, children) { + if (tmp->num > ch->num) + after = tmp; + else if (tmp->num < ch->num) + break; + } + if (after != NULL) { + CHN_INSERT_AFTER(after, ch, children); + } else { + CHN_INSERT_HEAD(parent, ch, children); + } parent->flags |= CHN_F_HAS_VCHAN; if (first) { @@ -695,18 +763,20 @@ if (parent_caps == NULL) err = EINVAL; - fake = pcm_getfakechan(d); - - if (!err && fake != NULL) { + if (!err) { /* - * Avoid querying kernel hint, use saved value - * from fake channel. + * Avoid querying kernel hint, use saved value. */ CHN_UNLOCK(parent); - CHN_LOCK(fake); - speed = fake->speed; - vchanfmt = fake->format; - CHN_UNLOCK(fake); + pcm_lock(d); + if (direction == PCMDIR_PLAY_VIRTUAL) { + vchanfmt = d->pvchanformat; + speed = d->pvchanrate; + } else { + vchanfmt = d->rvchanformat; + speed = d->rvchanrate; + } + pcm_unlock(d); CHN_LOCK(parent); } @@ -753,8 +823,7 @@ CHN_LOCK(parent); if (r != 0) { /* - * No saved value from fake channel, - * no hint, NOTHING. + * No saved value, no hint, NOTHING. * * Workaround for sb16 running * poorly at 45k / 49k. @@ -809,26 +878,30 @@ err = chn_setspeed(parent, speed); } - if (!err && fake != NULL) { + if (!err) { /* - * Save new value to fake channel. + * Save new value. */ CHN_UNLOCK(parent); - CHN_LOCK(fake); - fake->speed = speed; - fake->format = vchanfmt; - CHN_UNLOCK(fake); + pcm_lock(d); + if (direction == PCMDIR_PLAY_VIRTUAL) { + d->pvchanformat = vchanfmt; + d->pvchanrate = speed; + } else { + d->rvchanformat = vchanfmt; + d->rvchanrate = speed; + } + pcm_unlock(d); CHN_LOCK(parent); } } if (err) { - SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); + CHN_REMOVE(parent, ch, children); parent->flags &= ~CHN_F_HAS_VCHAN; CHN_UNLOCK(parent); - free(pce, M_DEVBUF); - if (pcm_chn_remove(d, child) == 0) - pcm_chn_destroy(child); + if (pcm_chn_remove(d, ch) == 0) + pcm_chn_destroy(ch); CHN_LOCK(parent); return err; } @@ -842,8 +915,6 @@ { struct pcm_channel *parent = c->parentchannel; struct snddev_info *d = parent->parentsnddev; - struct pcmchan_children *pce; - struct snddev_channel *sce; uint32_t spd; int err; @@ -852,45 +923,23 @@ CHN_UNLOCK(parent); return EBUSY; } - if (SLIST_EMPTY(&parent->children)) { + if (CHN_EMPTY(parent, children)) { CHN_UNLOCK(parent); return EINVAL; } /* remove us from our parent's children list */ - SLIST_FOREACH(pce, &parent->children, link) { - if (pce->channel == c) - goto gotch; - } - CHN_UNLOCK(parent); - return EINVAL; -gotch: - SLIST_FOREACH(sce, &d->channels, link) { - if (sce->channel == c) { - if (sce->dsp_devt) { - destroy_dev(sce->dsp_devt); - sce->dsp_devt = NULL; - } - if (sce->dspW_devt) { - destroy_dev(sce->dspW_devt); - sce->dspW_devt = NULL; - } - if (sce->audio_devt) { - destroy_dev(sce->audio_devt); - sce->audio_devt = NULL; - } - if (sce->dspHW_devt) { - destroy_dev(sce->dspHW_devt); - sce->dspHW_devt = NULL; - } - d->devcount--; - break; - } + CHN_REMOVE(parent, c, children); + + if (c->devt != NULL) { + if (c->devt->si_drv1 != NULL) + free(c->devt->si_drv1, M_DEVBUF); + destroy_dev(c->devt); } - SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); - free(pce, M_DEVBUF); - if (SLIST_EMPTY(&parent->children)) { + d->devcount--; + + if (CHN_EMPTY(parent, children)) { parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); spd = parent->speed; if (chn_reset(parent, parent->format) == 0) @@ -912,20 +961,39 @@ vchan_initsys(device_t dev) { #ifdef SND_DYNSYSCTL - struct snddev_info *d; + int unit; + + unit = device_get_unit(dev); - d = device_get_softc(dev); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), - OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, VCHAN_PLAY), sizeof(intptr_t), + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, VCHAN_PLAY), sizeof(intptr_t), + sysctl_hw_snd_vchanrate, "I", "virtual channel mixing speed/rate"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, VCHAN_PLAY), sizeof(intptr_t), + sysctl_hw_snd_vchanformat, "A", "virtual channel format"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "rvchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, VCHAN_REC), sizeof(intptr_t), sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), - OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + OID_AUTO, "rvchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, VCHAN_REC), sizeof(intptr_t), sysctl_hw_snd_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), - OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, d, sizeof(d), + OID_AUTO, "rvchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, VCHAN_REC), sizeof(intptr_t), sysctl_hw_snd_vchanformat, "A", "virtual channel format"); #endif --- sys/dev/sound/pcm/vchan.h.orig Thu Jan 6 09:43:21 2005 +++ sys/dev/sound/pcm/vchan.h Sun May 20 07:25:12 2007 @@ -30,4 +30,17 @@ int vchan_destroy(struct pcm_channel *c); int vchan_initsys(device_t dev); +/* + * Default speed / format + */ +#define VCHAN_DEFAULT_SPEED 48000 +#define VCHAN_DEFAULT_AFMT (AFMT_S16_LE | AFMT_STEREO) +#define VCHAN_DEFAULT_STRFMT "s16le" +#define VCHAN_PLAY 0 +#define VCHAN_REC 1 +#define VCHAN_SYSCTL_DATA(x, y) \ + ((void *)((intptr_t)(((((x) + 1) & 0xffff) << 1) | \ + ((y) & 0x1)))) +#define VCHAN_SYSCTL_UNIT(x) ((int)(((intptr_t)(x) >> 1) & 0xffff) - 1) +#define VCHAN_SYSCTL_DIR(x) ((int)((intptr_t)(x) & 0x1)) --- sys/dev/sound/usb/uaudio.c.orig Mon Apr 2 11:25:39 2007 +++ sys/dev/sound/usb/uaudio.c Sun May 20 06:19:54 2007 @@ -4499,10 +4499,8 @@ uaudio_sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) { struct snddev_info *d; - struct snddev_channel *sce; struct pcm_channel *c; struct pcm_feeder *f; - int pc, rc, vc; device_t pa_dev = device_get_parent(dev); struct uaudio_softc *sc = device_get_softc(pa_dev); @@ -4514,24 +4512,14 @@ return ENXIO; snd_mtxlock(d->lock); - if (SLIST_EMPTY(&d->channels)) { + if (CHN_EMPTY(d, channels.pcm)) { sbuf_printf(s, " (mixer only)"); snd_mtxunlock(d->lock); return 0; } - pc = rc = vc = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (c->direction == PCMDIR_PLAY) { - if (c->flags & CHN_F_VIRTUAL) - vc++; - else - pc++; - } else - rc++; - } - sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", - d->playcount, d->reccount, d->vchancount, + sbuf_printf(s, " (%dp:%dpv/%dr:%rv channels%s%s)", + d->playcount, d->pvchancount, + d->reccount, d->rvchancount, (d->flags & SD_F_SIMPLEX)? "" : " duplex", #ifdef USING_DEVFS (device_get_unit(dev) == snd_unit)? " default" : "" @@ -4549,8 +4537,7 @@ return 0; } - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { sbuf_printf(s, "\n\t"); KASSERT(c->bufhard != NULL && c->bufsoft != NULL, --- sys/modules/sound/sound/Makefile.orig Mon Sep 4 15:40:53 2006 +++ sys/modules/sound/sound/Makefile Thu May 3 02:44:57 2007 @@ -10,7 +10,7 @@ SRCS+= ac97_if.c channel_if.c feeder_if.c mixer_if.c SRCS+= mpu_if.h mpufoi_if.h synth_if.h SRCS+= mpu_if.c mpufoi_if.c synth_if.c -SRCS+= ac97.c ac97_patch.c buffer.c channel.c dsp.c +SRCS+= ac97.c ac97_patch.c buffer.c channel.c clone.c dsp.c SRCS+= fake.c feeder.c feeder_fmt.c feeder_rate.c feeder_volume.c SRCS+= mixer.c sndstat.c sound.c vchan.c SRCS+= midi.c mpu401.c sequencer.c