diff options
author | ariff <ariff@FreeBSD.org> | 2007-05-31 18:43:33 +0000 |
---|---|---|
committer | ariff <ariff@FreeBSD.org> | 2007-05-31 18:43:33 +0000 |
commit | e12a0ce02fe36373a2610fcdbf80521f4613b504 (patch) | |
tree | 0e66ac1b27ea2f1691da1ea5b896b26f55d8b668 /sys/dev/sound/pcm/channel.c | |
parent | 1469bf4a20a4d63ca2b38ad1eb3a7ebeb2c60a66 (diff) | |
download | FreeBSD-src-e12a0ce02fe36373a2610fcdbf80521f4613b504.zip FreeBSD-src-e12a0ce02fe36373a2610fcdbf80521f4613b504.tar.gz |
Last major commit and updates for RELENG_7:
- Rework the entire pcm_channel structure:
* Remove rarely used link placeholder, instead, make each pcm_channel
as head/link of each own/each other. Unlock - Lock sequence due to
sleep malloc has been reduced.
* Implement "busy" queue which will contain list of busy/active
channels. This greatly reduce locking contention for example while
servicing interrupt for hardware with many channels or when virtual
channels reach its 256 peak channels.
- So I heard you like v chan ... O RLY?
Welcome to Virtual **Record** Channels (vrec, rec vchans, vchans for
recording, Rec-Chan, you decide), the ultimate solutions for your
nagging O_RDWR full-duplex wannabe (note: flash plugins) monopolizing
single record channel causing EBUSY. Vrec works exactly like Vchans
(or, should I rename it to "Vplay" :) , except that it operates on the
opposite direction (recording). Up to 256 vrecs (like vchans) are
possible.
Notes:
* Relocate dev.pcm.%d.{vchans,vchanformat,vchanrate} to each of its
respective node/direction:
dev.pcm.%d.play.* for "play" (cdev = dsp%d.vp%d)
dev.pcm.%d.rec.* for "record" (cdev = dsp%d.vr%d)
* Don't expect that it will magically give you ability to split
"recording source" (eg: 1 channel for cdrom, 1 channel for mic,
etc). Just admit that you only have a *single* recording source /
channel. Please bug your hardware vendor instead :)
- Bump maxautovchans from 4 to 16. For a full-fledged multimedia
desktop/workstation with too many soundservers installed (esound,
artsd, jackd, pulse/polypaudio, ding-dong pling plong mudkip fuh fuh,
etc), 4 seems inadequate. There will be no memory penalty here, since
virtual channels are allocate only by demand.
- Nuke/Rework the entire statically created cdev entries. Everything is
clonable through snd own clone manager which designed to withstand many
kind of abusive devfs droids such as:
* while : ; do /bin/test -e /dev/dsp ; done
* jot 16777216 0 | while read x ; do ls /dev/dsp0.$x ; done
* hundreds (could be thousands) concurrent threads/process opening
"/dev/dsp" (previously, this might result EBUSY even with just
3 contesting threads/procs).
o Reusable clone objects (instead of creating new one like there's no
tomorrow) after certain expiration deadline. The clone allocator will
decide whether to reuse, share, or creating new clone.
o Automatic garbage collector.
- Dynamic unit magic allocator. Maximum attached soundcards can be tuned
using tunable "hw.snd.maxunit" (Default to 512). Minimum is 16, and
maximum is 2048.
- ..other fixes, mostly related to concurrency issues.
joel@ will do the manpage updates on sound(4).
Have fun.
Diffstat (limited to 'sys/dev/sound/pcm/channel.c')
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 143 |
1 files changed, 108 insertions, 35 deletions
diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index 83ce5eb..f5753b1 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -170,11 +170,14 @@ chn_lockinit(struct pcm_channel *c, int dir) 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 @@ static void 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 @@ chn_sleep(struct pcm_channel *c, char *str, int timeout) 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; } @@ -497,7 +510,7 @@ chn_rdfeed(struct pcm_channel *c) } #endif amt = sndbuf_getfree(bs); - ret = (amt > 0) ? sndbuf_feed(b, bs, c, c->feeder, amt) : 0; + ret = (amt > 0) ? sndbuf_feed(b, bs, c, c->feeder, amt) : ENOSPC; amt = sndbuf_getready(b); if (amt > 0) { @@ -519,7 +532,7 @@ chn_rdupdate(struct pcm_channel *c) 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 +658,7 @@ chn_start(struct pcm_channel *c, int force) 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 +1107,8 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) b = NULL; bs = NULL; + CHN_INIT(c, children); + CHN_INIT(c, children.busy); c->devinfo = NULL; c->feeder = NULL; c->latency = -1; @@ -1193,9 +1208,13 @@ chn_kill(struct pcm_channel *c) struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; - if (CHN_STARTED(c)) + if (CHN_STARTED(c)) { + CHN_LOCK(c); chn_trigger(c, PCMTRIG_ABORT); - while (chn_removefeeder(c) == 0); + CHN_UNLOCK(c); + } + while (chn_removefeeder(c) == 0) + ; if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); c->flags |= CHN_F_DEAD; @@ -1528,7 +1547,7 @@ chn_resizebuf(struct pcm_channel *c, int latency, 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 +1769,7 @@ 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); @@ -1757,8 +1777,50 @@ chn_trigger(struct pcm_channel *c, int go) 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 == c->trigger) + return 0; + ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); + if (ret == 0) { + 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); + } + 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 ret; } @@ -1840,7 +1902,10 @@ chn_buildfeeder(struct pcm_channel *c) c->align = sndbuf_getalign(c->bufsoft); - if (SLIST_EMPTY(&c->children)) { + if (CHN_EMPTY(c, children) || c->direction == PCMDIR_REC) { + /* + * Virtual rec need this. + */ fc = feeder_getclass(NULL); KASSERT(fc != NULL, ("can't find root feeder")); @@ -1851,7 +1916,7 @@ chn_buildfeeder(struct pcm_channel *c) 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 +1939,9 @@ chn_buildfeeder(struct pcm_channel *c) return err; } - } + } else + return EOPNOTSUPP; + c->feederflags &= ~(1 << FEEDER_VOLUME); if (c->direction == PCMDIR_PLAY && !(c->flags & CHN_F_VIRTUAL) && c->parentsnddev && @@ -1974,6 +2041,26 @@ chn_buildfeeder(struct pcm_channel *c) if (hwfmt == 0 || !fmtvalid(hwfmt, fmtlist)) { DEB(printf("Invalid hardware format: 0x%08x\n", hwfmt)); return ENODEV; + } else if (c->direction == PCMDIR_REC && !CHN_EMPTY(c, children)) { + /* + * Kind of awkward. This whole "MIXER" concept need a + * rethinking, I guess :) . Recording is the inverse + * of Playback, which is why we push mixer vchan down here. + */ + if (c->flags & CHN_F_HAS_VCHAN) { + desc.type = FEEDER_MIXER; + desc.in = c->format; + } else + return EOPNOTSUPP; + desc.out = c->format; + desc.flags = 0; + fc = feeder_getclass(&desc); + if (fc == NULL) + return EOPNOTSUPP; + + err = chn_addfeeder(c, fc, &desc); + if (err != 0) + return err; } sndbuf_setfmt(c->bufhard, hwfmt); @@ -2020,13 +2107,11 @@ chn_buildfeeder(struct pcm_channel *c) 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 +2149,8 @@ chn_notify(struct pcm_channel *c, u_int32_t flags) } 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) |