diff --git a/sys/dev/sound/pcm/fake.c b/sys/dev/sound/pcm/fake.c index 28ae84a95514..e73fb6d27c6c 100644 --- a/sys/dev/sound/pcm/fake.c +++ b/sys/dev/sound/pcm/fake.c @@ -1,120 +1,127 @@ /* * Copyright (c) 1999 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. * * $FreeBSD$ */ #include static u_int32_t fk_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, AFMT_U16_LE, AFMT_STEREO | AFMT_U16_LE, AFMT_S16_BE, AFMT_STEREO | AFMT_S16_BE, AFMT_U16_BE, AFMT_STEREO | AFMT_U16_BE, 0 }; static pcmchan_caps fk_caps = {0, 1000000, fk_fmt, 0}; /* channel interface */ static void * fkchan_init(kobj_t obj, void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) { b->bufsize = 16384; b->buf = malloc(b->bufsize, M_DEVBUF, M_NOWAIT); return (void *)0xbabef00d; } +static int +fkchan_free(kobj_t obj, void *data) +{ + return 0; +} + static int fkchan_setformat(kobj_t obj, void *data, u_int32_t format) { return 0; } static int fkchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { return speed; } static int fkchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { return blocksize; } static int fkchan_trigger(kobj_t obj, void *data, int go) { return 0; } static int fkchan_getptr(kobj_t obj, void *data) { return 0; } static pcmchan_caps * fkchan_getcaps(kobj_t obj, void *data) { return &fk_caps; } static kobj_method_t fkchan_methods[] = { KOBJMETHOD(channel_init, fkchan_init), + KOBJMETHOD(channel_free, fkchan_free), KOBJMETHOD(channel_setformat, fkchan_setformat), KOBJMETHOD(channel_setspeed, fkchan_setspeed), KOBJMETHOD(channel_setblocksize, fkchan_setblocksize), KOBJMETHOD(channel_trigger, fkchan_trigger), KOBJMETHOD(channel_getptr, fkchan_getptr), KOBJMETHOD(channel_getcaps, fkchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(fkchan); int fkchan_setup(pcm_channel *c) { c->methods = kobj_create(&fkchan_class, M_DEVBUF, M_WAITOK); return 0; } int fkchan_kill(pcm_channel *c) { kobj_delete(c->methods, M_DEVBUF); c->methods = NULL; return 0; } diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c index 5853770388d5..1d7af27a9c1a 100644 --- a/sys/dev/sound/pcm/feeder.c +++ b/sys/dev/sound/pcm/feeder.c @@ -1,333 +1,346 @@ /* * Copyright (c) 1999 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. * * $FreeBSD$ */ #include #include "feeder_if.h" MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); #define MAXFEEDERS 256 #undef FEEDER_DEBUG struct feedertab_entry { SLIST_ENTRY(feedertab_entry) link; struct feeder_class *feederclass; struct pcm_feederdesc *desc; int idx; }; static SLIST_HEAD(, feedertab_entry) feedertab; /*****************************************************************************/ void feeder_register(void *p) { struct feeder_class *fc = p; struct feedertab_entry *fte; static int feedercnt = 0; int i; if (feedercnt == 0) { if (fc->desc) panic("FIRST FEEDER NOT ROOT: %s\n", fc->name); SLIST_INIT(&feedertab); fte = malloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); fte->feederclass = fc; fte->desc = NULL; fte->idx = feedercnt; SLIST_INSERT_HEAD(&feedertab, fte, link); feedercnt++; return; } - /* printf("installing feeder: %s\n", f->name); */ i = 0; while ((feedercnt < MAXFEEDERS) && (fc->desc[i].type > 0)) { fte = malloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); fte->feederclass = fc; fte->desc = &fc->desc[i]; fte->idx = feedercnt; fte->desc->idx = feedercnt; SLIST_INSERT_HEAD(&feedertab, fte, link); i++; } feedercnt++; if (feedercnt >= MAXFEEDERS) printf("MAXFEEDERS exceeded\n"); } +static void +feeder_unregisterall(void *p) +{ + struct feedertab_entry *fte, *next; + + next = SLIST_FIRST(&feedertab); + while (next != NULL) { + fte = next; + next = SLIST_NEXT(fte, link); + free(fte, M_FEEDER); + } +} + static int cmpdesc(struct pcm_feederdesc *n, struct pcm_feederdesc *m) { return ((n->type == m->type) && ((n->in == 0) || (n->in == m->in)) && ((n->out == 0) || (n->out == m->out)) && (n->flags == m->flags)); } static void feeder_destroy(pcm_feeder *f) { FEEDER_FREE(f); free(f->desc, M_FEEDER); kobj_delete((kobj_t)f, M_FEEDER); } static pcm_feeder * feeder_create(struct feeder_class *fc, struct pcm_feederdesc *desc) { pcm_feeder *f; int err; f = (pcm_feeder *)kobj_create((kobj_class_t)fc, M_FEEDER, M_WAITOK | M_ZERO); f->align = fc->align; f->desc = malloc(sizeof(*(f->desc)), M_FEEDER, M_WAITOK | M_ZERO); if (desc) *(f->desc) = *desc; else { f->desc->type = FEEDER_ROOT; f->desc->in = 0; f->desc->out = 0; f->desc->flags = 0; f->desc->idx = 0; } f->data = fc->data; f->source = NULL; err = FEEDER_INIT(f); if (err) { feeder_destroy(f); return NULL; } else return f; } struct feeder_class * feeder_getclass(struct pcm_feederdesc *desc) { struct feedertab_entry *fte; SLIST_FOREACH(fte, &feedertab, link) { if ((desc == NULL) && (fte->desc == NULL)) return fte->feederclass; if ((fte->desc != NULL) && (desc != NULL) && cmpdesc(desc, fte->desc)) return fte->feederclass; } return NULL; } int chn_addfeeder(pcm_channel *c, struct feeder_class *fc, struct pcm_feederdesc *desc) { pcm_feeder *nf; nf = feeder_create(fc, desc); if (nf == NULL) return -1; nf->source = c->feeder; if (nf->align > 0) c->align += nf->align; else if (nf->align < 0 && c->align < -nf->align) c->align = -nf->align; c->feeder = nf; return 0; } int chn_removefeeder(pcm_channel *c) { pcm_feeder *f; if (c->feeder == NULL) return -1; f = c->feeder; c->feeder = c->feeder->source; feeder_destroy(f); return 0; } pcm_feeder * chn_findfeeder(pcm_channel *c, u_int32_t type) { pcm_feeder *f; f = c->feeder; while (f != NULL) { if (f->desc->type == type) return f; f = f->source; } return NULL; } static int chainok(pcm_feeder *test, pcm_feeder *stop) { u_int32_t visited[MAXFEEDERS / 32]; u_int32_t idx, mask; bzero(visited, sizeof(visited)); while (test && (test != stop)) { idx = test->desc->idx; if (idx < 0) panic("bad idx %d", idx); if (idx >= MAXFEEDERS) panic("bad idx %d", idx); mask = 1 << (idx & 31); idx >>= 5; if (visited[idx] & mask) return 0; visited[idx] |= mask; test = test->source; } return 1; } static pcm_feeder * feeder_fmtchain(u_int32_t *to, pcm_feeder *source, pcm_feeder *stop, int maxdepth) { struct feedertab_entry *fte; pcm_feeder *try, *ret; struct pcm_feederdesc trydesc; /* printf("trying %s...\n", source->name); */ if (fmtvalid(source->desc->out, to)) { /* printf("got it\n"); */ return source; } if (maxdepth < 0) return NULL; trydesc.type = FEEDER_FMT; trydesc.in = source->desc->out; trydesc.out = 0; trydesc.flags = 0; trydesc.idx = -1; SLIST_FOREACH(fte, &feedertab, link) { if ((fte->desc) && (fte->desc->in == source->desc->out)) { trydesc.out = fte->desc->out; trydesc.idx = fte->idx; try = feeder_create(fte->feederclass, &trydesc); if (try == NULL) return NULL; try->source = source; ret = chainok(try, stop)? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; if (ret != NULL) return ret; feeder_destroy(try); } } /* printf("giving up %s...\n", source->name); */ return NULL; } u_int32_t chn_fmtchain(pcm_channel *c, u_int32_t *to) { pcm_feeder *try, *stop; int max; stop = c->feeder; try = NULL; max = 0; while (try == NULL && max < 8) { try = feeder_fmtchain(to, c->feeder, stop, max); max++; } if (try == NULL) return 0; c->feeder = try; c->align = 0; #ifdef FEEDER_DEBUG printf("chain: "); #endif while (try && (try != stop)) { #ifdef FEEDER_DEBUG printf("%s [%d]", try->name, try->desc->idx); if (try->source) printf(" -> "); #endif if (try->align > 0) c->align += try->align; else if (try->align < 0 && c->align < -try->align) c->align = -try->align; try = try->source; } #ifdef FEEDER_DEBUG printf("%s [%d]\n", try->name, try->desc->idx); #endif return c->feeder->desc->out; } /*****************************************************************************/ static int feed_root(pcm_feeder *feeder, pcm_channel *ch, u_int8_t *buffer, u_int32_t count, struct uio *stream) { int ret, s; KASSERT(count, ("feed_root: count == 0")); count &= ~((1 << ch->align) - 1); KASSERT(count, ("feed_root: aligned count == 0 (align = %d)", ch->align)); s = spltty(); count = min(count, stream->uio_resid); if (count) { ret = uiomove(buffer, count, stream); KASSERT(ret == 0, ("feed_root: uiomove failed (%d)", ret)); } splx(s); return count; } static kobj_method_t feeder_root_methods[] = { KOBJMETHOD(feeder_feed, feed_root), { 0, 0 } }; static struct feeder_class feeder_root_class = { name: "feeder_root", methods: feeder_root_methods, size: sizeof(pcm_feeder), align: 0, desc: NULL, data: NULL, }; SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register, &feeder_root_class); +SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index fa4698f034bb..95e7762f68fa 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -1,760 +1,761 @@ /* * 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. * * $FreeBSD$ */ #include #include 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); static d_open_t sndopen; static d_close_t sndclose; static d_ioctl_t sndioctl; static d_read_t sndread; static d_write_t sndwrite; static d_mmap_t sndmmap; static d_poll_t sndpoll; #define CDEV_MAJOR 30 static struct cdevsw snd_cdevsw = { /* open */ sndopen, /* close */ sndclose, /* read */ sndread, /* write */ sndwrite, /* ioctl */ sndioctl, /* poll */ sndpoll, /* mmap */ sndmmap, /* strategy */ nostrategy, /* name */ "snd", /* maj */ CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -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)) static devclass_t pcm_devclass; #ifdef USING_DEVFS int snd_unit; TUNABLE_INT_DECL("hw.snd.unit", 0, snd_unit); #endif SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); void * snd_mtxcreate(const char *desc) { #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, MTX_RECURSE); 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 struct mtx *mtx = m; mtx_assert(mtx, MA_OWNED); #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_TTY; #else flags = INTR_TYPE_TTY; #endif return bus_setup_intr(dev, res, flags, hand, param, cookiep); } #ifdef USING_DEVFS static void pcm_makelinks(void *dummy) { int unit; dev_t pdev; static dev_t dsp = 0, dspW = 0, audio = 0, mixer = 0; if (pcm_devclass == NULL || devfs_present == 0) return; if (dsp) { destroy_dev(dsp); dsp = 0; } if (dspW) { destroy_dev(dspW); dspW = 0; } if (audio) { destroy_dev(audio); audio = 0; } if (mixer) { destroy_dev(mixer); mixer = 0; } unit = snd_unit; if (unit < 0 || unit > devclass_get_maxunit(pcm_devclass)) return; if (devclass_get_softc(pcm_devclass, unit) == NULL) return; pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP, 0)); dsp = make_dev_alias(pdev, "dsp"); pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_DSP16, 0)); dspW = make_dev_alias(pdev, "dspW"); pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_AUDIO, 0)); audio = make_dev_alias(pdev, "audio"); pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_CTL, 0)); mixer = make_dev_alias(pdev, "mixer"); } static int sysctl_hw_sndunit(SYSCTL_HANDLER_ARGS) { int error, unit; unit = snd_unit; error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); if (error == 0 && req->newptr != NULL) { snd_unit = unit; pcm_makelinks(NULL); } return (error); } 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) { int unit = device_get_unit(dev), idx; snddev_info *d = device_get_softc(dev); pcm_channel *chns, *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++); if (chns == NULL) { device_printf(dev, "bad channel add (%s:%d)\n", dirs, idx); return 1; } ch = &chns[idx]; ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); ch->parent = d; 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; } 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), UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d", unit, d->chancount); 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) pcm_makelinks(NULL); #endif return 0; } static int pcm_killchan(device_t dev, int dir) { int unit = device_get_unit(dev), idx; snddev_info *d = device_get_softc(dev); pcm_channel *chns, *ch; char *dirs; 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; } kobj_delete(ch->methods, M_DEVBUF); ch->methods = NULL; d->chancount--; 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_setstatus(device_t dev, char *str) { snddev_info *d = device_get_softc(dev); strncpy(d->status, str, SND_STATUSLEN); return 0; } u_int32_t pcm_getflags(device_t dev) { snddev_info *d = device_get_softc(dev); return d->flags; } void pcm_setflags(device_t dev, u_int32_t val) { snddev_info *d = device_get_softc(dev); d->flags = val; } void * pcm_getdevinfo(device_t dev) { snddev_info *d = device_get_softc(dev); return d->devinfo; } /* This is the generic init routine */ int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { int sz, unit = device_get_unit(dev); snddev_info *d = device_get_softc(dev); if (!pcm_devclass) { pcm_devclass = device_get_devclass(dev); 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->maxchans = numplay + numrec; sz = (numplay + numrec) * sizeof(pcm_channel *); if (sz > 0) { d->aplay = (pcm_channel **)malloc(sz, M_DEVBUF, M_NOWAIT); if (!d->aplay) goto no; bzero(d->aplay, sz); d->arec = (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->atype = (int *)malloc(sz, M_DEVBUF, M_NOWAIT); if (!d->atype) goto no; bzero(d->atype, sz); } if (numplay > 0) { d->play = (pcm_channel *)malloc(numplay * sizeof(pcm_channel), M_DEVBUF, M_NOWAIT); if (!d->play) goto no; bzero(d->play, numplay * sizeof(pcm_channel)); } else d->play = NULL; if (numrec > 0) { d->rec = (pcm_channel *)malloc(numrec * sizeof(pcm_channel), M_DEVBUF, M_NOWAIT); if (!d->rec) goto no; bzero(d->rec, numrec * sizeof(pcm_channel)); } else d->rec = NULL; #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; } #endif if (numplay == 0 || numrec == 0) d->flags |= SD_F_SIMPLEX; fkchan_setup(&d->fakechan); chn_init(&d->fakechan, NULL, 0); d->magic = MAGIC(unit); /* debugging... */ 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); if (d->atype) free(d->atype, M_DEVBUF); return ENXIO; } int pcm_unregister(device_t dev) { int r, i, unit = device_get_unit(dev); snddev_info *d = device_get_softc(dev); dev_t pdev; #ifdef SND_DYNSYSCTL sysctl_remove_oid(d->sysctl_tree_top, 1, 1); d->sysctl_tree_top = NULL; sysctl_ctx_free(&d->sysctl_tree); #endif 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; } if (mixer_isbusy(d->mixer)) { device_printf(dev, "unregister: mixer busy"); return EBUSY; } pdev = makedev(CDEV_MAJOR, PCMMKMINOR(unit, SND_DEV_CTL, 0)); destroy_dev(pdev); mixer_uninit(dev); while (d->playcount > 0) pcm_killchan(dev, PCMDIR_PLAY); while (d->reccount > 0) pcm_killchan(dev, PCMDIR_REC); d->magic = 0; 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); if (d->atype) free(d->atype, M_DEVBUF); + chn_kill(&d->fakechan); fkchan_kill(&d->fakechan); #ifdef USING_DEVFS pcm_makelinks(NULL); #endif return 0; } /* * a small utility function which, given a device number, returns * a pointer to the associated snddev_info struct, and sets the unit * number. */ static snddev_info * get_snddev_info(dev_t i_dev, int *unit, int *dev, int *chan) { snddev_info *sc; int u, d, c; u = PCMUNIT(i_dev); d = PCMDEV(i_dev); c = PCMCHAN(i_dev); if (u > devclass_get_maxunit(pcm_devclass)) u = -1; if (unit) *unit = u; if (dev) *dev = d; if (chan) *chan = c; if (u < 0) return NULL; sc = devclass_get_softc(pcm_devclass, u); if (sc == NULL || sc->magic == 0) return NULL; switch(d) { case SND_DEV_CTL: /* /dev/mixer handled by pcm */ case SND_DEV_STATUS: /* /dev/sndstat handled by pcm */ case SND_DEV_DSP: case SND_DEV_DSP16: case SND_DEV_AUDIO: return sc; case SND_DEV_SEQ: /* XXX when enabled... */ case SND_DEV_SEQ2: case SND_DEV_MIDIN: case SND_DEV_SNDPROC: /* /dev/sndproc handled by pcm */ default: printf("unsupported subdevice %d\n", d); return NULL; } } static int sndopen(dev_t i_dev, int flags, int mode, struct proc *p) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("open snd%d subdev %d flags 0x%08x mode 0x%08x\n", unit, dev, flags, mode)); switch(dev) { case SND_DEV_STATUS: if (status_isopen) return EBUSY; status_isopen = 1; return 0; case SND_DEV_CTL: return d? mixer_busy(d->mixer, 1) : ENXIO; case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: case SND_DEV_NORESET: return d? dsp_open(d, chan, flags, dev) : ENXIO; default: return ENXIO; } } static int sndclose(dev_t i_dev, int flags, int mode, struct proc *p) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("close snd%d subdev %d\n", unit, dev)); switch(dev) { /* only those for which close makes sense */ case SND_DEV_STATUS: if (!status_isopen) return EBADF; status_isopen = 0; return 0; case SND_DEV_CTL: return d? mixer_busy(d->mixer, 0) : ENXIO; case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return d? dsp_close(d, chan, dev) : ENXIO; default: return ENXIO; } } static int sndread(dev_t i_dev, struct uio *buf, int flag) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("read snd%d subdev %d flag 0x%08x\n", unit, dev, flag)); switch(dev) { case SND_DEV_STATUS: return status_isopen? status_read(buf) : EBADF; case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return d? dsp_read(d, chan, buf, flag) : EBADF; default: return ENXIO; } } static int sndwrite(dev_t i_dev, struct uio *buf, int flag) { int dev, unit, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("write snd%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); switch(dev) { /* only writeable devices */ case SND_DEV_DSP: case SND_DEV_DSP16: case SND_DEV_AUDIO: return d? dsp_write(d, chan, buf, flag) : EBADF; default: return EPERM; /* for non-writeable devices ; */ } } static int sndioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc * p) { int dev, chan; snddev_info *d = get_snddev_info(i_dev, NULL, &dev, &chan); if (d == NULL) return ENXIO; switch(dev) { case SND_DEV_CTL: return mixer_ioctl(d, cmd, arg); case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: if (IOCGROUP(cmd) == 'M') return mixer_ioctl(d, cmd, arg); else return dsp_ioctl(d, chan, cmd, arg); default: return ENXIO; } } static int sndpoll(dev_t i_dev, int events, struct proc *p) { int dev, chan; snddev_info *d = get_snddev_info(i_dev, NULL, &dev, &chan); DEB(printf("sndpoll d 0x%p dev 0x%04x events 0x%08x\n", d, dev, events)); if (d == NULL) return ENXIO; switch(dev) { case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return dsp_poll(d, chan, events, p); default: return (events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)) | POLLHUP; } } /* * The mmap interface allows access to the play and read buffer, * plus the device descriptor. * The various blocks are accessible at the following offsets: * * 0x00000000 ( 0 ) : write buffer ; * 0x01000000 (16 MB) : read buffer ; * 0x02000000 (32 MB) : device descriptor (dangerous!) * * WARNING: the mmap routines assume memory areas are aligned. This * is true (probably) for the dma buffers, but likely false for the * device descriptor. As a consequence, we do not know where it is * located in the requested area. */ static int sndmmap(dev_t i_dev, vm_offset_t offset, int nprot) { int unit, dev, chan; snddev_info *d = get_snddev_info(i_dev, &unit, &dev, &chan); DEB(printf("sndmmap d 0x%p dev 0x%04x ofs 0x%08x nprot 0x%08x\n", d, dev, offset, nprot)); if (d == NULL || nprot & PROT_EXEC) return -1; /* forbidden */ switch(dev) { case SND_DEV_AUDIO: case SND_DEV_DSP: case SND_DEV_DSP16: return dsp_mmap(d, chan, offset, nprot); default: return -1; } } static int status_init(char *buf, int size) { int i; device_t dev; snddev_info *d; snprintf(buf, size, "FreeBSD Audio Driver (newpcm) %s %s\n" "Installed devices:\n", __DATE__, __TIME__); for (i = 0; i <= devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); 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"); } } return strlen(buf); } static int status_read(struct uio *buf) { static char status_buf[4096]; static int bufptr = 0, buflen = 0; int l; if (status_isopen == 1) { status_isopen++; bufptr = 0; buflen = status_init(status_buf, sizeof status_buf); } l = min(buf->uio_resid, buflen - bufptr); bufptr += l; return (l > 0)? uiomove(status_buf + bufptr - l, l, buf) : 0; } static int sndpcm_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: break; case MOD_UNLOAD: if (status_isopen) return EBUSY; if (status_dev) destroy_dev(status_dev); status_dev = 0; break; default: break; } return 0; } static moduledata_t sndpcm_mod = { "snd_pcm", sndpcm_modevent, NULL }; DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(snd_pcm, PCM_MODVER);