summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorariff <ariff@FreeBSD.org>2007-05-31 18:43:33 +0000
committerariff <ariff@FreeBSD.org>2007-05-31 18:43:33 +0000
commite12a0ce02fe36373a2610fcdbf80521f4613b504 (patch)
tree0e66ac1b27ea2f1691da1ea5b896b26f55d8b668
parent1469bf4a20a4d63ca2b38ad1eb3a7ebeb2c60a66 (diff)
downloadFreeBSD-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/files2
-rw-r--r--sys/dev/sound/pcm/buffer.c6
-rw-r--r--sys/dev/sound/pcm/channel.c143
-rw-r--r--sys/dev/sound/pcm/channel.h95
-rw-r--r--sys/dev/sound/pcm/dsp.c644
-rw-r--r--sys/dev/sound/pcm/dsp.h1
-rw-r--r--sys/dev/sound/pcm/feeder.c8
-rw-r--r--sys/dev/sound/pcm/mixer.c67
-rw-r--r--sys/dev/sound/pcm/sndstat.c112
-rw-r--r--sys/dev/sound/pcm/sound.c1016
-rw-r--r--sys/dev/sound/pcm/sound.h84
-rw-r--r--sys/dev/sound/pcm/vchan.c630
-rw-r--r--sys/dev/sound/pcm/vchan.h21
-rw-r--r--sys/dev/sound/usb/uaudio.c23
-rw-r--r--sys/modules/sound/sound/Makefile5
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
OpenPOWER on IntegriCloud