summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/sound/pcm/channel.c135
-rw-r--r--sys/dev/sound/pcm/channel.h24
-rw-r--r--sys/dev/sound/pcm/channel_if.m19
-rw-r--r--sys/dev/sound/pcm/dsp.c83
-rw-r--r--sys/dev/sound/pcm/dsp.h2
-rw-r--r--sys/dev/sound/pcm/fake.c2
-rw-r--r--sys/dev/sound/pcm/sound.c438
-rw-r--r--sys/dev/sound/pcm/sound.h20
-rw-r--r--sys/dev/sound/pcm/vchan.c383
-rw-r--r--sys/dev/sound/pcm/vchan.h33
10 files changed, 927 insertions, 212 deletions
diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c
index 8eec528..727c509 100644
--- a/sys/dev/sound/pcm/channel.c
+++ b/sys/dev/sound/pcm/channel.c
@@ -121,6 +121,7 @@ chn_dmaupdate(struct pcm_channel *c)
struct snd_dbuf *b = c->bufhard;
unsigned int delta, old, hwptr, amt;
+ KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0"));
CHN_LOCKASSERT(c);
old = sndbuf_gethwptr(b);
hwptr = chn_getptr(c);
@@ -130,7 +131,7 @@ chn_dmaupdate(struct pcm_channel *c)
DEB(
if (delta >= ((sndbuf_getsize(b) * 15) / 16)) {
if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING)))
- device_printf(c->parent->dev, "hwptr went backwards %d -> %d\n", old, hwptr);
+ device_printf(c->parentsnddev->dev, "hwptr went backwards %d -> %d\n", old, hwptr);
}
);
@@ -231,13 +232,13 @@ chn_write(struct pcm_channel *c, struct uio *buf)
* the write operation avoids blocking.
*/
if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) {
- DEB(device_printf(c->parent->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n",
+ DEB(device_printf(c->parentsnddev->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n",
buf->uio_resid, sndbuf_getblksz(bs)));
newsize = 16;
while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2))
newsize <<= 1;
chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize);
- DEB(device_printf(c->parent->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs)));
+ DEB(device_printf(c->parentsnddev->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs)));
}
ret = 0;
@@ -271,7 +272,7 @@ chn_write(struct pcm_channel *c, struct uio *buf)
if (count <= 0) {
c->flags |= CHN_F_DEAD;
- device_printf(c->parent->dev, "play interrupt timeout, channel dead\n");
+ device_printf(c->parentsnddev->dev, "play interrupt timeout, channel dead\n");
}
return ret;
@@ -391,7 +392,7 @@ chn_read(struct pcm_channel *c, struct uio *buf)
if (count <= 0) {
c->flags |= CHN_F_DEAD;
- device_printf(c->parent->dev, "record interrupt timeout, channel dead\n");
+ device_printf(c->parentsnddev->dev, "record interrupt timeout, channel dead\n");
}
return ret;
@@ -423,8 +424,19 @@ chn_start(struct pcm_channel *c, int force)
i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs);
if (force || (i >= sndbuf_getblksz(b))) {
c->flags |= CHN_F_TRIGGERED;
- if (c->direction == PCMDIR_PLAY)
- chn_wrfeed(c);
+ /*
+ * if we're starting because a vchan started, don't feed any data
+ * or it becomes impossible to start vchans synchronised with the
+ * first one. the hardbuf should be empty so we top it up with
+ * silence to give it something to chew. the real data will be
+ * fed at the first irq.
+ */
+ if (c->direction == PCMDIR_PLAY) {
+ if (SLIST_EMPTY(&c->children))
+ chn_wrfeed(c);
+ else
+ sndbuf_fillsilence(b);
+ }
sndbuf_setrun(b, 1);
chn_trigger(c, PCMTRIG_START);
return 0;
@@ -458,7 +470,6 @@ chn_sync(struct pcm_channel *c, int threshold)
CHN_LOCKASSERT(c);
for (;;) {
- chn_wrupdate(c);
rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs);
if (rdy <= threshold) {
ret = chn_sleep(c, "pcmsyn", 1);
@@ -495,7 +506,7 @@ chn_poll(struct pcm_channel *c, int ev, struct proc *p)
* chn_abort terminates a running dma transfer. it may sleep up to 200ms.
* it returns the number of bytes that have not been transferred.
*
- * called from: dsp_close, dsp_ioctl, with both bufhards locked
+ * called from: dsp_close, dsp_ioctl, with channel locked
*/
int
chn_abort(struct pcm_channel *c)
@@ -509,7 +520,10 @@ chn_abort(struct pcm_channel *c)
return 0;
c->flags |= CHN_F_ABORTING;
- /* wait up to 200ms for the secondary bufhard to empty */
+ /*
+ * wait up to 200ms for the secondary buffer to empty-
+ * a vchan will never have data in the secondary buffer so we won't sleep
+ */
cnt = 10;
while ((sndbuf_getready(bs) > 0) && (cnt-- > 0)) {
chn_sleep(c, "pcmabr", hz / 50);
@@ -649,7 +663,7 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir)
sndbuf_destroy(b);
return ENODEV;
}
- if (sndbuf_getsize(b) == 0) {
+ if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0)) {
sndbuf_destroy(bs);
sndbuf_destroy(b);
return ENOMEM;
@@ -714,6 +728,7 @@ chn_tryspeed(struct pcm_channel *c, int speed)
int r, delta;
CHN_LOCKASSERT(c);
+ DEB(printf("setspeed, channel %s\n", c->name));
DEB(printf("want speed %d, ", speed));
if (speed <= 0)
return EINVAL;
@@ -724,7 +739,7 @@ chn_tryspeed(struct pcm_channel *c, int speed)
RANGE(speed, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed);
DEB(printf("try speed %d, ", speed));
sndbuf_setspd(b, CHANNEL_SETSPEED(c->methods, c->devinfo, speed));
- DEB(printf("got speed %d, ", sndbuf_getspd(b)));
+ DEB(printf("got speed %d\n", sndbuf_getspd(b)));
delta = sndbuf_getspd(b) - sndbuf_getspd(bs);
if (delta < 0)
@@ -762,6 +777,7 @@ chn_tryspeed(struct pcm_channel *c, int speed)
r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(b));
DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(b), r));
out:
+ DEB(printf("setspeed done, r = %d\n", r));
return r;
} else
return EINVAL;
@@ -957,17 +973,32 @@ chn_buildfeeder(struct pcm_channel *c)
c->align = sndbuf_getalign(c->bufsoft);
- fc = feeder_getclass(NULL);
- if (fc == NULL) {
- DEB(printf("can't find root feeder\n"));
- return EINVAL;
- }
- if (chn_addfeeder(c, fc, NULL)) {
- DEB(printf("can't add root feeder\n"));
- return EINVAL;
+ if (SLIST_EMPTY(&c->children)) {
+ fc = feeder_getclass(NULL);
+ if (fc == NULL) {
+ DEB(printf("can't find root feeder\n"));
+ return EINVAL;
+ }
+ if (chn_addfeeder(c, fc, NULL)) {
+ DEB(printf("can't add root feeder\n"));
+ return EINVAL;
+ }
+ c->feeder->desc->out = c->format;
+ } else {
+ desc.type = FEEDER_MIXER;
+ desc.in = 0;
+ desc.out = c->format;
+ desc.flags = 0;
+ fc = feeder_getclass(&desc);
+ if (fc == NULL) {
+ DEB(printf("can't find vchan feeder\n"));
+ return EINVAL;
+ }
+ if (chn_addfeeder(c, fc, &desc)) {
+ DEB(printf("can't add vchan feeder\n"));
+ return EINVAL;
+ }
}
- c->feeder->desc->out = c->format;
-
flags = c->feederflags;
if ((c->flags & CHN_F_MAPPED) && (flags != 0)) {
@@ -1020,3 +1051,63 @@ chn_buildfeeder(struct pcm_channel *c)
return 0;
}
+int
+chn_notify(struct pcm_channel *c, u_int32_t flags)
+{
+ struct pcmchan_children *pce;
+ struct pcm_channel *child;
+
+ if (SLIST_EMPTY(&c->children))
+ return ENODEV;
+
+ if (flags & CHN_N_RATE) {
+ /*
+ * we could do something here, like scan children and decide on
+ * the most appropriate rate to mix at, but we don't for now
+ */
+ }
+ if (flags & CHN_N_FORMAT) {
+ /*
+ * we could do something here, like scan children and decide on
+ * the most appropriate mixer feeder to use, but we don't for now
+ */
+ }
+ if (flags & CHN_N_VOLUME) {
+ /*
+ * we could do something here but we don't for now
+ */
+ }
+ if (flags & CHN_N_BLOCKSIZE) {
+ int blksz;
+ /*
+ * scan the children, find the lowest blocksize and use that
+ * for the hard blocksize
+ */
+ blksz = sndbuf_getmaxsize(c->bufhard) / 2;
+ SLIST_FOREACH(pce, &c->children, link) {
+ child = pce->channel;
+ if (sndbuf_getblksz(child->bufhard) < blksz)
+ blksz = sndbuf_getblksz(child->bufhard);
+ }
+ chn_setblocksize(c, 2, blksz);
+ }
+ if (flags & CHN_N_TRIGGER) {
+ int run;
+ /*
+ * 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
+ */
+ run = 0;
+ SLIST_FOREACH(pce, &c->children, link) {
+ child = pce->channel;
+ if (child->flags & CHN_F_TRIGGERED)
+ run = 1;
+ }
+ if (run && !(c->flags & CHN_F_TRIGGERED))
+ chn_start(c, 1);
+ if (!run && (c->flags & CHN_F_TRIGGERED))
+ chn_abort(c);
+ }
+ return 0;
+}
diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h
index 2b4bfba..bc0dff2 100644
--- a/sys/dev/sound/pcm/channel.h
+++ b/sys/dev/sound/pcm/channel.h
@@ -26,6 +26,11 @@
* $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;
@@ -36,6 +41,8 @@ struct pcmchan_caps {
struct pcm_channel {
kobj_t methods;
+ pid_t pid;
+ int refcount;
struct pcm_feeder *feeder;
u_int32_t align;
@@ -48,10 +55,12 @@ struct pcm_channel {
int direction;
struct snd_dbuf *bufhard, *bufsoft;
- struct snddev_info *parent;
+ struct snddev_info *parentsnddev;
+ struct pcm_channel *parentchannel;
void *devinfo;
char name[CHN_NAMELEN];
void *lock;
+ SLIST_HEAD(, pcmchan_children) children;
};
#include "channel_if.h"
@@ -86,12 +95,15 @@ int chn_abort(struct pcm_channel *c);
void chn_wrupdate(struct pcm_channel *c);
void chn_rdupdate(struct pcm_channel *c);
+int chn_notify(struct pcm_channel *c, u_int32_t flags);
+
#define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock))
#define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock))
#define CHN_LOCKASSERT(c)
int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist);
+#define PCMDIR_VIRTUAL 2
#define PCMDIR_PLAY 1
#define PCMDIR_REC -1
@@ -114,7 +126,15 @@ int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist);
#define CHN_F_DEAD 0x00020000
#define CHN_F_BADSETTING 0x00040000
-#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD)
+#define CHN_F_VIRTUAL 0x10000000 /* not backed by hardware */
+
+#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | CHN_F_VIRTUAL)
+
+#define CHN_N_RATE 0x00000001
+#define CHN_N_FORMAT 0x00000002
+#define CHN_N_VOLUME 0x00000004
+#define CHN_N_BLOCKSIZE 0x00000008
+#define CHN_N_TRIGGER 0x00000010
/*
* This should be large enough to hold all pcm data between
diff --git a/sys/dev/sound/pcm/channel_if.m b/sys/dev/sound/pcm/channel_if.m
index c5675da..9d8e289 100644
--- a/sys/dev/sound/pcm/channel_if.m
+++ b/sys/dev/sound/pcm/channel_if.m
@@ -57,6 +57,18 @@ CODE {
return 1;
}
+ static u_int32_t
+ channel_nogetptr(kobj_t obj, void *data)
+ {
+ return 0;
+ }
+
+ static int
+ channel_nonotify(kobj_t obj, void *data, u_int32_t changed)
+ {
+ return 0;
+ }
+
};
METHOD void* init {
@@ -115,10 +127,15 @@ METHOD int trigger {
METHOD u_int32_t getptr {
kobj_t obj;
void *data;
-};
+} DEFAULT channel_nogetptr;
METHOD struct pcmchan_caps* getcaps {
kobj_t obj;
void *data;
};
+METHOD int notify {
+ kobj_t obj;
+ void *data;
+ u_int32_t changed;
+} DEFAULT channel_nonotify;
diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c
index 0954442..c31d955 100644
--- a/sys/dev/sound/pcm/dsp.c
+++ b/sys/dev/sound/pcm/dsp.c
@@ -33,22 +33,6 @@
#define OLDPCM_IOCTL
-static int getchns(struct snddev_info *d, int chan, struct pcm_channel **rdch, struct pcm_channel **wrch);
-
-static struct pcm_channel *
-allocchn(struct snddev_info *d, int direction)
-{
- struct pcm_channel *chns = (direction == PCMDIR_PLAY)? d->play : d->rec;
- int i, cnt = (direction == PCMDIR_PLAY)? d->playcount : d->reccount;
- for (i = 0; i < cnt; i++) {
- if (!(chns[i].flags & (CHN_F_BUSY | CHN_F_DEAD))) {
- chns[i].flags |= CHN_F_BUSY;
- return &chns[i];
- }
- }
- return NULL;
-}
-
static int
getchns(struct snddev_info *d, int chan, struct pcm_channel **rdch, struct pcm_channel **wrch)
{
@@ -75,35 +59,54 @@ setchns(struct snddev_info *d, int chan)
}
int
-dsp_open(struct snddev_info *d, int chan, int oflags, int devtype)
+dsp_open(struct snddev_info *d, int chan, int oflags, int devtype, pid_t pid)
{
struct pcm_channel *rdch, *wrch;
u_int32_t fmt;
- if (chan >= d->chancount) return ENODEV;
- if ((d->flags & SD_F_SIMPLEX) && (d->ref[chan] > 0)) return EBUSY;
+ if (chan >= d->chancount)
+ return ENODEV;
+ if ((d->flags & SD_F_SIMPLEX) && (d->arec[chan] || d->aplay[chan]))
+ return EBUSY;
rdch = d->arec[chan];
wrch = d->aplay[chan];
+
if (oflags & FREAD) {
if (rdch == NULL) {
- rdch = allocchn(d, PCMDIR_REC);
- if (!rdch) return EBUSY;
- } else return EBUSY;
+ rdch = pcm_chnalloc(d, PCMDIR_REC);
+ if (!rdch)
+ return EBUSY;
+ rdch->pid = pid;
+ } else
+ return EBUSY;
}
+
if (oflags & FWRITE) {
if (wrch == NULL) {
- wrch = allocchn(d, PCMDIR_PLAY);
+ wrch = pcm_chnalloc(d, PCMDIR_PLAY);
if (!wrch) {
if (rdch && (oflags & FREAD))
- rdch->flags &= ~CHN_F_BUSY;
+ pcm_chnfree(rdch);
return EBUSY;
}
- } else return EBUSY;
+ wrch->pid = pid;
+ } else
+ return EBUSY;
+ }
+
+ if (rdch) {
+ CHN_LOCK(rdch);
+ pcm_chnref(rdch, 1);
+ }
+ if (wrch) {
+ CHN_LOCK(wrch);
+ pcm_chnref(wrch, 1);
}
+
d->aplay[chan] = wrch;
d->arec[chan] = rdch;
- d->ref[chan]++;
+
switch (devtype) {
case SND_DEV_DSP16:
fmt = AFMT_S16_LE;
@@ -124,10 +127,6 @@ dsp_open(struct snddev_info *d, int chan, int oflags, int devtype)
default:
return ENXIO;
}
- if (rdch)
- CHN_LOCK(rdch);
- if (wrch)
- CHN_LOCK(wrch);
if (rdch && (oflags & FREAD)) {
chn_reset(rdch, fmt);
@@ -149,28 +148,38 @@ dsp_close(struct snddev_info *d, int chan, int devtype)
{
struct pcm_channel *rdch, *wrch;
- d->ref[chan]--;
- if (d->ref[chan]) return 0;
- d->flags &= ~SD_F_TRANSIENT;
rdch = d->arec[chan];
wrch = d->aplay[chan];
+ if (rdch && pcm_chnref(rdch, -1))
+ return 0;
+ if (wrch && pcm_chnref(wrch, -1))
+ return 0;
+
+ d->aplay[chan] = NULL;
+ d->arec[chan] = NULL;
+
+ d->flags &= ~SD_F_TRANSIENT;
+
if (rdch) {
CHN_LOCK(rdch);
chn_abort(rdch);
- rdch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD);
+ rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD);
chn_reset(rdch, 0);
+ rdch->pid = -1;
+ pcm_chnfree(rdch);
CHN_UNLOCK(rdch);
}
if (wrch) {
CHN_LOCK(wrch);
chn_flush(wrch);
- wrch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD);
+ wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD);
chn_reset(wrch, 0);
+ wrch->pid = -1;
+ pcm_chnfree(wrch);
CHN_UNLOCK(wrch);
}
- d->aplay[chan] = NULL;
- d->arec[chan] = NULL;
+
return 0;
}
diff --git a/sys/dev/sound/pcm/dsp.h b/sys/dev/sound/pcm/dsp.h
index 89d0624..8fa1a9e 100644
--- a/sys/dev/sound/pcm/dsp.h
+++ b/sys/dev/sound/pcm/dsp.h
@@ -26,7 +26,7 @@
* $FreeBSD$
*/
-int dsp_open(struct snddev_info *d, int chan, int oflags, int devtype);
+int dsp_open(struct snddev_info *d, int chan, int oflags, int devtype, pid_t pid);
int dsp_close(struct snddev_info *d, int chan, int devtype);
int dsp_read(struct snddev_info *d, int chan, struct uio *buf, int flag);
int dsp_write(struct snddev_info *d, int chan, struct uio *buf, int flag);
diff --git a/sys/dev/sound/pcm/fake.c b/sys/dev/sound/pcm/fake.c
index 9001653..aeb47ef 100644
--- a/sys/dev/sound/pcm/fake.c
+++ b/sys/dev/sound/pcm/fake.c
@@ -119,7 +119,7 @@ fkchan_setup(device_t dev)
c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK);
c->methods = kobj_create(&fkchan_class, M_DEVBUF, M_WAITOK);
- c->parent = d;
+ c->parentsnddev = d;
snprintf(c->name, CHN_NAMELEN, "%s:fake", device_get_nameunit(dev));
return c;
diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c
index a3039e3..7fb6951 100644
--- a/sys/dev/sound/pcm/sound.c
+++ b/sys/dev/sound/pcm/sound.c
@@ -28,12 +28,16 @@
*/
#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pcm/vchan.h>
#include <sys/sysctl.h>
+#include <sys/sbuf.h>
-static dev_t status_dev = 0;
-static int status_isopen = 0;
-static int status_init(char *buf, int size);
-static int status_read(struct uio *buf);
+#include "feeder_if.h"
+
+#undef SNDSTAT_VERBOSE
+
+static dev_t status_dev = 0;
+static int do_status(int action, struct uio *buf);
static d_open_t sndopen;
static d_close_t sndclose;
@@ -88,7 +92,7 @@ nomenclature:
static devclass_t pcm_devclass;
#ifdef USING_DEVFS
-int snd_unit;
+static int snd_unit;
TUNABLE_INT_DECL("hw.snd.unit", 0, snd_unit);
#endif
@@ -164,6 +168,36 @@ snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand
return bus_setup_intr(dev, res, flags, hand, param, cookiep);
}
+struct pcm_channel *
+pcm_chnalloc(struct snddev_info *d, int direction)
+{
+ struct pcm_channel *c;
+ struct snddev_channel *sce;
+
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) {
+ c->flags |= CHN_F_BUSY;
+ return c;
+ }
+ }
+ return NULL;
+}
+
+int
+pcm_chnfree(struct pcm_channel *c)
+{
+ c->flags &= ~CHN_F_BUSY;
+ return 0;
+}
+
+int
+pcm_chnref(struct pcm_channel *c, int ref)
+{
+ c->refcount += ref;
+ return c->refcount;
+}
+
#ifdef USING_DEVFS
static void
pcm_makelinks(void *dummy)
@@ -171,6 +205,7 @@ pcm_makelinks(void *dummy)
int unit;
dev_t pdev;
static dev_t dsp = 0, dspW = 0, audio = 0, mixer = 0;
+ struct snddev_info *d;
if (pcm_devclass == NULL || devfs_present == 0)
return;
@@ -194,7 +229,8 @@ pcm_makelinks(void *dummy)
unit = snd_unit;
if (unit < 0 || unit > devclass_get_maxunit(pcm_devclass))
return;
- if (devclass_get_softc(pcm_devclass, unit) == NULL)
+ d = devclass_get_softc(pcm_devclass, unit);
+ if (d == NULL || d->chancount == 0)
return;
pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP, 0));
@@ -224,32 +260,86 @@ SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW,
0, sizeof(int), sysctl_hw_sndunit, "I", "");
#endif
-int
-pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
+struct pcm_channel *
+pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo)
{
- int unit = device_get_unit(dev), idx;
- struct snddev_info *d = device_get_softc(dev);
- struct pcm_channel *chns, *ch;
+ struct pcm_channel *ch;
char *dirs;
- int err;
-
- dirs = ((dir == PCMDIR_PLAY)? "play" : "record");
- chns = ((dir == PCMDIR_PLAY)? d->play : d->rec);
- idx = ((dir == PCMDIR_PLAY)? d->playcount++ : d->reccount++);
+ int err;
- if (chns == NULL) {
- device_printf(dev, "bad channel add (%s:%d)\n", dirs, idx);
- return 1;
+ switch(dir) {
+ case PCMDIR_PLAY:
+ dirs = "play";
+ break;
+ case PCMDIR_REC:
+ dirs = "record";
+ break;
+ case PCMDIR_VIRTUAL:
+ dirs = "virtual";
+ dir = PCMDIR_PLAY;
+ break;
+ default:
+ return NULL;
}
- ch = &chns[idx];
+
+ ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
+ if (!ch)
+ return NULL;
+
ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK);
- ch->parent = d;
- snprintf(ch->name, 32, "%s:%s:%d", device_get_nameunit(dev), dirs, idx);
+ if (!ch->methods) {
+ free(ch, M_DEVBUF);
+ return NULL;
+ }
+
+ ch->pid = -1;
+ ch->parentsnddev = d;
+ ch->parentchannel = parent;
+ snprintf(ch->name, 32, "%s:%d:%s", device_get_nameunit(d->dev), d->chancount, dirs);
+
err = chn_init(ch, devinfo, dir);
if (err) {
- device_printf(dev, "chn_init() for (%s:%d) failed: err = %d\n", dirs, idx, err);
- return 1;
+ device_printf(d->dev, "chn_init() for channel %d (%s) failed: err = %d\n", d->chancount, dirs, err);
+ kobj_delete(ch->methods, M_DEVBUF);
+ free(ch, M_DEVBUF);
+ return NULL;
+ }
+
+ return ch;
+}
+
+int
+pcm_chn_destroy(struct pcm_channel *ch)
+{
+ int err;
+
+ err = chn_kill(ch);
+ if (err) {
+ device_printf(ch->parentsnddev->dev, "chn_kill() for %s failed, err = %d\n", ch->name, err);
+ return err;
}
+
+ kobj_delete(ch->methods, M_DEVBUF);
+ free(ch, M_DEVBUF);
+
+ return 0;
+}
+
+int
+pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch)
+{
+ struct snddev_channel *sce;
+ int unit = device_get_unit(d->dev);
+
+ sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO);
+ if (!sce) {
+ free(ch, M_DEVBUF);
+ return ENOMEM;
+ }
+
+ sce->channel = ch;
+ SLIST_INSERT_HEAD(&d->channels, sce, link);
+
make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_DSP, d->chancount),
UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d", unit, d->chancount);
make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_DSP16, d->chancount),
@@ -257,49 +347,79 @@ pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_AUDIO, d->chancount),
UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", unit, d->chancount);
/* XXX SND_DEV_NORESET? */
- d->chancount++;
+
#ifdef USING_DEVFS
- if (d->chancount == 1)
+ if (d->chancount++ == 0)
pcm_makelinks(NULL);
#endif
+
return 0;
}
-static int
-pcm_killchan(device_t dev, int dir)
+int
+pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch)
{
- int unit = device_get_unit(dev), idx;
- struct snddev_info *d = device_get_softc(dev);
- struct pcm_channel *chns, *ch;
- char *dirs;
+ struct snddev_channel *sce;
+ int unit = device_get_unit(d->dev);
dev_t pdev;
- dirs = ((dir == PCMDIR_PLAY)? "play" : "record");
- chns = ((dir == PCMDIR_PLAY)? d->play : d->rec);
- idx = ((dir == PCMDIR_PLAY)? --d->playcount : --d->reccount);
-
- if (chns == NULL || idx < 0) {
- device_printf(dev, "bad channel kill (%s:%d)\n", dirs, idx);
- return 1;
- }
- ch = &chns[idx];
- if (chn_kill(ch)) {
- device_printf(dev, "chn_kill() for (%s:%d) failed\n", dirs, idx);
- return 1;
+ SLIST_FOREACH(sce, &d->channels, link) {
+ if (sce->channel == ch)
+ goto gotit;
}
- kobj_delete(ch->methods, M_DEVBUF);
- ch->methods = NULL;
+ return EINVAL;
+gotit:
d->chancount--;
+ SLIST_REMOVE(&d->channels, sce, snddev_channel, link);
+ free(sce, M_DEVBUF);
+
+#ifdef USING_DEVFS
+ if (d->chancount == 0)
+ pcm_makelinks(NULL);
+#endif
pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP, d->chancount));
destroy_dev(pdev);
pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP16, d->chancount));
destroy_dev(pdev);
pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_AUDIO, d->chancount));
destroy_dev(pdev);
+
return 0;
}
int
+pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
+{
+ struct snddev_info *d = device_get_softc(dev);
+ struct pcm_channel *ch;
+ int err;
+
+ ch = pcm_chn_create(d, NULL, cls, dir, devinfo);
+ if (!ch) {
+ device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo);
+ return ENODEV;
+ }
+ err = pcm_chn_add(d, ch);
+ if (err) {
+ device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err);
+ pcm_chn_destroy(ch);
+ }
+
+ return err;
+}
+
+static int
+pcm_killchan(device_t dev)
+{
+ struct snddev_info *d = device_get_softc(dev);
+ struct snddev_channel *sce;
+
+ sce = SLIST_FIRST(&d->channels);
+
+ return pcm_chn_remove(d, sce->channel);
+}
+
+int
pcm_setstatus(device_t dev, char *str)
{
struct snddev_info *d = device_get_softc(dev);
@@ -340,44 +460,27 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
status_dev = make_dev(&snd_cdevsw, PCMMKMINOR(0, SND_DEV_STATUS, 0),
UID_ROOT, GID_WHEEL, 0444, "sndstat");
}
+
make_dev(&snd_cdevsw, PCMMKMINOR(unit, SND_DEV_CTL, 0),
UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit);
+
d->dev = dev;
d->devinfo = devinfo;
- d->chancount = d->playcount = d->reccount = 0;
+ d->chancount = 0;
d->maxchans = numplay + numrec;
- sz = (numplay + numrec) * sizeof(struct pcm_channel *);
+ sz = d->maxchans * sizeof(struct pcm_channel *);
if (sz > 0) {
- d->aplay = (struct pcm_channel **)malloc(sz, M_DEVBUF, M_NOWAIT);
- if (!d->aplay) goto no;
- bzero(d->aplay, sz);
-
- d->arec = (struct pcm_channel **)malloc(sz, M_DEVBUF, M_NOWAIT);
- if (!d->arec) goto no;
- bzero(d->arec, sz);
-
- sz = (numplay + numrec) * sizeof(int);
- d->ref = (int *)malloc(sz, M_DEVBUF, M_NOWAIT);
- if (!d->ref) goto no;
- bzero(d->ref, sz);
- }
+ d->aplay = (struct pcm_channel **)malloc(sz, M_DEVBUF, M_WAITOK | M_ZERO);
+ d->arec = (struct pcm_channel **)malloc(sz, M_DEVBUF, M_WAITOK | M_ZERO);
+ if (!d->arec || !d->aplay) goto no;
- if (numplay > 0) {
- d->play = (struct pcm_channel *)malloc(numplay * sizeof(struct pcm_channel),
- M_DEVBUF, M_NOWAIT);
- if (!d->play) goto no;
- bzero(d->play, numplay * sizeof(struct pcm_channel));
- } else
- d->play = NULL;
-
- if (numrec > 0) {
- d->rec = (struct pcm_channel *)malloc(numrec * sizeof(struct pcm_channel),
- M_DEVBUF, M_NOWAIT);
- if (!d->rec) goto no;
- bzero(d->rec, numrec * sizeof(struct pcm_channel));
- } else
- d->rec = NULL;
+ if (numplay == 0 || numrec == 0)
+ d->flags |= SD_F_SIMPLEX;
+
+ d->fakechan = fkchan_setup(dev);
+ chn_init(d->fakechan, NULL, 0);
+ }
#ifdef SND_DYNSYSCTL
sysctl_ctx_init(&d->sysctl_tree);
@@ -389,36 +492,29 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
goto no;
}
#endif
-
- if (numplay == 0 || numrec == 0)
- d->flags |= SD_F_SIMPLEX;
-
- d->fakechan = fkchan_setup(dev);
- chn_init(d->fakechan, NULL, 0);
-
+#if 0
+ vchan_initsys(d);
+#endif
return 0;
no:
if (d->aplay) free(d->aplay, M_DEVBUF);
- if (d->play) free(d->play, M_DEVBUF);
if (d->arec) free(d->arec, M_DEVBUF);
- if (d->rec) free(d->rec, M_DEVBUF);
- if (d->ref) free(d->ref, M_DEVBUF);
return ENXIO;
}
int
pcm_unregister(device_t dev)
{
- int r, i, unit = device_get_unit(dev);
+ int unit = device_get_unit(dev);
struct snddev_info *d = device_get_softc(dev);
+ struct snddev_channel *sce;
dev_t pdev;
- r = 0;
- for (i = 0; i < d->chancount; i++)
- if (d->ref[i]) r = EBUSY;
- if (r) {
- device_printf(dev, "unregister: channel busy");
- return r;
+ SLIST_FOREACH(sce, &d->channels, link) {
+ if (sce->channel->refcount > 0) {
+ device_printf(dev, "unregister: channel busy");
+ return EBUSY;
+ }
}
if (mixer_isbusy(d->mixer)) {
device_printf(dev, "unregister: mixer busy");
@@ -434,23 +530,15 @@ pcm_unregister(device_t dev)
destroy_dev(pdev);
mixer_uninit(dev);
- while (d->playcount > 0)
- pcm_killchan(dev, PCMDIR_PLAY);
- while (d->reccount > 0)
- pcm_killchan(dev, PCMDIR_REC);
+ while (d->chancount > 0)
+ pcm_killchan(dev);
if (d->aplay) free(d->aplay, M_DEVBUF);
- if (d->play) free(d->play, M_DEVBUF);
if (d->arec) free(d->arec, M_DEVBUF);
- if (d->rec) free(d->rec, M_DEVBUF);
- if (d->ref) free(d->ref, M_DEVBUF);
chn_kill(d->fakechan);
fkchan_kill(d->fakechan);
-#ifdef USING_DEVFS
- pcm_makelinks(NULL);
-#endif
return 0;
}
@@ -506,9 +594,7 @@ sndopen(dev_t i_dev, int flags, int mode, struct proc *p)
switch(dev) {
case SND_DEV_STATUS:
- if (status_isopen) return EBUSY;
- status_isopen = 1;
- return 0;
+ return do_status(0, NULL);
case SND_DEV_CTL:
return d? mixer_busy(d->mixer, 1) : ENXIO;
@@ -517,7 +603,7 @@ sndopen(dev_t i_dev, int flags, int mode, struct proc *p)
case SND_DEV_DSP:
case SND_DEV_DSP16:
case SND_DEV_NORESET:
- return d? dsp_open(d, chan, flags, dev) : ENXIO;
+ return d? dsp_open(d, chan, flags, dev, p->p_pid) : ENXIO;
default:
return ENXIO;
@@ -534,9 +620,7 @@ sndclose(dev_t i_dev, int flags, int mode, struct proc *p)
switch(dev) { /* only those for which close makes sense */
case SND_DEV_STATUS:
- if (!status_isopen) return EBADF;
- status_isopen = 0;
- return 0;
+ return do_status(1, NULL);
case SND_DEV_CTL:
return d? mixer_busy(d->mixer, 0) : ENXIO;
@@ -560,7 +644,7 @@ sndread(dev_t i_dev, struct uio *buf, int flag)
switch(dev) {
case SND_DEV_STATUS:
- return status_isopen? status_read(buf) : EBADF;
+ return do_status(2, buf);
case SND_DEV_AUDIO:
case SND_DEV_DSP:
@@ -675,52 +759,116 @@ sndmmap(dev_t i_dev, vm_offset_t offset, int nprot)
}
static int
-status_init(char *buf, int size)
+status_init(struct sbuf *s)
{
- int i;
- device_t dev;
- struct snddev_info *d;
+ int i, pc, rc, vc;
+ device_t dev;
+ struct snddev_info *d;
+ struct snddev_channel *sce;
+ struct pcm_channel *c;
+ struct pcm_feeder *f;
- snprintf(buf, size, "FreeBSD Audio Driver (newpcm) %s %s\n"
- "Installed devices:\n", __DATE__, __TIME__);
+ sbuf_printf(s, "FreeBSD Audio Driver (newpcm) %s %s\nInstalled devices:\n",
+ __DATE__, __TIME__);
for (i = 0; i <= devclass_get_maxunit(pcm_devclass); i++) {
d = devclass_get_softc(pcm_devclass, i);
- if (!d) continue;
+ if (!d)
+ continue;
dev = devclass_get_device(pcm_devclass, i);
- if (1) {
- snprintf(buf + strlen(buf), size - strlen(buf),
- "pcm%d: <%s> %s",
- i, device_get_desc(dev), d->status);
- if (d->chancount > 0)
- snprintf(buf + strlen(buf), size - strlen(buf),
- " (%dp/%dr channels%s)\n",
- d->playcount, d->reccount,
- (!(d->flags & SD_F_SIMPLEX))? " duplex" : "");
- else
- snprintf(buf + strlen(buf), size - strlen(buf),
- " (mixer only)\n");
- }
+ sbuf_printf(s, "pcm%d: <%s> %s", i, device_get_desc(dev), d->status);
+ if (d->chancount > 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)\n", pc, rc, vc,
+ (d->flags & SD_F_SIMPLEX)? "" : " duplex",
+#ifdef USING_DEVFS
+ (i == snd_unit)? " default" : ""
+#else
+ ""
+#endif
+ );
+#ifdef SNDSTAT_VERBOSE
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ sbuf_printf(s, "\t%s[%s]: speed %d, format %08x, flags %08x",
+ c->parentchannel? c->parentchannel->name : "",
+ c->name, c->speed, c->format, c->flags);
+ if (c->pid != -1)
+ sbuf_printf(s, ", pid %d", c->pid);
+ sbuf_printf(s, "\n\t");
+ f = c->feeder;
+ while (f) {
+ sbuf_printf(s, "%s", f->class->name);
+ if (f->desc->type == FEEDER_FMT)
+ sbuf_printf(s, "(%08x <- %08x)", f->desc->out, f->desc->in);
+ if (f->desc->type == FEEDER_RATE)
+ sbuf_printf(s, "(%d <- %d)", FEEDER_GET(f, FEEDRATE_DST), FEEDER_GET(f, FEEDRATE_SRC));
+ if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER)
+ sbuf_printf(s, "(%08x)", f->desc->out);
+ if (f->source)
+ sbuf_printf(s, " <- ");
+ f = f->source;
+ }
+ sbuf_printf(s, "\n");
+ }
+#endif
+ } else
+ sbuf_printf(s, " (mixer only)\n");
}
- return strlen(buf);
+ sbuf_finish(s);
+ return sbuf_len(s);
}
static int
-status_read(struct uio *buf)
+do_status(int action, struct uio *buf)
{
- static char status_buf[4096];
- static int bufptr = 0, buflen = 0;
- int l;
-
- if (status_isopen == 1) {
- status_isopen++;
+ static struct sbuf s;
+ static int bufptr = 0;
+ static int status_open = 0;
+ int l, err;
+
+ switch(action) {
+ case 0: /* open */
+ if (status_open)
+ return EBUSY;
+ if (sbuf_new(&s, NULL, 4096, 0))
+ return ENXIO;
bufptr = 0;
- buflen = status_init(status_buf, sizeof status_buf);
- }
+ err = (status_init(&s) > 0)? 0 : ENOMEM;
+ if (!err)
+ status_open = 1;
+ return err;
+
+ case 1: /* close */
+ if (!status_open)
+ return EBADF;
+ sbuf_delete(&s);
+ status_open = 0;
+ return 0;
+
+ case 2:
+ if (!status_open)
+ return EBADF;
+ l = min(buf->uio_resid, sbuf_len(&s) - bufptr);
+ err = (l > 0)? uiomove(sbuf_data(&s) + bufptr, l, buf) : 0;
+ bufptr += l;
+ return err;
+
+ case 3:
+ return status_open;
+ }
- l = min(buf->uio_resid, buflen - bufptr);
- bufptr += l;
- return (l > 0)? uiomove(status_buf + bufptr - l, l, buf) : 0;
+ return EBADF;
}
static int
@@ -731,7 +879,7 @@ sndpcm_modevent(module_t mod, int type, void *data)
case MOD_LOAD:
break;
case MOD_UNLOAD:
- if (status_isopen)
+ if (do_status(3, NULL))
return EBUSY;
if (status_dev)
destroy_dev(status_dev);
diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h
index 007285b..df6d729 100644
--- a/sys/dev/sound/pcm/sound.h
+++ b/sys/dev/sound/pcm/sound.h
@@ -98,12 +98,17 @@ struct snd_mixer;
#include <dev/sound/pcm/mixer.h>
#include <dev/sound/pcm/dsp.h>
+struct snddev_channel {
+ SLIST_ENTRY(snddev_channel) link;
+ struct pcm_channel *channel;
+};
+
#define SND_STATUSLEN 64
/* descriptor of audio device */
struct snddev_info {
- struct pcm_channel *play, *rec, **aplay, **arec, *fakechan;
- int *ref;
- unsigned playcount, reccount, chancount, maxchans;
+ SLIST_HEAD(, snddev_channel) channels;
+ struct pcm_channel **aplay, **arec, *fakechan;
+ unsigned chancount, maxchans;
struct snd_mixer *mixer;
unsigned flags;
void *devinfo;
@@ -193,6 +198,15 @@ int fkchan_kill(struct pcm_channel *c);
SYSCTL_DECL(_hw_snd);
+struct pcm_channel *pcm_chnalloc(struct snddev_info *d, int direction);
+int pcm_chnfree(struct pcm_channel *c);
+int pcm_chnref(struct pcm_channel *c, int ref);
+
+struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo);
+int pcm_chn_destroy(struct pcm_channel *ch);
+int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch);
+int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch);
+
int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo);
int pcm_register(device_t dev, void *devinfo, int numplay, int numrec);
int pcm_unregister(device_t dev);
diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c
new file mode 100644
index 0000000..c96f0f3
--- /dev/null
+++ b/sys/dev/sound/pcm/vchan.c
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pcm/vchan.h>
+#include "feeder_if.h"
+
+struct vchinfo {
+ u_int32_t spd, fmt, blksz, run;
+ struct pcm_channel *channel, *parent;
+ struct pcmchan_caps caps;
+};
+
+static u_int32_t vchan_fmt[] = {
+ AFMT_S16_LE,
+ AFMT_STEREO | AFMT_S16_LE,
+ 0
+};
+
+
+static int
+vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count)
+{
+ /*
+ * to is the output buffer, tmp is the input buffer
+ * count is the number of 16bit samples to mix
+ */
+ int i;
+ int x;
+
+ for(i = 0; i < count; i++) {
+ x = to[i];
+ x += tmp[i];
+ if (x < -32768) {
+ /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */
+ x = -32768;
+ }
+ if (x > 32767) {
+ /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */
+ x = 32767;
+ }
+ to[i] = x & 0x0000ffff;
+ }
+ return 0;
+}
+
+static int
+feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source)
+{
+ /* we're going to abuse things a bit */
+ struct snd_dbuf *src = source;
+ struct pcmchan_children *cce;
+ struct pcm_channel *ch;
+ int16_t *tmp, *dst;
+ unsigned int cnt;
+
+ KASSERT(sndbuf_getsize(src) >= count, ("bad bufsize"));
+ count &= ~1;
+ bzero(b, count);
+
+ /*
+ * we are going to use our source as a temporary buffer since it's
+ * got no other purpose. we obtain our data by traversing the channel
+ * list of children and calling vchan_mix_* to mix count bytes from each
+ * into our destination buffer, b
+ */
+ dst = (int16_t *)b;
+ tmp = (int16_t *)sndbuf_getbuf(src);
+ bzero(tmp, count);
+ SLIST_FOREACH(cce, &c->children, link) {
+ ch = cce->channel;
+ cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft);
+ vchan_mix_s16(dst, tmp, cnt / 2);
+ }
+
+ return count;
+}
+
+static struct pcm_feederdesc feeder_vchan_s16_desc[] = {
+ {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0},
+ {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0},
+ {0},
+};
+static kobj_method_t feeder_vchan_s16_methods[] = {
+ KOBJMETHOD(feeder_feed, feed_vchan_s16),
+ { 0, 0 }
+};
+FEEDER_DECLARE(feeder_vchan_s16, 2, NULL);
+
+/************************************************************/
+
+static void *
+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"));
+ 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;
+
+ c->flags |= CHN_F_VIRTUAL;
+
+ return ch;
+}
+
+static int
+vchan_free(kobj_t obj, void *data)
+{
+ return 0;
+}
+
+static int
+vchan_setformat(kobj_t obj, void *data, u_int32_t format)
+{
+ struct vchinfo *ch = data;
+ struct pcm_channel *parent = ch->parent;
+
+ ch->fmt = format;
+ chn_notify(parent, CHN_N_FORMAT);
+ return 0;
+}
+
+static int
+vchan_setspeed(kobj_t obj, void *data, u_int32_t speed)
+{
+ struct vchinfo *ch = data;
+ struct pcm_channel *parent = ch->parent;
+
+ ch->spd = speed;
+ chn_notify(parent, CHN_N_RATE);
+ return speed;
+}
+
+static int
+vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
+{
+ struct vchinfo *ch = data;
+ struct pcm_channel *parent = ch->parent;
+
+ ch->blksz = blocksize;
+ chn_notify(parent, CHN_N_BLOCKSIZE);
+ return blocksize;
+}
+
+static int
+vchan_trigger(kobj_t obj, void *data, int go)
+{
+ struct vchinfo *ch = data;
+ struct pcm_channel *parent = ch->parent;
+
+ if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
+ return 0;
+
+ ch->run = (go == PCMTRIG_START)? 1 : 0;
+ chn_notify(parent, CHN_N_TRIGGER);
+
+ return 0;
+}
+
+static struct pcmchan_caps *
+vchan_getcaps(kobj_t obj, void *data)
+{
+ struct vchinfo *ch = data;
+
+ ch->caps.minspeed = sndbuf_getspd(ch->parent->bufhard);
+ ch->caps.maxspeed = ch->caps.minspeed;
+ ch->caps.fmtlist = vchan_fmt;
+ ch->caps.caps = 0;
+
+ return &ch->caps;
+}
+
+static kobj_method_t vchan_methods[] = {
+ KOBJMETHOD(channel_init, vchan_init),
+ 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 }
+};
+CHANNEL_DECLARE(vchan);
+
+/* virtual channel interface */
+
+int
+vchan_create(struct pcm_channel *parent)
+{
+ struct snddev_info *d = parent->parentsnddev;
+ struct pcmchan_children *pce;
+ struct pcm_channel *child;
+ int err, first;
+
+ if (!(parent->flags & CHN_F_BUSY))
+ return EBUSY;
+
+ pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO);
+ if (!pce)
+ return ENOMEM;
+
+ /* create a new playback channel */
+ child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent);
+ if (!child) {
+ free(pce, M_DEVBUF);
+ return ENODEV;
+ }
+
+ first = SLIST_EMPTY(&parent->children);
+ /* add us to our parent channel's children */
+ pce->channel = child;
+ SLIST_INSERT_HEAD(&parent->children, pce, link);
+
+ /* add us to our grandparent's channel list */
+ err = pcm_chn_add(d, child);
+ if (err) {
+ pcm_chn_destroy(child);
+ free(pce, M_DEVBUF);
+ }
+
+ /* XXX gross ugly hack, kill murder death */
+ if (first && !err) {
+ err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE);
+ if (err)
+ printf("chn_reset: %d\n", err);
+ err = chn_setspeed(parent, 44100);
+ if (err)
+ printf("chn_setspeed: %d\n", err);
+ }
+
+ return err;
+}
+
+int
+vchan_destroy(struct pcm_channel *c)
+{
+ struct pcm_channel *parent = c->parentchannel;
+ struct snddev_info *d = parent->parentsnddev;
+ struct pcmchan_children *pce;
+ int err;
+
+ if (!(parent->flags & CHN_F_BUSY))
+ return EBUSY;
+ if (SLIST_EMPTY(&parent->children))
+ return EINVAL;
+
+ /* remove us from our grantparent's channel list */
+ err = pcm_chn_remove(d, c);
+ if (err)
+ return err;
+
+ /* remove us from our parent's children list */
+ SLIST_FOREACH(pce, &parent->children, link) {
+ if (pce->channel == c)
+ goto gotch;
+ }
+ return EINVAL;
+gotch:
+ SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
+ free(pce, M_DEVBUF);
+
+ if (SLIST_EMPTY(&parent->children))
+ parent->flags &= ~CHN_F_BUSY;
+ /* destroy ourselves */
+ err = pcm_chn_destroy(c);
+
+ return err;
+}
+
+#ifdef SND_DYNSYSCTL
+static int
+sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
+{
+ struct snddev_info *d;
+ struct snddev_channel *sce;
+ struct pcm_channel *c;
+ int err, newcnt, cnt;
+
+ d = oidp->oid_arg1;
+
+ cnt = 0;
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL))
+ cnt++;
+ }
+ newcnt = cnt;
+
+ err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req);
+ if (err == 0 && req->newptr != NULL) {
+ if (newcnt > cnt) {
+ /* add new vchans - find a parent channel first */
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ /* not a candidate if not a play channel */
+ if (c->direction != PCMDIR_PLAY)
+ goto addskip;
+ /* not a candidate if a virtual channel */
+ if (c->flags & CHN_F_VIRTUAL)
+ goto addskip;
+ /* not a candidate if it's in use */
+ if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children)))
+ goto addskip;
+ /*
+ * if we get here we're a nonvirtual play channel, and either
+ * 1) not busy
+ * 2) busy with children, not directly open
+ *
+ * thus we can add children
+ */
+ goto addok;
+addskip:
+ }
+ return EBUSY;
+addok:
+ c->flags |= CHN_F_BUSY;
+ while (err == 0 && newcnt > cnt) {
+ err = vchan_create(c);
+ if (err == 0)
+ cnt++;
+ }
+ } else if (newcnt < cnt) {
+ while (err == 0 && newcnt < cnt) {
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL)
+ goto remok;
+ }
+ return EINVAL;
+remok:
+ err = vchan_destroy(c);
+ if (err == 0)
+ cnt--;
+ }
+ }
+ }
+
+ return err;
+}
+#endif
+
+int
+vchan_initsys(struct snddev_info *d)
+{
+#ifdef SND_DYNSYSCTL
+ SYSCTL_ADD_PROC(&d->sysctl_tree, SYSCTL_CHILDREN(d->sysctl_tree_top),
+ OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
+ sysctl_hw_snd_vchans, "I", "")
+#endif
+ return 0;
+}
+
+
diff --git a/sys/dev/sound/pcm/vchan.h b/sys/dev/sound/pcm/vchan.h
new file mode 100644
index 0000000..7e057ce
--- /dev/null
+++ b/sys/dev/sound/pcm/vchan.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+int vchan_create(struct pcm_channel *parent);
+int vchan_destroy(struct pcm_channel *c);
+int vchan_initsys(struct snddev_info *d);
+
+
OpenPOWER on IntegriCloud