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 | |
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.
-rw-r--r-- | sys/conf/files | 2 | ||||
-rw-r--r-- | sys/dev/sound/pcm/buffer.c | 6 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 143 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.h | 95 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 644 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.h | 1 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder.c | 8 | ||||
-rw-r--r-- | sys/dev/sound/pcm/mixer.c | 67 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sndstat.c | 112 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.c | 1016 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.h | 84 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.c | 630 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.h | 21 | ||||
-rw-r--r-- | sys/dev/sound/usb/uaudio.c | 23 | ||||
-rw-r--r-- | sys/modules/sound/sound/Makefile | 5 |
15 files changed, 1813 insertions, 1044 deletions
diff --git a/sys/conf/files b/sys/conf/files index 18552dc..632ffb9 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -915,6 +915,8 @@ dev/sn/if_sn.c optional sn dev/sn/if_sn_isa.c optional sn isa dev/sn/if_sn_pccard.c optional sn pccard dev/snp/snp.c optional snp +dev/sound/clone.c optional sound +dev/sound/unit.c optional sound dev/sound/isa/ad1816.c optional snd_ad1816 isa dev/sound/isa/ess.c optional snd_ess isa dev/sound/isa/gusc.c optional snd_gusc isa diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c index 1cf26d0..3d10357 100644 --- a/sys/dev/sound/pcm/buffer.c +++ b/sys/dev/sound/pcm/buffer.c @@ -664,10 +664,10 @@ sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *chan do { cnt = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); - if (cnt) + if (cnt) { sndbuf_acquire(to, to->tmpbuf, cnt); - /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ - count -= cnt; + count -= cnt; + } } while (count && cnt); return 0; 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) diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h index 3d4f537..d443a01 100644 --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -26,11 +26,6 @@ * $FreeBSD$ */ -struct pcmchan_children { - SLIST_ENTRY(pcmchan_children) link; - struct pcm_channel *channel; -}; - struct pcmchan_caps { u_int32_t minspeed, maxspeed; u_int32_t *fmtlist; @@ -72,7 +67,6 @@ struct pcmchan_syncmember { struct pcm_channel { kobj_t methods; - int num; pid_t pid; int refcount; struct pcm_feeder *feeder; @@ -94,8 +88,10 @@ struct pcm_channel { struct pcm_channel *parentchannel; void *devinfo; device_t dev; + int unit; char name[CHN_NAMELEN]; struct mtx *lock; + int trigger; /** * Increment,decrement this around operations that temporarily yield * lock. @@ -123,9 +119,86 @@ struct pcm_channel { #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) + +#define CHN_UNIT(x) (snd_unit2u((x)->unit)) +#define CHN_DEV(x) (snd_unit2d((x)->unit)) +#define CHN_CHAN(x) (snd_unit2c((x)->unit)) + #include "channel_if.h" int chn_reinit(struct pcm_channel *c); @@ -208,9 +281,10 @@ extern int chn_latency; 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 +297,7 @@ extern int report_soft_formats; #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 */ diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 56abbae..2d633b2 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -24,13 +24,28 @@ * SUCH DAMAGE. */ -#include <sys/param.h> -#include <sys/queue.h> - #include <dev/sound/pcm/sound.h> +#include <sys/ctype.h> SND_DECLARE_FILE("$FreeBSD$"); +struct dsp_cdevinfo { + struct pcm_channel *rdch, *wrch; +}; + +#define PCM_RDCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->rdch) +#define PCM_WRCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->wrch) + +#define PCMDEV_ACQUIRE(x) do { \ + if ((x)->si_drv1 == NULL) \ + (x)->si_drv1 = x; \ +} while(0) + +#define PCMDEV_RELEASE(x) do { \ + if ((x)->si_drv1 == x) \ + (x)->si_drv1 = NULL; \ +} while(0) + #define OLDPCM_IOCTL static d_open_t dsp_open; @@ -55,7 +70,9 @@ struct cdevsw dsp_cdevsw = { }; #ifdef USING_DEVFS -static eventhandler_tag dsp_ehtag; +static eventhandler_tag dsp_ehtag = NULL; +static int dsp_umax = -1; +static int dsp_cmax = -1; #endif static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); @@ -75,43 +92,28 @@ static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, o static struct snddev_info * dsp_get_info(struct cdev *dev) { - struct snddev_info *d; - int unit; - - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return NULL; - d = devclass_get_softc(pcm_devclass, unit); - - return d; + return (devclass_get_softc(pcm_devclass, PCMUNIT(dev))); } static u_int32_t dsp_get_flags(struct cdev *dev) { device_t bdev; - int unit; - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return 0xffffffff; - bdev = devclass_get_device(pcm_devclass, unit); + bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); - return pcm_getflags(bdev); + return ((bdev != NULL) ? pcm_getflags(bdev) : 0xffffffff); } static void dsp_set_flags(struct cdev *dev, u_int32_t flags) { device_t bdev; - int unit; - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return; - bdev = devclass_get_device(pcm_devclass, unit); + bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); - pcm_setflags(bdev, flags); + if (bdev != NULL) + pcm_setflags(bdev, flags); } /* @@ -126,10 +128,12 @@ getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch, struct snddev_info *d; u_int32_t flags; - flags = dsp_get_flags(dev); d = dsp_get_info(dev); + if (d == NULL) + return -1; pcm_inprog(d, 1); pcm_lock(d); + flags = dsp_get_flags(dev); KASSERT((flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \ ("getchns: read and write both prioritised")); @@ -138,15 +142,15 @@ getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch, 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 +174,8 @@ relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, u_ 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)) @@ -177,90 +183,183 @@ relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, u_ pcm_inprog(d, -1); } +static void +dsp_cdevinfo_alloc(struct cdev *dev, + struct pcm_channel *rdch, struct pcm_channel *wrch) +{ + KASSERT(dev != NULL && dev->si_drv1 == dev && rdch != wrch, + ("bogus %s(), what are you trying to accomplish here?", __func__)); + + dev->si_drv1 = malloc(sizeof(struct dsp_cdevinfo), M_DEVBUF, + M_WAITOK | M_ZERO); + PCM_RDCH(dev) = rdch; + PCM_WRCH(dev) = wrch; +} + +static void +dsp_cdevinfo_free(struct cdev *dev) +{ + KASSERT(dev != NULL && dev->si_drv1 != NULL && + PCM_RDCH(dev) == NULL && PCM_WRCH(dev) == NULL, + ("bogus %s(), what are you trying to accomplish here?", __func__)); + + free(dev->si_drv1, M_DEVBUF); + dev->si_drv1 = NULL; +} + +/* duplex / simplex cdev type */ +enum { + DSP_CDEV_TYPE_RDONLY, /* simplex read-only (record) */ + DSP_CDEV_TYPE_WRONLY, /* simplex write-only (play) */ + DSP_CDEV_TYPE_RDWR, /* duplex read, write, or both */ +}; + +#define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) +#define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) +#define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) +#define DSP_F_READ(x) ((x) & FREAD) +#define DSP_F_WRITE(x) ((x) & FWRITE) + +static const struct { + int type; + char *name; + char *sep; + int use_sep; + int hw; + int max; + uint32_t fmt, spd; + int query; +} dsp_cdevs[] = { + { SND_DEV_DSP, "dsp", ".", 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_AUDIO, "audio", ".", 0, 0, 0, + AFMT_MU_LAW, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP16, "dspW", ".", 0, 0, 0, + AFMT_S16_LE, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSPHW_PLAY, "dsp", ".p", 1, 1, SND_MAXHWCHAN, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_WRONLY }, + { SND_DEV_DSPHW_VPLAY, "dsp", ".vp", 1, 1, SND_MAXVCHANS, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_WRONLY }, + { SND_DEV_DSPHW_REC, "dsp", ".r", 1, 1, SND_MAXHWCHAN, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_RDONLY }, + { SND_DEV_DSPHW_VREC, "dsp", ".vr", 1, 1, SND_MAXVCHANS, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_RDONLY }, +}; + static int dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct pcm_channel *rdch, *wrch; struct snddev_info *d; - u_int32_t fmt; - int devtype; - int error; - int chnum; + uint32_t fmt, spd; + int i, error, devtype; + int wdevunit, rdevunit; + /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) return ENODEV; - if ((flags & (FREAD | FWRITE)) == 0) - return EINVAL; - + /* This too.. */ d = dsp_get_info(i_dev); - devtype = PCMDEV(i_dev); - chnum = -1; + if (d == NULL) + return EBADF; - /* 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; + /* Lock snddev so nobody else can monkey with it. */ + pcm_lock(d); - case SND_DEV_NORESET: - fmt = 0; - break; + /* + * Try to acquire cloned device before someone else pick it. + * ENODEV means this is not a cloned droids. + */ + error = snd_clone_acquire(i_dev); + if (!(error == 0 || error == ENODEV)) { + pcm_unlock(d); + return error; + } - case SND_DEV_DSPHW: - /* - * HW *specific* access without channel numbering confusion - * caused by "first come first served" by dsp_clone(). - */ - fmt = AFMT_U8; - chnum = PCMCHAN(i_dev); - break; + if (!DSP_F_VALID(flags)) + error = EINVAL; + else if (i_dev->si_drv1 != NULL) + error = EBUSY; + else if (DSP_F_DUPLEX(flags) && + (dsp_get_flags(i_dev) & SD_F_SIMPLEX)) + error = ENOTSUP; + else + error = 0; - default: - panic("impossible devtype %d", devtype); + if (error != 0) { + (void)snd_clone_release(i_dev); + pcm_unlock(d); + return error; } - /* lock snddev so nobody else can monkey with it */ - pcm_lock(d); + /* + * Fake busy state by pointing si_drv1 to something else since + * we have to give up locking somewhere during setup process. + */ + PCMDEV_ACQUIRE(i_dev); - rdch = i_dev->si_drv1; - wrch = i_dev->si_drv2; + devtype = PCMDEV(i_dev); + wdevunit = -1; + rdevunit = -1; + fmt = 0; + spd = 0; - if (rdch || wrch || ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && - (flags & (FREAD | FWRITE)) == (FREAD | FWRITE))) { - /* simplex or not, better safe than sorry. */ - pcm_unlock(d); - return EBUSY; + for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + if (devtype != dsp_cdevs[i].type) + continue; + if (DSP_F_SIMPLEX(flags) && + ((dsp_cdevs[i].query == DSP_CDEV_TYPE_WRONLY && + DSP_F_READ(flags)) || + (dsp_cdevs[i].query == DSP_CDEV_TYPE_RDONLY && + DSP_F_WRITE(flags)))) { + /* + * simplex, opposite direction? Please be gone.. + */ + (void)snd_clone_release(i_dev); + PCMDEV_RELEASE(i_dev); + pcm_unlock(d); + return ENOTSUP; + } + if (dsp_cdevs[i].query == DSP_CDEV_TYPE_WRONLY) + wdevunit = dev2unit(i_dev); + else if (dsp_cdevs[i].query == DSP_CDEV_TYPE_RDONLY) + rdevunit = dev2unit(i_dev); + fmt = dsp_cdevs[i].fmt; + spd = dsp_cdevs[i].spd; + break; } + /* No matching devtype? */ + if (fmt == 0 || spd == 0) + panic("impossible devtype %d", devtype); + + rdch = NULL; + wrch = NULL; + /* * if we get here, the open request is valid- either: * * we were previously not open * * we were open for play xor record and the opener wants * the non-open direction */ - if (flags & FREAD) { + if (DSP_F_READ(flags)) { /* 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, + rdevunit); - 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, spd) != 0))) error = ENODEV; if (error != 0) { - if (rdch) + if (rdch != NULL) pcm_chnrelease(rdch); + pcm_lock(d); + (void)snd_clone_release(i_dev); + PCMDEV_RELEASE(i_dev); + pcm_unlock(d); return error; } @@ -271,43 +370,55 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) pcm_lock(d); } - 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 (DSP_F_WRITE(flags)) { + /* open for write */ + pcm_unlock(d); + error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, + wdevunit); - if (error == 0 && (chn_reset(wrch, fmt) || - (fmt && chn_setspeed(wrch, DSP_DEFAULT_SPEED)))) - error = ENODEV; + if (error == 0 && (chn_reset(wrch, fmt) != 0 || + (chn_setspeed(wrch, spd) != 0))) + error = ENODEV; - 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) { + 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); + PCMDEV_RELEASE(i_dev); + pcm_unlock(d); + return error; } - return error; - } - - if (flags & O_NONBLOCK) - wrch->flags |= CHN_F_NBIO; - pcm_chnref(wrch, 1); - CHN_UNLOCK(wrch); - pcm_lock(d); + 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; + /* + * Increase clone refcount for its automatic garbage collector. + */ + (void)snd_clone_ref(i_dev); pcm_unlock(d); + + /* + * We're done. Allocate and point si_drv1 to a real + * allocated structure. + */ + dsp_cdevinfo_alloc(i_dev, rdch, wrch); + return 0; } @@ -319,10 +430,11 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) 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 +444,7 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) if (rdch || wrch) { refs = 0; + pcm_unlock(d); if (rdch) { /* * The channel itself need not be locked because: @@ -371,22 +484,31 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) 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); + dsp_cdevinfo_free(i_dev); + /* + * Release clone busy state and unref it + * so the automatic garbage collector will + * get the hint and do the remaining cleanup + * process. + */ + (void)snd_clone_release(i_dev); + (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 +526,8 @@ dsp_read(struct cdev *i_dev, struct uio *buf, int flag) 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 +549,8 @@ dsp_write(struct cdev *i_dev, struct uio *buf, int flag) 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 +591,8 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * */ 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 +640,7 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * wrch = NULL; if (kill & 2) rdch = NULL; - + switch(cmd) { #ifdef OLDPCM_IOCTL /* @@ -1503,90 +1627,235 @@ dsp_mmap(struct cdev *i_dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) #ifdef USING_DEVFS -/* - * Clone logic is this: - * x E X = {dsp, dspW, audio} - * x -> x${sysctl("hw.snd.unit")} - * xN-> - * for i N = 1 to channels of device N - * if xN.i isn't busy, return its dev_t - */ +/* So much for dev_stdclone() */ +static int +dsp_stdclone(char *name, char *namep, char *sep, int use_sep, int *u, int *c) +{ + size_t len; + + len = strlen(namep); + + if (bcmp(name, namep, len) != 0) + return (ENODEV); + + name += len; + + if (isdigit(*name) == 0) + return (ENODEV); + + len = strlen(sep); + + if (*name == '0' && !(name[1] == '\0' || bcmp(name + 1, sep, len) == 0)) + return (ENODEV); + + for (*u = 0; isdigit(*name) != 0; name++) { + *u *= 10; + *u += *name - '0'; + if (*u > dsp_umax) + return (ENODEV); + } + + if (*name == '\0') + return ((use_sep == 0) ? 0 : ENODEV); + + if (bcmp(name, sep, len) != 0 || isdigit(name[len]) == 0) + return (ENODEV); + + name += len; + + if (*name == '0' && name[1] != '\0') + return (ENODEV); + + for (*c = 0; isdigit(*name) != 0; name++) { + *c *= 10; + *c += *name - '0'; + if (*c > dsp_cmax) + return (ENODEV); + } + + if (*name != '\0') + return (ENODEV); + + return (0); +} + static void -dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, - struct cdev **dev) +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"}; + struct snddev_info *d; + struct snd_clone_entry *ce; + struct pcm_channel *c; + int i, unit, udcmask, cunit, devtype, devhw, devcmax, tumax; + char *devname, *devsep; + + KASSERT(dsp_umax >= 0 && dsp_cmax >= 0, ("Uninitialized unit!")); if (*dev != NULL) return; - if (pcm_devclass == NULL) - return; - devtype = 0; unit = -1; - for (i = 0; (i < 3) && (unit == -1); i++) { - devtype = devtypes[i]; - if (strcmp(name, devnames[i]) == 0) { + cunit = -1; + devtype = -1; + devhw = 0; + devcmax = -1; + tumax = -1; + devname = NULL; + devsep = NULL; + + for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])) && + unit == -1; i++) { + devtype = dsp_cdevs[i].type; + devname = dsp_cdevs[i].name; + devsep = dsp_cdevs[i].sep; + devhw = dsp_cdevs[i].hw; + devcmax = dsp_cdevs[i].max - 1; + if (strcmp(name, devname) == 0) unit = snd_unit; - } else { - if (dev_stdclone(name, NULL, devnames[i], &unit) != 1) - unit = -1; + else if (dsp_stdclone(name, devname, devsep, + dsp_cdevs[i].use_sep, &unit, &cunit) != 0) { + unit = -1; + cunit = -1; } } - if (unit == -1 || unit >= devclass_get_maxunit(pcm_devclass)) - return; - pcm_dev = devclass_get_softc(pcm_devclass, unit); - - if (pcm_dev == NULL) + d = devclass_get_softc(pcm_devclass, unit); + if (d == NULL || d->clones == NULL) return; - SLIST_FOREACH(pcm_chan, &pcm_dev->channels, link) { + pcm_lock(d); + if (snd_clone_disabled(d->clones)) { + pcm_unlock(d); + return; + } - 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); - } + udcmask = snd_u2unit(unit) | snd_d2unit(devtype); - if ((pdev != NULL) && (pdev->si_drv1 == NULL) && (pdev->si_drv2 == NULL)) { - *dev = pdev; - dev_ref(*dev); + if (devhw != 0) { + KASSERT(devcmax <= dsp_cmax, + ("overflow: devcmax=%d, dsp_cmax=%d", devcmax, dsp_cmax)); + if (cunit > devcmax) { + pcm_unlock(d); return; } + udcmask |= snd_c2unit(cunit); + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->unit != udcmask) { + CHN_UNLOCK(c); + continue; + } + CHN_UNLOCK(c); + udcmask &= ~snd_c2unit(cunit); + /* + * Temporarily increase clone maxunit to overcome + * vchan flexibility. + * + * # sysctl dev.pcm.0.play.vchans=256 + * dev.pcm.0.play.vchans: 1 -> 256 + * # cat /dev/zero > /dev/dsp0.vp255 & + * [1] 17296 + * # sysctl dev.pcm.0.play.vchans=0 + * dev.pcm.0.play.vchans: 256 -> 1 + * # fg + * [1] + running cat /dev/zero > /dev/dsp0.vp255 + * ^C + * # cat /dev/zero > /dev/dsp0.vp255 + * zsh: operation not supported: /dev/dsp0.vp255 + */ + tumax = snd_clone_getmaxunit(d->clones); + if (cunit > tumax) + snd_clone_setmaxunit(d->clones, cunit); + else + tumax = -1; + goto dsp_clone_alloc; + } + /* + * Ok, so we're requesting unallocated vchan, but still + * within maximum vchan limit. + */ + if (((devtype == SND_DEV_DSPHW_VPLAY && d->pvchancount > 0) || + (devtype == SND_DEV_DSPHW_VREC && d->rvchancount > 0)) && + cunit < snd_maxautovchans) { + udcmask &= ~snd_c2unit(cunit); + tumax = snd_clone_getmaxunit(d->clones); + if (cunit > tumax) + snd_clone_setmaxunit(d->clones, cunit); + else + tumax = -1; + goto dsp_clone_alloc; + } + pcm_unlock(d); + return; } + +dsp_clone_alloc: + ce = snd_clone_alloc(d->clones, dev, &cunit, udcmask); + if (tumax != -1) + snd_clone_setmaxunit(d->clones, tumax); + if (ce != NULL) { + udcmask |= snd_c2unit(cunit); + pcm_unlock(d); + *dev = make_dev(&dsp_cdevsw, unit2minor(udcmask), + UID_ROOT, GID_WHEEL, 0666, "%s%d%s%d", + devname, unit, devsep, cunit); + pcm_lock(d); + snd_clone_register(ce, *dev); + } + pcm_unlock(d); + + if (*dev != NULL) + dev_ref(*dev); } static void dsp_sysinit(void *p) { + if (dsp_ehtag != NULL) + return; + /* initialize unit numbering */ + snd_unit_init(); + dsp_umax = PCMMAXUNIT; + dsp_cmax = PCMMAXCHAN; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { - if (dsp_ehtag != NULL) - 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); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); #endif +char * +dsp_unit2name(char *buf, size_t len, int unit) +{ + int i, dtype; + + KASSERT(buf != NULL && len != 0, ("bogus buf=%p len=%u", buf, len)); + + dtype = snd_unit2d(unit); + + for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + if (dtype != dsp_cdevs[i].type) + continue; + snprintf(buf, len, "%s%d%s%d", dsp_cdevs[i].name, + snd_unit2u(unit), dsp_cdevs[i].sep, snd_unit2c(unit)); + return (buf); + } + + return (NULL); +} + /** * @brief Handler for SNDCTL_AUDIOINFO. * @@ -1622,13 +1891,12 @@ SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); int dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) { - struct snddev_channel *sce; struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; - struct cdev *t_cdev; uint32_t fmts; int i, nchan, ret, *rates, minch, maxch; + char *devname, buf[CHN_NAMELEN]; /* * If probing the device that received the ioctl, make sure it's a @@ -1639,10 +1907,11 @@ dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) return EINVAL; ch = NULL; - t_cdev = NULL; + devname = NULL; nchan = 0; ret = 0; - + bzero(buf, sizeof(buf)); + /* * Search for the requested audio device (channel). Start by * iterating over pcm devices. @@ -1657,21 +1926,24 @@ dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) 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 */ - t_cdev = i_dev; + if ((ch == PCM_RDCH(i_dev)) || /* record ch */ + (ch == PCM_WRCH(i_dev))) { /* playback ch */ + devname = i_dev->si_name; goto dspfound; } } else if (ai->dev == nchan) { - t_cdev = sce->dsp_devt; + devname = dsp_unit2name(buf, sizeof(buf), + ch->unit); goto dspfound; } CHN_UNLOCK(ch); + /* + * XXX I really doubt if this is correct. + */ ++nchan; } @@ -1684,7 +1956,7 @@ dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) dspfound: /* Should've found the device, but something isn't right */ - if (t_cdev == NULL) { + if (devname == NULL) { ret = EINVAL; goto out; } @@ -1721,7 +1993,7 @@ dspfound: * 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 @@ -1784,7 +2056,7 @@ dspfound: * @c real_device - OSSv4 docs: "Obsolete." */ ai->real_device = -1; - strlcpy(ai->devnode, t_cdev->si_name, sizeof(ai->devnode)); + strlcpy(ai->devnode, devname, sizeof(ai->devnode)); ai->enabled = device_is_attached(d->dev) ? 1 : 0; /** * @note @@ -2008,7 +2280,7 @@ dsp_oss_syncstart(int sg_id) struct pcmchan_syncgroup *sg; struct pcm_channel *c; int ret, needlocks; - + /* Get the synclists lock */ PCM_SG_LOCK(); diff --git a/sys/dev/sound/pcm/dsp.h b/sys/dev/sound/pcm/dsp.h index 3b27e4d..015e4ac 100644 --- a/sys/dev/sound/pcm/dsp.h +++ b/sys/dev/sound/pcm/dsp.h @@ -31,6 +31,7 @@ extern struct cdevsw dsp_cdevsw; +char *dsp_unit2name(char *, size_t, int); int dsp_oss_audioinfo(struct cdev *, oss_audioinfo *); #endif /* !_PCMDSP_H_ */ diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c index 42a2835..8e97960 100644 --- a/sys/dev/sound/pcm/feeder.c +++ b/sys/dev/sound/pcm/feeder.c @@ -111,10 +111,12 @@ feeder_register(void *p) /* initialize global variables */ - if (snd_verbose < 0 || snd_verbose > 3) + if (snd_verbose < 0 || snd_verbose > 4) snd_verbose = 1; - if (snd_unit < 0 || snd_unit > PCMMAXDEV) + /* initialize unit numbering */ + snd_unit_init(); + if (snd_unit < 0 || snd_unit > PCMMAXUNIT) snd_unit = 0; if (snd_maxautovchans < 0 || @@ -344,7 +346,7 @@ chainok(struct pcm_feeder *test, struct pcm_feeder *stop) } /* - * See feeder_fmtchain() for the mumbo-jumbo ridiculous explaination + * See feeder_fmtchain() for the mumbo-jumbo ridiculous explanation * of what the heck is this FMT_Q_* */ #define FMT_Q_UP 1 diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c index 8212570..b0c2176 100644 --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -99,7 +99,7 @@ static struct cdevsw mixer_cdevsw = { int mixer_count = 0; #ifdef USING_DEVFS -static eventhandler_tag mixer_ehtag; +static eventhandler_tag mixer_ehtag = NULL; #endif static struct cdev * @@ -130,26 +130,35 @@ static int 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; + 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(c, d, 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(c, d, 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; } @@ -480,7 +489,7 @@ mixer_init(device_t dev, kobj_class_t cls, void *devinfo) struct snd_mixer *m; u_int16_t v; struct cdev *pdev; - int i, unit, val; + int i, unit, devunit, val; m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); snprintf(m->name, MIXER_NAMELEN, "%s:mixer", device_get_nameunit(dev)); @@ -514,7 +523,8 @@ mixer_init(device_t dev, kobj_class_t cls, void *devinfo) mixer_setrecsrc(m, SOUND_MASK_MIC); unit = device_get_unit(dev); - pdev = make_dev(&mixer_cdevsw, PCMMKMINOR(unit, SND_DEV_CTL, 0), + devunit = snd_mkunit(unit, SND_DEV_CTL, 0); + pdev = make_dev(&mixer_cdevsw, unit2minor(devunit), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m; snddev = device_get_softc(dev); @@ -845,17 +855,20 @@ mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread #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; + struct snddev_info *d; if (*dev != NULL) return; if (strcmp(name, "mixer") == 0) { - sd = devclass_get_softc(pcm_devclass, snd_unit); - if (sd != NULL && sd->mixer_dev != NULL) { - *dev = sd->mixer_dev; + d = devclass_get_softc(pcm_devclass, snd_unit); + if (d != NULL && d->mixer_dev != NULL) { + *dev = d->mixer_dev; dev_ref(*dev); } } @@ -864,14 +877,18 @@ mixer_clone(void *arg, struct ucred *cred, char *name, int namelen, static void mixer_sysinit(void *p) { + if (mixer_ehtag != NULL) + return; mixer_ehtag = EVENTHANDLER_REGISTER(dev_clone, mixer_clone, 0, 1000); } static void mixer_sysuninit(void *p) { - if (mixer_ehtag != NULL) - EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); + if (mixer_ehtag == NULL) + return; + EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); + mixer_ehtag = NULL; } SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); diff --git a/sys/dev/sound/pcm/sndstat.c b/sys/dev/sound/pcm/sndstat.c index 0473ad9..c07e1de 100644 --- a/sys/dev/sound/pcm/sndstat.c +++ b/sys/dev/sound/pcm/sndstat.c @@ -64,18 +64,21 @@ struct sndstat_entry { static struct mtx sndstat_lock; #endif static struct sbuf sndstat_sbuf; -static struct cdev *sndstat_dev = 0; -static int sndstat_isopen = 0; -static int sndstat_bufptr; +static struct cdev *sndstat_dev = NULL; +static int sndstat_bufptr = -1; static int sndstat_maxunit = -1; static int sndstat_files = 0; -static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none); +#define SNDSTAT_PID(x) ((pid_t)((intptr_t)((x)->si_drv1))) +#define SNDSTAT_PID_SET(x, y) (x)->si_drv1 = (void *)((intptr_t)(y)) +#define SNDSTAT_FLUSH() do { \ + if (sndstat_bufptr != -1) { \ + sbuf_delete(&sndstat_sbuf); \ + sndstat_bufptr = -1; \ + } \ +} while(0) -#ifdef SND_DEBUG -SYSCTL_INT(_hw_snd, OID_AUTO, sndstat_isopen, CTLFLAG_RW, - &sndstat_isopen, 1, "sndstat emergency exit"); -#endif +static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none); int snd_verbose = 1; #ifdef USING_MUTEX @@ -84,6 +87,31 @@ TUNABLE_INT("hw.snd.verbose", &snd_verbose); TUNABLE_INT_DECL("hw.snd.verbose", 1, snd_verbose); #endif +#ifdef SND_DEBUG +static int +sysctl_hw_snd_sndstat_pid(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + if (sndstat_dev == NULL) + return (EINVAL); + + mtx_lock(&sndstat_lock); + val = (int)SNDSTAT_PID(sndstat_dev); + mtx_unlock(&sndstat_lock); + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (err == 0 && req->newptr != NULL && val == 0) { + mtx_lock(&sndstat_lock); + SNDSTAT_FLUSH(); + SNDSTAT_PID_SET(sndstat_dev, 0); + mtx_unlock(&sndstat_lock); + } + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, sndstat_pid, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_sndstat_pid, "I", "sndstat busy pid"); +#endif + static int sndstat_prepare(struct sbuf *s); static int @@ -111,12 +139,15 @@ sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { int error; + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; + mtx_lock(&sndstat_lock); - if (sndstat_isopen) { + if (SNDSTAT_PID(i_dev) != 0) { mtx_unlock(&sndstat_lock); return EBUSY; } - sndstat_isopen = 1; + SNDSTAT_PID_SET(i_dev, td->td_proc->p_pid); mtx_unlock(&sndstat_lock); if (sbuf_new(&sndstat_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { error = ENXIO; @@ -127,7 +158,8 @@ sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) out: if (error) { mtx_lock(&sndstat_lock); - sndstat_isopen = 0; + SNDSTAT_FLUSH(); + SNDSTAT_PID_SET(i_dev, 0); mtx_unlock(&sndstat_lock); } return (error); @@ -136,17 +168,18 @@ out: static int sndstat_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; + mtx_lock(&sndstat_lock); - if (!sndstat_isopen) { + if (SNDSTAT_PID(i_dev) == 0) { mtx_unlock(&sndstat_lock); return EBADF; } - mtx_unlock(&sndstat_lock); - sbuf_delete(&sndstat_sbuf); + SNDSTAT_FLUSH(); + SNDSTAT_PID_SET(i_dev, 0); - mtx_lock(&sndstat_lock); - sndstat_isopen = 0; mtx_unlock(&sndstat_lock); return 0; @@ -157,8 +190,12 @@ sndstat_read(struct cdev *i_dev, struct uio *buf, int flag) { int l, err; + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; + mtx_lock(&sndstat_lock); - if (!sndstat_isopen) { + if (SNDSTAT_PID(i_dev) != buf->uio_td->td_proc->p_pid || + sndstat_bufptr == -1) { mtx_unlock(&sndstat_lock); return EBADF; } @@ -187,27 +224,33 @@ sndstat_find(int type, int unit) } int -sndstat_acquire(void) +sndstat_acquire(struct thread *td) { + if (sndstat_dev == NULL) + return EBADF; + mtx_lock(&sndstat_lock); - if (sndstat_isopen) { + if (SNDSTAT_PID(sndstat_dev) != 0) { mtx_unlock(&sndstat_lock); return EBUSY; } - sndstat_isopen = 1; + SNDSTAT_PID_SET(sndstat_dev, td->td_proc->p_pid); mtx_unlock(&sndstat_lock); return 0; } int -sndstat_release(void) +sndstat_release(struct thread *td) { + if (sndstat_dev == NULL) + return EBADF; + mtx_lock(&sndstat_lock); - if (!sndstat_isopen) { + if (SNDSTAT_PID(sndstat_dev) != td->td_proc->p_pid) { mtx_unlock(&sndstat_lock); return EBADF; } - sndstat_isopen = 0; + SNDSTAT_PID_SET(sndstat_dev, 0); mtx_unlock(&sndstat_lock); return 0; } @@ -349,27 +392,32 @@ sndstat_prepare(struct sbuf *s) static int sndstat_init(void) { + if (sndstat_dev != NULL) + return EINVAL; mtx_init(&sndstat_lock, "sndstat", "sndstat lock", MTX_DEF); - sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, UID_ROOT, GID_WHEEL, 0444, "sndstat"); - - return (sndstat_dev != 0)? 0 : ENXIO; + sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, + UID_ROOT, GID_WHEEL, 0444, "sndstat"); + return 0; } static int sndstat_uninit(void) { + if (sndstat_dev == NULL) + return EINVAL; + mtx_lock(&sndstat_lock); - if (sndstat_isopen) { + if (SNDSTAT_PID(sndstat_dev) != curthread->td_proc->p_pid) { mtx_unlock(&sndstat_lock); return EBUSY; } - sndstat_isopen = 1; + SNDSTAT_FLUSH(); + mtx_unlock(&sndstat_lock); - if (sndstat_dev) - destroy_dev(sndstat_dev); - sndstat_dev = 0; + destroy_dev(sndstat_dev); + sndstat_dev = NULL; mtx_destroy(&sndstat_lock); return 0; @@ -392,5 +440,3 @@ sndstat_sysuninit(void *p) SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); - - diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index 9d46d83..3fe301d 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -29,6 +29,7 @@ #include <dev/sound/pcm/ac97.h> #include <dev/sound/pcm/vchan.h> #include <dev/sound/pcm/dsp.h> +#include <dev/sound/version.h> #include <sys/limits.h> #include <sys/sysctl.h> @@ -45,7 +46,7 @@ int snd_unit = 0; TUNABLE_INT("hw.snd.default_unit", &snd_unit); #endif -int snd_maxautovchans = 4; +int snd_maxautovchans = 16; /* XXX: a tunable implies that we may need more than one sound channel before the system can change a sysctl (/etc/sysctl.conf), do we really need this? */ @@ -53,6 +54,15 @@ TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); +/* + * XXX I've had enough with people not telling proper version/arch + * while reporting problems, not after 387397913213th questions/requests. + */ +static const char snd_driver_version[] = + __XSTRING(SND_DRV_VERSION)"/"MACHINE_ARCH; +SYSCTL_STRING(_hw_snd, OID_AUTO, version, CTLFLAG_RD, &snd_driver_version, + 0, "Driver version/arch"); + /** * @brief Unit number allocator for syncgroup IDs */ @@ -154,113 +164,128 @@ pcm_getfakechan(struct snddev_info *d) return d->fakechan; } +static void +pcm_clonereset(struct snddev_info *d) +{ + int cmax; + + snd_mtxassert(d->lock); + + cmax = d->playcount + d->reccount - 1; + if (d->pvchancount > 0) + cmax += MAX(d->pvchancount, snd_maxautovchans) - 1; + if (d->rvchancount > 0) + cmax += MAX(d->rvchancount, snd_maxautovchans) - 1; + if (cmax > PCMMAXCLONE) + cmax = PCMMAXCLONE; + (void)snd_clone_gc(d->clones); + (void)snd_clone_setmaxunit(d->clones, cmax); +} + static int -pcm_setvchans(struct snddev_info *d, int newcnt) +pcm_setvchans(struct snddev_info *d, int direction, int newcnt, int num) { - struct snddev_channel *sce = NULL; - struct pcm_channel *c = NULL; - int err = 0, vcnt, dcnt, i; + struct pcm_channel *c, *ch, *nch; + int err, vcnt; + + err = 0; 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) { + KASSERT(num == -1 || + (num >= 0 && num < SND_MAXVCHANS && (newcnt - 1) == vcnt), + ("bogus vchan_create() request num=%d newcnt=%d vcnt=%d", + num, 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) { + err = vchan_create(c, num); + 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); + pcm_lock(d); + pcm_clonereset(d); + pcm_unlock(d); } 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++; - } + KASSERT(num == -1, + ("bogus vchan_destroy() request num=%d", num)); + 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); + pcm_clonereset(d); + pcm_unlock(d); } -setvchans_out: +pcm_setvchans_out: pcm_inprog(d, -1); return err; } @@ -268,27 +293,50 @@ setvchans_out: /* 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 devunit) { struct pcm_channel *c; - struct snddev_channel *sce; - int err; + int err, vchancount; + + KASSERT(d != NULL && ch != NULL && (devunit == -1 || + !(devunit & ~(SND_U_MASK | SND_D_MASK | SND_C_MASK))) && + (direction == PCMDIR_PLAY || direction == PCMDIR_REC), + ("%s() invalid d=%p ch=%p direction=%d pid=%d devunit=%d", + __func__, d, ch, direction, pid, devunit)); + + /* Double check again. */ + if (devunit != -1) { + switch (snd_unit2d(devunit)) { + case SND_DEV_DSPHW_PLAY: + case SND_DEV_DSPHW_VPLAY: + if (direction != PCMDIR_PLAY) + return (EOPNOTSUPP); + break; + case SND_DEV_DSPHW_REC: + case SND_DEV_DSPHW_VREC: + if (direction != PCMDIR_REC) + return (EOPNOTSUPP); + break; + default: + if (!(direction == PCMDIR_PLAY || + direction == PCMDIR_REC)) + return (EOPNOTSUPP); + break; + } + } 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->direction == direction && !(c->flags & CHN_F_BUSY) && + (devunit == -1 || devunit == -2 || c->unit == devunit)) { + c->flags |= CHN_F_BUSY; + c->pid = pid; + *ch = c; + return (0); + } else if (c->unit == devunit) { if (c->direction != direction) err = EOPNOTSUPP; else if (c->flags & CHN_F_BUSY) @@ -296,24 +344,34 @@ retry_chnalloc: else err = EINVAL; CHN_UNLOCK(c); - return err; - } else if (c->direction == direction && (c->flags & CHN_F_BUSY)) + return (err); + } else if ((devunit == -1 || devunit == -2) && + c->direction == direction && (c->flags & CHN_F_BUSY)) err = EBUSY; CHN_UNLOCK(c); } /* 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 (devunit == -1 || (devunit != -2 && + (snd_unit2d(devunit) == SND_DEV_DSPHW_VPLAY || + snd_unit2d(devunit) == SND_DEV_DSPHW_VREC))) { + if (direction == PCMDIR_PLAY) + vchancount = d->pvchancount; + else + vchancount = d->rvchancount; + if (!(vchancount > 0 && vchancount < snd_maxautovchans) && + (devunit == -1 || snd_unit2c(devunit) < snd_maxautovchans)) + return (err); + err = pcm_setvchans(d, direction, vchancount + 1, + (devunit == -1) ? -1 : snd_unit2c(devunit)); if (err == 0) { - chnum = -2; + if (devunit == -1) + devunit = -2; goto retry_chnalloc; } } - return err; + return (err); } /* release a locked channel and unlock it */ @@ -357,10 +415,22 @@ pcm_inprog(struct snddev_info *d, int delta) 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) + return; + + if (num >= 0 && d->pvchancount > num) + (void)pcm_setvchans(d, PCMDIR_PLAY, num, -1); + else if (num > 0 && d->pvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_PLAY, 1, -1); + + if (num >= 0 && d->rvchancount > num) + (void)pcm_setvchans(d, PCMDIR_REC, num, -1); + else if (num > 0 && d->rvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_REC, 1, -1); + + pcm_lock(d); + pcm_clonereset(d); + pcm_unlock(d); } #ifdef USING_DEVFS @@ -373,10 +443,8 @@ sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) unit = snd_unit; error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); if (error == 0 && req->newptr != NULL) { - 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,17 +464,17 @@ sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) 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; snd_maxautovchans = v; + for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (d == NULL) + continue; + pcm_setmaxautovchans(d, v); + } } return (error); } @@ -414,99 +482,122 @@ SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); struct pcm_channel * -pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) +pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo) { - struct snddev_channel *sce; - struct pcm_channel *ch, *c; - char *dirs; - uint32_t flsearch = 0; - int direction, err, rpnum, *pnum; + struct pcm_channel *ch; + int direction, err, rpnum, *pnum, max; + int udc, device, chan; + char *dirs, *devname, buf[CHN_NAMELEN]; + + KASSERT(num >= -1, ("invalid num=%d", num)); + + pcm_lock(d); switch(dir) { case PCMDIR_PLAY: dirs = "play"; direction = PCMDIR_PLAY; pnum = &d->playcount; + device = SND_DEV_DSPHW_PLAY; + max = SND_MAXHWCHAN; + break; + case PCMDIR_PLAY_VIRTUAL: + dirs = "virtual"; + direction = PCMDIR_PLAY; + pnum = &d->pvchancount; + device = SND_DEV_DSPHW_VPLAY; + max = SND_MAXVCHANS; break; - case PCMDIR_REC: dirs = "record"; direction = PCMDIR_REC; pnum = &d->reccount; + device = SND_DEV_DSPHW_REC; + max = SND_MAXHWCHAN; break; - - case PCMDIR_VIRTUAL: + case PCMDIR_REC_VIRTUAL: dirs = "virtual"; - direction = PCMDIR_PLAY; - pnum = &d->vchancount; - flsearch = CHN_F_VIRTUAL; + direction = PCMDIR_REC; + pnum = &d->rvchancount; + device = SND_DEV_DSPHW_VREC; + max = SND_MAXVCHANS; break; - default: + pcm_unlock(d); return NULL; } - ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); - ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); + chan = (num == -1) ? 0 : num; - 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 >= max || chan >= max) { + pcm_unlock(d); + return NULL; } - goto retry_num_search_out; -retry_num_search: + 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 (CHN_DEV(ch) != device) continue; - if (ch->num == c->num) { - ch->num++; - goto retry_num_search; + if (chan == CHN_CHAN(ch)) { + if (num != -1) { + device_printf(d->dev, + "channel num=%d allocated!\n", chan); + pcm_unlock(d); + return NULL; + } + chan++; + if (chan >= max) { + device_printf(d->dev, + "chan=%d > %d\n", chan, max); + pcm_unlock(d); + 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); + pcm_unlock(d); + return NULL; + } + + udc = snd_mkunit(device_get_unit(d->dev), device, chan); + devname = dsp_unit2name(buf, sizeof(buf), udc); + + if (devname == NULL) { + device_printf(d->dev, + "Failed to query device name udc=0x%08x\n", udc); + pcm_unlock(d); + return NULL; } + (*pnum)++; - snd_mtxunlock(d->lock); + pcm_unlock(d); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); + ch->unit = udc; 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->trigger = PCMTRIG_STOP; + snprintf(ch->name, sizeof(ch->name), "%s:%s:%s", + device_get_nameunit(ch->dev), dirs, devname); 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); + pcm_lock(d); (*pnum)--; - snd_mtxunlock(d->lock); + pcm_unlock(d); return NULL; } @@ -536,139 +627,50 @@ pcm_chn_destroy(struct pcm_channel *ch) 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; + int num; - /* - * Note it's confusing nomenclature. - * dev_t - * device -> pcm_device - * unit -> pcm_channel - * channel -> snddev_channel - * device_t - * unit -> pcm_device - */ + pcm_lock(d); - sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO); + KASSERT(ch != NULL && (ch->direction == PCMDIR_PLAY || + ch->direction == PCMDIR_REC), ("Invalid pcm channel")); - 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; + /* - * 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; + CHN_FOREACH(tmp, d, channels.pcm) { + if (tmp->unit == ch->unit) { + device_printf(d->dev, "%s(): Device collision " + "old=%p new=%p devunit=0x%08x\n", + __func__, tmp, ch, ch->unit); + pcm_unlock(d); + return ENODEV; } - if (sce->chan_num > tmp->chan_num) + if (CHN_DEV(tmp) < CHN_DEV(ch)) { + if (num == 0) + after = tmp; + continue; + } else if (CHN_DEV(tmp) > CHN_DEV(ch)) + break; + num++; + if (CHN_CHAN(tmp) < CHN_CHAN(ch)) 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; - } - d->devcount++; - if (after == NULL) { - SLIST_INSERT_HEAD(&d->channels, sce, link); - } 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++; - } + else if (CHN_CHAN(tmp) > CHN_CHAN(ch)) + break; } -#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); + if (after != NULL) { + CHN_INSERT_AFTER(after, ch, channels.pcm); + } else { + CHN_INSERT_HEAD(d, ch, channels.pcm); } - 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); + d->devcount++; + pcm_unlock(d); return 0; } @@ -676,41 +678,35 @@ retry_chan_num_search_out: 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--; - else if (ch->direction == PCMDIR_REC) - d->reccount--; - else - d->playcount--; + if (tmp != ch) + return EINVAL; -#if 0 - if (ourlock) - snd_mtxunlock(d->lock); -#endif - free(sce, M_DEVBUF); + CHN_REMOVE(d, ch, channels.pcm); + switch (CHN_DEV(ch)) { + case SND_DEV_DSPHW_PLAY: + d->playcount--; + break; + case SND_DEV_DSPHW_VPLAY: + d->pvchancount--; + break; + case SND_DEV_DSPHW_REC: + d->reccount--; + break; + case SND_DEV_DSPHW_VREC: + d->rvchancount--; + break; + default: + break; + } return 0; } @@ -722,7 +718,7 @@ pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo) struct pcm_channel *ch; int err; - ch = pcm_chn_create(d, NULL, cls, dir, devinfo); + ch = pcm_chn_create(d, NULL, cls, dir, -1, devinfo); if (!ch) { device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo); return ENODEV; @@ -742,14 +738,12 @@ static int 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)); @@ -760,11 +754,19 @@ pcm_setstatus(device_t dev, char *str) { struct snddev_info *d = device_get_softc(dev); - snd_mtxlock(d->lock); + pcm_setmaxautovchans(d, snd_maxautovchans); + + pcm_lock(d); + strlcpy(d->status, str, SND_STATUSLEN); - snd_mtxunlock(d->lock); - if (snd_maxautovchans > 0) - pcm_setvchans(d, 1); + + /* Last stage, enable cloning. */ + if (d->clones != NULL) { + (void)snd_clone_enable(d->clones); + } + + pcm_unlock(d); + return 0; } @@ -822,10 +824,117 @@ pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsig 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); + + pcm_lock(d); + flags = snd_clone_getflags(d->clones); + pcm_unlock(d); + 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); + + pcm_lock(d); + deadline = snd_clone_getdeadline(d->clones); + pcm_unlock(d); + 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); +} + +static int +sysctl_hw_snd_clone_gc(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int i, err, val; + + val = 0; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + + if (err == 0 && req->newptr != NULL && val != 0) { + for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (d == NULL || d->clones == NULL) + continue; + pcm_lock(d); + (void)snd_clone_gc(d->clones); + pcm_unlock(d); + } + } + + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, clone_gc, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_clone_gc, "I", + "global clone garbage collector"); +#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 +942,15 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) return EINVAL; } + if (device_get_unit(dev) > PCMMAXUNIT) { + device_printf(dev, "PCMMAXUNIT reached : unit=%d > %d\n", + device_get_unit(dev), PCMMAXUNIT); + device_printf(dev, + "Use 'hw.snd.maxunit' tunable to raise the limit.\n"); + return ENODEV; + } + + d = device_get_softc(dev); d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); #if 0 @@ -848,10 +966,38 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) 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; - SLIST_INIT(&d->channels); + /* + * Create clone manager, disabled by default. Cloning will be + * enabled during final stage of driver iniialization through + * pcm_setstatus(). + */ + d->clones = snd_clone_create( +#ifdef SND_DIAGNOSTIC + d->lock, +#endif + SND_U_MASK | SND_D_MASK, PCMMAXCLONE, SND_CLONE_DEADLINE_DEFAULT, + SND_CLONE_GC_ENABLE | SND_CLONE_GC_UNREF | + SND_CLONE_GC_LASTREF | SND_CLONE_GC_EXPIRED); + + if (bootverbose != 0 || snd_verbose > 3) { + pcm_lock(d); + device_printf(dev, + "clone manager: deadline=%dms flags=0x%08x\n", + snd_clone_getdeadline(d->clones), + snd_clone_getflags(d->clones)); + pcm_unlock(d); + } + + CHN_INIT(d, channels.pcm); + CHN_INIT(d, channels.pcm.busy); if ((numplay == 0 || numrec == 0) && numplay != numrec) d->flags |= SD_F_SIMPLEX; @@ -860,13 +1006,38 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) chn_init(d->fakechan, NULL, 0, 0); #ifdef SND_DYNSYSCTL + sysctl_ctx_init(&d->play_sysctl_ctx); + d->play_sysctl_tree = SYSCTL_ADD_NODE(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "play", + CTLFLAG_RD, 0, "playback channels node"); + sysctl_ctx_init(&d->rec_sysctl_ctx); + d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", + CTLFLAG_RD, 0, "record channels node"); /* XXX: an user should be able to set this with a control tool, the sysadmin then needs min+max sysctls for this */ 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) { + if (numplay > 0 || numrec > 0) { d->flags |= SD_F_AUTOVCHAN; vchan_initsys(dev); } @@ -879,107 +1050,95 @@ int 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; + struct thread *td; + int i; - if (sndstat_acquire() != 0) { + td = curthread; + + if (sndstat_acquire(td) != 0) { device_printf(dev, "unregister: sndstat busy\n"); return EBUSY; } - snd_mtxlock(d->lock); + pcm_lock(d); if (d->inprog) { device_printf(dev, "unregister: operation in progress\n"); - snd_mtxunlock(d->lock); - sndstat_release(); + pcm_unlock(d); + sndstat_release(td); 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); - sndstat_release(); + pcm_unlock(d); + sndstat_release(td); return EBUSY; } } + if (d->clones != NULL) { + if (snd_clone_busy(d->clones) != 0) { + device_printf(dev, "unregister: clone busy\n"); + pcm_unlock(d); + sndstat_release(td); + return EBUSY; + } else + (void)snd_clone_disable(d->clones); + } + if (mixer_uninit(dev) == EBUSY) { device_printf(dev, "unregister: mixer busy\n"); - snd_mtxunlock(d->lock); - sndstat_release(); + if (d->clones != NULL) + (void)snd_clone_enable(d->clones); + pcm_unlock(d); + sndstat_release(td); 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; - } - 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); - } + if (d->clones != NULL) { + snd_clone_destroy(d->clones); + d->clones = NULL; } -#ifdef SND_DYNSYSCTL -#if 0 - d->sysctl_tree_top = NULL; - sysctl_ctx_free(&d->sysctl_tree); -#endif -#endif + d->devcount = 0; -#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); +#ifdef SND_DYNSYSCTL + if (d->play_sysctl_tree != NULL) { + sysctl_ctx_free(&d->play_sysctl_ctx); + d->play_sysctl_tree = NULL; + } + if (d->rec_sysctl_tree != NULL) { + sysctl_ctx_free(&d->rec_sysctl_ctx); + d->rec_sysctl_tree = NULL; } #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 - snd_mtxunlock(d->lock); + pcm_unlock(d); snd_mtxfree(d->lock); sndstat_unregister(dev); - sndstat_release(); + sndstat_release(td); + + if (snd_unit == device_get_unit(dev)) { + /* + * Reassign default unit to the next available dev. + */ + for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { + if (device_get_unit(dev) == i || + devclass_get_softc(pcm_devclass, i) == NULL) + continue; + snd_unit = i; + break; + } + } + return 0; } @@ -989,10 +1148,8 @@ static int 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; @@ -1001,20 +1158,11 @@ sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) if (!d) 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, + pcm_lock(d); + 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" : "" @@ -1024,12 +1172,11 @@ sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) ); if (verbose <= 1) { - snd_mtxunlock(d->lock); + pcm_unlock(d); 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")); @@ -1087,7 +1234,7 @@ sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) } } else sbuf_printf(s, " (mixer only)"); - snd_mtxunlock(d->lock); + pcm_unlock(d); return 0; } @@ -1099,15 +1246,41 @@ int sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; + int direction, vchancount; int err, newcnt; - d = oidp->oid_arg1; + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (d == NULL) + return EINVAL; - newcnt = d->vchancount; + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + if (d->playcount < 1) + return ENODEV; + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + break; + case VCHAN_REC: + if (d->reccount < 1) + return ENODEV; + 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, -1); + } return err; } @@ -1139,7 +1312,6 @@ sound_oss_sysinfo(oss_sysinfo *si) 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 +1346,7 @@ sound_oss_sysinfo(oss_sysinfo *si) 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) @@ -1242,6 +1413,9 @@ sound_modevent(module_t mod, int type, void *data) break; case MOD_UNLOAD: case MOD_SHUTDOWN: + ret = sndstat_acquire(curthread); + if (ret != 0) + break; if (pcmsg_unrhdr != NULL) { delete_unrhdr(pcmsg_unrhdr); pcmsg_unrhdr = NULL; diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index e09babe..1f1aa0d 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -95,6 +95,8 @@ struct snd_mixer; #include <dev/sound/pcm/feeder.h> #include <dev/sound/pcm/mixer.h> #include <dev/sound/pcm/dsp.h> +#include <dev/sound/clone.h> +#include <dev/sound/unit.h> #define PCM_SOFTC_SIZE 512 @@ -108,32 +110,34 @@ struct snd_mixer; /* * We're abusing the fact that MAXMINOR still have enough room - * for our bit twiddling and nobody ever need 2048 unique soundcards, - * 32 unique device types and 256 unique cloneable devices for the - * next 100 years... or until the NextPCM. - * - * MAXMINOR 0xffff00ff - * | | - * | +--- PCMMAXCHAN - * | - * +-------- ((PCMMAXUNIT << 5) | PCMMAXDEV) << 16 + * for our bit twiddling and nobody ever need 512 unique soundcards, + * 32 unique device types and 1024 unique cloneable devices for the + * next 100 years... */ -#define PCMMAXCHAN 0xff -#define PCMMAXDEV 0x1f -#define PCMMAXUNIT 0x7ff -#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 PCMMAXUNIT (snd_max_u()) +#define PCMMAXDEV (snd_max_d()) +#define PCMMAXCHAN (snd_max_c()) + +#define PCMMAXCLONE PCMMAXCHAN + +#define PCMUNIT(x) (snd_unit2u(dev2unit(x))) +#define PCMDEV(x) (snd_unit2d(dev2unit(x))) +#define PCMCHAN(x) (snd_unit2c(dev2unit(x))) + +/* + * By design, limit possible channels for each direction. + */ +#define SND_MAXHWCHAN 256 +#define SND_MAXVCHANS SND_MAXHWCHAN #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 #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) @@ -433,9 +437,6 @@ typedef int32_t intpcm_t; struct pcm_channel *fkchan_setup(device_t dev); int fkchan_kill(struct pcm_channel *c); -/* XXX Flawed definition. I'll fix it someday. */ -#define SND_MAXVCHANS PCMMAXCHAN - /* * Minor numbers for the sound driver. * @@ -457,7 +458,11 @@ int fkchan_kill(struct pcm_channel *c); #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 @@ -485,12 +490,12 @@ extern struct unrhdr *pcmsg_unrhdr; 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 devunit); int pcm_chnrelease(struct pcm_channel *c); int pcm_chnref(struct pcm_channel *c, int ref); int pcm_inprog(struct snddev_info *d, int delta); -struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo); +struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo); int pcm_chn_destroy(struct pcm_channel *ch); int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch); int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch); @@ -517,8 +522,8 @@ void snd_mtxassert(void *m); int sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS); typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); -int sndstat_acquire(void); -int sndstat_release(void); +int sndstat_acquire(struct thread *td); +int sndstat_release(struct thread *td); int sndstat_register(device_t dev, char *str, sndstat_handler handler); int sndstat_registerfile(char *str); int sndstat_unregister(device_t dev); @@ -551,20 +556,18 @@ int sndstat_unregisterfile(char *str); * 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_info { - SLIST_HEAD(, snddev_channel) channels; + struct { + struct { + SLIST_HEAD(, pcm_channel) head; + struct { + SLIST_HEAD(, pcm_channel) head; + } busy; + } pcm; + } channels; + struct snd_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,7 +576,10 @@ struct snddev_info { char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; - + uint32_t pvchanrate, pvchanformat; + uint32_t rvchanrate, rvchanformat; + struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; + struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; }; void sound_oss_sysinfo(oss_sysinfo *); diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index 43b0bfe..d4a5356 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -36,19 +36,13 @@ SND_DECLARE_FILE("$FreeBSD$"); 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 { - uint32_t spd, fmt, fmts[2], blksz, bps, run; - struct pcm_channel *channel, *parent; + struct pcm_channel *channel; struct pcmchan_caps caps; + uint32_t fmtlist[2]; + int trigger; }; /* support everything (mono / stereo), except a-law / mu-law */ @@ -121,7 +115,7 @@ feed_vchan_mix_##SIGNS##FMTBIT##ENDIANS(uint8_t *to, uint8_t *tmp, \ VCHAN_PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(to, x); \ } while (i != 0); \ \ - return count; \ + return (count); \ } FEEDER_VCHAN_MIX(8, int32_t, S, s, NE, ne) @@ -172,7 +166,7 @@ feed_vchan_init(struct pcm_feeder *f) int i, channels; if (f->desc->out != f->desc->in) - return EINVAL; + return (EINVAL); channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; @@ -181,24 +175,109 @@ feed_vchan_init(struct pcm_feeder *f) if ((f->desc->out & ~AFMT_STEREO) == feed_vchan_info_tbl[i].format) { f->data = (void *)FVCHAN_DATA(i, channels); - return 0; + return (0); } } - return -1; + return (-1); +} + +static __inline int +feed_vchan_rec(struct pcm_channel *c) +{ + struct pcm_channel *ch; + struct snd_dbuf *b, *bs; + int cnt, rdy; + + /* + * Reset ready and moving pointer. We're not using bufsoft + * anywhere since its sole purpose is to become the primary + * distributor for the recorded buffer and also as an interrupt + * threshold progress indicator. + */ + b = c->bufsoft; + b->rp = 0; + b->rl = 0; + cnt = sndbuf_getsize(b); + + do { + cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, cnt, + c->bufhard); + if (cnt != 0) { + sndbuf_acquire(b, b->tmpbuf, cnt); + cnt = sndbuf_getfree(b); + } + } while (cnt != 0); + + /* Not enough data */ + if (b->rl < sndbuf_getbps(b)) { + b->rl = 0; + return (0); + } + + /* + * Keep track of ready and moving pointer since we will use + * bufsoft over and over again, pretending nothing has happened. + */ + rdy = b->rl; + + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + bs = ch->bufsoft; + cnt = sndbuf_getfree(bs); + if (!(ch->flags & CHN_F_TRIGGERED) || + cnt < sndbuf_getbps(bs)) { + CHN_UNLOCK(ch); + continue; + } + do { + cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf, cnt, b); + if (cnt != 0) { + sndbuf_acquire(bs, bs->tmpbuf, cnt); + cnt = sndbuf_getfree(bs); + } + } while (cnt != 0); + /* + * Not entirely flushed out... + */ + if (b->rl != 0) + ch->xruns++; + CHN_UNLOCK(ch); + /* + * Rewind buffer position for next virtual channel. + */ + b->rp = 0; + b->rl = rdy; + } + + /* + * Set ready pointer to indicate that our children are ready + * to be woken up, also as an interrupt threshold progress + * indicator. + */ + b->rl = 1; + + /* + * Return 0 to bail out early from sndbuf_feed() loop. + * No need to increase feedcount counter since part of this + * feeder chains already include feed_root(). + */ + return (0); } static int feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) + uint32_t count, void *source) { 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; + if (c->direction == PCMDIR_REC) + return (feed_vchan_rec(c)); + sz = sndbuf_getsize(src); if (sz < count) count = sz; @@ -207,7 +286,7 @@ feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, sz = info->bps * FVCHAN_CHANNELS((intptr_t)f->data); count -= count % sz; if (count < sz) - return 0; + return (0); /* * we are going to use our source as a temporary buffer since it's @@ -219,8 +298,7 @@ feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, 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); @@ -256,7 +334,7 @@ feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, if (++c->feedcount == 0) c->feedcount = 2; - return rcnt; + return (rcnt); } static struct pcm_feederdesc feeder_vchan_desc[] = { @@ -300,144 +378,119 @@ FEEDER_DECLARE(feeder_vchan, 2, NULL); /************************************************************/ static void * -vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { 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")); + KASSERT(c != NULL && c->parentchannel != NULL, + ("vchan_init: bad channels")); + 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->trigger = PCMTRIG_STOP; c->flags |= CHN_F_VIRTUAL; - return ch; + return (ch); } static int vchan_free(kobj_t obj, void *data) { free(data, M_DEVBUF); - return 0; + + return (0); } static int vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; - - ch->fmt = format; - ch->bps = 1; - ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0; - if (ch->fmt & AFMT_16BIT) - ch->bps <<= 1; - else if (ch->fmt & AFMT_24BIT) - ch->bps *= 3; - else if (ch->fmt & AFMT_32BIT) - ch->bps <<= 2; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_FORMAT); - CHN_LOCK(channel); - sndbuf_setfmt(channel->bufsoft, format); - return 0; -} -static int -vchan_setspeed(kobj_t obj, void *data, uint32_t speed) -{ - struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; + if (fmtvalid(format, ch->fmtlist) == 0) + return (-1); - ch->spd = speed; - CHN_UNLOCK(channel); - CHN_LOCK(parent); - speed = sndbuf_getspd(parent->bufsoft); - CHN_UNLOCK(parent); - CHN_LOCK(channel); - return speed; + return (0); } static int -vchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) +vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchinfo *ch = data; - struct pcm_channel *channel = ch->channel; - struct pcm_channel *parent = ch->parent; - /* struct pcm_channel *channel = ch->channel; */ - int prate, crate; - - ch->blksz = blocksize; - /* CHN_UNLOCK(channel); */ - sndbuf_setblksz(channel->bufhard, blocksize); - chn_notify(parent, CHN_N_BLOCKSIZE); - CHN_LOCK(parent); - /* CHN_LOCK(channel); */ + struct pcm_channel *p = ch->channel->parentchannel; - crate = ch->spd * ch->bps; - prate = sndbuf_getspd(parent->bufsoft) * sndbuf_getbps(parent->bufsoft); - blocksize = sndbuf_getblksz(parent->bufsoft); - CHN_UNLOCK(parent); - blocksize *= prate; - blocksize /= crate; - blocksize += ch->bps; - prate = 0; - while (blocksize >> prate) - prate++; - blocksize = 1 << (prate - 1); - blocksize -= blocksize % ch->bps; - /* XXX screwed !@#$ */ - if (blocksize < ch->bps) - blocksize = 4096 - (4096 % ch->bps); - - return blocksize; + return (sndbuf_getspd(p->bufsoft)); } static int 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 *c, *p; + int otrigger; + + if (!(go == PCMTRIG_START || go == PCMTRIG_STOP || + go == PCMTRIG_ABORT) || go == ch->trigger) + return (0); - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) - return 0; + c = ch->channel; + p = c->parentchannel; + otrigger = ch->trigger; + ch->trigger = go; - ch->run = (go == PCMTRIG_START)? 1 : 0; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_TRIGGER); - CHN_LOCK(channel); + CHN_UNLOCK(c); + CHN_LOCK(p); - return 0; + switch (go) { + case PCMTRIG_START: + if (otrigger != PCMTRIG_START) { + CHN_INSERT_HEAD(p, c, children.busy); + } + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + if (otrigger == PCMTRIG_START) { + CHN_REMOVE(p, c, children.busy); + } + break; + default: + break; + } + + CHN_UNLOCK(p); + chn_notify(p, CHN_N_TRIGGER); + CHN_LOCK(c); + + return (0); } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; + struct pcm_channel *c, *p; uint32_t fmt; - ch->caps.minspeed = sndbuf_getspd(ch->parent->bufsoft); + c = ch->channel; + p = c->parentchannel; + ch->caps.minspeed = sndbuf_getspd(p->bufsoft); ch->caps.maxspeed = ch->caps.minspeed; ch->caps.caps = 0; - ch->fmts[1] = 0; - fmt = sndbuf_getfmt(ch->parent->bufsoft); + ch->fmtlist[1] = 0; + fmt = sndbuf_getfmt(p->bufsoft); if (fmt != vchan_valid_format(fmt)) { - device_printf(ch->parent->dev, + device_printf(c->dev, "%s: WARNING: invalid vchan format! (0x%08x)\n", __func__, fmt); fmt = VCHAN_DEFAULT_AFMT; } - ch->fmts[0] = fmt; - ch->caps.fmtlist = ch->fmts; + ch->fmtlist[0] = fmt; + ch->caps.fmtlist = ch->fmtlist; - return &ch->caps; + return (&ch->caps); } static kobj_method_t vchan_methods[] = { @@ -445,7 +498,6 @@ static kobj_method_t vchan_methods[] = { KOBJMETHOD(channel_free, vchan_free), KOBJMETHOD(channel_setformat, vchan_setformat), KOBJMETHOD(channel_setspeed, vchan_setspeed), - KOBJMETHOD(channel_setblocksize, vchan_setblocksize), KOBJMETHOD(channel_trigger, vchan_trigger), KOBJMETHOD(channel_getcaps, vchan_getcaps), {0, 0} @@ -460,42 +512,61 @@ static int 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 direction; int err = 0; int newspd = 0; - d = oidp->oid_arg1; - if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) - return EINVAL; + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (d == NULL || !(d->flags & SD_F_AUTOVCHAN)) + 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 (vchancount < 1) + return (EINVAL); if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { pcm_inprog(d, -1); - return EINPROGRESS; + 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) { CHN_UNLOCK(c); pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } if (req->newptr != NULL && (c->flags & CHN_F_BUSY)) { CHN_UNLOCK(c); pcm_inprog(d, -1); - return EBUSY; + return (EBUSY); } } else if (c->flags & CHN_F_HAS_VCHAN) { /* No way!! */ if (ch != NULL) { CHN_UNLOCK(c); pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } ch = c; newspd = ch->speed; @@ -505,23 +576,23 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) } if (ch == NULL) { pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req); if (err == 0 && req->newptr != NULL) { if (newspd < 1 || newspd < feeder_rate_min || - newspd > feeder_rate_max) { + newspd > feeder_rate_max) { pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } CHN_LOCK(ch); if (feeder_rate_round) { caps = chn_getcaps(ch); if (caps == NULL || newspd < caps->minspeed || - newspd > caps->maxspeed) { + newspd > caps->maxspeed) { CHN_UNLOCK(ch); pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } } if (newspd != ch->speed) { @@ -531,72 +602,90 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) * requested value is not supported by the hardware. */ if (!err && feeder_rate_round && - (ch->feederflags & (1 << FEEDER_RATE))) { + (ch->feederflags & (1 << FEEDER_RATE))) { newspd = sndbuf_getspd(ch->bufhard); err = chn_setspeed(ch, newspd); } 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); } pcm_inprog(d, -1); - return err; + return (err); } static int 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 direction; int err = 0, i; + char fmtstr[AFMTSTR_MAXSZ]; + + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (d == NULL || !(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); + + 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; + } - d = oidp->oid_arg1; - if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) - return EINVAL; + if (vchancount < 1) + return (EINVAL); if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { pcm_inprog(d, -1); - return EINPROGRESS; + 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) { CHN_UNLOCK(c); pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } if (req->newptr != NULL && (c->flags & CHN_F_BUSY)) { CHN_UNLOCK(c); pcm_inprog(d, -1); - return EBUSY; + return (EBUSY); } } else if (c->flags & CHN_F_HAS_VCHAN) { /* No way!! */ if (ch != NULL) { CHN_UNLOCK(c); pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } ch = c; - if (ch->format != afmt2afmtstr(vchan_supported_fmts, - ch->format, fmtstr, sizeof(fmtstr), - AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { - strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, sizeof(fmtstr)); + if (ch->format != + afmt2afmtstr(vchan_supported_fmts, + ch->format, fmtstr, sizeof(fmtstr), + AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { + strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, + sizeof(fmtstr)); } } } @@ -604,20 +693,21 @@ sysctl_hw_snd_vchanformat(SYSCTL_HANDLER_ARGS) } if (ch == NULL) { pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); if (err == 0 && req->newptr != NULL) { for (i = 0; vchan_fmtstralias[i].alias != NULL; i++) { if (strcmp(fmtstr, vchan_fmtstralias[i].alias) == 0) { - strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, sizeof(fmtstr)); + strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, + sizeof(fmtstr)); break; } } newfmt = vchan_valid_strformat(fmtstr); if (newfmt == 0) { pcm_inprog(d, -1); - return EINVAL; + return (EINVAL); } CHN_LOCK(ch); if (newfmt != ch->format) { @@ -628,66 +718,80 @@ sysctl_hw_snd_vchanformat(SYSCTL_HANDLER_ARGS) 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); } pcm_inprog(d, -1); - return err; + return (err); } #endif /* virtual channel interface */ +#define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ + "play.vchanformat" : "rec.vchanformat" +#define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ + "play.vchanrate" : "rec.vchanrate" + int -vchan_create(struct pcm_channel *parent) +vchan_create(struct pcm_channel *parent, int num) { 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; + uint32_t vchanfmt; + int err, first, speed, r; + int direction; if (!(parent->flags & CHN_F_BUSY)) - return EBUSY; - - + return (EBUSY); + + if (parent->direction == PCMDIR_PLAY) { + direction = PCMDIR_PLAY_VIRTUAL; + vchanfmt = d->pvchanformat; + speed = d->pvchanrate; + } else if (parent->direction == PCMDIR_REC) { + direction = PCMDIR_REC_VIRTUAL; + vchanfmt = d->rvchanformat; + speed = d->rvchanrate; + } 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, num, parent); + if (ch == NULL) { CHN_LOCK(parent); - return ENODEV; + 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; + 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 (CHN_CHAN(tmp) > CHN_CHAN(ch)) + after = tmp; + else if (CHN_CHAN(tmp) < CHN_CHAN(ch)) + 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,37 +799,24 @@ vchan_create(struct pcm_channel *parent) if (parent_caps == NULL) err = EINVAL; - fake = pcm_getfakechan(d); - - if (!err && fake != NULL) { - /* - * Avoid querying kernel hint, use saved value - * from fake channel. - */ - CHN_UNLOCK(parent); - CHN_LOCK(fake); - speed = fake->speed; - vchanfmt = fake->format; - CHN_UNLOCK(fake); - CHN_LOCK(parent); - } - if (!err) { if (vchanfmt == 0) { const char *vfmt; CHN_UNLOCK(parent); - r = resource_string_value(device_get_name(parent->dev), - device_get_unit(parent->dev), - "vchanformat", &vfmt); + r = resource_string_value( + device_get_name(parent->dev), + device_get_unit(parent->dev), + VCHAN_FMT_HINT(direction), + &vfmt); CHN_LOCK(parent); if (r != 0) vfmt = NULL; if (vfmt != NULL) { vchanfmt = vchan_valid_strformat(vfmt); for (r = 0; vchanfmt == 0 && - vchan_fmtstralias[r].alias != NULL; - r++) { + vchan_fmtstralias[r].alias != NULL; + r++) { if (strcmp(vfmt, vchan_fmtstralias[r].alias) == 0) { vchanfmt = vchan_valid_strformat(vchan_fmtstralias[r].fmtstr); break; @@ -747,14 +838,15 @@ vchan_create(struct pcm_channel *parent) */ if (speed < 1) { CHN_UNLOCK(parent); - r = resource_int_value(device_get_name(parent->dev), - device_get_unit(parent->dev), - "vchanrate", &speed); + r = resource_int_value( + device_get_name(parent->dev), + device_get_unit(parent->dev), + VCHAN_SPD_HINT(direction), + &speed); 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. @@ -804,37 +896,45 @@ vchan_create(struct pcm_channel *parent) * requested value is not supported by the hardware. */ if (!err && feeder_rate_round && - (parent->feederflags & (1 << FEEDER_RATE))) { + (parent->feederflags & (1 << FEEDER_RATE))) { speed = sndbuf_getspd(parent->bufhard); 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); + pcm_lock(d); + if (pcm_chn_remove(d, ch) == 0) { + pcm_unlock(d); + pcm_chn_destroy(ch); + } else + pcm_unlock(d); CHN_LOCK(parent); - return err; + return (err); } } - return 0; + return (0); } int @@ -842,70 +942,41 @@ vchan_destroy(struct pcm_channel *c) { 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; CHN_LOCK(parent); if (!(parent->flags & CHN_F_BUSY)) { CHN_UNLOCK(parent); - return EBUSY; + return (EBUSY); } - if (SLIST_EMPTY(&parent->children)) { + if (CHN_EMPTY(parent, children)) { CHN_UNLOCK(parent); - return EINVAL; + 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; - } - } - SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); - free(pce, M_DEVBUF); + CHN_REMOVE(parent, c, children); - if (SLIST_EMPTY(&parent->children)) { + if (CHN_EMPTY(parent, children)) { parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); spd = parent->speed; if (chn_reset(parent, parent->format) == 0) chn_setspeed(parent, spd); } + CHN_UNLOCK(parent); + /* remove us from our grandparent's channel list */ + pcm_lock(d); err = pcm_chn_remove(d, c); + pcm_unlock(d); - CHN_UNLOCK(parent); /* destroy ourselves */ if (!err) err = pcm_chn_destroy(c); - return err; + return (err); } int @@ -913,21 +984,44 @@ 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), + + /* Play */ + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, 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), + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, 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), + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, + sysctl_hw_snd_vchanformat, "A", "virtual channel format"); + /* Rec */ + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_hw_snd_vchanrate, "I", "virtual channel base speed/rate"); + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_hw_snd_vchanformat, "A", "virtual channel format"); #endif - return 0; + return (0); } diff --git a/sys/dev/sound/pcm/vchan.h b/sys/dev/sound/pcm/vchan.h index cb9e1c8..076ec82 100644 --- a/sys/dev/sound/pcm/vchan.h +++ b/sys/dev/sound/pcm/vchan.h @@ -26,8 +26,27 @@ * $FreeBSD$ */ -int vchan_create(struct pcm_channel *parent); +int vchan_create(struct pcm_channel *parent, int num); 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 + +/* + * Offset by +/- 1 so we can distinguish bogus pointer. + */ +#define VCHAN_SYSCTL_DATA(x, y) \ + ((void *)((intptr_t)(((((x) + 1) & 0xfff) << 2) | \ + (((VCHAN_##y) + 1) & 0x3)))) +#define VCHAN_SYSCTL_DATA_SIZE sizeof(void *) +#define VCHAN_SYSCTL_UNIT(x) ((int)(((intptr_t)(x) >> 2) & 0xfff) - 1) +#define VCHAN_SYSCTL_DIR(x) ((int)((intptr_t)(x) & 0x3) - 1) diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c index bb94e8b..d331763 100644 --- a/sys/dev/sound/usb/uaudio.c +++ b/sys/dev/sound/usb/uaudio.c @@ -4499,10 +4499,8 @@ static int 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 @@ uaudio_sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) 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:%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" : "" @@ -4549,8 +4537,7 @@ uaudio_sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) 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, diff --git a/sys/modules/sound/sound/Makefile b/sys/modules/sound/sound/Makefile index 5b96cb4..adfd36b 100644 --- a/sys/modules/sound/sound/Makefile +++ b/sys/modules/sound/sound/Makefile @@ -1,5 +1,6 @@ # $FreeBSD$ +.PATH: ${.CURDIR}/../../../dev/sound .PATH: ${.CURDIR}/../../../dev/sound/pcm .PATH: ${.CURDIR}/../../../dev/sound/midi .PATH: ${.CURDIR}/../../../dev/sound/isa @@ -10,9 +11,9 @@ SRCS+= ac97_if.h channel_if.h feeder_if.h mixer_if.h 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+= mixer.c sndstat.c sound.c unit.c vchan.c SRCS+= midi.c mpu401.c sequencer.c EXPORT_SYMS= YES # XXX evaluate |