/*- * Copyright 2008 Marco Trillo. * * 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 ``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 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. * * $Id: snd.c,v 1.3 2008/08/31 20:33:40 mtrillo Exp $ */ /* * This is a work-in-progress minimalist machine-independent sound device. */ #include __KERNEL_RCSID(0, "$Id: snd.c,v 1.3 2008/08/31 20:33:40 mtrillo Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include dev_type_open(sndopen); dev_type_close(sndclose); dev_type_read(sndread); dev_type_write(sndwrite); dev_type_ioctl(sndioctl); const struct cdevsw snd_cdevsw = { sndopen, sndclose, sndread, sndwrite, sndioctl, nostop, notty, nopoll, nommap, nokqfilter, D_OTHER | D_MPSAFE }; /* -------------------------------------------------------------------------- */ struct stream { u_int bufsize; // buffer size u_int blksize; // block size u_int inbytes; // bytes written in buffer u_int minbytes; // min bytes to have in buffer u_int buflow; // watermark "buffer is low" u_int drop; // bytes to drop (for underruns) u_int head; // writing position u_int tail; // reading position u_char *start; // buffer address struct snd_spec spec; // sound spec void (*postproc)(void *, u_char *, u_int); // hw postprocessing function void *postproc_hdl; // handle for postproc lwp_t *silthread; // silence writer thread const struct snd_hw_if *hw; // hardware void *hw_arg; // hardware handle kmutex_t *mutex; // mutex kcondvar_t needsil; // need silence in buffer kcondvar_t blkconsume; // need more data in buffer kcondvar_t sildeath; // silence writer has died bool writing; // writer is in progress bool dying; // this stream is closing bool running; // hardware DMA is running }; /* -------------------------------------------------------------------------- */ struct snd_softc { struct device sc_dev; struct stream sc_play; struct stream sc_rec; const struct snd_hw_if *sc_hw; void *sc_hw_arg; #define SND_INPUT (1 << 0) #define SND_OUTPUT (1 << 1) #define SND_CONFIGURED (1 << 2) u_int sc_flags; }; /* -------------------------------------------------------------------------- */ static int snd_match(struct device *, struct cfdata *, void *); static void snd_attach(struct device *, struct device *, void *); static int snd_detach(struct device *, int); CFATTACH_DECL(snd, sizeof(struct snd_softc), snd_match, snd_attach, snd_detach, NULL); /* -------------------------------------------------------------------------- */ int snd_match(parent, match, aux) struct device *parent; struct cfdata *match; void *aux; { struct snd_attach_args *sa = aux; if (sa->sa_magic != SND_MAGIC || sa->sa_hw == 0 || sa->sa_hw_arg == 0) return (0); return (1); } /* -------------------------------------------------------------------------- */ void snd_attach(parent, self, aux) struct device *parent, *self; void *aux; { struct snd_softc *sc = (struct snd_softc *)self; struct snd_attach_args *sa = aux; sc->sc_hw = sa->sa_hw; sc->sc_hw_arg = sa->sa_hw_arg; printf("\n"); } /* -------------------------------------------------------------------------- */ int snd_detach(self, flags) struct device *self; int flags; { /* TODO */ return (EOPNOTSUPP); } /* -------------------------------------------------------------------------- */ static void stream_fill_silence (void *); static void stream_intr (void *); static int stream_write (struct stream *, struct uio *); static int stream_init (struct snd_softc *, struct stream *, u_int, struct snd_spec *); static int stream_trigger_output (struct stream *); static void stream_halt_output (struct stream *); static void stream_destroy (struct stream *); static void stream_close (struct stream *); /* -------------------------------------------------------------------------- */ void stream_fill_silence(h) void *h; { struct stream *s = h; u_int cnt, rem; u_int head; mutex_enter(s->mutex); while (!s->dying) { cv_wait(&s->needsil, s->mutex); if (s->dying) break; cnt = s->minbytes; head = s->head; s->head = (s->head + cnt) % s->bufsize; s->drop += cnt; s->inbytes += cnt; mutex_exit(s->mutex); /* * TODO - keep track of silence */ while (cnt > 0) { rem = MIN(s->bufsize - head, cnt); memset(s->start + head, 0, rem); cnt -= rem; head = (head + rem) % s->bufsize; } mutex_enter(s->mutex); } /* die */ cv_broadcast(&s->sildeath); mutex_exit(s->mutex); kthread_exit(0); } /* -------------------------------------------------------------------------- */ void stream_intr(h) void *h; { struct stream *s = h; mutex_enter(s->mutex); if (s->inbytes > s->blksize) s->inbytes -= s->blksize; s->tail = (s->tail + s->blksize) % s->bufsize; /* * If the writer is not keeping the buffer * filled, wake the silence thread. */ if (s->inbytes < s->minbytes && !s->writing) cv_broadcast(&s->needsil); /* * If the buffer is getting low, signal * the writer to proceed. */ if (s->inbytes < s->buflow) cv_broadcast(&s->blkconsume); mutex_exit(s->mutex); } /* -------------------------------------------------------------------------- */ int stream_write(s, uio) struct stream *s; struct uio *uio; { u_int cnt, resid, rem, head, in; if ((uio->uio_resid) % (s->spec.bytes_per_frame) != 0) return (EFAULT); while (uio->uio_resid > 0) { mutex_enter(s->mutex); if (s->drop > 0) { cnt = MIN(uio->uio_resid, s->drop); uio->uio_resid -= cnt; uio->uio_offset += cnt; s->drop -= cnt; mutex_exit(s->mutex); continue; } while (s->inbytes == s->bufsize) cv_wait(&s->blkconsume, s->mutex); resid = uio->uio_resid; cnt = MIN(s->bufsize - s->inbytes, resid); rem = cnt; head = s->head; s->writing = 1; mutex_exit(s->mutex); while (rem > 0) { if (0 != uiomove(s->start + head, MIN(rem, s->bufsize - head), uio)) return (EINVAL); in = resid - uio->uio_resid; if (s->postproc) (*s->postproc)(s->postproc_hdl, s->start + head, in); rem -= in; head = (head + in) % s->bufsize; resid = uio->uio_resid; } mutex_enter(s->mutex); s->writing = 0; s->inbytes += cnt; s->head = head; mutex_exit(s->mutex); /* * If we have written enough data to the buffer, * we can start the DMA. */ if (!s->running && s->inbytes > (2 * s->minbytes)) stream_trigger_output(s); } return (0); } /* -------------------------------------------------------------------------- */ int stream_init(sc, s, mode, spec) struct snd_softc *sc; struct stream *s; u_int mode; struct snd_spec *spec; { const struct snd_hw_if *hwp = sc->sc_hw; u_int blksize; int res; bzero(s, sizeof(*s)); memcpy(&s->spec, spec, sizeof(struct snd_spec)); s->hw = sc->sc_hw; s->hw_arg = sc->sc_hw_arg; if ((res = hwp->set_params(sc->sc_hw_arg, mode, spec, &s->postproc, &s->postproc_hdl)) != 0) return (res); s->mutex = hwp->get_mutex(sc->sc_hw_arg); cv_init(&s->needsil, "silwait"); cv_init(&s->blkconsume, "blkwait"); cv_init(&s->sildeath, "sildying"); s->bufsize = hwp->round_buffersize(sc->sc_hw_arg, mode, 16384 * spec->bytes_per_frame); if (s->bufsize == 0) return (EIO); blksize = 50 * spec->frames_per_sec / 1000; blksize = blksize * spec->bytes_per_frame; s->blksize = hwp->round_blocksize (sc->sc_hw_arg, mode, blksize ); s->start = hwp->allocm(sc->sc_hw_arg, mode, s->bufsize, M_WAITOK | M_ZERO); s->bufsize -= s->bufsize % s->blksize; s->head = 0; s->tail = 0; s->inbytes = 0; s->drop = 0; s->minbytes = 2 * s->blksize; s->buflow = s->bufsize * 3 / 4; s->writing = 0; s->running = 0; return (0); } /* -------------------------------------------------------------------------- */ int stream_trigger_output(s) struct stream *s; { const struct snd_hw_if *hwp = s->hw; int res; if (kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL, stream_fill_silence, s, &s->silthread, "snd_fill_silence") != 0) { return (ENOMEM); } res = hwp->trigger_output(s->hw_arg, s->start, s->start + s->bufsize, s->blksize, stream_intr, s); if (res != 0) { mutex_enter(s->mutex); s->dying = 1; cv_signal(&s->needsil); cv_wait(&s->sildeath, s->mutex); s->dying = 0; mutex_exit(s->mutex); } else { s->running = 1; } return (res); } /* -------------------------------------------------------------------------- */ void stream_halt_output(s) struct stream *s; { const struct snd_hw_if *hwp = s->hw; assert(mutex_owned(s->mutex)); if (s->running) { hwp->halt_output(s->hw_arg); s->dying = 1; cv_signal(&s->needsil); cv_wait(&s->sildeath, s->mutex); s->running = 0; s->dying = 0; } } /* -------------------------------------------------------------------------- */ void stream_destroy(s) struct stream *s; { const struct snd_hw_if *hwp = s->hw; mutex_enter(s->mutex); if (s->running) { stream_halt_output(s); } hwp->freem(s->hw_arg, s->start); mutex_exit(s->mutex); cv_destroy(&s->needsil); cv_destroy(&s->blkconsume); cv_destroy(&s->sildeath); } /* -------------------------------------------------------------------------- */ void stream_close(s) struct stream *s; { mutex_enter(s->mutex); if (s->running) { s->buflow = s->minbytes; cv_wait(&s->blkconsume, s->mutex); stream_halt_output(s); } mutex_exit(s->mutex); stream_destroy(s); } /* -------------------------------------------------------------------------- */ extern struct cfdriver snd_cd; #define ISMIXER(dev) ((minor(dev) & 0x10) == 0x10) #define UNIT(dev) (minor(dev) & 0x0f) #define SND_GET_SOFTC(dev) device_lookup_private(&snd_cd, UNIT(dev)) /* -------------------------------------------------------------------------- */ int sndopen(dev, flags, ifmt, l) dev_t dev; int flags, ifmt; struct lwp *l; { struct snd_softc *sc; sc = SND_GET_SOFTC(dev); if (sc == NULL) return (ENXIO); /* * Allow unlimited openings of the mixer device. */ if (ISMIXER(dev)) return (0); if (flags & FREAD) { return (EOPNOTSUPP); // for now } else if (flags & FWRITE) { if (sc->sc_flags & SND_OUTPUT) return (EBUSY); sc->sc_flags |= SND_OUTPUT; } return (0); } /* -------------------------------------------------------------------------- */ int sndclose(dev, flags, ifmt, l) dev_t dev; int flags, ifmt; struct lwp *l; { struct snd_softc *sc; sc = SND_GET_SOFTC(dev); if (sc == NULL) return (ENXIO); if (ISMIXER(dev)) return (0); if (sc->sc_flags & SND_OUTPUT) { if (sc->sc_flags & SND_CONFIGURED) { stream_close(&sc->sc_play); sc->sc_flags &= ~SND_CONFIGURED; } sc->sc_flags &= ~SND_OUTPUT; } return (0); } /* -------------------------------------------------------------------------- */ int sndread(dev, uio, ioflag) dev_t dev; struct uio *uio; int ioflag; { return (EOPNOTSUPP); } /* -------------------------------------------------------------------------- */ int sndwrite(dev, uio, ioflag) dev_t dev; struct uio *uio; int ioflag; { struct snd_softc *sc; sc = SND_GET_SOFTC(dev); if (sc == NULL) return (ENXIO); if (ISMIXER(dev)) return (EOPNOTSUPP); if ((SND_CONFIGURED | SND_OUTPUT) != (sc->sc_flags & (SND_CONFIGURED | SND_OUTPUT))) return (ENXIO); return (stream_write(&sc->sc_play, uio)); } /* -------------------------------------------------------------------------- */ int sndioctl(dev, cmd, addr, flags, l) dev_t dev; u_long cmd; void *addr; int flags; struct lwp *l; { struct snd_softc *sc; const struct snd_hw_if *hwp; int res; sc = SND_GET_SOFTC(dev); if (sc == NULL) return (ENXIO); hwp = sc->sc_hw; switch (cmd) { case SNDIOCCONF: { struct snd_spec *spec = addr; if (ISMIXER(dev)) return (EOPNOTSUPP); if (sc->sc_flags & SND_CONFIGURED) return (EBUSY); if (0 == (sc->sc_flags & (SND_OUTPUT | SND_INPUT))) return (ENXIO); res = 0; if (sc->sc_flags & SND_OUTPUT) res = stream_init(sc, &sc->sc_play, SND_OUTPUT, spec); if (res == 0) { if (sc->sc_flags & SND_INPUT) res = stream_init(sc, &sc->sc_rec, SND_INPUT, spec); if (res == 0) sc->sc_flags |= SND_CONFIGURED; else stream_destroy(&sc->sc_play); } break; } case SNDIOCGDEV: res = hwp->getdev(sc->sc_hw_arg, addr); break; case AUDIO_MIXER_DEVINFO: res = hwp->query_devinfo(sc->sc_hw_arg, addr); break; case AUDIO_MIXER_READ: res = hwp->get_port(sc->sc_hw_arg, addr); break; case AUDIO_MIXER_WRITE: res = hwp->set_port(sc->sc_hw_arg, addr); break; default: res = ENOTTY; } return (res); }