diff options
Diffstat (limited to 'sys/dev/sound/pcm/sound.c')
-rw-r--r-- | sys/dev/sound/pcm/sound.c | 438 |
1 files changed, 293 insertions, 145 deletions
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); |