diff --git a/sys/arch/i386/conf/GENERIC b/sys/arch/i386/conf/GENERIC index 74af10504b85..d156a535f223 100644 --- a/sys/arch/i386/conf/GENERIC +++ b/sys/arch/i386/conf/GENERIC @@ -1,4 +1,4 @@ -# $NetBSD: GENERIC,v 1.11 1995/01/25 04:58:18 cgd Exp $ +# $NetBSD: GENERIC,v 1.12 1995/02/21 01:43:02 brezak Exp $ # # GENERIC -- everything that's currently supported # @@ -149,6 +149,8 @@ ie0 at isa? port 0x360 iomem 0xd0000 irq 7 # StarLAN & 3C507 ethernet cards #le0 at isa? port 0x320 irq 10 drq 7 # IsoLan, NE2100, and DEPCA sb0 at isa? port 0x220 irq 7 drq 1 # SoundBlaster +wss0 at isa? port 0x530 irq 10 drq 0 # Windows Sound System +pas0 at isa? port 0x220 irq 7 drq 1 # ProAudio Spectrum #spkr0 at isa? ... pseudo-device loop 1 # network loopback diff --git a/sys/conf/files.newconf b/sys/conf/files.newconf index 444637282da7..7c4733c75307 100644 --- a/sys/conf/files.newconf +++ b/sys/conf/files.newconf @@ -1,4 +1,4 @@ -# $NetBSD: files.newconf,v 1.34 1995/01/25 04:48:09 cgd Exp $ +# $NetBSD: files.newconf,v 1.35 1995/02/21 01:35:58 brezak Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -7,6 +7,7 @@ define disk define tape define ifnet define tty +define audio # net device attributes - we have generic code for ether. # we should have imp but right now it is a pseudo-device. @@ -59,6 +60,7 @@ file ddb/db_trap.c ddb file ddb/db_variables.c ddb file ddb/db_watch.c ddb file ddb/db_write_cmd.c ddb +file dev/audio.c audio needs-flag file dev/vnd.c vnd needs-flag file isofs/cd9660/cd9660_bmap.c cd9660 file isofs/cd9660/cd9660_lookup.c cd9660 diff --git a/sys/dev/audio.c b/sys/dev/audio.c new file mode 100644 index 000000000000..d64e5b53a38c --- /dev/null +++ b/sys/dev/audio.c @@ -0,0 +1,1712 @@ +/* $NetBSD: audio.c,v 1.1 1995/02/21 01:36:56 brezak Exp $ */ + +/* + * Copyright (c) 1991-1993 Regents of the University of California. + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * $Id: audio.c,v 1.1 1995/02/21 01:36:56 brezak Exp $ + */ + +/* + * This is a (partially) SunOS-compatible /dev/audio driver for NetBSD. + * + * This code tries to do something half-way sensible with + * half-duplex hardware, such as with the SoundBlaster hardware. With + * half-duplex hardware allowing O_RDWR access doesn't really make + * sense. However, closing and opening the device to "turn around the + * line" is relatively expensive and costs a card reset (which can + * take some time, at least for the SoundBlaster hardware). Instead + * we allow O_RDWR access, and provide an ioctl to set the "mode", + * i.e. playing or recording. + * + * If you write to a half-duplex device in record mode, the data is + * tossed. If you read from the device in play mode, you get silence + * filled buffers at the rate at which samples are naturally + * generated. + * + * If you try to set both play and record mode on a half-duplex + * device, playing takes precedence. + */ + +/* + * Todo: + * - Add softaudio() isr processing for wakeup, select and signals. + * - Allow opens for READ and WRITE (one open each) + * - Setup for single isr for full-duplex + * - Add SIGIO generation for changes in the mixer device + */ + +#include "audio.h" +#if NAUDIO > 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef DEBUG +#include +#ifndef TOLOG +#define TOLOG 0x04 +#endif +void kprintf __P((const char *fmt, int flags, struct tty *tp, va_list ap)); + +void +#ifdef __STDC__ +Dprintf(const char *fmt, ...) +#else +Dprintf(fmt, va_alist) + char *fmt; +#endif +{ + va_list ap; + + va_start(ap, fmt); + kprintf(fmt, TOLOG, NULL, ap); + va_end(ap); +} + +#define DPRINTF(x) if (audiodebug) Dprintf x +int audiodebug = 0; +#else +#define DPRINTF(x) +#endif + +int naudio; /* Count of attached hardware drivers */ + +int audio_blk_ms = AUDIO_BLK_MS; +int audio_backlog = AUDIO_BACKLOG; +int audio_blocksize; + +/* XXX */ +#define splaudio splclock + +/* A block of silence */ +char *auzero_block; + +struct audio_softc *audio_softc[NAUDIO]; + +static int audiosetinfo __P((struct audio_softc *, struct audio_info *)); +static int audiogetinfo __P((struct audio_softc *, struct audio_info *)); + +int audio_open __P((dev_t, int, int, struct proc *)); +int audio_close __P((dev_t, int, int, struct proc *)); +int audio_read __P((dev_t, struct uio *, int)); +int audio_write __P((dev_t, struct uio *, int)); +int audio_ioctl __P((dev_t, int, caddr_t, int, struct proc *)); +int audio_select __P((dev_t, int, struct proc *)); + +int mixer_open __P((dev_t, int, int, struct proc *)); +int mixer_close __P((dev_t, int, int, struct proc *)); +int mixer_ioctl __P((dev_t, int, caddr_t, int, struct proc *)); + +void audio_init_record __P((struct audio_softc *)); +void audio_init_play __P((struct audio_softc *)); +void audiostartr __P((struct audio_softc *)); +void audiostartp __P((struct audio_softc *)); +void audio_rint __P((struct audio_softc *)); +void audio_pint __P((struct audio_softc *)); + +int audio_calc_blksize __P((struct audio_softc *)); +void audio_silence_fill __P((struct audio_softc *, u_char *, int)); +int audio_silence_copyout __P((struct audio_softc *, int, struct uio *)); +void audio_alloc_auzero __P((struct audio_softc *, int)); + +#ifdef DEBUG +void +audio_printsc(struct audio_softc *sc) +{ + printf("hwhandle %x hw_if %x ", sc->hw_hdl, sc->hw_if); + printf("open %x mode %x\n", sc->sc_open, sc->sc_mode); + printf("rchan %x wchan %x ", sc->sc_rchan, sc->sc_wchan); + printf("rring blk %x pring nblk %x\n", sc->rr.nblk, sc->pr.nblk); + printf("rbus %x pbus %x ", sc->sc_rbus, sc->sc_pbus); + printf("blksz %d sib %d ", sc->sc_blksize, sc->sc_smpl_in_blk); + printf("sp50ms %d backlog %d\n", sc->sc_50ms, sc->sc_backlog); + printf("hiwat %d lowat %d rblks %d\n", sc->sc_hiwat, sc->sc_lowat, + sc->sc_rblks); + +} +#endif + +void +audioattach(num) + int num; +{ +} + +/* + * Called from hardware driver. + */ +int +audio_hardware_attach(hwp, hdlp) + struct audio_hw_if *hwp; + caddr_t hdlp; +{ + int *zp, i; + struct audio_softc *sc; + + if (naudio > NAUDIO) { + DPRINTF(("audio_hardware_attach: not enough audio devices: %d > %d\n", + naudio, NAUDIO)); + return(EINVAL); + } + + /* + * Malloc a softc for the device + */ + /* XXX Find the first free slot */ + audio_softc[naudio] = malloc(sizeof(struct audio_softc), M_DEVBUF, M_WAITOK); + sc = audio_softc[naudio]; + bzero(sc, sizeof(struct audio_softc)); + + /* XXX too paranoid? */ + if (hwp->open == 0 || + hwp->close == 0 || + hwp->set_in_sr == 0 || + hwp->get_in_sr == 0 || + hwp->set_out_sr == 0 || + hwp->get_out_sr == 0 || + hwp->set_encoding == 0 || + hwp->get_encoding == 0 || + hwp->set_precision == 0 || + hwp->get_precision == 0 || + hwp->set_channels == 0 || + hwp->get_channels == 0 || + hwp->round_blocksize == 0 || + hwp->set_out_port == 0 || + hwp->get_out_port == 0 || + hwp->set_in_port == 0 || + hwp->get_in_port == 0 || + hwp->commit_settings == 0 || + hwp->get_silence == 0 || + hwp->start_output == 0 || + hwp->start_input == 0 || + hwp->halt_output == 0 || + hwp->halt_input == 0 || + hwp->cont_output == 0 || + hwp->cont_input == 0 || + hwp->getdev == 0 || + hwp->set_port == 0 || + hwp->get_port == 0 || + hwp->query_devinfo == 0) + return(EINVAL); + + sc->hw_if = hwp; + + sc->hw_hdl = hdlp; + + /* + * Alloc DMA play and record buffers + */ + sc->rr.bp = malloc(AU_RING_SIZE, M_DEVBUF, M_WAITOK); + if (sc->rr.bp == 0) { + return (ENOMEM); + } + sc->pr.bp = malloc(AU_RING_SIZE, M_DEVBUF, M_WAITOK); + if (sc->pr.bp == 0) { + free(sc->rr.bp, M_DEVBUF); + return (ENOMEM); + } + + /* + * Set default softc params + */ + sc->sc_pencoding = sc->sc_rencoding = AUDIO_ENCODING_LINEAR; + + /* + * Return the audio unit number + */ + hwp->audio_unit = naudio++; + +#ifdef DEBUG + printf("audio: unit %d attached\n", hwp->audio_unit); +#endif + + return(0); +} + +int +audio_hardware_detach(hwp) + struct audio_hw_if *hwp; +{ + struct audio_softc *sc; + +#ifdef DIAGNOSTIC + if (!hwp) + panic("audio_hardware_detach: null hwp"); + + if (hwp->audio_unit > naudio) + panic("audio_hardware_detach: invalid audio unit"); +#endif + + sc = audio_softc[hwp->audio_unit]; + + if (hwp != sc->hw_if) + return(EINVAL); + + if (sc->sc_open != 0) + return(EBUSY); + + sc->hw_if = 0; + + /* Free audio buffers */ + free(sc->rr.bp, M_DEVBUF); + free(sc->pr.bp, M_DEVBUF); + + free(sc, M_DEVBUF); + audio_softc[hwp->audio_unit] = NULL; + + return(0); +} + +int +audioopen(dev, flags, ifmt, p) + dev_t dev; + int flags, ifmt; + struct proc *p; +{ + switch(AUDIODEV(dev)) { + case SOUND_DEVICE: + case AUDIO_DEVICE: + return audio_open(dev, flags, ifmt, p); + /*NOTREACHED*/ + case MIXER_DEVICE: + return mixer_open(dev, flags, ifmt, p); + /*NOTREACHED*/ + default: + break; + } + return EIO; +} + +/* ARGSUSED */ +int +audioclose(dev, flags, ifmt, p) + dev_t dev; + int flags, ifmt; + struct proc *p; +{ + switch(AUDIODEV(dev)) { + case SOUND_DEVICE: + case AUDIO_DEVICE: + return audio_close(dev, flags, ifmt, p); + /*NOTREACHED*/ + case MIXER_DEVICE: + return mixer_close(dev, flags, ifmt, p); + /*NOTREACHED*/ + default: + break; + } + return EIO; +} + +int +audioread(dev, uio, ioflag) + dev_t dev; + struct uio *uio; + int ioflag; +{ + switch(AUDIODEV(dev)) { + case SOUND_DEVICE: + case AUDIO_DEVICE: + return audio_read(dev, uio, ioflag); + /*NOTREACHED*/ + case MIXER_DEVICE: + return ENODEV; + /*NOTREACHED*/ + default: + break; + } + return EIO; +} + +int +audiowrite(dev, uio, ioflag) + dev_t dev; + struct uio *uio; + int ioflag; +{ + switch(AUDIODEV(dev)) { + case SOUND_DEVICE: + case AUDIO_DEVICE: + return audio_write(dev, uio, ioflag); + /*NOTREACHED*/ + case MIXER_DEVICE: + return ENODEV; + /*NOTREACHED*/ + default: + break; + } + return EIO; +} + +int +audioioctl(dev, cmd, addr, flag, p) + dev_t dev; + int cmd; + caddr_t addr; + int flag; + struct proc *p; +{ + switch(AUDIODEV(dev)) { + case SOUND_DEVICE: + case AUDIO_DEVICE: + return audio_ioctl(dev, cmd, addr, flag, p); + /*NOTREACHED*/ + case MIXER_DEVICE: + return mixer_ioctl(dev, cmd, addr, flag, p); + /*NOTREACHED*/ + default: + break; + } + return EIO; +} + +int +audioselect(dev, rw, p) + dev_t dev; + int rw; + struct proc *p; +{ + switch(AUDIODEV(dev)) { + case SOUND_DEVICE: + case AUDIO_DEVICE: + return audio_select(dev, rw, p); + /*NOTREACHED*/ + case MIXER_DEVICE: + return 1; + /*NOTREACHED*/ + default: + break; + } + return EIO; +} + +/* + * Audio driver + */ +static void +audio_init_ring(rp, blksize) + struct audio_buffer *rp; + int blksize; +{ + int nblk = AU_RING_SIZE / blksize; + + rp->ep = rp->bp + nblk * blksize; + rp->hp = rp->tp = rp->bp; + rp->maxblk = nblk; + rp->nblk = 0; + rp->cb_drops = 0; + rp->cb_pdrops = 0; +} + +void +audio_initbufs(sc) + struct audio_softc *sc; +{ + int nblk = AU_RING_SIZE / sc->sc_blksize; + + audio_init_ring(&sc->rr, sc->sc_blksize); + audio_init_ring(&sc->pr, sc->sc_blksize); + sc->sc_lowat = 1; + sc->sc_hiwat = nblk - sc->sc_lowat; +} + +static inline int +audio_sleep(chan, label) + int *chan; + char *label; +{ + int st; + + if (!label) + label = "audio"; + + *chan = 1; + st = (tsleep((caddr_t)chan, PWAIT | PCATCH, label, 0)); + *chan = 0; + return (st); +} + +static inline void +audio_wakeup(chan) + int *chan; +{ + + if (*chan) { + wakeup((caddr_t)chan); + *chan = 0; + } +} + +int +audio_open(dev, flags, ifmt, p) + dev_t dev; + int flags, ifmt; + struct proc *p; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc; + int s, error; + struct audio_hw_if *hw; + + if (unit >= NAUDIO || !audio_softc[unit]) { + DPRINTF(("audioopen: invalid device unit - %d\n", unit)); + return (ENODEV); + } + + sc = audio_softc[unit]; + hw = sc->hw_if; + + DPRINTF(("audio: open(%x): flags=0x%x sc=0x%x hdl=0x%x\n", dev, flags, sc, sc->hw_hdl)); + if (hw == 0) /* Hardware has not attached to us... */ + return (ENXIO); + + if ((sc->sc_open & (AUOPEN_READ|AUOPEN_WRITE)) != 0) /* XXX use flags */ + return (EBUSY); + + if ((error = hw->open(dev, flags)) != 0) + return (error); + + if (flags&FREAD) + sc->sc_open |= AUOPEN_READ; + if (flags&FWRITE) + sc->sc_open |= AUOPEN_WRITE; + + /* + * Multiplex device: /dev/audio (MU-Law) and /dev/sound (linear) + * The /dev/audio is always (re)set to 8-bit MU-Law mono + * For the other devices, you get what they were last set to. + */ + if (ISDEVAUDIO(dev)) { + /* /dev/audio */ + hw->set_precision(sc->hw_hdl, 8); + hw->set_encoding(sc->hw_hdl, AUDIO_ENCODING_ULAW); + hw->set_in_sr(sc->hw_hdl, 8000); + hw->set_out_sr(sc->hw_hdl, 8000); + hw->set_channels(sc->hw_hdl, 1); + + sc->sc_pencoding = AUDIO_ENCODING_ULAW; + sc->sc_rencoding = AUDIO_ENCODING_ULAW; + } + + /* + * Sample rate and precision are supposed to be set to proper + * default values by the hardware driver, so that it may give + * us these values. + */ +#ifdef DIAGNOSTIC + if (hw->get_precision(sc->hw_hdl) == 0) + panic("audioopen: hardware driver returned 0 for get_precision"); +#endif + sc->sc_blksize = audio_blocksize = audio_calc_blksize(sc); + audio_alloc_auzero(sc, sc->sc_blksize); + + sc->sc_smpl_in_blk = audio_blocksize / + (hw->get_precision(sc->hw_hdl) / NBBY); + sc->sc_50ms = 50 * hw->get_out_sr(sc->hw_hdl) / 1000; + sc->sc_backlog = audio_backlog; + + audio_initbufs(sc); + DPRINTF(("audio: rr.bp=%x-%x pr.bp=%x-%x\n", + sc->rr.bp, sc->rr.ep, sc->pr.bp, sc->pr.ep)); + + hw->commit_settings(sc->hw_hdl); + + s = splaudio(); + + /* nothing read or written yet */ + sc->sc_rseek = 0; + sc->sc_wseek = 0; + + sc->sc_rchan = 0; + sc->sc_wchan = 0; + + sc->sc_rbus = 0; + sc->sc_pbus = 0; + + if ((flags & FWRITE) != 0) { + audio_init_play(sc); + /* audio_pint(sc); ??? */ + } + if ((flags & FREAD) != 0) { + /* Play takes precedence if HW is half-duplex */ + if (hw->full_duplex || ((flags & FWRITE) == 0)) { + audio_init_record(sc); + /* audiostartr(sc); don't start recording until read */ + } + } + splx(s); + return (0); +} + +/* + * Must be called from task context. + */ +void +audio_init_record(sc) + struct audio_softc *sc; +{ + int s = splaudio(); + + sc->sc_mode |= 1<hw_if->speaker_ctl) + sc->hw_if->speaker_ctl(sc->hw_hdl, SPKR_OFF); + splx(s); +} + +/* + * Must be called from task context. + */ +void +audio_init_play(sc) + struct audio_softc *sc; +{ + int s = splaudio(); + + sc->sc_mode |= 1<sc_rblks = 0; + if (sc->hw_if->speaker_ctl) + sc->hw_if->speaker_ctl(sc->hw_hdl, SPKR_ON); + splx(s); +} + +static int +audio_drain(sc) + struct audio_softc *sc; +{ + int error; + + while (sc->pr.nblk > 0) { + DPRINTF(("audio_drain: nblk=%d\n", sc->pr.nblk)); + error = audio_sleep(&sc->sc_wchan, "aud dr"); + if (error != 0) + return (error); + } + return (0); +} + +/* + * Close an audio chip. + */ +/* ARGSUSED */ +int +audio_close(dev, flags, ifmt, p) + dev_t dev; + int flags, ifmt; + struct proc *p; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc = audio_softc[unit]; + struct audio_hw_if *hw = sc->hw_if; + int s; + + DPRINTF(("audio: close; unit %d\n", unit)); + + /* + * Block until output drains, but allow ^C interrupt. + */ + sc->sc_lowat = 0; /* avoid excessive wakeups */ + s = splaudio(); + /* + * If there is pending output, let it drain (unless + * the output is paused). + */ + if (sc->sc_pbus && sc->pr.nblk > 0 && !sc->pr.cb_pause) { + if (!audio_drain(sc) && hw->drain) + (void)hw->drain(sc->hw_hdl); + } + + hw->close(sc->hw_hdl); + + if (flags&FREAD) + sc->sc_open &= ~AUOPEN_READ; + if (flags&FWRITE) + sc->sc_open &= ~AUOPEN_WRITE; + + splx(s); + DPRINTF(("audio: close done\n")); + + return (0); +} + +int +audio_read(dev, uio, ioflag) + dev_t dev; + struct uio *uio; + int ioflag; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc = audio_softc[unit]; + struct audio_hw_if *hw = sc->hw_if; + struct audio_buffer *cb = &sc->rr; + u_char *hp; + int blocksize = sc->sc_blksize; + int error, s; + + DPRINTF(("audio: read cc=%d mode=%d rblks=%d\n", + uio->uio_resid, sc->sc_mode, sc->sc_rblks)); + + if (uio->uio_resid == 0) + return (0); + + if (uio->uio_resid < blocksize) + return (EINVAL); + + /* First sample we'll read in sample space */ + sc->sc_rseek = cb->au_stamp - AU_RING_LEN(cb); + + /* + * If hardware is half-duplex and currently playing, return + * silence blocks based on the number of blocks we have output. + */ + if ((!hw->full_duplex) && + (sc->sc_mode & 1<sc_rblks <= 0) { + DPRINTF(("audioread: sc_rblks=%d\n", sc->sc_rblks)); + if (ioflag & IO_NDELAY) { + splx(s); + return (EWOULDBLOCK); + } + error = audio_sleep(&sc->sc_rchan, "aud hr"); + if (error != 0) { + splx(s); + return (error); + } + } + splx(s); + error = audio_silence_copyout(sc, blocksize, uio); + if (error) + break; + --sc->sc_rblks; + } while (uio->uio_resid >= blocksize); + return (error); + } + error = 0; + do { + while (cb->nblk <= 0) { + if (ioflag & IO_NDELAY) { + error = EWOULDBLOCK; + return (error); + } + s = splaudio(); + if (!sc->sc_rbus) + audiostartr(sc); + error = audio_sleep(&sc->sc_rchan, "aud rd"); + splx(s); + if (error != 0) + return (error); + } + hp = cb->hp; + if (hw->sw_decode) + hw->sw_decode(sc->sc_rencoding, hp, blocksize); + error = uiomove(hp, blocksize, uio); + if (error) + break; + hp += blocksize; + if (hp >= cb->ep) + hp = cb->bp; + cb->hp = hp; + --cb->nblk; + } while (uio->uio_resid >= blocksize); + + return (error); +} + +void +audio_clear(sc) + struct audio_softc *sc; +{ + int s = splaudio(); + + if (sc->sc_rbus || sc->sc_pbus) { + sc->hw_if->halt_output(sc->hw_hdl); + sc->hw_if->halt_input(sc->hw_hdl); + sc->sc_rbus = 0; + sc->sc_pbus = 0; + } + AU_RING_INIT(&sc->rr); + AU_RING_INIT(&sc->pr); + + splx(s); +} + +int +audio_calc_blksize(sc) + struct audio_softc *sc; +{ + struct audio_hw_if *hw = sc->hw_if; + int bs; + + bs = hw->get_precision(sc->hw_hdl) / NBBY; + bs = hw->get_out_sr(sc->hw_hdl) * audio_blk_ms * bs / 1000; + bs *= hw->get_channels(sc->hw_hdl); + bs = hw->round_blocksize(sc->hw_hdl, bs); + if (bs > AU_RING_SIZE) + bs = AU_RING_SIZE; + + bs &= ~1; /* make it even, in case of stereo */ + return(bs); +} + +void +audio_silence_fill(sc, p, n) + struct audio_softc *sc; + u_char *p; + int n; +{ + struct audio_hw_if *hw = sc->hw_if; + int i; + u_int auzero; + + auzero = hw->get_silence(sc->sc_pencoding); + + while (--n >= 0) + *p++ = auzero; +} + +audio_silence_copyout(sc, n, uio) + struct audio_softc *sc; + int n; + struct uio *uio; +{ + struct audio_hw_if *hw = sc->hw_if; + struct iovec *iov; + int error = 0; + u_int auzero; + + auzero = hw->get_silence(sc->sc_rencoding); + + while (n > 0 && uio->uio_resid) { + iov = uio->uio_iov; + if (iov->iov_len == 0) { + uio->uio_iov++; + uio->uio_iovcnt--; + continue; + } + switch (uio->uio_segflg) { + case UIO_USERSPACE: + error = copyout(&auzero, iov->iov_base, 1); + if (error) + return (error); + break; + + case UIO_SYSSPACE: + bcopy((caddr_t)&auzero, iov->iov_base, 1); + break; + } + iov->iov_base++; + iov->iov_len--; + uio->uio_resid--; + uio->uio_offset++; + n--; + } + return (error); +} + +void +audio_alloc_auzero(sc, bs) + struct audio_softc *sc; + int bs; +{ + struct audio_hw_if *hw = sc->hw_if; + u_char *p, silence; + int i; + + if (auzero_block) + free(auzero_block, M_DEVBUF); + + auzero_block = malloc(bs, M_DEVBUF, M_WAITOK); +#ifdef DIAGNOSTIC + if (auzero_block == 0) { + panic("audio_alloc_auzero: malloc auzero_block failed"); + } +#endif + silence = hw->get_silence(sc->sc_pencoding); + + p = auzero_block; + for (i = 0; i < bs; i++) + *p++ = silence; + + if (hw->sw_encode) + hw->sw_encode(sc->sc_pencoding, auzero_block, bs); +} + + +int +audio_write(dev, uio, ioflag) + dev_t dev; + struct uio *uio; + int ioflag; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc = audio_softc[unit]; + struct audio_hw_if *hw = sc->hw_if; + struct audio_buffer *cb = &sc->pr; + u_char *tp; + int error, s, cc; + int blocksize = sc->sc_blksize; + + DPRINTF(("audio: write cc=%d hiwat=%d\n", uio->uio_resid, sc->sc_hiwat)); + /* + * If half-duplex and currently recording, throw away data. + */ + if (!hw->full_duplex && + (sc->sc_mode & 1<uio_offset += uio->uio_resid; + uio->uio_resid = 0; + DPRINTF(("audiowrite: half-dpx read busy\n")); + return (0); + } + error = 0; + + while (uio->uio_resid > 0) { + int watermark = sc->sc_hiwat; + while (cb->nblk > watermark) { + DPRINTF(("audiowrite: nblk=%d watermark=%d\n", cb->nblk, watermark)); + if (ioflag & IO_NDELAY) { + error = EWOULDBLOCK; + return (error); + } + error = audio_sleep(&sc->sc_wchan, "aud wr"); + if (error != 0) { + return (error); + } + watermark = sc->sc_lowat; + } +#if 0 + if (cb->nblk == 0 && + cb->maxblk > sc->sc_backlog && + uio->uio_resid <= blocksize && + (cb->au_stamp - sc->sc_wseek) > sc->sc_50ms) { + /* + * the write is 'small', the buffer is empty + * and we have been silent for at least 50ms + * so we might be dealing with an application + * that writes frames synchronously with + * reading them. If so, we need an output + * backlog to cover scheduling delays or + * there will be gaps in the sound output. + * Also take this opportunity to reset the + * buffer pointers in case we ended up on + * a bad boundary (odd byte, blksize bytes + * from end, etc.). + */ + DPRINTF(("audiowrite: set backlog %d\n", sc->sc_backlog)); + s = splaudio(); + cb->hp = cb->bp; + cb->nblk = sc->sc_backlog; + cb->tp = cb->hp + sc->sc_backlog * blocksize; + splx(s); + audio_silence_fill(sc, cb->hp, sc->sc_backlog * blocksize); + } +#endif + /* Calculate sample number of first sample in block we write */ + s = splaudio(); + sc->sc_wseek = AU_RING_LEN(cb) + cb->au_stamp; + splx(s); + + tp = cb->tp; + cc = uio->uio_resid; + +#ifdef DEBUG + if (audiodebug > 1) { + int left = cb->ep - tp; + dprintf("audiowrite: cc=%d tp=0x%x bs=%d nblk=%d left=%d\n", cc, tp, blocksize, cb->nblk, left); + } +#endif +#ifdef DIAGNOSTIC + { + int towrite = (cc < blocksize)?cc:blocksize; + + /* check for an overwrite. Should never happen */ + if ((tp + towrite) > cb->ep) { + DPRINTF(("audiowrite: overwrite tp=0x%x towrite=%d ep=0x%x bs=%d\n", + tp, towrite, cb->ep, blocksize)); + printf("audiowrite: overwrite tp=0x%x towrite=%d ep=0x%x\n", + tp, towrite, cb->ep); + tp = cb->bp; + } + } +#endif + if (cc < blocksize) { + error = uiomove(tp, cc, uio); + if (error == 0) { + /* fill with audio silence */ + tp += cc; + cc = blocksize - cc; + audio_silence_fill(sc, tp, cc); + DPRINTF(("audiowrite: auzero 0x%x %d 0x%x\n", + tp, cc, *(int *)tp)); + tp += cc; + } + } else { + error = uiomove(tp, blocksize, uio); + if (error == 0) { + tp += blocksize; + } + } + if (error) { +#ifdef DEBUG + printf("audiowrite1: uiomove failed %d; cc=%d tp=0x%x bs=%d\n", error, cc, tp, blocksize); +#endif + break; + } + + if (hw->sw_encode) { + hw->sw_encode(sc->sc_pencoding, cb->tp, blocksize); + } + + /* wrap the ring buffer if at end */ + s = splaudio(); + if (tp >= cb->ep) + tp = cb->bp; + cb->tp = tp; + ++cb->nblk; + + /* + * If output isn't active, start it up. + */ + if (sc->sc_pbus == 0) + audiostartp(sc); + splx(s); + } + return (error); +} + +int +audio_ioctl(dev, cmd, addr, flag, p) + dev_t dev; + int cmd; + caddr_t addr; + int flag; + struct proc *p; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc = audio_softc[unit]; + struct audio_hw_if *hw = sc->hw_if; + int error = 0, s; + + DPRINTF(("audio: ioctl(%d,'%c',%d)\n", + IOCPARM_LEN(cmd), IOCGROUP(cmd), cmd&0xff)); + switch (cmd) { + + case AUDIO_FLUSH: + DPRINTF(("AUDIO_FLUSH\n")); + audio_clear(sc); + s = splaudio(); + if ((sc->sc_mode & 1<sc_pbus == 0)) + audiostartp(sc); + /* Again, play takes precedence on half-duplex hardware */ + if ((sc->sc_mode & 1<full_duplex || + ((sc->sc_mode & 1<rr.cb_drops != 0; + break; + + /* + * How many samples will elapse until mike hears the first + * sample of what we last wrote? + */ + case AUDIO_WSEEK: + s = splaudio(); + *(u_long *)addr = sc->sc_wseek - sc->pr.au_stamp + + AU_RING_LEN(&sc->rr); + splx(s); + break; + case AUDIO_SETINFO: + DPRINTF(("AUDIO_SETINFO\n")); + error = audiosetinfo(sc, (struct audio_info *)addr); + break; + + case AUDIO_GETINFO: + DPRINTF(("AUDIO_GETINFO\n")); + error = audiogetinfo(sc, (struct audio_info *)addr); + break; + + case AUDIO_DRAIN: + DPRINTF(("AUDIO_DRAIN\n")); + error = audio_drain(sc); + if (!error && hw->drain) + error = hw->drain(sc->hw_hdl); + break; + + case AUDIO_GETDEV: + DPRINTF(("AUDIO_GETDEV\n")); + error = hw->getdev(sc->hw_hdl, (audio_device_t *)addr); + break; + + case AUDIO_GETENC: + DPRINTF(("AUDIO_GETENC\n")); + error = hw->query_encoding(sc->hw_hdl, (struct audio_encoding *)addr); + break; + + case AUDIO_GETFD: + DPRINTF(("AUDIO_GETFD\n")); + *(int *)addr = hw->full_duplex; + break; + + case AUDIO_SETFD: + DPRINTF(("AUDIO_SETFD\n")); + error = hw->setfd(sc->hw_hdl, (int)addr); + break; + + default: + DPRINTF(("audio: unknown ioctl\n")); + error = EINVAL; + break; + } + DPRINTF(("audio: ioctl(%d,'%c',%d) result %d\n", + IOCPARM_LEN(cmd), IOCGROUP(cmd), cmd&0xff, error)); + return (error); +} + +int +audio_select(dev, rw, p) + dev_t dev; + int rw; + struct proc *p; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc = audio_softc[unit]; + int s = splaudio(); + +#if 0 + DPRINTF(("audio: select rw=%d mode=%d rblks=%d rr.nblk=%d\n", + rw, sc->sc_mode, sc->sc_rblks, sc->rr.nblk)); +#endif + switch (rw) { + + case FREAD: + if (sc->sc_mode & 1<sc_rblks > 0) { + splx(s); + return (1); + } + } else if (sc->rr.nblk > 0) { + splx(s); + return (1); + } + selrecord(p, &sc->sc_rsel); + break; + + case FWRITE: + /* + * Can write if we're recording because it gets preempted. + * Otherwise, can write when below low water. + * XXX this won't work right if we're in + * record mode -- we need to note that a write + * select has happed and flip the speaker. + * + * XXX The above XXX-comment is SoundBlaster-dependent, + * right? Or maybe specific to half-duplex devices? + */ + if (sc->sc_mode & 1<pr.nblk < sc->sc_lowat) { + splx(s); + return (1); + } + selrecord(p, &sc->sc_wsel); + break; + } + splx(s); + return (0); +} + +void +audiostartr(sc) + struct audio_softc *sc; +{ + int err; + + DPRINTF(("audiostartr: tp=0x%x\n", sc->rr.tp)); + + if (err = sc->hw_if->start_input(sc->hw_hdl, sc->rr.tp, sc->sc_blksize, + audio_rint, (void *)sc)) { + DPRINTF(("audiostartr failed: %d\n", err)); + audio_clear(sc); + } + else + sc->sc_rbus = 1; +} + +void +audiostartp(sc) + struct audio_softc *sc; +{ + int rval; + + DPRINTF(("audiostartp: hp=0x%x nblk=%d\n", sc->pr.hp, sc->pr.nblk)); + + if (sc->pr.nblk > 0) { + u_char *hp = sc->pr.hp; + if (rval = sc->hw_if->start_output(sc->hw_hdl, hp, sc->sc_blksize, + audio_pint, (void *)sc)) { + DPRINTF(("audiostartp failed: %d\n", rval)); + } + else { + --sc->pr.nblk; + sc->sc_pbus = 1; + hp += sc->sc_blksize; + if (hp >= sc->pr.ep) + hp = sc->pr.bp; + sc->pr.hp = hp; + } + } +} + +/* + * Called from HW driver module on completion of dma output. + * Start output of new block, wrap in ring buffer if needed. + * If no more buffers to play, output zero instead. + * Do a wakeup if necessary. + */ +void +audio_pint(sc) + struct audio_softc *sc; +{ + u_char *hp; + int cc = sc->sc_blksize; + struct audio_hw_if *hw = sc->hw_if; + struct audio_buffer *cb = &sc->pr; + int err; + + if (cb->nblk > 0) { + hp = cb->hp; + if (cb->cb_pause) { + cb->cb_pdrops++; +#ifdef DEBUG + if (audiodebug > 1) + dprintf("audio_pint: paused %d\n", cb->cb_pdrops); +#endif + goto psilence; + } + else { +#ifdef DEBUG + if (audiodebug > 1) + dprintf("audio_pint: hp=0x%x cc=%d\n", hp, cc); +#endif + if (err = hw->start_output(sc->hw_hdl, hp, cc, + audio_pint, (void *)sc)) { + DPRINTF(("audio_pint restart failed: %d\n", err)); + audio_clear(sc); + } + else { + --cb->nblk; + hp += cc; + if (hp >= cb->ep) + hp = cb->bp; + cb->hp = hp; + cb->au_stamp += sc->sc_smpl_in_blk; + + ++sc->sc_rblks; + } + } + } + else { + cb->cb_drops++; +#ifdef DEBUG + if (audiodebug > 1) + dprintf("audio_pint: drops=%d auzero %d 0x%x\n", cb->cb_drops, cc, *(int *)auzero_block); +#endif + psilence: + if (err = hw->start_output(sc->hw_hdl, + auzero_block, cc, + audio_pint, (void *)sc)) { + DPRINTF(("audio_pint zero failed: %d\n", err)); + audio_clear(sc); + } + } + +#ifdef DEBUG + DPRINTF(("audio_pint: mode=%d pause=%d nblk=%d lowat=%d\n", + sc->sc_mode, cb->cb_pause, cb->nblk, sc->sc_lowat)); +#endif + if (sc->sc_mode & 1<cb_pause) { + if (cb->nblk <= sc->sc_lowat) { + audio_wakeup(&sc->sc_wchan); + selwakeup(&sc->sc_wsel); + } + } + + /* XXX possible to return one or more "phantom blocks" now. */ + /* Only in half duplex? */ + if (hw->full_duplex) { + audio_wakeup(&sc->sc_rchan); + selwakeup(&sc->sc_rsel); + } +} + +/* + * Called from HW driver module on completion of dma input. + * Mark it as input in the ring buffer (fiddle pointers). + * Do a wakeup if necessary. + */ +void +audio_rint(sc) + struct audio_softc *sc; +{ + u_char *tp; + int cc = sc->sc_blksize; + struct audio_hw_if *hw = sc->hw_if; + struct audio_buffer *cb = &sc->rr; + int err; + + tp = cb->tp; + if (cb->cb_pause) { + cb->cb_pdrops++; + DPRINTF(("audio_rint: pdrops %d\n", cb->cb_pdrops)); + } + else { + tp += cc; + if (tp >= cb->ep) + tp = cb->bp; + if (++cb->nblk < cb->maxblk) { +#ifdef DEBUG + if (audiodebug > 1) + dprintf("audio_rint: tp=0x%x cc=%d\n", tp, cc); +#endif + if (err = hw->start_input(sc->hw_hdl, tp, cc, + audio_rint, (void *)sc)) { + DPRINTF(("audio_rint start failed: %d\n", err)); + audio_clear(sc); + } + cb->au_stamp += sc->sc_smpl_in_blk; + } else { + /* XXX */ + /* How do we count dropped input samples due to overrun? */ + /* Start a "dummy DMA transfer" when the input ring buffer */ + /* is full and count # of these? Seems pretty lame to */ + /* me, but how else are we going to do this? */ + cb->cb_drops++; + sc->sc_rbus = 0; + DPRINTF(("audio_rint: drops %d\n", cb->cb_drops)); + } + cb->tp = tp; + + audio_wakeup(&sc->sc_rchan); + selwakeup(&sc->sc_rsel); + } +} + +static int +audiosetinfo(sc, ai) + struct audio_softc *sc; + struct audio_info *ai; +{ + struct audio_prinfo *r = &ai->record, *p = &ai->play; + int cleared = 0; + int bsize, bps, error = 0; + struct audio_hw_if *hw = sc->hw_if; + mixer_ctrl_t ct; + + if (hw == 0) /* HW has not attached */ + return(ENXIO); + + if (p->sample_rate != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_out_sr(sc->hw_hdl, p->sample_rate); + if (error != 0) + return(error); + sc->sc_50ms = 50 * hw->get_out_sr(sc->hw_hdl) / 1000; + } + if (r->sample_rate != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_in_sr(sc->hw_hdl, r->sample_rate); + if (error != 0) + return(error); + sc->sc_50ms = 50 * hw->get_in_sr(sc->hw_hdl) / 1000; + } + if (p->encoding != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_encoding(sc->hw_hdl, p->encoding); + if (error != 0) + return(error); + sc->sc_pencoding = p->encoding; + } + if (r->encoding != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_encoding(sc->hw_hdl, r->encoding); + if (error != 0) + return(error); + sc->sc_rencoding = r->encoding; + } + if (p->precision != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_precision(sc->hw_hdl, p->precision); + if (error != 0) + return(error); + + sc->sc_blksize = audio_blocksize = audio_calc_blksize(sc); + audio_alloc_auzero(sc, sc->sc_blksize); + bps = hw->get_precision(sc->hw_hdl) / NBBY; + sc->sc_smpl_in_blk = sc->sc_blksize / bps; + audio_initbufs(sc); + } + if (r->precision != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_precision(sc->hw_hdl, r->precision); + if (error != 0) + return(error); + + sc->sc_blksize = audio_blocksize = audio_calc_blksize(sc); + audio_alloc_auzero(sc, sc->sc_blksize); + bps = hw->get_precision(sc->hw_hdl) / NBBY; + sc->sc_smpl_in_blk = sc->sc_blksize / bps; + audio_initbufs(sc); + } + if (p->channels != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_channels(sc->hw_hdl, p->channels); + if (error != 0) + return(error); + + sc->sc_blksize = audio_blocksize = audio_calc_blksize(sc); + audio_alloc_auzero(sc, sc->sc_blksize); + bps = hw->get_precision(sc->hw_hdl) / NBBY; + sc->sc_smpl_in_blk = sc->sc_blksize / bps; + audio_initbufs(sc); + } + if (r->channels != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_channels(sc->hw_hdl, r->channels); + if (error != 0) + return(error); + + sc->sc_blksize = audio_blocksize = audio_calc_blksize(sc); + audio_alloc_auzero(sc, sc->sc_blksize); + bps = hw->get_precision(sc->hw_hdl) / NBBY; + sc->sc_smpl_in_blk = sc->sc_blksize / bps; + audio_initbufs(sc); + } + if (p->port != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_out_port(sc->hw_hdl, p->port); + if (error != 0) + return(error); + } + if (r->port != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + error = hw->set_in_port(sc->hw_hdl, r->port); + if (error != 0) + return(error); + } + if (p->gain != ~0) { + ct.dev = hw->get_out_port(sc->hw_hdl); + ct.un.value.num_channels = 1; + ct.un.value.level[AUDIO_MIXER_LEVEL_MONO] = p->gain; + hw->set_port(sc->hw_hdl, &ct); + } + if (r->gain != ~0) { + ct.dev = hw->get_in_port(sc->hw_hdl); + ct.un.value.num_channels = 1; + ct.un.value.level[AUDIO_MIXER_LEVEL_MONO] = r->gain; + hw->set_port(sc->hw_hdl, &ct); + } + + if (p->pause != (u_char)~0) { + sc->pr.cb_pause = p->pause; + if (!p->pause) { + int s = splaudio(); + audiostartp(sc); + splx(s); + } + } + if (r->pause != (u_char)~0) { + sc->rr.cb_pause = r->pause; + if (!r->pause) { + int s = splaudio(); + audiostartr(sc); + splx(s); + } + } + + if (ai->blocksize != ~0) { /* Explicitly specified, possibly */ + /* overriding changes done above. */ + if (!cleared) + audio_clear(sc); + cleared = 1; + if (ai->blocksize == 0) + bsize = audio_blocksize; + else if (ai->blocksize > AU_RING_SIZE) + bsize = AU_RING_SIZE; + else + bsize = ai->blocksize; + bsize = hw->round_blocksize(sc->hw_hdl, bsize); + if (bsize > AU_RING_SIZE) + bsize = AU_RING_SIZE; + ai->blocksize = sc->sc_blksize = bsize; + audio_alloc_auzero(sc, sc->sc_blksize); + bps = hw->get_precision(sc->hw_hdl) / NBBY; + sc->sc_smpl_in_blk = bsize / bps; + audio_initbufs(sc); + } + if (ai->hiwat != ~0) { + if ((unsigned)ai->hiwat > sc->pr.maxblk) + ai->hiwat = sc->pr.maxblk; + if (sc->sc_hiwat != 0) + sc->sc_hiwat = ai->hiwat; + } + if (ai->lowat != ~0) { + if ((unsigned)ai->lowat > sc->pr.maxblk) + ai->lowat = sc->pr.maxblk; + sc->sc_lowat = ai->lowat; + } + if (ai->backlog != ~0) { + if ((unsigned)ai->backlog > (sc->pr.maxblk/2)) + ai->backlog = sc->pr.maxblk/2; + sc->sc_backlog = ai->backlog; + } + if (ai->mode != ~0) { + if (!cleared) + audio_clear(sc); + cleared = 1; + sc->sc_mode = ai->mode; + if (sc->sc_mode & 1<full_duplex) /* Play takes precedence */ + sc->sc_mode &= ~(1<sc_mode & 1<commit_settings(sc->hw_hdl); + if (error != 0) + return (error); + + if (cleared) { + int s = splaudio(); + if (sc->sc_mode & 1<sc_mode & 1<record, *p = &ai->play; + struct audio_hw_if *hw = sc->hw_if; + mixer_ctrl_t ct; + + if (hw == 0) /* HW has not attached */ + return(ENXIO); + + p->sample_rate = hw->get_out_sr(sc->hw_hdl); + r->sample_rate = hw->get_in_sr(sc->hw_hdl); + p->channels = r->channels = hw->get_channels(sc->hw_hdl); + p->precision = r->precision = hw->get_precision(sc->hw_hdl); + p->encoding = hw->get_encoding(sc->hw_hdl); + r->encoding = hw->get_encoding(sc->hw_hdl); + + r->port = hw->get_in_port(sc->hw_hdl); + p->port = hw->get_out_port(sc->hw_hdl); + + ct.dev = r->port; + ct.un.value.num_channels = 1; + if (hw->get_port(sc->hw_hdl, &ct) == 0) + r->gain = ct.un.value.level[AUDIO_MIXER_LEVEL_MONO]; + else + r->gain = AUDIO_MAX_GAIN/2; + + ct.dev = p->port; + ct.un.value.num_channels = 1; + if (hw->get_port(sc->hw_hdl, &ct) == 0) + p->gain = ct.un.value.level[AUDIO_MIXER_LEVEL_MONO]; + else + p->gain = AUDIO_MAX_GAIN/2; + + p->pause = sc->pr.cb_pause; + r->pause = sc->rr.cb_pause; + p->error = sc->pr.cb_drops != 0; + r->error = sc->rr.cb_drops != 0; + + p->open = ((sc->sc_open & AUOPEN_WRITE) != 0); + r->open = ((sc->sc_open & AUOPEN_READ) != 0); + + p->samples = sc->pr.au_stamp - sc->pr.cb_pdrops; + r->samples = sc->rr.au_stamp - sc->rr.cb_pdrops; + + p->seek = sc->sc_wseek; + r->seek = sc->sc_rseek; + + ai->blocksize = sc->sc_blksize; + ai->hiwat = sc->sc_hiwat; + ai->lowat = sc->sc_lowat; + ai->backlog = sc->sc_backlog; + ai->mode = (sc->sc_mode & (1<= NAUDIO || !audio_softc[unit]) { + DPRINTF(("mixeropen: invalid device unit - %d\n", unit)); + return (ENODEV); + } + + sc = audio_softc[unit]; + hw = sc->hw_if; + + DPRINTF(("mixer: open(%x): flags=0x%x sc=0x%x\n", dev, flags, sc)); + if (hw == 0) /* Hardware has not attached to us... */ + return (ENXIO); + + return (0); +} + +/* + * Close a mixer device + */ +/* ARGSUSED */ +int +mixer_close(dev, flags, ifmt, p) + dev_t dev; + int flags, ifmt; + struct proc *p; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc = audio_softc[unit]; + struct audio_hw_if *hw = sc->hw_if; + int s; + + DPRINTF(("mixer: close; unit %d\n", unit)); + + hw->close(sc->hw_hdl); + + return (0); +} + +int +mixer_ioctl(dev, cmd, addr, flag, p) + dev_t dev; + int cmd; + caddr_t addr; + int flag; + struct proc *p; +{ + int unit = AUDIOUNIT(dev); + struct audio_softc *sc = audio_softc[unit]; + struct audio_hw_if *hw = sc->hw_if; + int error = EINVAL; + + DPRINTF(("mixer: ioctl(%d,'%c',%d)\n", + IOCPARM_LEN(cmd), IOCGROUP(cmd), cmd&0xff)); + + switch (cmd) { + case AUDIO_GETDEV: + DPRINTF(("AUDIO_GETDEV\n")); + error = hw->getdev(sc->hw_hdl, (audio_device_t *)addr); + break; + + case AUDIO_MIXER_DEVINFO: + DPRINTF(("AUDIO_MIXER_DEVINFO\n")); + error = hw->query_devinfo(sc->hw_hdl, (mixer_devinfo_t *)addr); + break; + + case AUDIO_MIXER_READ: + DPRINTF(("AUDIO_MIXER_READ\n")); + error = hw->get_port(sc->hw_hdl, (mixer_ctrl_t *)addr); + break; + + case AUDIO_MIXER_WRITE: + DPRINTF(("AUDIO_MIXER_WRITE\n")); + error = hw->set_port(sc->hw_hdl, (mixer_ctrl_t *)addr); + break; + + default: + error = EINVAL; + break; + } + DPRINTF(("mixer: ioctl(%d,'%c',%d) result %d\n", + IOCPARM_LEN(cmd), IOCGROUP(cmd), cmd&0xff, error)); + return (error); +} +#endif diff --git a/sys/dev/audio_if.h b/sys/dev/audio_if.h new file mode 100644 index 000000000000..5905ddb6c6f8 --- /dev/null +++ b/sys/dev/audio_if.h @@ -0,0 +1,133 @@ +/* $NetBSD: audio_if.h,v 1.1 1995/02/21 01:36:58 brezak Exp $ */ + +/* + * Copyright (c) 1994 Havard Eidnes. + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * $Id: audio_if.h,v 1.1 1995/02/21 01:36:58 brezak Exp $ + */ + +/* + * Generic interface to hardware driver. + */ + +struct audio_hw_if { + int (*open)__P((dev_t, int)); /* open hardware */ + void (*close)__P((caddr_t)); /* close hardware */ + int (*drain)__P((caddr_t)); /* Optional: drain buffers */ + + /* Sample rate */ + int (*set_in_sr)__P((caddr_t, u_long)); + u_long (*get_in_sr)__P((caddr_t)); + int (*set_out_sr)__P((caddr_t, u_long)); + u_long (*get_out_sr)__P((caddr_t)); + + /* Encoding. */ + /* XXX should we have separate in/out? */ + int (*query_encoding)__P((caddr_t, struct audio_encoding *)); + int (*set_encoding)__P((caddr_t, u_int)); + int (*get_encoding)__P((caddr_t)); + + /* Precision = bits/sample, usually 8 or 16 */ + /* XXX should we have separate in/out? */ + int (*set_precision)__P((caddr_t, u_int)); + int (*get_precision)__P((caddr_t)); + + /* Channels - mono(1), stereo(2) */ + int (*set_channels)__P((caddr_t, int)); + int (*get_channels)__P((caddr_t)); + + /* Hardware may have some say in the blocksize to choose */ + int (*round_blocksize)__P((caddr_t, int)); + + /* Ports (in/out ports) */ + int (*set_out_port)__P((caddr_t, int)); + int (*get_out_port)__P((caddr_t)); + int (*set_in_port)__P((caddr_t, int)); + int (*get_in_port)__P((caddr_t)); + + /* + * Changing settings may require taking device out of "data mode", + * which can be quite expensive. Also, audiosetinfo() may + * change several settings in quick succession. To avoid + * having to take the device in/out of "data mode", we provide + * this function which indicates completion of settings + * adjustment. + */ + int (*commit_settings)__P((caddr_t)); + + /* Return silence value for encoding */ + u_int (*get_silence)__P((int)); + + /* Software en/decode functions, set if SW coding required by HW */ + void (*sw_encode)__P((int, u_char *, int)); + void (*sw_decode)__P((int, u_char *, int)); + + /* Start input/output routines. These usually control DMA. */ + int (*start_output)__P((caddr_t, void *, int, void (*)(), void *)); + int (*start_input)__P((caddr_t, void *, int, void (*)(), void *)); + int (*halt_output)__P((caddr_t)); + int (*halt_input)__P((caddr_t)); + int (*cont_output)__P((caddr_t)); + int (*cont_input)__P((caddr_t)); + + int (*speaker_ctl)__P((caddr_t, int)); +#define SPKR_ON 1 +#define SPKR_OFF 0 + + int (*getdev)__P((caddr_t, struct audio_device *)); + int (*setfd)__P((caddr_t, int)); + + /* Mixer (in/out ports) */ + int (*set_port)__P((caddr_t, mixer_ctrl_t *)); + int (*get_port)__P((caddr_t, mixer_ctrl_t *)); + + int (*query_devinfo)__P((caddr_t, mixer_devinfo_t *)); + + int full_duplex; /* non-null if HW is able to do full-duplex */ + int audio_unit; +}; + +/* Register / deregister hardware driver */ +extern int audio_hardware_attach __P((struct audio_hw_if *, caddr_t)); +extern int audio_hardware_detach __P((struct audio_hw_if *)); + +/* Device identity flags */ +#define SOUND_DEVICE 0 +#define AUDIO_DEVICE 0x80 +#define MIXER_DEVICE 0x10 + +#define ISDEVAUDIO(x) ((minor(x)&0xf0) == AUDIO_DEVICE) +#define ISDEVSOUND(x) ((minor(x)&0xf0) == SOUND_DEVICE) +#define ISDEVMIXER(x) ((minor(x)&0xf0) == MIXER_DEVICE) + +#define AUDIOUNIT(x) (minor(x)&0x0f) +#define AUDIODEV(x) (minor(x)&0xf0) diff --git a/sys/dev/audiovar.h b/sys/dev/audiovar.h new file mode 100644 index 000000000000..e2efb2e00453 --- /dev/null +++ b/sys/dev/audiovar.h @@ -0,0 +1,122 @@ +/* $NetBSD: audiovar.h,v 1.1 1995/02/21 01:36:59 brezak Exp $ */ + +/* + * Copyright (c) 1991-1993 Regents of the University of California. + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * From: Header: audiovar.h,v 1.3 93/07/18 14:07:25 mccanne Exp (LBL) + * $Id: audiovar.h,v 1.1 1995/02/21 01:36:59 brezak Exp $ + */ + +/* + * Initial/default block duration is both configurable and patchable. + */ +#ifndef AUDIO_BLK_MS +#define AUDIO_BLK_MS 20 /* 20 ms */ +#endif + +#ifndef AUDIO_BACKLOG +#define AUDIO_BACKLOG 3 /* 60 ms */ +#endif + +/* + * Use a single page as the size of the audio ring buffers, so that + * the data won't cross a page boundary. This way the dma carried out + * in the hardware module will be efficient (i.e., at_dma() won't have + * to make a copy). + */ +#ifndef AU_RING_SIZE +#define AU_RING_SIZE NBPG +#endif + +#define AU_RING_MOD(k) ((k) & (AU_RING_SIZE - 1)) +#define AU_RING_EMPTY(rp) ((rp)->hp == (rp)->tp) +#define AU_RING_FULL(rp) (AU_RING_MOD((rp)->tp + 1) == (rp)->hp) +#define AU_RING_LEN(rp) (AU_RING_MOD((rp)->tp - (rp)->hp)) +#define AU_RING_INIT(rp) { \ + (rp)->nblk = (rp)->au_stamp = 0; \ + (rp)->hp = (rp)->tp = (rp)->bp; \ + } + +struct audio_buffer { + u_char *hp; /* head */ + u_char *tp; /* tail */ + u_char *bp; /* start of buffer */ + u_char *ep; /* end of buffer */ + + int nblk; /* number of active blocks in buffer */ + int maxblk; /* max # of active blocks in buffer */ + u_long au_stamp; /* number of audio samples read/written */ + + u_short cb_pause; /* io paused */ + u_long cb_drops; /* missed samples from over/underrun */ + u_long cb_pdrops; /* paused samples */ +}; + +/* + * Software state, per audio device. + */ +struct audio_softc { + caddr_t hw_hdl; /* Hardware driver handle */ + struct audio_hw_if *hw_if; /* Hardware interface */ + u_char sc_open; /* single use device */ +#define AUOPEN_READ 0x01 +#define AUOPEN_WRITE 0x02 + u_char sc_mode; /* bitmask for RECORD/PLAY */ + + struct selinfo sc_wsel; /* write selector */ + struct selinfo sc_rsel; /* read selector */ + + /* Sleep channels for reading and writing. */ + int sc_rchan; + int sc_wchan; + + /* Ring buffers, separate for record and play. */ + struct audio_buffer rr; /* Record ring */ + struct audio_buffer pr; /* Play ring */ + + u_char sc_rbus; /* input dma in progress */ + u_char sc_pbus; /* output dma in progress */ + + u_long sc_wseek; /* timestamp of last frame written */ + u_long sc_rseek; /* timestamp of last frame read */ + + int sc_blksize; /* recv block (chunk) size in bytes */ + int sc_smpl_in_blk; /* # samples in a block */ + int sc_50ms; /* # of samples for 50ms? */ + int sc_backlog; /* # blks of xmit backlog to generate */ + int sc_lowat; /* xmit low water mark (for wakeup) */ + int sc_hiwat; /* xmit high water mark (for wakeup) */ + + int sc_rblks; /* number of phantom record blocks */ + int sc_pencoding; /* current encoding; play */ + int sc_rencoding; /* current encoding; record */ +}; diff --git a/sys/sys/audioio.h b/sys/sys/audioio.h new file mode 100644 index 000000000000..aaa265c211fa --- /dev/null +++ b/sys/sys/audioio.h @@ -0,0 +1,235 @@ +/* $NetBSD: audioio.h,v 1.1 1995/02/21 01:37:22 brezak Exp $ */ + +/* + * Copyright (c) 1991-1993 Regents of the University of California. + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * $Header: /cvsroot/src/sys/sys/audioio.h,v 1.1 1995/02/21 01:37:22 brezak Exp $ (LBL) + */ + +#ifndef _AUDIOIO_H_ +#define _AUDIOIO_H_ + +/* + * Audio device + */ +struct audio_prinfo { + u_int sample_rate; /* sample rate in bit/s */ + u_int channels; /* number of channels, usually 1 or 2 */ + u_int precision; /* number of bits/sample */ + u_int encoding; /* data encoding (AUDIO_ENCODING_* above) */ + u_int gain; /* volume level */ + u_int port; /* selected I/O port */ + u_long seek; /* BSD extension */ + u_int ispare[3]; + /* Current state of device: */ + u_int samples; /* number of samples */ + u_int eof; /* End Of File (zero-size writes) counter */ + u_char pause; /* non-zero if paused, zero to resume */ + u_char error; /* non-zero if underflow/overflow ocurred */ + u_char waiting; /* non-zero if another process hangs in open */ + u_char cspare[3]; + u_char open; /* non-zero if currently open */ + u_char active; /* non-zero if I/O is currently active */ +}; + +struct audio_info { + struct audio_prinfo play; /* Info for play (output) side */ + struct audio_prinfo record; /* Info for record (input) side */ + u_int __spare; + /* BSD extensions */ + u_int blocksize; /* input blocking threshold */ + u_int hiwat; /* output high water mark */ + u_int lowat; /* output low water mark */ + u_int backlog; /* samples of output backlog to gen. */ + u_int mode; /* bitmask of AUMODE_* values */ + /* (Some devices can do both simultaneously) */ +#define AUMODE_PLAY 0 +#define AUMODE_RECORD 1 +}; +typedef struct audio_info audio_info_t; + +#define AUDIO_INITINFO(p)\ + (void)memset((void *)(p), 0xff, sizeof(struct audio_info)) + +/* + * Parameter for the AUDIO_GETDEV ioctl to determine current + * audio devices. + */ +#define MAX_AUDIO_DEV_LEN 16 +typedef struct audio_device { + char name[MAX_AUDIO_DEV_LEN]; + char version[MAX_AUDIO_DEV_LEN]; + char config[MAX_AUDIO_DEV_LEN]; +} audio_device_t; + +/* + * Supported audio encodings + */ +/* Encoding ID's */ +#define AUDIO_ENCODING_NONE 0 /* no encoding assigned */ +#define AUDIO_ENCODING_ULAW 1 +#define AUDIO_ENCODING_ALAW 2 +#define AUDIO_ENCODING_PCM16 3 +#define AUDIO_ENCODING_LINEAR AUDIO_ENCODING_PCM16 +#define AUDIO_ENCODING_PCM8 4 +#define AUDIO_ENCODING_ADPCM 5 + +typedef struct audio_encoding { + int index; + char name[MAX_AUDIO_DEV_LEN]; + int format_id; +} audio_encoding_t; + +/* + * Audio device operations + */ +#define AUDIO_GETINFO _IOR('A', 21, struct audio_info) +#define AUDIO_SETINFO _IOWR('A', 22, struct audio_info) +#define AUDIO_DRAIN _IO('A', 23) +#define AUDIO_FLUSH _IO('A', 24) +#define AUDIO_WSEEK _IOR('A', 25, u_long) +#define AUDIO_RERROR _IOR('A', 26, int) +#define AUDIO_GETDEV _IOR('A', 27, struct audio_device) +#define AUDIO_GETENC _IOWR('A', 28, struct audio_encoding) +#define AUDIO_GETFD _IOR('A', 29, int) +#define AUDIO_SETFD _IOWR('A', 30, int) + +/* + * Mixer device + */ +#define AUDIO_MIN_GAIN 0 +#define AUDIO_MAX_GAIN 255 + +typedef struct mixer_level { + int num_channels; + u_char level[8]; /* [num_channels] */ +} mixer_level_t; +#define AUDIO_MIXER_LEVEL_MONO 0 +#define AUDIO_MIXER_LEVEL_LEFT 0 +#define AUDIO_MIXER_LEVEL_RIGHT 1 + +/* + * Device operations + */ + +typedef struct audio_mixer_name { + char name[MAX_AUDIO_DEV_LEN]; + int msg_id; +} audio_mixer_name_t; + +typedef struct mixer_devinfo { + int index; + audio_mixer_name_t label; + int type; +#define AUDIO_MIXER_CLASS 0 +#define AUDIO_MIXER_ENUM 1 +#define AUDIO_MIXER_SET 2 +#define AUDIO_MIXER_VALUE 3 + int mixer_class; + int next, prev; +#define AUDIO_MIXER_LAST -1 + union { + struct audio_mixer_enum { + int num_mem; + struct { + audio_mixer_name_t label; + int ord; + } member[32]; + } e; + struct audio_mixer_set { + int num_mem; + struct { + audio_mixer_name_t label; + int mask; + } member[32]; + } s; + struct audio_mixer_value { + audio_mixer_name_t units; + int num_channels; + } v; + } un; +} mixer_devinfo_t; + + +typedef struct mixer_ctrl { + int dev; + int type; + union { + int ord; /* enum */ + int mask; /* set */ + mixer_level_t value; /* value */ + } un; +} mixer_ctrl_t; + +/* + * Mixer operations + */ +#define AUDIO_MIXER_READ _IOWR('M', 0, mixer_ctrl_t) +#define AUDIO_MIXER_WRITE _IOWR('M', 1, mixer_ctrl_t) +#define AUDIO_MIXER_DEVINFO _IOWR('M', 2, mixer_devinfo_t) + +/* + * Well known device names + */ +#define AudioNmicrophone "mic" +#define AudioNline "line" +#define AudioNcd "CD" +#define AudioNdac "DAC" +#define AudioNrecord "record" +#define AudioNvolume "volume" +#define AudioNmonitor "monitor" +#define AudioNtreble "treble" +#define AudioNbass "bass" +#define AudioNspeaker "speaker" +#define AudioNheadphone "headphones" +#define AudioNoutput "output" +#define AudioNinput "input" +#define AudioNmaster "master" +#define AudioNstereo "stereo" +#define AudioNmono "mono" +#define AudioNspatial "spatial" +#define AudioNsurround "surround" +#define AudioNpseudo "pseudo" +#define AudioNmute "mute" +#define AudioNenhanced "enhanced" +#define AudioNon "on" +#define AudioNoff "off" +#define AudioNmode "mode" +#define AudioNsource "source" + +#define AudioCInputs "Inputs" +#define AudioCOutputs "Outputs" +#define AudioCRecord "Record" +#define AudioCMonitor "Monitor" +#define AudioCEqualization "Equalization" + +#endif /* _AUDIOIO_H_ */