diff options
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 135 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.h | 24 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel_if.m | 19 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 83 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.h | 2 | ||||
-rw-r--r-- | sys/dev/sound/pcm/fake.c | 2 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.c | 438 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.h | 20 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.c | 383 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.h | 33 |
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); + + |