diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index ec27a3887940..c984bc52d46b 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -1,1058 +1,1099 @@ /*- * Copyright (c) 1999 Cameron Grant * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) * 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. */ #include #include #include #include #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); devclass_t pcm_devclass; int pcm_veto_load = 1; #ifdef USING_DEVFS int snd_unit = 0; TUNABLE_INT("hw.snd.unit", &snd_unit); #endif int snd_maxautovchans = 0; TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); struct sysctl_ctx_list * snd_sysctl_tree(device_t dev) { struct snddev_info *d = device_get_softc(dev); return &d->sysctl_tree; } struct sysctl_oid * snd_sysctl_tree_top(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->sysctl_tree_top; } void * snd_mtxcreate(const char *desc, const char *type) { #ifdef USING_MUTEX struct mtx *m; m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); if (m == NULL) return NULL; mtx_init(m, desc, type, MTX_DEF); return m; #else return (void *)0xcafebabe; #endif } void snd_mtxfree(void *m) { #ifdef USING_MUTEX struct mtx *mtx = m; /* mtx_assert(mtx, MA_OWNED); */ mtx_destroy(mtx); free(mtx, M_DEVBUF); #endif } void snd_mtxassert(void *m) { #ifdef USING_MUTEX #ifdef INVARIANTS struct mtx *mtx = m; mtx_assert(mtx, MA_OWNED); #endif #endif } /* void snd_mtxlock(void *m) { #ifdef USING_MUTEX struct mtx *mtx = m; mtx_lock(mtx); #endif } void snd_mtxunlock(void *m) { #ifdef USING_MUTEX struct mtx *mtx = m; mtx_unlock(mtx); #endif } */ int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) { #ifdef USING_MUTEX flags &= INTR_MPSAFE; flags |= INTR_TYPE_AV; #else flags = INTR_TYPE_AV; #endif return bus_setup_intr(dev, res, flags, hand, param, cookiep); } #ifndef PCM_DEBUG_MTX void pcm_lock(struct snddev_info *d) { snd_mtxlock(d->lock); } void pcm_unlock(struct snddev_info *d) { snd_mtxunlock(d->lock); } #endif struct pcm_channel * pcm_getfakechan(struct snddev_info *d) { return d->fakechan; } /* return a locked channel */ struct pcm_channel * pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum) { struct pcm_channel *c; struct snddev_channel *sce; int err; /* scan for a free channel */ SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; CHN_LOCK(c); if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) { if (chnum == -1 || c->num == chnum) { c->flags |= CHN_F_BUSY; c->pid = pid; return c; } } CHN_UNLOCK(c); } /* no channel available */ - if (direction == PCMDIR_PLAY) { - if ((d->vchancount > 0) && (d->vchancount < snd_maxautovchans)) { - /* try to create a vchan */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if ((c->flags & CHN_F_HAS_VCHAN) && - !SLIST_EMPTY(&c->children)) { - err = vchan_create(c); - CHN_UNLOCK(c); - if (!err) - return pcm_chnalloc(d, direction, pid, -1); - else - device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); - } else - CHN_UNLOCK(c); - } + if (direction == PCMDIR_PLAY && d->vchancount > 0 && + d->vchancount < snd_maxautovchans && + d->devcount <= PCMMAXCHAN) { + /* try to create a vchan */ + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if ((c->flags & CHN_F_HAS_VCHAN) && + !SLIST_EMPTY(&c->children)) { + err = vchan_create(c); + CHN_UNLOCK(c); + if (!err) + return pcm_chnalloc(d, direction, pid, -1); + else + device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); + } else + CHN_UNLOCK(c); } } return NULL; } /* release a locked channel and unlock it */ int pcm_chnrelease(struct pcm_channel *c) { CHN_LOCKASSERT(c); c->flags &= ~CHN_F_BUSY; c->pid = -1; CHN_UNLOCK(c); return 0; } int pcm_chnref(struct pcm_channel *c, int ref) { int r; CHN_LOCKASSERT(c); c->refcount += ref; r = c->refcount; return r; } int pcm_inprog(struct snddev_info *d, int delta) { int r; if (delta == 0) return d->inprog; /* backtrace(); */ pcm_lock(d); d->inprog += delta; r = d->inprog; pcm_unlock(d); return r; } static void pcm_setmaxautovchans(struct snddev_info *d, int num) { struct pcm_channel *c, *ch; struct snddev_channel *sce; int err, done; /* * XXX WOAH... NEED SUPER CLEANUP!!! * Robust, yet confusing. Understanding these will * cause your brain spinning like a Doki Doki Dynamo. */ if (num > 0 && d->vchancount == 0) { SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; CHN_LOCK(c); if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY) && SLIST_EMPTY(&c->children)) { c->flags |= CHN_F_BUSY; err = vchan_create(c); if (err) { c->flags &= ~CHN_F_BUSY; device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); } CHN_UNLOCK(c); return; } CHN_UNLOCK(c); } return; } if (num == 0 && d->vchancount > 0) { /* * XXX Keep retrying... */ for (done = 0; done < 1024; done++) { ch = NULL; SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; CHN_LOCK(c); if (c->direction == PCMDIR_PLAY && !(c->flags & CHN_F_BUSY) && (c->flags & CHN_F_VIRTUAL)) { ch = c; break; } CHN_UNLOCK(c); } if (ch != NULL) { CHN_UNLOCK(ch); snd_mtxlock(d->lock); err = vchan_destroy(ch); if (err) device_printf(d->dev, "vchan_destroy(%s) == %d\n", ch->name, err); snd_mtxunlock(d->lock); } else return; } return; } } #ifdef USING_DEVFS static int sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int error, unit; 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)) return EINVAL; snd_unit = unit; } return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_unit, "I", ""); #endif static int sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int i, v, error; v = snd_maxautovchans; error = sysctl_handle_int(oidp, &v, sizeof(v), req); if (error == 0 && req->newptr != NULL) { - if (v < 0 || v >= SND_MAXVCHANS || pcm_devclass == NULL) + if (v < 0 || v > (PCMMAXCHAN + 1) || pcm_devclass == NULL) return EINVAL; if (v != snd_maxautovchans) { for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!d) continue; if (d->flags & SD_F_AUTOVCHAN) { if (pcm_inprog(d, 1) == 1) pcm_setmaxautovchans(d, v); pcm_inprog(d, -1); } } } snd_maxautovchans = v; } return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", ""); 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 *ch; char *dirs; int direction, err, *pnum; switch(dir) { case PCMDIR_PLAY: dirs = "play"; direction = PCMDIR_PLAY; pnum = &d->playcount; break; case PCMDIR_REC: dirs = "record"; direction = PCMDIR_REC; pnum = &d->reccount; break; case PCMDIR_VIRTUAL: dirs = "virtual"; direction = PCMDIR_PLAY; pnum = &d->vchancount; break; default: return NULL; } ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); if (!ch) return NULL; ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); if (!ch->methods) { free(ch, M_DEVBUF); return NULL; } snd_mtxlock(d->lock); ch->num = (*pnum)++; snd_mtxunlock(d->lock); ch->pid = -1; ch->parentsnddev = d; ch->parentchannel = parent; ch->dev = d->dev; snprintf(ch->name, 32, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num); err = chn_init(ch, devinfo, dir, direction); if (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); (*pnum)--; snd_mtxunlock(d->lock); return NULL; } return ch; } int pcm_chn_destroy(struct pcm_channel *ch) { struct snddev_info *d; int err; d = ch->parentsnddev; err = chn_kill(ch); if (err) { device_printf(d->dev, "chn_kill(%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, *tmp, *after; int device = device_get_unit(d->dev); /* * Note it's confusing nomenclature. * dev_t * device -> pcm_device * unit -> pcm_channel * channel -> snddev_channel * device_t * unit -> pcm_device */ sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO); if (!sce) { return ENOMEM; } snd_mtxlock(d->lock); sce->channel = ch; - sce->chan_num= d->devcount++; if (SLIST_EMPTY(&d->channels)) { SLIST_INSERT_HEAD(&d->channels, sce, link); + sce->chan_num = 0; } else { + sce->chan_num = 0; +retry_search: + SLIST_FOREACH(tmp, &d->channels, link) { + if (tmp == NULL) + continue; + if (sce->chan_num == tmp->chan_num) { + sce->chan_num++; + goto retry_search; + } + } + /* + * 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; + } /* * Micro optimization, channel ordering: * hw,hw,hw,vch,vch,vch,rec */ after = NULL; if (ch->flags & CHN_F_VIRTUAL) { /* virtual channel to the end */ SLIST_FOREACH(tmp, &d->channels, link) { if (tmp->channel->direction == PCMDIR_REC) break; after = tmp; } } else { if (ch->direction == PCMDIR_REC) { SLIST_FOREACH(tmp, &d->channels, link) { after = tmp; } } else { SLIST_FOREACH(tmp, &d->channels, link) { if (tmp->channel->direction == PCMDIR_REC) break; if (!(tmp->channel->flags & CHN_F_VIRTUAL)) after = tmp; } } } if (after == NULL) { SLIST_INSERT_HEAD(&d->channels, sce, link); } else { SLIST_INSERT_AFTER(after, sce, link); } } + d->devcount++; snd_mtxunlock(d->lock); 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); if (ch->direction == PCMDIR_REC) sce->dspr_devt = make_dev(&dsp_cdevsw, PCMMKMINOR(device, SND_DEV_DSPREC, sce->chan_num), UID_ROOT, GID_WHEEL, 0666, "dspr%d.%d", device, sce->chan_num); return 0; } int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch) { struct snddev_channel *sce; #if 0 int ourlock; ourlock = 0; if (!mtx_owned(d->lock)) { snd_mtxlock(d->lock); ourlock = 1; } #endif SLIST_FOREACH(sce, &d->channels, link) { if (sce->channel == ch) goto gotit; } #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 0 if (ourlock) snd_mtxunlock(d->lock); #endif free(sce, M_DEVBUF); 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; } CHN_LOCK(ch); if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) && ch->direction == PCMDIR_PLAY && d->vchancount == 0) { ch->flags |= CHN_F_BUSY; err = vchan_create(ch); if (err) { ch->flags &= ~CHN_F_BUSY; CHN_UNLOCK(ch); device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err); return err; } } CHN_UNLOCK(ch); return err; } 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; error = pcm_chn_remove(d, sce->channel); if (error) return (error); return (pcm_chn_destroy(ch)); } int pcm_setstatus(device_t dev, char *str) { struct snddev_info *d = device_get_softc(dev); snd_mtxlock(d->lock); strncpy(d->status, str, SND_STATUSLEN); snd_mtxunlock(d->lock); return 0; } u_int32_t pcm_getflags(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->flags; } void pcm_setflags(device_t dev, u_int32_t val) { struct snddev_info *d = device_get_softc(dev); d->flags = val; } void * pcm_getdevinfo(device_t dev) { struct snddev_info *d = device_get_softc(dev); return d->devinfo; } unsigned int pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max) { struct snddev_info *d = device_get_softc(dev); int sz, x; sz = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) { x = sz; RANGE(sz, min, max); if (x != sz) device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, min, max, sz); x = min; while (x < sz) x <<= 1; if (x > sz) x >>= 1; if (x != sz) { device_printf(dev, "'buffersize=%d' hint is not a power of 2, using %d\n", sz, x); sz = x; } } else { sz = deflt; } d->bufsz = sz; return sz; } int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { struct snddev_info *d = device_get_softc(dev); if (pcm_veto_load) { device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); return EINVAL; } d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); #if 0 /* * d->flags should be cleared by the allocator of the softc. * We cannot clear this field here because several devices set * this flag before calling pcm_register(). */ d->flags = 0; #endif d->dev = dev; d->devinfo = devinfo; d->devcount = 0; d->reccount = 0; d->playcount = 0; d->vchancount = 0; d->inprog = 0; SLIST_INIT(&d->channels); if (((numplay == 0) || (numrec == 0)) && (numplay != numrec)) d->flags |= SD_F_SIMPLEX; d->fakechan = fkchan_setup(dev); chn_init(d->fakechan, NULL, 0, 0); #ifdef SND_DYNSYSCTL sysctl_ctx_init(&d->sysctl_tree); d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree, SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO, device_get_nameunit(dev), CTLFLAG_RD, 0, ""); if (d->sysctl_tree_top == NULL) { sysctl_ctx_free(&d->sysctl_tree); goto no; } SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, ""); #endif if (numplay > 0) { vchan_initsys(dev); d->flags |= SD_F_AUTOVCHAN; } sndstat_register(dev, d->status, sndstat_prepare_pcm); return 0; no: snd_mtxfree(d->lock); return ENXIO; } int pcm_unregister(device_t dev) { struct snddev_info *d = device_get_softc(dev); struct snddev_channel *sce; struct pcm_channel *ch; if (sndstat_acquire() != 0) { device_printf(dev, "unregister: sndstat busy\n"); return EBUSY; } snd_mtxlock(d->lock); if (d->inprog) { device_printf(dev, "unregister: operation in progress\n"); snd_mtxunlock(d->lock); sndstat_release(); return EBUSY; } SLIST_FOREACH(sce, &d->channels, link) { ch = sce->channel; if (ch->refcount > 0) { device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid); snd_mtxunlock(d->lock); sndstat_release(); return EBUSY; } } if (mixer_uninit(dev)) { device_printf(dev, "unregister: mixer busy\n"); snd_mtxunlock(d->lock); sndstat_release(); return EBUSY; } SLIST_FOREACH(sce, &d->channels, link) { - if (sce->dsp_devt) + if (sce->dsp_devt) { destroy_dev(sce->dsp_devt); - if (sce->dspW_devt) + sce->dsp_devt = NULL; + } + if (sce->dspW_devt) { destroy_dev(sce->dspW_devt); - if (sce->audio_devt) + sce->dspW_devt = NULL; + } + if (sce->audio_devt) { destroy_dev(sce->audio_devt); - if (sce->dspr_devt) + sce->audio_devt = NULL; + } + if (sce->dspr_devt) { destroy_dev(sce->dspr_devt); + sce->dspr_devt = NULL; + } + d->devcount--; } #ifdef SND_DYNSYSCTL d->sysctl_tree_top = NULL; sysctl_ctx_free(&d->sysctl_tree); #endif while (!SLIST_EMPTY(&d->channels)) pcm_killchan(dev); chn_kill(d->fakechan); fkchan_kill(d->fakechan); snd_mtxunlock(d->lock); snd_mtxfree(d->lock); sndstat_unregister(dev); sndstat_release(); return 0; } /************************************************************************/ 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; d = device_get_softc(dev); 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, (d->flags & SD_F_SIMPLEX)? "" : " duplex", #ifdef USING_DEVFS (device_get_unit(dev) == snd_unit)? " default" : "" #else "" #endif ); if (verbose <= 1) { snd_mtxunlock(d->lock); return 0; } SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; KASSERT(c->bufhard != NULL && c->bufsoft != NULL, ("hosed pcm channel setup")); sbuf_printf(s, "\n\t"); /* it would be better to indent child channels */ sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name); sbuf_printf(s, "spd %d", c->speed); if (c->speed != sndbuf_getspd(c->bufhard)) sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard)); sbuf_printf(s, ", fmt 0x%08x", c->format); if (c->format != sndbuf_getfmt(c->bufhard)) sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard)); sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags); if (c->pid != -1) sbuf_printf(s, ", pid %d", c->pid); sbuf_printf(s, "\n\t"); sbuf_printf(s, "interrupts %d, ", c->interrupts); if (c->direction == PCMDIR_REC) sbuf_printf(s, "overruns %d, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft), sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), sndbuf_getblkcnt(c->bufhard), sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), sndbuf_getblkcnt(c->bufsoft)); else sbuf_printf(s, "underruns %d, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", c->xruns, sndbuf_getready(c->bufsoft), sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), sndbuf_getblkcnt(c->bufhard), sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), sndbuf_getblkcnt(c->bufsoft)); sbuf_printf(s, "\n\t"); sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland"); sbuf_printf(s, " -> "); f = c->feeder; while (f->source != NULL) f = f->source; while (f != NULL) { sbuf_printf(s, "%s", f->class->name); if (f->desc->type == FEEDER_FMT) sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); if (f->desc->type == FEEDER_RATE) sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER || f->desc->type == FEEDER_VOLUME) sbuf_printf(s, "(0x%08x)", f->desc->out); sbuf_printf(s, " -> "); f = f->parent; } sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware"); } } else sbuf_printf(s, " (mixer only)"); snd_mtxunlock(d->lock); return 0; } /************************************************************************/ #ifdef SND_DYNSYSCTL int sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct snddev_channel *sce; struct pcm_channel *c; int err, newcnt, cnt; /* * XXX WOAH... NEED SUPER CLEANUP!!! * Robust, yet confusing. Understanding these will * cause your brain spinning like a Doki Doki Dynamo. */ d = oidp->oid_arg1; if (!(d->flags & SD_F_AUTOVCHAN)) { pcm_inprog(d, -1); return EINVAL; } cnt = 0; SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; CHN_LOCK(c); if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) { cnt++; if (req->newptr != NULL && c->flags & CHN_F_BUSY) { /* Better safe than sorry */ CHN_UNLOCK(c); return EBUSY; } } CHN_UNLOCK(c); } newcnt = cnt; err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); if (err == 0 && req->newptr != NULL) { - if (newcnt < 0 || newcnt > SND_MAXVCHANS) + if (newcnt < 0 || newcnt > (PCMMAXCHAN + 1) || + (d->playcount + d->reccount + newcnt) > (PCMMAXCHAN + 1)) return E2BIG; if (pcm_inprog(d, 1) != 1) { pcm_inprog(d, -1); return EINPROGRESS; } if (newcnt > cnt) { /* add new vchans - find a parent channel first */ SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; CHN_LOCK(c); /* not a candidate if not a play channel */ if (c->direction != PCMDIR_PLAY) goto next; /* not a candidate if a virtual channel */ if (c->flags & CHN_F_VIRTUAL) goto next; /* not a candidate if it's in use */ if (!(c->flags & CHN_F_BUSY) || !(SLIST_EMPTY(&c->children))) /* * 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; next: CHN_UNLOCK(c); } pcm_inprog(d, -1); return EBUSY; addok: c->flags |= CHN_F_BUSY; while (err == 0 && newcnt > cnt) { + if (d->devcount > PCMMAXCHAN) { + device_printf(d->dev, "%s: Maximum channel reached.\n", __func__); + break; + } err = vchan_create(c); if (err == 0) cnt++; + if (newcnt > cnt && err == E2BIG) { + device_printf(d->dev, "%s: err=%d Maximum channel reached.\n", __func__, err); + err = 0; + break; + } } CHN_UNLOCK(c); } else if (newcnt < cnt) { snd_mtxlock(d->lock); while (err == 0 && newcnt < cnt) { + c = NULL; SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY && - (c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL) - goto remok; - - CHN_UNLOCK(c); + CHN_LOCK(sce->channel); + if (sce->channel->direction == PCMDIR_PLAY && + (sce->channel->flags & CHN_F_VIRTUAL)) + c = sce->channel; + CHN_UNLOCK(sce->channel); } + if (c != NULL) + goto remok; snd_mtxunlock(d->lock); pcm_inprog(d, -1); return EINVAL; remok: - CHN_UNLOCK(c); err = vchan_destroy(c); if (err == 0) cnt--; } snd_mtxunlock(d->lock); } pcm_inprog(d, -1); } return err; } #endif /************************************************************************/ static int sound_modevent(module_t mod, int type, void *data) { #if 0 return (midi_modevent(mod, type, data)); #else return 0; #endif } DEV_MODULE(sound, sound_modevent, NULL); MODULE_VERSION(sound, SOUND_MODVER); diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index af539a66bf0b..2bf490c3675f 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -1,319 +1,324 @@ /*- * Copyright (c) 1999 Cameron Grant * Copyright by Hannu Savolainen 1995 * 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$ */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #if __FreeBSD_version < 500000 #include #endif #include /* for DELAY */ #include #include #include #include #include #include #include #include #include #include #include #undef USING_MUTEX #undef USING_DEVFS #if __FreeBSD_version > 500000 #include #include #define USING_MUTEX #define USING_DEVFS #else #define INTR_TYPE_AV INTR_TYPE_TTY #define INTR_MPSAFE 0 #endif #define SND_DYNSYSCTL struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #define PCM_SOFTC_SIZE 512 #define SND_STATUSLEN 64 #define SOUND_MODVER 1 #define SOUND_MINVER 1 #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER 1 /* PROPOSAL: each unit needs: status, mixer, dsp, dspW, audio, sequencer, midi-in, seq2, sndproc = 9 devices dspW and audio are deprecated. dsp needs min 64 channels, will give it 256 minor = (unit << 20) + (dev << 16) + channel currently minor = (channel << 16) + (unit << 4) + dev nomenclature: /dev/pcmX/dsp.(0..255) /dev/pcmX/dspW /dev/pcmX/audio /dev/pcmX/status /dev/pcmX/mixer [etc.] */ -#define PCMMINOR(x) (minor(x)) -#define PCMCHAN(x) ((PCMMINOR(x) & 0x00ff0000) >> 16) -#define PCMUNIT(x) ((PCMMINOR(x) & 0x000000f0) >> 4) -#define PCMDEV(x) (PCMMINOR(x) & 0x0000000f) -#define PCMMKMINOR(u, d, c) ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) +#define PCMMAXCHAN 0xff +#define PCMMAXDEV 0x0f +#define PCMMAXUNIT 0x0f +#define PCMMINOR(x) (minor(x)) +#define PCMCHAN(x) ((PCMMINOR(x) >> 16) & PCMMAXCHAN) +#define PCMUNIT(x) ((PCMMINOR(x) >> 4) & PCMMAXUNIT) +#define PCMDEV(x) (PCMMINOR(x) & PCMMAXDEV) +#define PCMMKMINOR(u, d, c) ((((c) & PCMMAXCHAN) << 16) | \ + (((u) & PCMMAXUNIT) << 4) | ((d) & PCMMAXDEV)) #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTVOL 0x00000004 #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) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) #define DSP_BUFFSIZE (8192) /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_8BIT (AFMT_MU_LAW | AFMT_A_LAW | AFMT_U8 | AFMT_S8) #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ AFMT_S16_BE | AFMT_U16_BE) struct pcm_channel *fkchan_setup(device_t dev); int fkchan_kill(struct pcm_channel *c); -#define SND_MAXVCHANS 255 +/* XXX Flawed definition. I'll fix it someday. */ +#define SND_MAXVCHANS PCMMAXCHAN /* * Minor numbers for the sound driver. * * Unfortunately Creative called the codec chip of SB as a DSP. For this * reason the /dev/dsp is reserved for digitized audio use. There is a * device for true DSP processors but it will be called something else. * In v3.0 it's /dev/sndproc but this could be a temporary solution. */ #define SND_DEV_CTL 0 /* Control port /dev/mixer */ #define SND_DEV_SEQ 1 /* Sequencer /dev/sequencer */ #define SND_DEV_MIDIN 2 /* Raw midi access */ #define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ #define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ #define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ #define SND_DEV_STATUS 6 /* /dev/sndstat */ /* #7 not in use now. */ #define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ #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_DSPREC 11 /* recording channels */ #define DSP_DEFAULT_SPEED 8000 #define ON 1 #define OFF 0 extern int pcm_veto_load; extern int snd_unit; extern devclass_t pcm_devclass; /* * some macros for debugging purposes * DDB/DEB to enable/disable debugging stuff * BVDDB to enable debugging when bootverbose */ #define BVDDB(x) if (bootverbose) x #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); struct sysctl_ctx_list *snd_sysctl_tree(device_t dev); struct sysctl_oid *snd_sysctl_tree_top(device_t dev); struct pcm_channel *pcm_getfakechan(struct snddev_info *d); struct pcm_channel *pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum); 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); 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); unsigned int pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max); int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); int pcm_unregister(device_t dev); int pcm_setstatus(device_t dev, char *str); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep); void *snd_mtxcreate(const char *desc, const char *type); void snd_mtxfree(void *m); void snd_mtxassert(void *m); #define snd_mtxlock(m) mtx_lock(m) #define snd_mtxunlock(m) mtx_unlock(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_register(device_t dev, char *str, sndstat_handler handler); int sndstat_registerfile(char *str); int sndstat_unregister(device_t dev); int sndstat_unregisterfile(char *str); #define SND_DECLARE_FILE(version) \ _SND_DECLARE_FILE(__LINE__, version) #define _SND_DECLARE_FILE(uniq, version) \ __SND_DECLARE_FILE(uniq, version) #define __SND_DECLARE_FILE(uniq, version) \ static char sndstat_vinfo[] = version; \ SYSINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_registerfile, sndstat_vinfo); \ SYSUNINIT(sdf_ ## uniq, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, sndstat_unregisterfile, sndstat_vinfo); /* usage of flags in device config entry (config file) */ #define DV_F_DRQ_MASK 0x00000007 /* mask for secondary drq */ #define DV_F_DUAL_DMA 0x00000010 /* set to use secondary dma channel */ /* ought to be made obsolete but still used by mss */ #define DV_F_DEV_MASK 0x0000ff00 /* force device type/class */ #define DV_F_DEV_SHIFT 8 /* force device type/class */ #define PCM_DEBUG_MTX /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * 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 *dspr_devt; }; struct snddev_info { SLIST_HEAD(, snddev_channel) channels; struct pcm_channel *fakechan; unsigned devcount, playcount, reccount, vchancount; unsigned flags; int inprog; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct sysctl_ctx_list sysctl_tree; struct sysctl_oid *sysctl_tree_top; struct mtx *lock; struct cdev *mixer_dev; }; #ifdef PCM_DEBUG_MTX #define pcm_lock(d) mtx_lock(((struct snddev_info *)(d))->lock) #define pcm_unlock(d) mtx_unlock(((struct snddev_info *)(d))->lock) #else void pcm_lock(struct snddev_info *d); void pcm_unlock(struct snddev_info *d); #endif #ifdef KLD_MODULE #define PCM_KLDSTRING(a) ("kld " # a) #else #define PCM_KLDSTRING(a) "" #endif #endif /* _KERNEL */ #endif /* _OS_H_ */ diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index 308addcdec4f..0627be606eb2 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -1,603 +1,612 @@ /*- * Copyright (c) 2001 Cameron Grant * 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. */ #include #include #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); /* * Default speed */ #define VCHAN_DEFAULT_SPEED 48000 extern int feeder_rate_ratemin; extern int feeder_rate_ratemax; struct vchinfo { u_int32_t spd, fmt, blksz, bps, run; struct pcm_channel *channel, *parent; struct pcmchan_caps caps; }; static u_int32_t vchan_fmt[] = { 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; uint32_t sz; int16_t *tmp, *dst; unsigned int cnt, rcnt = 0; #if 0 if (sndbuf_getsize(src) < count) panic("feed_vchan_s16(%s): tmp buffer size %d < count %d, flags = 0x%x", c->name, sndbuf_getsize(src), count, c->flags); #endif sz = sndbuf_getsize(src); if (sz < count) count = sz; count &= ~1; if (count < 2) return 0; 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; CHN_LOCK(ch); if (ch->flags & CHN_F_TRIGGERED) { if (ch->flags & CHN_F_MAPPED) sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); vchan_mix_s16(dst, tmp, cnt >> 1); if (cnt > rcnt) rcnt = cnt; } CHN_UNLOCK(ch); } return rcnt & ~1; } static struct pcm_feederdesc feeder_vchan_s16_desc[] = { {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); if (!ch) return NULL; 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; 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, u_int32_t speed) { struct vchinfo *ch = data; struct pcm_channel *parent = ch->parent; struct pcm_channel *channel = ch->channel; ch->spd = speed; CHN_UNLOCK(channel); CHN_LOCK(parent); speed = sndbuf_getspd(parent->bufsoft); CHN_UNLOCK(parent); CHN_LOCK(channel); return speed; } static int vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { 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); */ 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; return blocksize; } 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; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; ch->run = (go == PCMTRIG_START)? 1 : 0; CHN_UNLOCK(channel); chn_notify(parent, CHN_N_TRIGGER); CHN_LOCK(channel); return 0; } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; ch->caps.minspeed = sndbuf_getspd(ch->parent->bufsoft); 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); /* * On the fly vchan rate settings */ #ifdef SND_DYNSYSCTL 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 pcmchan_caps *caps; int err = 0; int newspd = 0; d = oidp->oid_arg1; if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) return EINVAL; SLIST_FOREACH(sce, &d->channels, link) { c = sce->channel; CHN_LOCK(c); if (c->direction == PCMDIR_PLAY) { if (c->flags & CHN_F_VIRTUAL) { if (req->newptr != NULL && (c->flags & CHN_F_BUSY)) { CHN_UNLOCK(c); return EBUSY; } if (ch == NULL) ch = c->parentchannel; } } CHN_UNLOCK(c); } if (ch != NULL) { CHN_LOCK(ch); newspd = ch->speed; CHN_UNLOCK(ch); } err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req); if (err == 0 && req->newptr != NULL) { if (ch == NULL || newspd < 1 || newspd < feeder_rate_ratemin || newspd > feeder_rate_ratemax) return EINVAL; if (pcm_inprog(d, 1) != 1) { pcm_inprog(d, -1); return EINPROGRESS; } CHN_LOCK(ch); caps = chn_getcaps(ch); if (caps == NULL || newspd < caps->minspeed || newspd > caps->maxspeed) { CHN_UNLOCK(ch); pcm_inprog(d, -1); return EINVAL; } if (newspd != ch->speed) { err = chn_setspeed(ch, newspd); /* * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ if (!err && (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); } } } else CHN_UNLOCK(ch); pcm_inprog(d, -1); } return err; } #endif /* virtual channel interface */ int vchan_create(struct pcm_channel *parent) { struct snddev_info *d = parent->parentsnddev; struct pcmchan_children *pce; struct pcm_channel *child, *fake; struct pcmchan_caps *parent_caps; int err, first, speed = 0; if (!(parent->flags & CHN_F_BUSY)) return EBUSY; CHN_UNLOCK(parent); pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); if (!pce) { CHN_LOCK(parent); return ENOMEM; } /* create a new playback channel */ child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); if (!child) { free(pce, M_DEVBUF); CHN_LOCK(parent); 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); if (err) { pcm_chn_destroy(child); free(pce, M_DEVBUF); CHN_LOCK(parent); 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); parent->flags |= CHN_F_HAS_VCHAN; if (first) { parent_caps = chn_getcaps(parent); if (parent_caps == NULL) err = EINVAL; if (!err) err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); if (!err) { fake = pcm_getfakechan(d); if (fake != NULL) { /* * Avoid querying kernel hint, use saved value * from fake channel. */ CHN_UNLOCK(parent); CHN_LOCK(fake); speed = fake->speed; CHN_UNLOCK(fake); CHN_LOCK(parent); } /* * This is very sad. Few soundcards advertised as being * able to do (insanely) higher/lower speed, but in * reality, they simply can't. At least, we give user chance * to set sane value via kernel hints or sysctl. */ if (speed < 1) { int r; CHN_UNLOCK(parent); r = resource_int_value(device_get_name(parent->dev), device_get_unit(parent->dev), "vchanrate", &speed); CHN_LOCK(parent); if (r != 0) { /* * Workaround for sb16 running * poorly at 45k / 49k. */ switch (parent_caps->maxspeed) { case 45000: case 49000: speed = 44100; break; default: speed = VCHAN_DEFAULT_SPEED; break; } } } /* * Limit speed based on driver caps. * This is supposed to help fixed rate, non-VRA * AC97 cards, but.. (see below) */ if (speed < parent_caps->minspeed) speed = parent_caps->minspeed; if (speed > parent_caps->maxspeed) speed = parent_caps->maxspeed; /* * We still need to limit the speed between * feeder_rate_ratemin <-> feeder_rate_ratemax. This is * just an escape goat if all of the above failed * miserably. */ if (speed < feeder_rate_ratemin) speed = feeder_rate_ratemin; if (speed > feeder_rate_ratemax) speed = feeder_rate_ratemax; err = chn_setspeed(parent, speed); /* * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ if (!err && (parent->feederflags & (1 << FEEDER_RATE))) { speed = sndbuf_getspd(parent->bufhard); err = chn_setspeed(parent, speed); } if (!err && fake != NULL) { /* * Save new value to fake channel. */ CHN_UNLOCK(parent); CHN_LOCK(fake); fake->speed = speed; CHN_UNLOCK(fake); CHN_LOCK(parent); } } if (err) { SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); parent->flags &= ~CHN_F_HAS_VCHAN; CHN_UNLOCK(parent); free(pce, M_DEVBUF); - pcm_chn_remove(d, child); - pcm_chn_destroy(child); + if (pcm_chn_remove(d, child) == 0) + pcm_chn_destroy(child); CHN_LOCK(parent); return err; } } return 0; } int 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; int err, last; CHN_LOCK(parent); if (!(parent->flags & CHN_F_BUSY)) { CHN_UNLOCK(parent); return EBUSY; } if (SLIST_EMPTY(&parent->children)) { CHN_UNLOCK(parent); 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) + if (sce->dsp_devt) { destroy_dev(sce->dsp_devt); - if (sce->dspW_devt) + sce->dsp_devt = NULL; + } + if (sce->dspW_devt) { destroy_dev(sce->dspW_devt); - if (sce->audio_devt) + sce->dspW_devt = NULL; + } + if (sce->audio_devt) { destroy_dev(sce->audio_devt); - if (sce->dspr_devt) + sce->audio_devt = NULL; + } + if (sce->dspr_devt) { destroy_dev(sce->dspr_devt); + sce->dspr_devt = NULL; + } + d->devcount--; break; } } SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); free(pce, M_DEVBUF); last = SLIST_EMPTY(&parent->children); if (last) { parent->flags &= ~CHN_F_BUSY; parent->flags &= ~CHN_F_HAS_VCHAN; } /* remove us from our grandparent's channel list */ err = pcm_chn_remove(d, c); CHN_UNLOCK(parent); /* destroy ourselves */ if (!err) err = pcm_chn_destroy(c); #if 0 if (!err && last) { CHN_LOCK(parent); chn_reset(parent, chn_getcaps(parent)->fmtlist[0]); chn_setspeed(parent, chn_getcaps(parent)->minspeed); CHN_UNLOCK(parent); } #endif return err; } int vchan_initsys(device_t dev) { #ifdef SND_DYNSYSCTL struct snddev_info *d; d = device_get_softc(dev); SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), sysctl_hw_snd_vchans, "I", ""); SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), sysctl_hw_snd_vchanrate, "I", ""); #endif return 0; }