NetBSD/sys/dev/audio.c
fredb 7f674884ad Fix a couple of long-standing bugs in the volume control(s) part of the
audio device interface:

1) When attempting to match the appropriate mixer control, we weren't
checking the class label, but only the second level label, so for
devices that had both an "inputs.cd" and a "record.cd", for example,
we could never do the right thing except by chance. For this reason,
evidently, all the record masters were labeled (by the underlying
drivers) either "record.record" or "record.volume", to distinguish
from "outputs.master". We'll now accept "record.master", and document
that as the the new preferred way.

2) More importantly, the model was deficient. Selecting a port on many
chips completely disables most of the level controls, which doesn't play
nice with other applications which are trying to use the interface. Now,
selecting a port simply sets which mixer input control shall be changed,
setting state in the audio layer. In other words, the "mixerout" port
is really selected all the time, enabling the final stage mixer, and
setting "gain" sets the level of the appropriate input. It should be
possible for separate applications to each control the mic, dac, and cd
inputs at the same time using this interface, simply by reiterating their
port selections with each change, but applications that don't bother to
do that aren't any worse off than they were before.

The user is expected to set the master output with another application,
dedicated to that task. Though it is now meaningful to select "no port"
with the audio device interface, to manipulate the master output, there's
no particular reason for an audio device consumer to do that. (I added
the capability in order to restore the initial state of the audio device,
for testing purposes. It might also be useful to users of broken binary-
only applications.)

Observe that the mixer device interface (and so, "mixerctl") still
retains all capabilities, including the ability to set the actual input
port on the chip, overriding the level controls. No change is being made
to the mixer device interface. The mixer device simply presents all the
controls on the chip, with no attempt at abstraction, so there are no
bugs there.

The upshot is, that applications that have been trying to use the audio
device interface to change the volume, such as mplayer, now "just work".

I've tested these changes extensively with "eso" and "eap" since first
proposing them on tech-kern last January, and somewhat with "esm" and a
few others. This closes both PR kern/10221, and PR kern/17159.
2004-01-31 00:07:56 +00:00

3421 lines
83 KiB
C

/* $NetBSD: audio.c,v 1.182 2004/01/31 00:07:56 fredb 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.
*/
/*
* 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, poll, signals,
* and silence fill.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: audio.c,v 1.182 2004/01/31 00:07:56 fredb Exp $");
#include "audio.h"
#if NAUDIO > 0
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <sys/vnode.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/syslog.h>
#include <sys/kernel.h>
#include <sys/signalvar.h>
#include <sys/conf.h>
#include <sys/audioio.h>
#include <sys/device.h>
#include <dev/audio_if.h>
#include <dev/audiovar.h>
#include <machine/endian.h>
#ifdef AUDIO_DEBUG
#define DPRINTF(x) if (audiodebug) printf x
#define DPRINTFN(n,x) if (audiodebug>(n)) printf x
int audiodebug = AUDIO_DEBUG;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif
#define ROUNDSIZE(x) x &= -16 /* round to nice boundary */
int audio_blk_ms = AUDIO_BLK_MS;
int audiosetinfo(struct audio_softc *, struct audio_info *);
int audiogetinfo(struct audio_softc *, struct audio_info *);
int audio_open(dev_t, struct audio_softc *, int, int, struct proc *);
int audio_close(struct audio_softc *, int, int, struct proc *);
int audio_read(struct audio_softc *, struct uio *, int);
int audio_write(struct audio_softc *, struct uio *, int);
int audio_ioctl(struct audio_softc *, u_long, caddr_t, int, struct proc *);
int audio_poll(struct audio_softc *, int, struct proc *);
int audio_kqfilter(struct audio_softc *, struct knote *);
paddr_t audio_mmap(struct audio_softc *, off_t, int);
int mixer_open(dev_t, struct audio_softc *, int, int, struct proc *);
int mixer_close(struct audio_softc *, int, int, struct proc *);
int mixer_ioctl(struct audio_softc *, u_long, caddr_t, int, struct proc *);
static void mixer_remove(struct audio_softc *, struct proc *p);
static void mixer_signal(struct audio_softc *);
void audio_init_record(struct audio_softc *);
void audio_init_play(struct audio_softc *);
int audiostartr(struct audio_softc *);
int audiostartp(struct audio_softc *);
void audio_rint(void *);
void audio_pint(void *);
int audio_check_params(struct audio_params *);
void audio_calc_blksize(struct audio_softc *, int);
void audio_fill_silence(struct audio_params *, u_char *, int);
int audio_silence_copyout(struct audio_softc *, int, struct uio *);
void audio_init_ringbuffer(struct audio_softc *, struct audio_ringbuffer *);
int audio_initbufs(struct audio_softc *);
void audio_calcwater(struct audio_softc *);
static __inline int audio_sleep_timo(int *, char *, int);
static __inline int audio_sleep(int *, char *);
static __inline void audio_wakeup(int *);
int audio_drain(struct audio_softc *);
void audio_clear(struct audio_softc *);
static __inline void audio_pint_silence
(struct audio_softc *, struct audio_ringbuffer *, u_char *, int);
int audio_alloc_ring
(struct audio_softc *, struct audio_ringbuffer *, int, size_t);
void audio_free_ring(struct audio_softc *, struct audio_ringbuffer *);
int audio_set_defaults(struct audio_softc *, u_int);
int audioprobe(struct device *, struct cfdata *, void *);
void audioattach(struct device *, struct device *, void *);
int audiodetach(struct device *, int);
int audioactivate(struct device *, enum devact);
struct portname {
const char *name;
int mask;
};
static const struct portname itable[] = {
{ AudioNmicrophone, AUDIO_MICROPHONE },
{ AudioNline, AUDIO_LINE_IN },
{ AudioNcd, AUDIO_CD },
{ 0 }
};
static const struct portname otable[] = {
{ AudioNspeaker, AUDIO_SPEAKER },
{ AudioNheadphone, AUDIO_HEADPHONE },
{ AudioNline, AUDIO_LINE_OUT },
{ 0 }
};
void au_setup_ports(struct audio_softc *, struct au_mixer_ports *,
mixer_devinfo_t *, const struct portname *);
int au_set_gain(struct audio_softc *, struct au_mixer_ports *,
int, int);
void au_get_gain(struct audio_softc *, struct au_mixer_ports *,
u_int *, u_char *);
int au_set_port(struct audio_softc *, struct au_mixer_ports *,
u_int);
int au_get_port(struct audio_softc *, struct au_mixer_ports *);
int au_get_lr_value(struct audio_softc *, mixer_ctrl_t *,
int *, int *r);
int au_set_lr_value(struct audio_softc *, mixer_ctrl_t *,
int, int);
int au_portof(struct audio_softc *, char *, int);
dev_type_open(audioopen);
dev_type_close(audioclose);
dev_type_read(audioread);
dev_type_write(audiowrite);
dev_type_ioctl(audioioctl);
dev_type_poll(audiopoll);
dev_type_mmap(audiommap);
dev_type_kqfilter(audiokqfilter);
const struct cdevsw audio_cdevsw = {
audioopen, audioclose, audioread, audiowrite, audioioctl,
nostop, notty, audiopoll, audiommap, audiokqfilter,
};
/* The default audio mode: 8 kHz mono mu-law */
const struct audio_params audio_default = {
.sample_rate = 8000,
.encoding = AUDIO_ENCODING_ULAW,
.precision = 8,
.channels = 1,
.sw_code = 0,
.factor = 1,
.factor_denom = 1
};
CFATTACH_DECL(audio, sizeof(struct audio_softc),
audioprobe, audioattach, audiodetach, audioactivate);
extern struct cfdriver audio_cd;
int
audioprobe(struct device *parent, struct cfdata *match, void *aux)
{
struct audio_attach_args *sa = aux;
DPRINTF(("audioprobe: type=%d sa=%p hw=%p\n",
sa->type, sa, sa->hwif));
return (sa->type == AUDIODEV_TYPE_AUDIO) ? 1 : 0;
}
void
audioattach(struct device *parent, struct device *self, void *aux)
{
struct audio_softc *sc = (void *)self;
struct audio_attach_args *sa = aux;
struct audio_hw_if *hwp = sa->hwif;
void *hdlp = sa->hdl;
int error;
mixer_devinfo_t mi;
int iclass, mclass, oclass, props;
#ifdef DIAGNOSTIC
if (hwp == 0 ||
hwp->open == 0 ||
hwp->close == 0 ||
hwp->query_encoding == 0 ||
hwp->set_params == 0 ||
(hwp->start_output == 0 && hwp->trigger_output == 0) ||
(hwp->start_input == 0 && hwp->trigger_input == 0) ||
hwp->halt_output == 0 ||
hwp->halt_input == 0 ||
hwp->getdev == 0 ||
hwp->set_port == 0 ||
hwp->get_port == 0 ||
hwp->query_devinfo == 0 ||
hwp->get_props == 0) {
printf(": missing method\n");
sc->hw_if = 0;
return;
}
#endif
props = hwp->get_props(hdlp);
if (props & AUDIO_PROP_FULLDUPLEX)
printf(": full duplex");
else
printf(": half duplex");
if (props & AUDIO_PROP_MMAP)
printf(", mmap");
if (props & AUDIO_PROP_INDEPENDENT)
printf(", independent");
printf("\n");
sc->hw_if = hwp;
sc->hw_hdl = hdlp;
sc->sc_dev = parent;
error = audio_alloc_ring(sc, &sc->sc_pr, AUMODE_PLAY, AU_RING_SIZE);
if (error) {
sc->hw_if = 0;
printf("audio: could not allocate play buffer\n");
return;
}
error = audio_alloc_ring(sc, &sc->sc_rr, AUMODE_RECORD, AU_RING_SIZE);
if (error) {
audio_free_ring(sc, &sc->sc_pr);
sc->hw_if = 0;
printf("audio: could not allocate record buffer\n");
return;
}
sc->sc_pconvbuffer = malloc(AU_RING_SIZE, M_DEVBUF, M_WAITOK);
sc->sc_pconvbuffer_size = AU_RING_SIZE;
sc->sc_rconvbuffer = malloc(AU_RING_SIZE, M_DEVBUF, M_WAITOK);
sc->sc_rconvbuffer_size = AU_RING_SIZE;
if (audio_set_defaults(sc, 0))
panic("audioattach: audio_set_defaults failed");
sc->sc_input_fragment_length = 0;
iclass = mclass = oclass = -1;
sc->sc_inports.index = -1;
sc->sc_inports.master = -1;
sc->sc_inports.nports = 0;
sc->sc_inports.isenum = 0;
sc->sc_inports.allports = 0;
sc->sc_inports.isdual = 0;
sc->sc_inports.mixerout = -1;
sc->sc_inports.cur_port = -1;
sc->sc_outports.index = -1;
sc->sc_outports.master = -1;
sc->sc_outports.nports = 0;
sc->sc_outports.isenum = 0;
sc->sc_outports.allports = 0;
sc->sc_outports.isdual = 0;
sc->sc_outports.mixerout = -1;
sc->sc_outports.cur_port = -1;
sc->sc_monitor_port = -1;
for(mi.index = 0; ; mi.index++) {
if (hwp->query_devinfo(hdlp, &mi) != 0)
break;
if (mi.type == AUDIO_MIXER_CLASS) {
if (strcmp(mi.label.name, AudioCrecord) == 0)
iclass = mi.mixer_class;
if (strcmp(mi.label.name, AudioCmonitor) == 0)
mclass = mi.mixer_class;
if (strcmp(mi.label.name, AudioCoutputs) == 0)
oclass = mi.mixer_class;
}
}
for(mi.index = 0; ; mi.index++) {
if (hwp->query_devinfo(hdlp, &mi) != 0)
break;
if (mi.type == AUDIO_MIXER_CLASS)
continue;
if (mi.mixer_class == iclass) {
if (strcmp(mi.label.name, AudioNmaster) == 0)
sc->sc_inports.master = mi.index;
#if 1 /* Deprecated. Use AudioNmaster. */
if (strcmp(mi.label.name, AudioNrecord) == 0)
sc->sc_inports.master = mi.index;
if (strcmp(mi.label.name, AudioNvolume) == 0)
sc->sc_inports.master = mi.index;
#endif
if (strcmp(mi.label.name, AudioNsource) == 0) {
if (mi.type == AUDIO_MIXER_ENUM) {
int i;
for(i = 0; i < mi.un.e.num_mem; i++)
if (strcmp(mi.un.e.member[i].label.name,
AudioNmixerout) == 0)
sc->sc_inports.mixerout =
mi.un.e.member[i].ord;
}
au_setup_ports(sc, &sc->sc_inports, &mi,
itable);
}
} else if (mi.mixer_class == mclass) {
if (strcmp(mi.label.name, AudioNmonitor) == 0)
sc->sc_monitor_port = mi.index;
} else if (mi.mixer_class == oclass) {
if (strcmp(mi.label.name, AudioNmaster) == 0)
sc->sc_outports.master = mi.index;
if (strcmp(mi.label.name, AudioNselect) == 0)
au_setup_ports(sc, &sc->sc_outports, &mi,
otable);
}
}
DPRINTF(("audio_attach: inputs ports=0x%x, input master=%d, "
"output ports=0x%x, output master=%d\n",
sc->sc_inports.allports, sc->sc_inports.master,
sc->sc_outports.allports, sc->sc_outports.master));
}
int
audioactivate(struct device *self, enum devact act)
{
struct audio_softc *sc = (struct audio_softc *)self;
switch (act) {
case DVACT_ACTIVATE:
return (EOPNOTSUPP);
case DVACT_DEACTIVATE:
sc->sc_dying = 1;
break;
}
return (0);
}
int
audiodetach(struct device *self, int flags)
{
struct audio_softc *sc = (struct audio_softc *)self;
int maj, mn;
int s;
DPRINTF(("audio_detach: sc=%p flags=%d\n", sc, flags));
sc->sc_dying = 1;
wakeup(&sc->sc_wchan);
wakeup(&sc->sc_rchan);
s = splaudio();
if (--sc->sc_refcnt >= 0) {
if (tsleep(&sc->sc_refcnt, PZERO, "auddet", hz * 120))
printf("audiodetach: %s didn't detach\n",
sc->dev.dv_xname);
}
splx(s);
/* free resources */
audio_free_ring(sc, &sc->sc_pr);
audio_free_ring(sc, &sc->sc_rr);
free(sc->sc_pconvbuffer, M_DEVBUF);
free(sc->sc_rconvbuffer, M_DEVBUF);
/* locate the major number */
maj = cdevsw_lookup_major(&audio_cdevsw);
/* Nuke the vnodes for any open instances (calls close). */
mn = self->dv_unit;
vdevgone(maj, mn | SOUND_DEVICE, mn | SOUND_DEVICE, VCHR);
vdevgone(maj, mn | AUDIO_DEVICE, mn | AUDIO_DEVICE, VCHR);
vdevgone(maj, mn | AUDIOCTL_DEVICE, mn | AUDIOCTL_DEVICE, VCHR);
vdevgone(maj, mn | MIXER_DEVICE, mn | MIXER_DEVICE, VCHR);
return (0);
}
int
au_portof(struct audio_softc *sc, char *name, int class)
{
mixer_devinfo_t mi;
for(mi.index = 0;
sc->hw_if->query_devinfo(sc->hw_hdl, &mi) == 0;
mi.index++)
if (mi.mixer_class == class && strcmp(mi.label.name, name) == 0)
return mi.index;
return -1;
}
void
au_setup_ports(struct audio_softc *sc, struct au_mixer_ports *ports,
mixer_devinfo_t *mi, const struct portname *tbl)
{
int i, j;
ports->index = mi->index;
if (mi->type == AUDIO_MIXER_ENUM) {
ports->isenum = 1;
for(i = 0; tbl[i].name; i++)
for(j = 0; j < mi->un.e.num_mem; j++)
if (strcmp(mi->un.e.member[j].label.name,
tbl[i].name) == 0) {
ports->allports |= tbl[i].mask;
ports->aumask[ports->nports] = tbl[i].mask;
ports->misel[ports->nports] =
mi->un.e.member[j].ord;
ports->miport[ports->nports] =
au_portof(sc, mi->un.e.member[j].label.name,
mi->mixer_class);
if (ports->mixerout != -1 &&
ports->miport[ports->nports++] != -1)
ports->isdual = 1;
}
} else if (mi->type == AUDIO_MIXER_SET) {
for(i = 0; tbl[i].name; i++)
for(j = 0; j < mi->un.s.num_mem; j++)
if (strcmp(mi->un.s.member[j].label.name,
tbl[i].name) == 0) {
ports->allports |= tbl[i].mask;
ports->aumask[ports->nports] = tbl[i].mask;
ports->misel[ports->nports] =
mi->un.s.member[j].mask;
ports->miport[ports->nports++] =
au_portof(sc, mi->un.s.member[j].label.name,
mi->mixer_class);
}
}
}
/*
* Called from hardware driver. This is where the MI audio driver gets
* probed/attached to the hardware driver.
*/
struct device *
audio_attach_mi(struct audio_hw_if *ahwp, void *hdlp, struct device *dev)
{
struct audio_attach_args arg;
#ifdef DIAGNOSTIC
if (ahwp == NULL) {
aprint_error("audio_attach_mi: NULL\n");
return (0);
}
#endif
arg.type = AUDIODEV_TYPE_AUDIO;
arg.hwif = ahwp;
arg.hdl = hdlp;
return (config_found(dev, &arg, audioprint));
}
#ifdef AUDIO_DEBUG
void audio_printsc(struct audio_softc *);
void audio_print_params(char *, struct audio_params *);
void
audio_printsc(struct audio_softc *sc)
{
printf("hwhandle %p hw_if %p ", sc->hw_hdl, sc->hw_if);
printf("open 0x%x mode 0x%x\n", sc->sc_open, sc->sc_mode);
printf("rchan 0x%x wchan 0x%x ", sc->sc_rchan, sc->sc_wchan);
printf("rring used 0x%x pring used=%d\n",
sc->sc_rr.used, sc->sc_pr.used);
printf("rbus 0x%x pbus 0x%x ", sc->sc_rbus, sc->sc_pbus);
printf("blksize %d", sc->sc_pr.blksize);
printf("hiwat %d lowat %d\n", sc->sc_pr.usedhigh, sc->sc_pr.usedlow);
}
void
audio_print_params(char *s, struct audio_params *p)
{
printf("audio: %s sr=%ld, enc=%d, chan=%d, prec=%d\n", s,
p->sample_rate, p->encoding, p->channels, p->precision);
}
#endif
int
audio_alloc_ring(struct audio_softc *sc, struct audio_ringbuffer *r,
int direction, size_t bufsize)
{
struct audio_hw_if *hw = sc->hw_if;
void *hdl = sc->hw_hdl;
/*
* Alloc DMA play and record buffers
*/
if (bufsize < AUMINBUF)
bufsize = AUMINBUF;
ROUNDSIZE(bufsize);
if (hw->round_buffersize)
bufsize = hw->round_buffersize(hdl, direction, bufsize);
if (hw->allocm)
r->start = hw->allocm(hdl, direction, bufsize,
M_DEVBUF, M_WAITOK);
else
r->start = malloc(bufsize, M_DEVBUF, M_WAITOK);
if (r->start == 0)
return ENOMEM;
r->bufsize = bufsize;
return 0;
}
void
audio_free_ring(struct audio_softc *sc, struct audio_ringbuffer *r)
{
if (sc->hw_if->freem)
sc->hw_if->freem(sc->hw_hdl, r->start, M_DEVBUF);
else
free(r->start, M_DEVBUF);
r->start = 0;
}
int
audioopen(dev_t dev, int flags, int ifmt, struct proc *p)
{
struct audio_softc *sc;
int error;
sc = device_lookup(&audio_cd, AUDIOUNIT(dev));
if (sc == NULL)
return (ENXIO);
if (sc->sc_dying)
return (EIO);
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
error = audio_open(dev, sc, flags, ifmt, p);
break;
case AUDIOCTL_DEVICE:
error = 0;
break;
case MIXER_DEVICE:
error = mixer_open(dev, sc, flags, ifmt, p);
break;
default:
error = ENXIO;
break;
}
if (--sc->sc_refcnt < 0)
wakeup(&sc->sc_refcnt);
return (error);
}
int
audioclose(dev_t dev, int flags, int ifmt, struct proc *p)
{
int unit = AUDIOUNIT(dev);
struct audio_softc *sc = audio_cd.cd_devs[unit];
int error;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
error = audio_close(sc, flags, ifmt, p);
break;
case MIXER_DEVICE:
error = mixer_close(sc, flags, ifmt, p);
break;
case AUDIOCTL_DEVICE:
error = 0;
break;
default:
error = ENXIO;
break;
}
return (error);
}
int
audioread(dev_t dev, struct uio *uio, int ioflag)
{
struct audio_softc *sc;
int error;
sc = device_lookup(&audio_cd, AUDIOUNIT(dev));
if (sc == NULL)
return (ENXIO);
if (sc->sc_dying)
return (EIO);
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
error = audio_read(sc, uio, ioflag);
break;
case AUDIOCTL_DEVICE:
case MIXER_DEVICE:
error = ENODEV;
break;
default:
error = ENXIO;
break;
}
if (--sc->sc_refcnt < 0)
wakeup(&sc->sc_refcnt);
return (error);
}
int
audiowrite(dev_t dev, struct uio *uio, int ioflag)
{
struct audio_softc *sc;
int error;
sc = device_lookup(&audio_cd, AUDIOUNIT(dev));
if (sc == NULL)
return (ENXIO);
if (sc->sc_dying)
return (EIO);
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
error = audio_write(sc, uio, ioflag);
break;
case AUDIOCTL_DEVICE:
case MIXER_DEVICE:
error = ENODEV;
break;
default:
error = ENXIO;
break;
}
if (--sc->sc_refcnt < 0)
wakeup(&sc->sc_refcnt);
return (error);
}
int
audioioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
{
int unit = AUDIOUNIT(dev);
struct audio_softc *sc = audio_cd.cd_devs[unit];
int error;
if (sc->sc_dying)
return (EIO);
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
case AUDIOCTL_DEVICE:
error = audio_ioctl(sc, cmd, addr, flag, p);
break;
case MIXER_DEVICE:
error = mixer_ioctl(sc, cmd, addr, flag, p);
break;
default:
error = ENXIO;
break;
}
if (--sc->sc_refcnt < 0)
wakeup(&sc->sc_refcnt);
return (error);
}
int
audiopoll(dev_t dev, int events, struct proc *p)
{
int unit = AUDIOUNIT(dev);
struct audio_softc *sc = audio_cd.cd_devs[unit];
int error;
if (sc->sc_dying)
return (EIO);
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
error = audio_poll(sc, events, p);
break;
case AUDIOCTL_DEVICE:
case MIXER_DEVICE:
error = ENODEV;
break;
default:
error = ENXIO;
break;
}
if (--sc->sc_refcnt < 0)
wakeup(&sc->sc_refcnt);
return (error);
}
int
audiokqfilter(dev_t dev, struct knote *kn)
{
int unit = AUDIOUNIT(dev);
struct audio_softc *sc = audio_cd.cd_devs[unit];
int rv;
if (sc->sc_dying)
return (1);
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
rv = audio_kqfilter(sc, kn);
break;
case AUDIOCTL_DEVICE:
case MIXER_DEVICE:
rv = 1;
break;
default:
rv = 1;
}
if (--sc->sc_refcnt < 0)
wakeup(&sc->sc_refcnt);
return (rv);
}
paddr_t
audiommap(dev_t dev, off_t off, int prot)
{
int unit = AUDIOUNIT(dev);
struct audio_softc *sc = audio_cd.cd_devs[unit];
paddr_t error;
if (sc->sc_dying)
return (-1);
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
error = audio_mmap(sc, off, prot);
break;
case AUDIOCTL_DEVICE:
case MIXER_DEVICE:
error = -1;
break;
default:
error = -1;
break;
}
if (--sc->sc_refcnt < 0)
wakeup(&sc->sc_refcnt);
return (error);
}
/*
* Audio driver
*/
void
audio_init_ringbuffer(struct audio_softc *sc, struct audio_ringbuffer *rp)
{
int nblks;
int blksize = rp->blksize;
if (blksize < AUMINBLK)
blksize = AUMINBLK;
if (blksize > rp->bufsize / AUMINNOBLK)
blksize = rp->bufsize / AUMINNOBLK;
ROUNDSIZE(blksize);
if (sc->hw_if->round_blocksize)
blksize = sc->hw_if->round_blocksize(sc->hw_hdl, blksize);
if (blksize <= 0)
panic("audio_init_ringbuffer: blksize");
nblks = rp->bufsize / blksize;
DPRINTF(("audio_init_ringbuffer: blksize=%d\n", blksize));
rp->blksize = blksize;
rp->maxblks = nblks;
rp->used = 0;
rp->end = rp->start + nblks * blksize;
rp->inp = rp->outp = rp->start;
rp->stamp = 0;
rp->drops = 0;
rp->pause = 0;
rp->copying = 0;
rp->needfill = 0;
rp->mmapped = 0;
}
int
audio_initbufs(struct audio_softc *sc)
{
struct audio_hw_if *hw = sc->hw_if;
int error;
DPRINTF(("audio_initbufs: mode=0x%x\n", sc->sc_mode));
audio_init_ringbuffer(sc, &sc->sc_rr);
#if NAURATECONV > 0
auconv_init_context(&sc->sc_rconv, sc->sc_rparams.hw_sample_rate,
sc->sc_rparams.sample_rate,
sc->sc_rr.start, sc->sc_rr.end);
#endif
if (hw->init_input && (sc->sc_mode & AUMODE_RECORD)) {
error = hw->init_input(sc->hw_hdl, sc->sc_rr.start,
sc->sc_rr.end - sc->sc_rr.start);
if (error)
return error;
}
audio_init_ringbuffer(sc, &sc->sc_pr);
#if NAURATECONV > 0
auconv_init_context(&sc->sc_pconv, sc->sc_pparams.sample_rate,
sc->sc_pparams.hw_sample_rate,
sc->sc_pr.start, sc->sc_pr.end);
#endif
sc->sc_sil_count = 0;
if (hw->init_output && (sc->sc_mode & AUMODE_PLAY)) {
error = hw->init_output(sc->hw_hdl, sc->sc_pr.start,
sc->sc_pr.end - sc->sc_pr.start);
if (error)
return error;
}
#ifdef AUDIO_INTR_TIME
#define double u_long
sc->sc_pnintr = 0;
sc->sc_pblktime = (u_long)(
(double)sc->sc_pr.blksize * 100000 /
(double)(sc->sc_pparams.precision / NBBY *
sc->sc_pparams.channels *
sc->sc_pparams.sample_rate)) * 10;
DPRINTF(("audio: play blktime = %lu for %d\n",
sc->sc_pblktime, sc->sc_pr.blksize));
sc->sc_rnintr = 0;
sc->sc_rblktime = (u_long)(
(double)sc->sc_rr.blksize * 100000 /
(double)(sc->sc_rparams.precision / NBBY *
sc->sc_rparams.channels *
sc->sc_rparams.sample_rate)) * 10;
DPRINTF(("audio: record blktime = %lu for %d\n",
sc->sc_rblktime, sc->sc_rr.blksize));
#undef double
#endif
return 0;
}
void
audio_calcwater(struct audio_softc *sc)
{
sc->sc_pr.usedhigh = sc->sc_pr.end - sc->sc_pr.start;
sc->sc_pr.usedlow = sc->sc_pr.usedhigh * 3 / 4; /* set low at 75% */
if (sc->sc_pr.usedlow == sc->sc_pr.usedhigh)
sc->sc_pr.usedlow -= sc->sc_pr.blksize;
sc->sc_rr.usedhigh =
sc->sc_rr.end - sc->sc_rr.start - sc->sc_rr.blksize;
sc->sc_rr.usedlow = 0;
}
static __inline int
audio_sleep_timo(int *chan, char *label, int timo)
{
int st;
if (!label)
label = "audio";
DPRINTFN(3, ("audio_sleep_timo: chan=%p, label=%s, timo=%d\n",
chan, label, timo));
*chan = 1;
st = tsleep(chan, PWAIT | PCATCH, label, timo);
*chan = 0;
#ifdef AUDIO_DEBUG
if (st != 0 && st != EINTR)
DPRINTF(("audio_sleep: woke up st=%d\n", st));
#endif
return (st);
}
static __inline int
audio_sleep(int *chan, char *label)
{
return audio_sleep_timo(chan, label, 0);
}
/* call at splaudio() */
static __inline void
audio_wakeup(int *chan)
{
DPRINTFN(3, ("audio_wakeup: chan=%p, *chan=%d\n", chan, *chan));
if (*chan) {
wakeup(chan);
*chan = 0;
}
}
int
audio_open(dev_t dev, struct audio_softc *sc, int flags, int ifmt,
struct proc *p)
{
int error;
u_int mode;
struct audio_hw_if *hw;
hw = sc->hw_if;
if (!hw)
return ENXIO;
DPRINTF(("audio_open: flags=0x%x sc=%p hdl=%p\n",
flags, sc, sc->hw_hdl));
if ((sc->sc_open & (AUOPEN_READ|AUOPEN_WRITE)) != 0)
return (EBUSY);
error = hw->open(sc->hw_hdl, flags);
if (error)
return (error);
sc->sc_async_audio = 0;
sc->sc_rchan = 0;
sc->sc_wchan = 0;
sc->sc_sil_count = 0;
sc->sc_rbus = 0;
sc->sc_pbus = 0;
sc->sc_eof = 0;
sc->sc_playdrop = 0;
sc->sc_full_duplex = 0;
/* doesn't always work right on SB.
(flags & (FWRITE|FREAD)) == (FWRITE|FREAD) &&
(hw->get_props(sc->hw_hdl) & AUDIO_PROP_FULLDUPLEX);
*/
mode = 0;
if (flags & FREAD) {
sc->sc_open |= AUOPEN_READ;
mode |= AUMODE_RECORD;
}
if (flags & FWRITE) {
sc->sc_open |= AUOPEN_WRITE;
mode |= AUMODE_PLAY | AUMODE_PLAY_ALL;
}
/*
* 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)) {
error = audio_set_defaults(sc, mode);
} else {
struct audio_info ai;
AUDIO_INITINFO(&ai);
ai.mode = mode;
error = audiosetinfo(sc, &ai);
}
if (error)
goto bad;
#ifdef DIAGNOSTIC
/*
* 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.
*/
if (sc->sc_rparams.precision == 0 || sc->sc_pparams.precision == 0) {
printf("audio_open: 0 precision\n");
return EINVAL;
}
#endif
/* audio_close() decreases sc_pr.usedlow, recalculate here */
audio_calcwater(sc);
DPRINTF(("audio_open: done sc_mode = 0x%x\n", sc->sc_mode));
return 0;
bad:
hw->close(sc->hw_hdl);
sc->sc_open = 0;
sc->sc_mode = 0;
sc->sc_full_duplex = 0;
return error;
}
/*
* Must be called from task context.
*/
void
audio_init_record(struct audio_softc *sc)
{
int s = splaudio();
if (sc->hw_if->speaker_ctl &&
(!sc->sc_full_duplex || (sc->sc_mode & AUMODE_PLAY) == 0))
sc->hw_if->speaker_ctl(sc->hw_hdl, SPKR_OFF);
sc->sc_rconvbuffer_begin = 0;
sc->sc_rconvbuffer_end = 0;
splx(s);
}
/*
* Must be called from task context.
*/
void
audio_init_play(struct audio_softc *sc)
{
int s = splaudio();
sc->sc_wstamp = sc->sc_pr.stamp;
if (sc->hw_if->speaker_ctl)
sc->hw_if->speaker_ctl(sc->hw_hdl, SPKR_ON);
sc->sc_input_fragment_length = 0;
splx(s);
}
int
audio_drain(struct audio_softc *sc)
{
int error, drops;
struct audio_ringbuffer *cb = &sc->sc_pr;
int s;
DPRINTF(("audio_drain: enter busy=%d used=%d\n",
sc->sc_pbus, sc->sc_pr.used));
if (sc->sc_pr.mmapped || sc->sc_pr.used <= 0)
return 0;
if (!sc->sc_pbus) {
/* We've never started playing, probably because the
* block was too short. Pad it and start now.
*/
int cc;
u_char *inp = cb->inp;
cc = cb->blksize - (inp - cb->start) % cb->blksize;
audio_fill_silence(&sc->sc_pparams, inp, cc);
inp += cc;
if (inp >= cb->end)
inp = cb->start;
s = splaudio();
cb->used += cc;
cb->inp = inp;
error = audiostartp(sc);
splx(s);
if (error)
return error;
}
/*
* Play until a silence block has been played, then we
* know all has been drained.
* XXX This should be done some other way to avoid
* playing silence.
*/
#ifdef DIAGNOSTIC
if (cb->copying) {
printf("audio_drain: copying in progress!?!\n");
cb->copying = 0;
}
#endif
drops = cb->drops;
error = 0;
s = splaudio();
while (cb->drops == drops && !error) {
DPRINTF(("audio_drain: used=%d, drops=%ld\n",
sc->sc_pr.used, cb->drops));
/*
* When the process is exiting, it ignores all signals and
* we can't interrupt this sleep, so we set a timeout
* just in case.
*/
error = audio_sleep_timo(&sc->sc_wchan, "aud_dr", 30*hz);
if (sc->sc_dying)
error = EIO;
}
splx(s);
return error;
}
/*
* Close an audio chip.
*/
/* ARGSUSED */
int
audio_close(struct audio_softc *sc, int flags, int ifmt, struct proc *p)
{
struct audio_hw_if *hw = sc->hw_if;
int s;
DPRINTF(("audio_close: sc=%p\n", sc));
s = splaudio();
/* Stop recording. */
if ((flags & FREAD) && sc->sc_rbus) {
/*
* XXX Some drivers (e.g. SB) use the same routine
* to halt input and output so don't halt input if
* in full duplex mode. These drivers should be fixed.
*/
if (!sc->sc_full_duplex ||
sc->hw_if->halt_input != sc->hw_if->halt_output)
sc->hw_if->halt_input(sc->hw_hdl);
sc->sc_rbus = 0;
}
/*
* Block until output drains, but allow ^C interrupt.
*/
sc->sc_pr.usedlow = sc->sc_pr.blksize; /* avoid excessive wakeups */
/*
* If there is pending output, let it drain (unless
* the output is paused).
*/
if ((flags & FWRITE) && sc->sc_pbus) {
if (!sc->sc_pr.pause && !audio_drain(sc) && hw->drain)
(void)hw->drain(sc->hw_hdl);
sc->hw_if->halt_output(sc->hw_hdl);
sc->sc_pbus = 0;
}
hw->close(sc->hw_hdl);
if (flags & FREAD) {
sc->sc_open &= ~AUOPEN_READ;
sc->sc_mode &= ~AUMODE_RECORD;
}
if (flags & FWRITE) {
sc->sc_open &= ~AUOPEN_WRITE;
sc->sc_mode &= ~(AUMODE_PLAY|AUMODE_PLAY_ALL);
}
sc->sc_async_audio = 0;
sc->sc_full_duplex = 0;
splx(s);
DPRINTF(("audio_close: done\n"));
return (0);
}
int
audio_read(struct audio_softc *sc, struct uio *uio, int ioflag)
{
struct audio_ringbuffer *cb = &sc->sc_rr;
u_char *outp;
int error, s, used, cc, n;
const struct audio_params *params;
int hw_bits_per_sample;
if (cb->mmapped)
return EINVAL;
DPRINTFN(1,("audio_read: cc=%lu mode=%d\n",
(unsigned long)uio->uio_resid, sc->sc_mode));
params = &sc->sc_rparams;
switch (params->hw_encoding) {
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_SLINEAR_BE:
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_ULINEAR_BE:
hw_bits_per_sample = params->hw_channels * params->precision
* params->factor;
break;
default:
hw_bits_per_sample = 8 * params->factor / params->factor_denom;
}
error = 0;
/*
* If hardware is half-duplex and currently playing, return
* silence blocks based on the number of blocks we have output.
*/
if (!sc->sc_full_duplex &&
(sc->sc_mode & AUMODE_PLAY)) {
while (uio->uio_resid > 0 && !error) {
s = splaudio();
for(;;) {
cc = sc->sc_pr.stamp - sc->sc_wstamp;
if (cc > 0)
break;
DPRINTF(("audio_read: stamp=%lu, wstamp=%lu\n",
sc->sc_pr.stamp, sc->sc_wstamp));
if (ioflag & IO_NDELAY) {
splx(s);
return (EWOULDBLOCK);
}
error = audio_sleep(&sc->sc_rchan, "aud_hr");
if (sc->sc_dying)
error = EIO;
if (error) {
splx(s);
return (error);
}
}
splx(s);
if (uio->uio_resid < cc)
cc = uio->uio_resid;
DPRINTFN(1,("audio_read: reading in write mode, "
"cc=%d\n", cc));
error = audio_silence_copyout(sc, cc, uio);
sc->sc_wstamp += cc;
}
return (error);
}
while (uio->uio_resid > 0 && !error) {
if (sc->sc_rconvbuffer_end - sc->sc_rconvbuffer_begin <= 0) {
s = splaudio();
while (cb->used * 8 < hw_bits_per_sample) {
if (!sc->sc_rbus) {
error = audiostartr(sc);
if (error) {
splx(s);
return (error);
}
}
if (ioflag & IO_NDELAY) {
splx(s);
return (EWOULDBLOCK);
}
DPRINTFN(1, ("audio_read: sleep used=%d\n",
cb->used));
error = audio_sleep(&sc->sc_rchan, "aud_rd");
if (sc->sc_dying)
error = EIO;
if (error) {
splx(s);
return error;
}
}
/*
* Move data in the ring buffer to sc_rconvbuffer as
* possible with/without rate conversion.
*/
used = cb->used;
outp = cb->outp;
cb->copying = 1;
splx(s);
cc = used - cb->usedlow; /* maximum to read */
if (cc > sc->sc_rconvbuffer_size)
cc = sc->sc_rconvbuffer_size;
n = cc * params->factor / params->factor_denom;
if (n < cc)
cc = n;
/* cc must be aligned by the sample size */
cc = (cc * 8 / hw_bits_per_sample) * hw_bits_per_sample / 8;
#ifdef DIAGNOSTIC
if (cc == 0)
printf("audio_read: cc=0 hw_bits_per_sample=%d\n",
hw_bits_per_sample);
#endif
/*
* The format of data in the ring buffer is
* [hw_sample_rate, hw_encoding, hw_precision, hw_channels]
*/
#if NAURATECONV > 0
sc->sc_rconvbuffer_end =
auconv_record(&sc->sc_rconv, params,
sc->sc_rconvbuffer, outp, cc);
#else
n = cb->end - outp;
if (cc <= n) {
memcpy(sc->sc_rconvbuffer, outp, cc);
} else {
memcpy(sc->sc_rconvbuffer, outp, n);
memcpy(sc->sc_rconvbuffer + n, cb->start,
cc - n);
}
sc->sc_rconvbuffer_end = cc;
#endif /* !NAURATECONV */
/*
* The format of data in sc_rconvbuffer is
* [sample_rate, hw_encoding, hw_precision, channels]
*/
outp += cc;
if (outp >= cb->end)
outp -= cb->end - cb->start;
s = splaudio();
cb->outp = outp;
cb->used -= cc;
cb->copying = 0;
splx(s);
if (params->sw_code) {
cc = sc->sc_rconvbuffer_end;
#ifdef DIAGNOSTIC
if (cc % params->factor != 0)
printf("audio_read: cc is not aligned"
": cc=%d factor=%d\n", cc,
params->factor);
#endif
cc = cc * params->factor_denom / params->factor;
#ifdef DIAGNOSTIC
if (cc == 0)
printf("audio_read: cc=0 "
"factor=%d/%d\n",
params->factor,
params->factor_denom);
#endif
params->sw_code(sc->hw_hdl, sc->sc_rconvbuffer,
cc);
sc->sc_rconvbuffer_end = cc;
}
sc->sc_rconvbuffer_begin = 0;
/*
* The format of data in sc_rconvbuffer is
* [sample_rate, encoding, precision, channels]
*/
}
cc = sc->sc_rconvbuffer_end - sc->sc_rconvbuffer_begin;
if (uio->uio_resid < cc)
cc = uio->uio_resid; /* and no more than we want */
DPRINTFN(0,("audio_read: buffer=%p[%d] (~ %d), cc=%d\n",
sc->sc_rconvbuffer, sc->sc_rconvbuffer_begin,
sc->sc_rconvbuffer_end, cc));
n = uio->uio_resid;
error = uiomove(sc->sc_rconvbuffer + sc->sc_rconvbuffer_begin,
cc, uio);
cc = n - uio->uio_resid; /* number of bytes actually moved */
sc->sc_rconvbuffer_begin += cc;
}
return (error);
}
void
audio_clear(struct audio_softc *sc)
{
int s = splaudio();
if (sc->sc_rbus) {
audio_wakeup(&sc->sc_rchan);
sc->hw_if->halt_input(sc->hw_hdl);
sc->sc_rbus = 0;
}
if (sc->sc_pbus) {
audio_wakeup(&sc->sc_wchan);
sc->hw_if->halt_output(sc->hw_hdl);
sc->sc_pbus = 0;
}
splx(s);
}
void
audio_calc_blksize(struct audio_softc *sc, int mode)
{
struct audio_params *parm;
struct audio_ringbuffer *rb;
if (sc->sc_blkset)
return;
if (mode == AUMODE_PLAY) {
parm = &sc->sc_pparams;
rb = &sc->sc_pr;
} else {
parm = &sc->sc_rparams;
rb = &sc->sc_rr;
}
rb->blksize = parm->hw_sample_rate * audio_blk_ms / 1000 *
parm->hw_channels * parm->precision / NBBY *
parm->factor;
DPRINTF(("audio_calc_blksize: %s blksize=%d\n",
mode == AUMODE_PLAY ? "play" : "record", rb->blksize));
}
void
audio_fill_silence(struct audio_params *params, u_char *p, int n)
{
u_char auzero0, auzero1 = 0; /* initialize to please gcc */
int nfill = 1;
switch (params->hw_encoding) {
case AUDIO_ENCODING_ULAW:
auzero0 = 0x7f;
break;
case AUDIO_ENCODING_ALAW:
auzero0 = 0x55;
break;
case AUDIO_ENCODING_MPEG_L1_STREAM:
case AUDIO_ENCODING_MPEG_L1_PACKETS:
case AUDIO_ENCODING_MPEG_L1_SYSTEM:
case AUDIO_ENCODING_MPEG_L2_STREAM:
case AUDIO_ENCODING_MPEG_L2_PACKETS:
case AUDIO_ENCODING_MPEG_L2_SYSTEM:
case AUDIO_ENCODING_ADPCM: /* is this right XXX */
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_SLINEAR_BE:
auzero0 = 0;/* fortunately this works for any number of bits */
break;
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_ULINEAR_BE:
if (params->hw_precision > 8) {
nfill = (params->hw_precision + NBBY - 1)/ NBBY;
auzero0 = 0x80;
auzero1 = 0;
} else
auzero0 = 0x80;
break;
default:
DPRINTF(("audio: bad encoding %d\n", params->encoding));
auzero0 = 0;
break;
}
if (nfill == 1) {
while (--n >= 0)
*p++ = auzero0; /* XXX memset */
} else /* nfill must no longer be 2 */ {
if (params->hw_encoding == AUDIO_ENCODING_ULINEAR_LE) {
int k = nfill;
while (--k > 0)
*p++ = auzero1;
n -= nfill - 1;
}
while (n >= nfill) {
int k = nfill;
*p++ = auzero0;
while (--k > 0)
*p++ = auzero1;
n -= nfill;
}
if (n-- > 0) /* XXX must be 1 - DIAGNOSTIC check? */
*p++ = auzero0;
}
}
int
audio_silence_copyout(struct audio_softc *sc, int n, struct uio *uio)
{
int error;
int k;
u_char zerobuf[128];
audio_fill_silence(&sc->sc_rparams, zerobuf, sizeof zerobuf);
error = 0;
while (n > 0 && uio->uio_resid > 0 && !error) {
k = min(n, min(uio->uio_resid, sizeof zerobuf));
error = uiomove(zerobuf, k, uio);
n -= k;
}
return (error);
}
int
audio_write(struct audio_softc *sc, struct uio *uio, int ioflag)
{
struct audio_ringbuffer *cb = &sc->sc_pr;
u_char *inp, *einp;
int saveerror, error, s, n, cc, used;
struct audio_params *params;
int samples, hw_bits_per_sample, user_bits_per_sample;
int input_remain, space;
DPRINTFN(2,("audio_write: sc=%p count=%lu used=%d(hi=%d)\n",
sc, (unsigned long)uio->uio_resid, sc->sc_pr.used,
sc->sc_pr.usedhigh));
if (cb->mmapped)
return EINVAL;
if (uio->uio_resid == 0) {
sc->sc_eof++;
return 0;
}
/*
* If half-duplex and currently recording, throw away data.
*/
if (!sc->sc_full_duplex &&
(sc->sc_mode & AUMODE_RECORD)) {
uio->uio_offset += uio->uio_resid;
uio->uio_resid = 0;
DPRINTF(("audio_write: half-dpx read busy\n"));
return (0);
}
if (!(sc->sc_mode & AUMODE_PLAY_ALL) && sc->sc_playdrop > 0) {
n = min(sc->sc_playdrop, uio->uio_resid);
DPRINTF(("audio_write: playdrop %d\n", n));
uio->uio_offset += n;
uio->uio_resid -= n;
sc->sc_playdrop -= n;
if (uio->uio_resid == 0)
return 0;
}
params = &sc->sc_pparams;
DPRINTFN(1, ("audio_write: sr=%ld, enc=%d, prec=%d, chan=%d, sw=%p, "
"fact=%d\n",
sc->sc_pparams.sample_rate, sc->sc_pparams.encoding,
sc->sc_pparams.precision, sc->sc_pparams.channels,
sc->sc_pparams.sw_code, sc->sc_pparams.factor));
/*
* For some encodings, handle data in sample unit.
*/
switch (params->hw_encoding) {
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_SLINEAR_BE:
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_ULINEAR_BE:
hw_bits_per_sample = params->hw_channels * params->precision
* params->factor;
user_bits_per_sample = params->channels * params->precision;
break;
default:
hw_bits_per_sample = 8 * params->factor / params->factor_denom;
user_bits_per_sample = 8;
}
#ifdef DIAGNOSTIC
if (hw_bits_per_sample > MAX_SAMPLE_SIZE * 8) {
printf("audio_write(): Invalid sample size: cur=%d max=%d\n",
hw_bits_per_sample / 8, MAX_SAMPLE_SIZE);
}
#endif
space = ((params->hw_sample_rate / params->sample_rate) + 1)
* hw_bits_per_sample / 8;
error = 0;
while ((input_remain = uio->uio_resid + sc->sc_input_fragment_length) > 0
&& !error) {
s = splaudio();
if (input_remain < user_bits_per_sample / 8) {
n = uio->uio_resid;
DPRINTF(("audio_write: fragment uiomove length=%d\n", n));
error = uiomove(sc->sc_input_fragment
+ sc->sc_input_fragment_length,
n, uio);
if (!error)
sc->sc_input_fragment_length += n;
splx(s);
return (error);
}
while (cb->used + space >= cb->usedhigh) {
DPRINTFN(2, ("audio_write: sleep used=%d lowat=%d "
"hiwat=%d\n",
cb->used, cb->usedlow, cb->usedhigh));
if (ioflag & IO_NDELAY) {
splx(s);
return (EWOULDBLOCK);
}
error = audio_sleep(&sc->sc_wchan, "aud_wr");
if (sc->sc_dying)
error = EIO;
if (error) {
splx(s);
return (error);
}
}
used = cb->used;
inp = cb->inp;
cb->copying = 1;
splx(s);
cc = cb->usedhigh - used; /* maximum to write */
/* cc may be greater than the size of the ring buffer */
if (cc > cb->end - cb->start)
cc = cb->end - cb->start;
/* cc: # of bytes we can write to the ring buffer */
samples = cc * 8 / hw_bits_per_sample;
#ifdef DIAGNOSTIC
if (samples == 0)
printf("audio_write: samples (cc/hw_bps) == 0\n");
#endif
/* samples: # of samples we can write to the ring buffer */
samples = samples * params->sample_rate / params->hw_sample_rate;
#ifdef DIAGNOSTIC
if (samples == 0)
printf("audio_write: samples (rate/hw_rate) == 0 "
"usedhigh-used=%d cc/hw_bps=%d/%d "
"rate/hw_rate=%ld/%ld space=%d\n",
cb->usedhigh - cb->used, cc,
hw_bits_per_sample / 8, params->sample_rate,
params->hw_sample_rate, space);
#endif
/* samples: # of samples in source data */
cc = samples * user_bits_per_sample / 8;
/* cc: # of bytes in source data */
if (input_remain < cc) /* and no more than we have */
cc = (input_remain * 8 / user_bits_per_sample)
* user_bits_per_sample / 8;
#ifdef DIAGNOSTIC
if (cc == 0)
printf("audio_write: cc == 0\n");
#endif
if (cc * params->factor / params->factor_denom
> sc->sc_pconvbuffer_size) {
/*
* cc = (pconv / factor / user_bps ) * user_bps
*/
cc = (sc->sc_pconvbuffer_size * params->factor_denom
* 8 / params->factor / user_bits_per_sample)
* user_bits_per_sample / 8;
}
#ifdef DIAGNOSTIC
/*
* This should never happen since the block size and and
* block pointers are always nicely aligned.
*/
if (cc == 0) {
printf("audio_write: cc == 0, swcode=%p, factor=%d "
"remain=%d u_bps=%d hw_bps=%d\n",
sc->sc_pparams.sw_code, sc->sc_pparams.factor,
input_remain, user_bits_per_sample,
hw_bits_per_sample);
cb->copying = 0;
return EINVAL;
}
#endif
DPRINTFN(1, ("audio_write: uiomove cc=%d inp=%p, left=%lu\n",
cc, inp, (unsigned long)uio->uio_resid));
memcpy(sc->sc_pconvbuffer, sc->sc_input_fragment,
sc->sc_input_fragment_length);
cc -= sc->sc_input_fragment_length;
n = uio->uio_resid;
error = uiomove(sc->sc_pconvbuffer + sc->sc_input_fragment_length,
cc, uio);
if (cc != n - uio->uio_resid) {
printf("audio_write: uiomove didn't move requested "
"amount: requested=%d, actual=%ld\n",
cc, (long)n - uio->uio_resid);
}
/* number of bytes actually moved */
cc = sc->sc_input_fragment_length + n - uio->uio_resid;
sc->sc_input_fragment_length = 0;
#ifdef AUDIO_DEBUG
if (error)
printf("audio_write:(1) uiomove failed %d; cc=%d "
"inp=%p\n", error, cc, inp);
#endif
/*
* Continue even if uiomove() failed because we may have
* gotten a partial block.
*/
/*
* The format of data in sc_pconvbuffer is:
* [sample_rate, encoding, precision, channels]
*/
if (sc->sc_pparams.sw_code) {
sc->sc_pparams.sw_code(sc->hw_hdl,
sc->sc_pconvbuffer, cc);
/* Adjust count after the expansion. */
cc = cc * sc->sc_pparams.factor
/ sc->sc_pparams.factor_denom;
DPRINTFN(1, ("audio_write: expanded cc=%d\n", cc));
}
/*
* The format of data in sc_pconvbuffer is:
* [sample_rate, hw_encoding, hw_precision, channels]
*/
#if NAURATECONV > 0
cc = auconv_play(&sc->sc_pconv, params, inp,
sc->sc_pconvbuffer, cc);
#else
n = cb->end - inp;
if (cc <= n) {
memcpy(inp, sc->sc_pconvbuffer, cc);
} else {
memcpy(inp, sc->sc_pconvbuffer, n);
memcpy(cb->start, sc->sc_pconvbuffer + n, cc - n);
}
#endif /* !NAURATECONV */
/*
* The format of data in inp is:
* [hw_sample_rate, hw_encoding, hw_precision, hw_channels]
* cc is the size of data actually written to inp.
*/
einp = cb->inp + cc;
if (einp >= cb->end)
einp -= cb->end - cb->start; /* not cb->bufsize */
s = splaudio();
/*
* This is a very suboptimal way of keeping track of
* silence in the buffer, but it is simple.
*/
sc->sc_sil_count = 0;
cb->inp = einp;
cb->used += cc;
/*
* If the interrupt routine wants the last block filled AND
* the copy did not fill the last block completely it needs to
* be padded.
*/
if (cb->needfill &&
(inp - cb->start) / cb->blksize ==
(einp - cb->start) / cb->blksize) {
/* Figure out how many bytes to a block boundary. */
cc = cb->blksize - (einp - cb->start) % cb->blksize;
DPRINTF(("audio_write: partial fill %d\n", cc));
} else
cc = 0;
cb->needfill = 0;
cb->copying = 0;
if (!sc->sc_pbus && !cb->pause) {
saveerror = error;
error = audiostartp(sc);
if (saveerror != 0) {
/* Report the first error that occurred. */
error = saveerror;
}
}
splx(s);
if (cc != 0) {
DPRINTFN(1, ("audio_write: fill %d\n", cc));
audio_fill_silence(&sc->sc_pparams, einp, cc);
}
}
return (error);
}
int
audio_ioctl(struct audio_softc *sc, u_long cmd, caddr_t addr, int flag,
struct proc *p)
{
struct audio_hw_if *hw = sc->hw_if;
struct audio_offset *ao;
int error = 0, s, offs, fd;
u_long stamp;
int rbus, pbus;
DPRINTF(("audio_ioctl(%lu,'%c',%lu)\n",
IOCPARM_LEN(cmd), (char)IOCGROUP(cmd), cmd&0xff));
switch (cmd) {
case FIONBIO:
/* All handled in the upper FS layer. */
break;
case FIONREAD:
*(int *)addr = sc->sc_rr.used;
break;
case FIOASYNC:
if (*(int *)addr) {
if (sc->sc_async_audio)
return (EBUSY);
sc->sc_async_audio = p;
DPRINTF(("audio_ioctl: FIOASYNC %p\n", p));
} else
sc->sc_async_audio = 0;
break;
case AUDIO_FLUSH:
DPRINTF(("AUDIO_FLUSH\n"));
rbus = sc->sc_rbus;
pbus = sc->sc_pbus;
audio_clear(sc);
s = splaudio();
error = audio_initbufs(sc);
if (error) {
splx(s);
return error;
}
if ((sc->sc_mode & AUMODE_PLAY) && !sc->sc_pbus && pbus)
error = audiostartp(sc);
if (!error &&
(sc->sc_mode & AUMODE_RECORD) && !sc->sc_rbus && rbus)
error = audiostartr(sc);
splx(s);
break;
/*
* Number of read (write) samples dropped. We don't know where or
* when they were dropped.
*/
case AUDIO_RERROR:
*(int *)addr = sc->sc_rr.drops;
break;
case AUDIO_PERROR:
*(int *)addr = sc->sc_pr.drops;
break;
/*
* Offsets into buffer.
*/
case AUDIO_GETIOFFS:
ao = (struct audio_offset *)addr;
s = splaudio();
/* figure out where next DMA will start */
stamp = sc->sc_rr.stamp;
offs = sc->sc_rr.inp - sc->sc_rr.start;
splx(s);
ao->samples = stamp;
ao->deltablks =
(stamp / sc->sc_rr.blksize) -
(sc->sc_rr.stamp_last / sc->sc_rr.blksize);
sc->sc_rr.stamp_last = stamp;
ao->offset = offs;
break;
case AUDIO_GETOOFFS:
ao = (struct audio_offset *)addr;
s = splaudio();
/* figure out where next DMA will start */
stamp = sc->sc_pr.stamp;
offs = sc->sc_pr.outp - sc->sc_pr.start + sc->sc_pr.blksize;
splx(s);
ao->samples = stamp;
ao->deltablks =
(stamp / sc->sc_pr.blksize) -
(sc->sc_pr.stamp_last / sc->sc_pr.blksize);
sc->sc_pr.stamp_last = stamp;
if (sc->sc_pr.start + offs >= sc->sc_pr.end)
offs = 0;
ao->offset = offs;
break;
/*
* How many bytes will elapse until mike hears the first
* sample of what we write next?
*/
case AUDIO_WSEEK:
*(u_long *)addr = sc->sc_rr.used;
break;
case AUDIO_SETINFO:
DPRINTF(("AUDIO_SETINFO mode=0x%x\n", sc->sc_mode));
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 = sc->sc_full_duplex;
break;
case AUDIO_SETFD:
DPRINTF(("AUDIO_SETFD\n"));
fd = *(int *)addr;
if (hw->get_props(sc->hw_hdl) & AUDIO_PROP_FULLDUPLEX) {
if (hw->setfd)
error = hw->setfd(sc->hw_hdl, fd);
else
error = 0;
if (!error)
sc->sc_full_duplex = fd;
} else {
if (fd)
error = ENOTTY;
else
error = 0;
}
break;
case AUDIO_GETPROPS:
DPRINTF(("AUDIO_GETPROPS\n"));
*(int *)addr = hw->get_props(sc->hw_hdl);
break;
default:
if (hw->dev_ioctl) {
error = hw->dev_ioctl(sc->hw_hdl, cmd, addr, flag, p);
} else {
DPRINTF(("audio_ioctl: unknown ioctl\n"));
error = EINVAL;
}
break;
}
DPRINTF(("audio_ioctl(%lu,'%c',%lu) result %d\n",
IOCPARM_LEN(cmd), (char)IOCGROUP(cmd), cmd&0xff, error));
return (error);
}
int
audio_poll(struct audio_softc *sc, int events, struct proc *p)
{
int revents = 0;
int s = splaudio();
DPRINTF(("audio_poll: events=0x%x mode=%d\n", events, sc->sc_mode));
if (events & (POLLIN | POLLRDNORM)) {
/*
* If half duplex and playing, audio_read() will generate
* silence at the play rate; poll for silence being
* available. Otherwise, poll for recorded sound.
*/
if ((!sc->sc_full_duplex && (sc->sc_mode & AUMODE_PLAY)) ?
sc->sc_pr.stamp > sc->sc_wstamp :
sc->sc_rr.used > sc->sc_rr.usedlow)
revents |= events & (POLLIN | POLLRDNORM);
}
if (events & (POLLOUT | POLLWRNORM)) {
/*
* If half duplex and recording, audio_write() will throw
* away play data, which means we are always ready to write.
* Otherwise, poll for play buffer being below its low water
* mark.
*/
if ((!sc->sc_full_duplex && (sc->sc_mode & AUMODE_RECORD)) ||
sc->sc_pr.used <= sc->sc_pr.usedlow)
revents |= events & (POLLOUT | POLLWRNORM);
}
if (revents == 0) {
if (events & (POLLIN | POLLRDNORM))
selrecord(p, &sc->sc_rsel);
if (events & (POLLOUT | POLLWRNORM))
selrecord(p, &sc->sc_wsel);
}
splx(s);
return (revents);
}
static void
filt_audiordetach(struct knote *kn)
{
struct audio_softc *sc = kn->kn_hook;
int s;
s = splaudio();
SLIST_REMOVE(&sc->sc_rsel.sel_klist, kn, knote, kn_selnext);
splx(s);
}
static int
filt_audioread(struct knote *kn, long hint)
{
struct audio_softc *sc = kn->kn_hook;
int s;
/* XXXLUKEM (thorpej): please make sure this is right */
s = splaudio();
if (sc->sc_mode & AUMODE_PLAY)
kn->kn_data = sc->sc_pr.stamp - sc->sc_wstamp;
else
kn->kn_data = sc->sc_rr.used - sc->sc_rr.usedlow;
splx(s);
return (kn->kn_data > 0);
}
static const struct filterops audioread_filtops =
{ 1, NULL, filt_audiordetach, filt_audioread };
static void
filt_audiowdetach(struct knote *kn)
{
struct audio_softc *sc = kn->kn_hook;
int s;
s = splaudio();
SLIST_REMOVE(&sc->sc_wsel.sel_klist, kn, knote, kn_selnext);
splx(s);
}
static int
filt_audiowrite(struct knote *kn, long hint)
{
struct audio_softc *sc = kn->kn_hook;
int s;
/* XXXLUKEM (thorpej): please make sure this is right */
s = splaudio();
kn->kn_data = sc->sc_pr.usedlow - sc->sc_pr.used;
splx(s);
return (kn->kn_data > 0);
}
static const struct filterops audiowrite_filtops =
{ 1, NULL, filt_audiowdetach, filt_audiowrite };
int
audio_kqfilter(struct audio_softc *sc, struct knote *kn)
{
struct klist *klist;
int s;
switch (kn->kn_filter) {
case EVFILT_READ:
klist = &sc->sc_rsel.sel_klist;
kn->kn_fop = &audioread_filtops;
break;
case EVFILT_WRITE:
klist = &sc->sc_wsel.sel_klist;
kn->kn_fop = &audiowrite_filtops;
break;
default:
return (1);
}
kn->kn_hook = sc;
s = splaudio();
SLIST_INSERT_HEAD(klist, kn, kn_selnext);
splx(s);
return (0);
}
paddr_t
audio_mmap(struct audio_softc *sc, off_t off, int prot)
{
struct audio_hw_if *hw = sc->hw_if;
struct audio_ringbuffer *cb;
int s;
DPRINTF(("audio_mmap: off=%lld, prot=%d\n", (long long)off, prot));
if (!(hw->get_props(sc->hw_hdl) & AUDIO_PROP_MMAP) || !hw->mappage)
return -1;
#if 0
/* XXX
* The idea here was to use the protection to determine if
* we are mapping the read or write buffer, but it fails.
* The VM system is broken in (at least) two ways.
* 1) If you map memory VM_PROT_WRITE you SIGSEGV
* when writing to it, so VM_PROT_READ|VM_PROT_WRITE
* has to be used for mmapping the play buffer.
* 2) Even if calling mmap() with VM_PROT_READ|VM_PROT_WRITE
* audio_mmap will get called at some point with VM_PROT_READ
* only.
* So, alas, we always map the play buffer for now.
*/
if (prot == (VM_PROT_READ|VM_PROT_WRITE) ||
prot == VM_PROT_WRITE)
cb = &sc->sc_pr;
else if (prot == VM_PROT_READ)
cb = &sc->sc_rr;
else
return -1;
#else
cb = &sc->sc_pr;
#endif
if ((u_int)off >= cb->bufsize)
return -1;
if (!cb->mmapped) {
cb->mmapped = 1;
if (cb == &sc->sc_pr) {
audio_fill_silence(&sc->sc_pparams, cb->start,
cb->bufsize);
s = splaudio();
if (!sc->sc_pbus)
(void)audiostartp(sc);
splx(s);
} else {
s = splaudio();
if (!sc->sc_rbus)
(void)audiostartr(sc);
splx(s);
}
}
return hw->mappage(sc->hw_hdl, cb->start, off, prot);
}
int
audiostartr(struct audio_softc *sc)
{
int error;
DPRINTF(("audiostartr: start=%p used=%d(hi=%d) mmapped=%d\n",
sc->sc_rr.start, sc->sc_rr.used, sc->sc_rr.usedhigh,
sc->sc_rr.mmapped));
if (sc->hw_if->trigger_input)
error = sc->hw_if->trigger_input(sc->hw_hdl, sc->sc_rr.start,
sc->sc_rr.end, sc->sc_rr.blksize,
audio_rint, (void *)sc, &sc->sc_rparams);
else
error = sc->hw_if->start_input(sc->hw_hdl, sc->sc_rr.start,
sc->sc_rr.blksize, audio_rint, (void *)sc);
if (error) {
DPRINTF(("audiostartr failed: %d\n", error));
return error;
}
sc->sc_rbus = 1;
return 0;
}
int
audiostartp(struct audio_softc *sc)
{
int error;
DPRINTF(("audiostartp: start=%p used=%d(hi=%d) mmapped=%d\n",
sc->sc_pr.start, sc->sc_pr.used, sc->sc_pr.usedhigh,
sc->sc_pr.mmapped));
if (!sc->sc_pr.mmapped && sc->sc_pr.used < sc->sc_pr.blksize)
return 0;
if (sc->hw_if->trigger_output)
error = sc->hw_if->trigger_output(sc->hw_hdl, sc->sc_pr.start,
sc->sc_pr.end, sc->sc_pr.blksize,
audio_pint, (void *)sc, &sc->sc_pparams);
else
error = sc->hw_if->start_output(sc->hw_hdl, sc->sc_pr.outp,
sc->sc_pr.blksize, audio_pint, (void *)sc);
if (error) {
DPRINTF(("audiostartp failed: %d\n", error));
return error;
}
sc->sc_pbus = 1;
return 0;
}
/*
* When the play interrupt routine finds that the write isn't keeping
* the buffer filled it will insert silence in the buffer to make up
* for this. The part of the buffer that is filled with silence
* is kept track of in a very approximate way: it starts at sc_sil_start
* and extends sc_sil_count bytes. If there is already silence in
* the requested area nothing is done; so when the whole buffer is
* silent nothing happens. When the writer starts again sc_sil_count
* is set to 0.
*/
/* XXX
* Putting silence into the output buffer should not really be done
* at splaudio, but there is no softaudio level to do it at yet.
*/
static __inline void
audio_pint_silence(struct audio_softc *sc, struct audio_ringbuffer *cb,
u_char *inp, int cc)
{
u_char *s, *e, *p, *q;
if (sc->sc_sil_count > 0) {
s = sc->sc_sil_start; /* start of silence */
e = s + sc->sc_sil_count; /* end of sil., may be beyond end */
p = inp; /* adjusted pointer to area to fill */
if (p < s)
p += cb->end - cb->start;
q = p+cc;
/* Check if there is already silence. */
if (!(s <= p && p < e &&
s <= q && q <= e)) {
if (s <= p)
sc->sc_sil_count = max(sc->sc_sil_count, q-s);
DPRINTFN(5,("audio_pint_silence: fill cc=%d inp=%p, "
"count=%d size=%d\n",
cc, inp, sc->sc_sil_count,
(int)(cb->end - cb->start)));
audio_fill_silence(&sc->sc_pparams, inp, cc);
} else {
DPRINTFN(5,("audio_pint_silence: already silent "
"cc=%d inp=%p\n", cc, inp));
}
} else {
sc->sc_sil_start = inp;
sc->sc_sil_count = cc;
DPRINTFN(5, ("audio_pint_silence: start fill %p %d\n",
inp, cc));
audio_fill_silence(&sc->sc_pparams, inp, cc);
}
}
/*
* 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(void *v)
{
struct audio_softc *sc = v;
struct audio_hw_if *hw = sc->hw_if;
struct audio_ringbuffer *cb = &sc->sc_pr;
u_char *inp;
int cc, ccr;
int blksize;
int error;
if (!sc->sc_open)
return; /* ignore interrupt if not open */
blksize = cb->blksize;
cb->outp += blksize;
if (cb->outp >= cb->end)
cb->outp = cb->start;
cb->stamp += blksize / sc->sc_pparams.factor;
if (cb->mmapped) {
DPRINTFN(5, ("audio_pint: mmapped outp=%p cc=%d inp=%p\n",
cb->outp, blksize, cb->inp));
if (!hw->trigger_output)
(void)hw->start_output(sc->hw_hdl, cb->outp,
blksize, audio_pint, (void *)sc);
return;
}
#ifdef AUDIO_INTR_TIME
{
struct timeval tv;
u_long t;
microtime(&tv);
t = tv.tv_usec + 1000000 * tv.tv_sec;
if (sc->sc_pnintr) {
long lastdelta, totdelta;
lastdelta = t - sc->sc_plastintr - sc->sc_pblktime;
if (lastdelta > sc->sc_pblktime / 3) {
printf("audio: play interrupt(%d) off "
"relative by %ld us (%lu)\n",
sc->sc_pnintr, lastdelta,
sc->sc_pblktime);
}
totdelta = t - sc->sc_pfirstintr -
sc->sc_pblktime * sc->sc_pnintr;
if (totdelta > sc->sc_pblktime) {
printf("audio: play interrupt(%d) off "
"absolute by %ld us (%lu) (LOST)\n",
sc->sc_pnintr, totdelta,
sc->sc_pblktime);
sc->sc_pnintr++; /* avoid repeated messages */
}
} else
sc->sc_pfirstintr = t;
sc->sc_plastintr = t;
sc->sc_pnintr++;
}
#endif
cb->used -= blksize;
if (cb->used < blksize) {
/* we don't have a full block to use */
if (cb->copying) {
/* writer is in progress, don't disturb */
cb->needfill = 1;
DPRINTFN(1, ("audio_pint: copying in progress\n"));
} else {
inp = cb->inp;
cc = blksize - (inp - cb->start) % blksize;
ccr = cc / sc->sc_pparams.factor;
if (cb->pause)
cb->pdrops += ccr;
else {
cb->drops += ccr;
sc->sc_playdrop += ccr;
}
audio_pint_silence(sc, cb, inp, cc);
inp += cc;
if (inp >= cb->end)
inp = cb->start;
cb->inp = inp;
cb->used += cc;
/* Clear next block so we keep ahead of the DMA. */
if (cb->used + cc < cb->usedhigh)
audio_pint_silence(sc, cb, inp, blksize);
}
}
DPRINTFN(5, ("audio_pint: outp=%p cc=%d\n", cb->outp, blksize));
if (!hw->trigger_output) {
error = hw->start_output(sc->hw_hdl, cb->outp, blksize,
audio_pint, (void *)sc);
if (error) {
/* XXX does this really help? */
DPRINTF(("audio_pint restart failed: %d\n", error));
audio_clear(sc);
}
}
DPRINTFN(2, ("audio_pint: mode=%d pause=%d used=%d lowat=%d\n",
sc->sc_mode, cb->pause, cb->used, cb->usedlow));
if ((sc->sc_mode & AUMODE_PLAY) && !cb->pause) {
if (cb->used <= cb->usedlow) {
audio_wakeup(&sc->sc_wchan);
selnotify(&sc->sc_wsel, 0);
if (sc->sc_async_audio) {
DPRINTFN(3, ("audio_pint: sending SIGIO %p\n",
sc->sc_async_audio));
psignal(sc->sc_async_audio, SIGIO);
}
}
}
/* Possible to return one or more "phantom blocks" now. */
if (!sc->sc_full_duplex && sc->sc_rchan) {
audio_wakeup(&sc->sc_rchan);
selnotify(&sc->sc_rsel, 0);
if (sc->sc_async_audio)
psignal(sc->sc_async_audio, SIGIO);
}
}
/*
* 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(void *v)
{
struct audio_softc *sc = v;
struct audio_hw_if *hw = sc->hw_if;
struct audio_ringbuffer *cb = &sc->sc_rr;
int blksize;
int error;
if (!sc->sc_open)
return; /* ignore interrupt if not open */
blksize = cb->blksize;
cb->inp += blksize;
if (cb->inp >= cb->end)
cb->inp = cb->start;
cb->stamp += blksize;
if (cb->mmapped) {
DPRINTFN(2, ("audio_rint: mmapped inp=%p cc=%d\n",
cb->inp, blksize));
if (!hw->trigger_input)
(void)hw->start_input(sc->hw_hdl, cb->inp, blksize,
audio_rint, (void *)sc);
return;
}
#ifdef AUDIO_INTR_TIME
{
struct timeval tv;
u_long t;
microtime(&tv);
t = tv.tv_usec + 1000000 * tv.tv_sec;
if (sc->sc_rnintr) {
long lastdelta, totdelta;
lastdelta = t - sc->sc_rlastintr - sc->sc_rblktime;
if (lastdelta > sc->sc_rblktime / 5) {
printf("audio: record interrupt(%d) off "
"relative by %ld us (%lu)\n",
sc->sc_rnintr, lastdelta,
sc->sc_rblktime);
}
totdelta = t - sc->sc_rfirstintr -
sc->sc_rblktime * sc->sc_rnintr;
if (totdelta > sc->sc_rblktime / 2) {
sc->sc_rnintr++;
printf("audio: record interrupt(%d) off "
"absolute by %ld us (%lu)\n",
sc->sc_rnintr, totdelta,
sc->sc_rblktime);
sc->sc_rnintr++; /* avoid repeated messages */
}
} else
sc->sc_rfirstintr = t;
sc->sc_rlastintr = t;
sc->sc_rnintr++;
}
#endif
cb->used += blksize;
if (cb->pause) {
DPRINTFN(1, ("audio_rint: pdrops %lu\n", cb->pdrops));
cb->pdrops += blksize;
cb->outp += blksize;
if (cb->outp >= cb->end)
cb->outp = cb->start;
cb->used -= blksize;
} else if (cb->used + blksize >= cb->usedhigh && !cb->copying) {
DPRINTFN(1, ("audio_rint: drops %lu\n", cb->drops));
cb->drops += blksize;
cb->outp += blksize;
if (cb->outp >= cb->end)
cb->outp = cb->start;
cb->used -= blksize;
}
DPRINTFN(2, ("audio_rint: inp=%p cc=%d used=%d\n",
cb->inp, blksize, cb->used));
if (!hw->trigger_input) {
error = hw->start_input(sc->hw_hdl, cb->inp, blksize,
audio_rint, (void *)sc);
if (error) {
/* XXX does this really help? */
DPRINTF(("audio_rint: restart failed: %d\n", error));
audio_clear(sc);
}
}
audio_wakeup(&sc->sc_rchan);
selnotify(&sc->sc_rsel, 0);
if (sc->sc_async_audio)
psignal(sc->sc_async_audio, SIGIO);
}
int
audio_check_params(struct audio_params *p)
{
if (p->encoding == AUDIO_ENCODING_PCM16) {
if (p->precision == 8)
p->encoding = AUDIO_ENCODING_ULINEAR;
else
p->encoding = AUDIO_ENCODING_SLINEAR;
} else if (p->encoding == AUDIO_ENCODING_PCM8) {
if (p->precision == 8)
p->encoding = AUDIO_ENCODING_ULINEAR;
else
return EINVAL;
}
if (p->encoding == AUDIO_ENCODING_SLINEAR)
#if BYTE_ORDER == LITTLE_ENDIAN
p->encoding = AUDIO_ENCODING_SLINEAR_LE;
#else
p->encoding = AUDIO_ENCODING_SLINEAR_BE;
#endif
if (p->encoding == AUDIO_ENCODING_ULINEAR)
#if BYTE_ORDER == LITTLE_ENDIAN
p->encoding = AUDIO_ENCODING_ULINEAR_LE;
#else
p->encoding = AUDIO_ENCODING_ULINEAR_BE;
#endif
switch (p->encoding) {
case AUDIO_ENCODING_ULAW:
case AUDIO_ENCODING_ALAW:
if (p->precision != 8)
return (EINVAL);
break;
case AUDIO_ENCODING_ADPCM:
if (p->precision != 4 && p->precision != 8)
return (EINVAL);
break;
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_SLINEAR_BE:
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_ULINEAR_BE:
/* XXX is: our zero-fill can handle any multiple of 8 */
if (p->precision != 8 && p->precision != 16 &&
p->precision != 24 && p->precision != 32)
return (EINVAL);
break;
case AUDIO_ENCODING_MPEG_L1_STREAM:
case AUDIO_ENCODING_MPEG_L1_PACKETS:
case AUDIO_ENCODING_MPEG_L1_SYSTEM:
case AUDIO_ENCODING_MPEG_L2_STREAM:
case AUDIO_ENCODING_MPEG_L2_PACKETS:
case AUDIO_ENCODING_MPEG_L2_SYSTEM:
break;
default:
return (EINVAL);
}
if (p->channels < 1 || p->channels > 8) /* sanity check # of channels*/
return (EINVAL);
return (0);
}
int
audio_set_defaults(struct audio_softc *sc, u_int mode)
{
struct audio_info ai;
/* default parameters */
sc->sc_rparams = audio_default;
sc->sc_pparams = audio_default;
sc->sc_blkset = 0;
AUDIO_INITINFO(&ai);
ai.record.sample_rate = sc->sc_rparams.sample_rate;
ai.record.encoding = sc->sc_rparams.encoding;
ai.record.channels = sc->sc_rparams.channels;
ai.record.precision = sc->sc_rparams.precision;
ai.play.sample_rate = sc->sc_pparams.sample_rate;
ai.play.encoding = sc->sc_pparams.encoding;
ai.play.channels = sc->sc_pparams.channels;
ai.play.precision = sc->sc_pparams.precision;
ai.mode = mode;
return (audiosetinfo(sc, &ai));
}
int
au_set_lr_value(struct audio_softc *sc, mixer_ctrl_t *ct, int l, int r)
{
ct->type = AUDIO_MIXER_VALUE;
ct->un.value.num_channels = 2;
ct->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
ct->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
if (sc->hw_if->set_port(sc->hw_hdl, ct) == 0)
return 0;
ct->un.value.num_channels = 1;
ct->un.value.level[AUDIO_MIXER_LEVEL_MONO] = (l+r)/2;
return sc->hw_if->set_port(sc->hw_hdl, ct);
}
int
au_set_gain(struct audio_softc *sc, struct au_mixer_ports *ports,
int gain, int balance)
{
mixer_ctrl_t ct;
int i, error;
int l, r;
u_int mask;
int nset;
if (balance == AUDIO_MID_BALANCE) {
l = r = gain;
} else if (balance < AUDIO_MID_BALANCE) {
l = gain;
r = (balance * gain) / AUDIO_MID_BALANCE;
} else {
r = gain;
l = ((AUDIO_RIGHT_BALANCE - balance) * gain)
/ AUDIO_MID_BALANCE;
}
DPRINTF(("au_set_gain: gain=%d balance=%d, l=%d r=%d\n",
gain, balance, l, r));
if (ports->index == -1) {
usemaster:
if (ports->master == -1)
return 0; /* just ignore it silently */
ct.dev = ports->master;
error = au_set_lr_value(sc, &ct, l, r);
} else {
ct.dev = ports->index;
if (ports->isenum) {
ct.type = AUDIO_MIXER_ENUM;
error = sc->hw_if->get_port(sc->hw_hdl, &ct);
if (error)
return error;
if (ports->isdual) {
if (ports->cur_port == -1)
ct.dev = ports->master;
else
ct.dev = ports->miport[ports->cur_port];
error = au_set_lr_value(sc, &ct, l, r);
} else {
for(i = 0; i < ports->nports; i++)
if (ports->misel[i] == ct.un.ord) {
ct.dev = ports->miport[i];
if (ct.dev == -1 ||
au_set_lr_value(sc, &ct, l, r))
goto usemaster;
else
break;
}
}
} else {
ct.type = AUDIO_MIXER_SET;
error = sc->hw_if->get_port(sc->hw_hdl, &ct);
if (error)
return error;
mask = ct.un.mask;
nset = 0;
for(i = 0; i < ports->nports; i++) {
if (ports->misel[i] & mask) {
ct.dev = ports->miport[i];
if (ct.dev != -1 &&
au_set_lr_value(sc, &ct, l, r) == 0)
nset++;
}
}
if (nset == 0)
goto usemaster;
}
}
if (!error)
mixer_signal(sc);
return error;
}
int
au_get_lr_value(struct audio_softc *sc, mixer_ctrl_t *ct, int *l, int *r)
{
int error;
ct->un.value.num_channels = 2;
if (sc->hw_if->get_port(sc->hw_hdl, ct) == 0) {
*l = ct->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
*r = ct->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
} else {
ct->un.value.num_channels = 1;
error = sc->hw_if->get_port(sc->hw_hdl, ct);
if (error)
return error;
*r = *l = ct->un.value.level[AUDIO_MIXER_LEVEL_MONO];
}
return 0;
}
void
au_get_gain(struct audio_softc *sc, struct au_mixer_ports *ports,
u_int *pgain, u_char *pbalance)
{
mixer_ctrl_t ct;
int i, l, r, n;
int lgain = AUDIO_MAX_GAIN/2, rgain = AUDIO_MAX_GAIN/2;
if (ports->index == -1) {
usemaster:
if (ports->master == -1)
goto bad;
ct.dev = ports->master;
ct.type = AUDIO_MIXER_VALUE;
if (au_get_lr_value(sc, &ct, &lgain, &rgain))
goto bad;
} else {
ct.dev = ports->index;
if (ports->isenum) {
ct.type = AUDIO_MIXER_ENUM;
if (sc->hw_if->get_port(sc->hw_hdl, &ct))
goto bad;
ct.type = AUDIO_MIXER_VALUE;
if (ports->isdual) {
if (ports->cur_port == -1)
ct.dev = ports->master;
else
ct.dev = ports->miport[ports->cur_port];
au_get_lr_value(sc, &ct, &lgain, &rgain);
} else {
for(i = 0; i < ports->nports; i++)
if (ports->misel[i] == ct.un.ord) {
ct.dev = ports->miport[i];
if (ct.dev == -1 ||
au_get_lr_value(sc, &ct,
&lgain, &rgain))
goto usemaster;
else
break;
}
}
} else {
ct.type = AUDIO_MIXER_SET;
if (sc->hw_if->get_port(sc->hw_hdl, &ct))
goto bad;
ct.type = AUDIO_MIXER_VALUE;
lgain = rgain = n = 0;
for(i = 0; i < ports->nports; i++) {
if (ports->misel[i] & ct.un.mask) {
ct.dev = ports->miport[i];
if (ct.dev == -1 ||
au_get_lr_value(sc, &ct, &l, &r))
goto usemaster;
else {
lgain += l;
rgain += r;
n++;
}
}
}
if (n != 0) {
lgain /= n;
rgain /= n;
}
}
}
bad:
if (lgain == rgain) { /* handles lgain==rgain==0 */
*pgain = lgain;
*pbalance = AUDIO_MID_BALANCE;
} else if (lgain < rgain) {
*pgain = rgain;
/* balance should be > AUDIO_MID_BALANCE */
*pbalance = AUDIO_RIGHT_BALANCE -
(AUDIO_MID_BALANCE * lgain) / rgain;
} else /* lgain > rgain */ {
*pgain = lgain;
/* balance should be < AUDIO_MID_BALANCE */
*pbalance = (AUDIO_MID_BALANCE * rgain) / lgain;
}
}
int
au_set_port(struct audio_softc *sc, struct au_mixer_ports *ports, u_int port)
{
mixer_ctrl_t ct;
int i, error, use_mixerout;
use_mixerout = 1;
if (port == 0) {
if (ports->allports == 0)
return 0; /* Allow this special case. */
else if (ports->isdual) {
if (ports->cur_port == -1) {
return 0;
} else {
port = ports->aumask[ports->cur_port];
ports->cur_port = -1;
use_mixerout = 0;
}
}
}
if (ports->index == -1)
return EINVAL;
ct.dev = ports->index;
if (ports->isenum) {
if (port & (port-1))
return EINVAL; /* Only one port allowed */
ct.type = AUDIO_MIXER_ENUM;
error = EINVAL;
for(i = 0; i < ports->nports; i++)
if (ports->aumask[i] == port) {
if (ports->isdual && use_mixerout) {
ct.un.ord = ports->mixerout;
ports->cur_port = i;
} else {
ct.un.ord = ports->misel[i];
}
error = sc->hw_if->set_port(sc->hw_hdl, &ct);
break;
}
} else {
ct.type = AUDIO_MIXER_SET;
ct.un.mask = 0;
for(i = 0; i < ports->nports; i++)
if (ports->aumask[i] & port)
ct.un.mask |= ports->misel[i];
if (port != 0 && ct.un.mask == 0)
error = EINVAL;
else
error = sc->hw_if->set_port(sc->hw_hdl, &ct);
}
if (!error)
mixer_signal(sc);
return error;
}
int
au_get_port(struct audio_softc *sc, struct au_mixer_ports *ports)
{
mixer_ctrl_t ct;
int i, aumask;
if (ports->index == -1)
return 0;
ct.dev = ports->index;
ct.type = ports->isenum ? AUDIO_MIXER_ENUM : AUDIO_MIXER_SET;
if (sc->hw_if->get_port(sc->hw_hdl, &ct))
return 0;
aumask = 0;
if (ports->isenum) {
if (ports->isdual && ports->cur_port != -1) {
if (ports->mixerout == ct.un.ord)
aumask = ports->aumask[ports->cur_port];
else
ports->cur_port = -1;
}
if (aumask == 0)
for(i = 0; i < ports->nports; i++)
if (ports->misel[i] == ct.un.ord)
aumask = ports->aumask[i];
} else {
for(i = 0; i < ports->nports; i++)
if (ct.un.mask & ports->misel[i])
aumask |= ports->aumask[i];
}
return aumask;
}
#if NAURATECONV <= 0
/* dummy function for the case that aurateconv is not linked */
int
auconv_check_params(const struct audio_params *params)
{
if (params->hw_channels == params->channels
&& params->hw_sample_rate == params->sample_rate)
return 0; /* No conversion */
return (EINVAL);
}
#endif /* !NAURATECONV */
int
audiosetinfo(struct audio_softc *sc, struct audio_info *ai)
{
struct audio_prinfo *r = &ai->record, *p = &ai->play;
int cleared;
int s, setmode, modechange = 0;
int error;
struct audio_hw_if *hw = sc->hw_if;
struct audio_params pp, rp;
int np, nr;
unsigned int blks;
int oldpblksize, oldrblksize;
int rbus, pbus;
u_int gain;
u_char balance;
if (hw == 0) /* HW has not attached */
return(ENXIO);
rbus = sc->sc_rbus;
pbus = sc->sc_pbus;
error = 0;
cleared = 0;
pp = sc->sc_pparams; /* Temporary encoding storage in */
rp = sc->sc_rparams; /* case setting the modes fails. */
nr = np = 0;
if (p->sample_rate != ~0) {
pp.sample_rate = p->sample_rate;
np++;
}
if (r->sample_rate != ~0) {
rp.sample_rate = r->sample_rate;
nr++;
}
if (p->encoding != ~0) {
pp.encoding = p->encoding;
np++;
}
if (r->encoding != ~0) {
rp.encoding = r->encoding;
nr++;
}
if (p->precision != ~0) {
pp.precision = p->precision;
np++;
}
if (r->precision != ~0) {
rp.precision = r->precision;
nr++;
}
if (p->channels != ~0) {
pp.channels = p->channels;
np++;
}
if (r->channels != ~0) {
rp.channels = r->channels;
nr++;
}
#ifdef AUDIO_DEBUG
if (audiodebug && nr)
audio_print_params("Setting record params", &rp);
if (audiodebug && np)
audio_print_params("Setting play params", &pp);
#endif
if (nr && (error = audio_check_params(&rp)))
return error;
if (np && (error = audio_check_params(&pp)))
return error;
setmode = 0;
if (nr) {
if (!cleared) {
audio_clear(sc);
cleared = 1;
}
modechange = 1;
rp.sw_code = 0;
rp.factor = 1;
rp.factor_denom = 1;
rp.hw_sample_rate = rp.sample_rate;
rp.hw_encoding = rp.encoding;
rp.hw_precision = rp.precision;
rp.hw_channels = rp.channels;
setmode |= AUMODE_RECORD;
}
if (np) {
if (!cleared) {
audio_clear(sc);
cleared = 1;
}
modechange = 1;
pp.sw_code = 0;
pp.factor = 1;
pp.factor_denom = 1;
pp.hw_sample_rate = pp.sample_rate;
pp.hw_encoding = pp.encoding;
pp.hw_precision = pp.precision;
pp.hw_channels = pp.channels;
setmode |= AUMODE_PLAY;
}
if (ai->mode != ~0) {
if (!cleared) {
audio_clear(sc);
cleared = 1;
}
modechange = 1;
sc->sc_mode = ai->mode;
if (sc->sc_mode & AUMODE_PLAY_ALL)
sc->sc_mode |= AUMODE_PLAY;
if ((sc->sc_mode & AUMODE_PLAY) && !sc->sc_full_duplex)
/* Play takes precedence */
sc->sc_mode &= ~AUMODE_RECORD;
}
if (modechange) {
int orig_p_channels, orig_p_rate;
int orig_r_channels, orig_r_rate;
int indep;
indep = hw->get_props(sc->hw_hdl) & AUDIO_PROP_INDEPENDENT;
if (!indep) {
if (setmode == AUMODE_RECORD)
pp = rp;
else if (setmode == AUMODE_PLAY)
rp = pp;
}
/* Some device drivers change channels/sample_rate and change
* no channels/sample_rate. */
orig_p_channels = pp.channels;
orig_p_rate = pp.sample_rate;
orig_r_channels = rp.channels;
orig_r_rate = rp.sample_rate;
error = hw->set_params(sc->hw_hdl, setmode,
sc->sc_mode & (AUMODE_PLAY | AUMODE_RECORD), &pp, &rp);
if (error)
return (error);
if (np) {
if (orig_p_channels != pp.channels)
pp.hw_channels = pp.channels;
if (orig_p_rate != pp.sample_rate)
pp.hw_sample_rate = pp.sample_rate;
error = auconv_check_params(&pp);
if (error)
return (error);
}
if (nr) {
if (orig_r_channels != rp.channels)
rp.hw_channels = rp.channels;
if (orig_r_rate != rp.sample_rate)
rp.hw_sample_rate = rp.sample_rate;
error = auconv_check_params(&rp);
if (error)
return (error);
}
if (!indep) {
if (setmode == AUMODE_RECORD) {
pp.sample_rate = rp.sample_rate;
pp.encoding = rp.encoding;
pp.channels = rp.channels;
pp.precision = rp.precision;
} else if (setmode == AUMODE_PLAY) {
rp.sample_rate = pp.sample_rate;
rp.encoding = pp.encoding;
rp.channels = pp.channels;
rp.precision = pp.precision;
}
}
sc->sc_rparams = rp;
sc->sc_pparams = pp;
}
oldpblksize = sc->sc_pr.blksize;
oldrblksize = sc->sc_rr.blksize;
/* Play params can affect the record params, so recalculate blksize. */
if (nr || np) {
audio_calc_blksize(sc, AUMODE_RECORD);
audio_calc_blksize(sc, AUMODE_PLAY);
}
#ifdef AUDIO_DEBUG
if (audiodebug > 1 && nr)
audio_print_params("After setting record params", &sc->sc_rparams);
if (audiodebug > 1 && np)
audio_print_params("After setting play params", &sc->sc_pparams);
#endif
if (p->port != ~0) {
if (!cleared) {
audio_clear(sc);
cleared = 1;
}
error = au_set_port(sc, &sc->sc_outports, p->port);
if (error)
return(error);
}
if (r->port != ~0) {
if (!cleared) {
audio_clear(sc);
cleared = 1;
}
error = au_set_port(sc, &sc->sc_inports, r->port);
if (error)
return(error);
}
if (p->gain != ~0) {
au_get_gain(sc, &sc->sc_outports, &gain, &balance);
error = au_set_gain(sc, &sc->sc_outports, p->gain, balance);
if (error)
return(error);
}
if (r->gain != ~0) {
au_get_gain(sc, &sc->sc_inports, &gain, &balance);
error = au_set_gain(sc, &sc->sc_inports, r->gain, balance);
if (error)
return(error);
}
if (p->balance != (u_char)~0) {
au_get_gain(sc, &sc->sc_outports, &gain, &balance);
error = au_set_gain(sc, &sc->sc_outports, gain, p->balance);
if (error)
return(error);
}
if (r->balance != (u_char)~0) {
au_get_gain(sc, &sc->sc_inports, &gain, &balance);
error = au_set_gain(sc, &sc->sc_inports, gain, r->balance);
if (error)
return(error);
}
if (ai->monitor_gain != ~0 &&
sc->sc_monitor_port != -1) {
mixer_ctrl_t ct;
ct.dev = sc->sc_monitor_port;
ct.type = AUDIO_MIXER_VALUE;
ct.un.value.num_channels = 1;
ct.un.value.level[AUDIO_MIXER_LEVEL_MONO] = ai->monitor_gain;
error = sc->hw_if->set_port(sc->hw_hdl, &ct);
if (error)
return(error);
}
if (p->pause != (u_char)~0) {
sc->sc_pr.pause = p->pause;
if (!p->pause && !sc->sc_pbus && (sc->sc_mode & AUMODE_PLAY)) {
s = splaudio();
error = audiostartp(sc);
splx(s);
if (error)
return error;
}
}
if (r->pause != (u_char)~0) {
sc->sc_rr.pause = r->pause;
if (!r->pause && !sc->sc_rbus &&
(sc->sc_mode & AUMODE_RECORD)) {
s = splaudio();
error = audiostartr(sc);
splx(s);
if (error)
return error;
}
}
if (ai->blocksize != ~0) {
/* Block size specified explicitly. */
if (!cleared) {
audio_clear(sc);
cleared = 1;
}
if (ai->blocksize == 0) {
sc->sc_blkset = 0;
audio_calc_blksize(sc, AUMODE_RECORD);
audio_calc_blksize(sc, AUMODE_PLAY);
} else {
sc->sc_blkset = 1;
sc->sc_pr.blksize = sc->sc_rr.blksize = ai->blocksize;
}
}
if (ai->mode != ~0) {
if (sc->sc_mode & AUMODE_PLAY)
audio_init_play(sc);
if (sc->sc_mode & AUMODE_RECORD)
audio_init_record(sc);
}
if (hw->commit_settings) {
error = hw->commit_settings(sc->hw_hdl);
if (error)
return (error);
}
if (cleared) {
s = splaudio();
error = audio_initbufs(sc);
if (error) goto err;
if (sc->sc_pr.blksize != oldpblksize ||
sc->sc_rr.blksize != oldrblksize)
audio_calcwater(sc);
if ((sc->sc_mode & AUMODE_PLAY) &&
pbus && !sc->sc_pbus)
error = audiostartp(sc);
if (!error &&
(sc->sc_mode & AUMODE_RECORD) &&
rbus && !sc->sc_rbus)
error = audiostartr(sc);
err:
splx(s);
if (error)
return error;
}
/* Change water marks after initializing the buffers. */
if (ai->hiwat != ~0) {
blks = ai->hiwat;
if (blks > sc->sc_pr.maxblks)
blks = sc->sc_pr.maxblks;
if (blks < 2)
blks = 2;
sc->sc_pr.usedhigh = blks * sc->sc_pr.blksize;
}
if (ai->lowat != ~0) {
blks = ai->lowat;
if (blks > sc->sc_pr.maxblks - 1)
blks = sc->sc_pr.maxblks - 1;
sc->sc_pr.usedlow = blks * sc->sc_pr.blksize;
}
if (ai->hiwat != ~0 || ai->lowat != ~0) {
if (sc->sc_pr.usedlow > sc->sc_pr.usedhigh - sc->sc_pr.blksize)
sc->sc_pr.usedlow =
sc->sc_pr.usedhigh - sc->sc_pr.blksize;
}
return (0);
}
int
audiogetinfo(struct audio_softc *sc, struct audio_info *ai)
{
struct audio_prinfo *r = &ai->record, *p = &ai->play;
struct audio_hw_if *hw = sc->hw_if;
if (hw == 0) /* HW has not attached */
return(ENXIO);
p->sample_rate = sc->sc_pparams.sample_rate;
r->sample_rate = sc->sc_rparams.sample_rate;
p->channels = sc->sc_pparams.channels;
r->channels = sc->sc_rparams.channels;
p->precision = sc->sc_pparams.precision;
r->precision = sc->sc_rparams.precision;
p->encoding = sc->sc_pparams.encoding;
r->encoding = sc->sc_rparams.encoding;
r->port = au_get_port(sc, &sc->sc_inports);
p->port = au_get_port(sc, &sc->sc_outports);
r->avail_ports = sc->sc_inports.allports;
p->avail_ports = sc->sc_outports.allports;
au_get_gain(sc, &sc->sc_inports, &r->gain, &r->balance);
au_get_gain(sc, &sc->sc_outports, &p->gain, &p->balance);
if (sc->sc_monitor_port != -1) {
mixer_ctrl_t ct;
ct.dev = sc->sc_monitor_port;
ct.type = AUDIO_MIXER_VALUE;
ct.un.value.num_channels = 1;
if (sc->hw_if->get_port(sc->hw_hdl, &ct))
ai->monitor_gain = 0;
else
ai->monitor_gain =
ct.un.value.level[AUDIO_MIXER_LEVEL_MONO];
} else
ai->monitor_gain = 0;
p->seek = sc->sc_pr.used;
r->seek = sc->sc_rr.used;
p->samples = sc->sc_pr.stamp - sc->sc_pr.drops;
r->samples = sc->sc_rr.stamp - sc->sc_rr.drops;
p->eof = sc->sc_eof;
r->eof = 0;
p->pause = sc->sc_pr.pause;
r->pause = sc->sc_rr.pause;
p->error = sc->sc_pr.drops != 0;
r->error = sc->sc_rr.drops != 0;
p->waiting = r->waiting = 0; /* open never hangs */
p->open = (sc->sc_open & AUOPEN_WRITE) != 0;
r->open = (sc->sc_open & AUOPEN_READ) != 0;
p->active = sc->sc_pbus;
r->active = sc->sc_rbus;
p->buffer_size = sc->sc_pr.bufsize;
r->buffer_size = sc->sc_rr.bufsize;
ai->blocksize = sc->sc_pr.blksize;
ai->hiwat = sc->sc_pr.usedhigh / sc->sc_pr.blksize;
ai->lowat = sc->sc_pr.usedlow / sc->sc_pr.blksize;
ai->mode = sc->sc_mode;
return (0);
}
/*
* Mixer driver
*/
int
mixer_open(dev_t dev, struct audio_softc *sc, int flags, int ifmt,
struct proc *p)
{
if (!sc->hw_if)
return (ENXIO);
DPRINTF(("mixer_open: flags=0x%x sc=%p\n", flags, sc));
return (0);
}
/*
* Remove a process from those to be signalled on mixer activity.
*/
static void
mixer_remove(struct audio_softc *sc, struct proc *p)
{
struct mixer_asyncs **pm, *m;
for(pm = &sc->sc_async_mixer; *pm; pm = &(*pm)->next) {
if ((*pm)->proc == p) {
m = *pm;
*pm = m->next;
free(m, M_DEVBUF);
return;
}
}
}
/*
* Signal all processes waiting for the mixer.
*/
static void
mixer_signal(struct audio_softc *sc)
{
struct mixer_asyncs *m;
for(m = sc->sc_async_mixer; m; m = m->next)
psignal(m->proc, SIGIO);
}
/*
* Close a mixer device
*/
/* ARGSUSED */
int
mixer_close(struct audio_softc *sc, int flags, int ifmt, struct proc *p)
{
DPRINTF(("mixer_close: sc %p\n", sc));
mixer_remove(sc, p);
return (0);
}
int
mixer_ioctl(struct audio_softc *sc, u_long cmd, caddr_t addr, int flag,
struct proc *p)
{
struct audio_hw_if *hw = sc->hw_if;
int error = EINVAL;
DPRINTF(("mixer_ioctl(%lu,'%c',%lu)\n",
IOCPARM_LEN(cmd), (char)IOCGROUP(cmd), cmd&0xff));
switch (cmd) {
case FIOASYNC:
mixer_remove(sc, p); /* remove old entry */
if (*(int *)addr) {
struct mixer_asyncs *ma;
ma = malloc(sizeof (struct mixer_asyncs),
M_DEVBUF, M_WAITOK);
ma->next = sc->sc_async_mixer;
ma->proc = p;
sc->sc_async_mixer = ma;
}
error = 0;
break;
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"));
((mixer_devinfo_t *)addr)->un.v.delta = 0; /* default */
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);
if (!error && hw->commit_settings)
error = hw->commit_settings(sc->hw_hdl);
if (!error)
mixer_signal(sc);
break;
default:
if (hw->dev_ioctl)
error = hw->dev_ioctl(sc->hw_hdl, cmd, addr, flag, p);
else
error = EINVAL;
break;
}
DPRINTF(("mixer_ioctl(%lu,'%c',%lu) result %d\n",
IOCPARM_LEN(cmd), (char)IOCGROUP(cmd), cmd&0xff, error));
return (error);
}
#endif /* NAUDIO > 0 */
#include "midi.h"
#if NAUDIO == 0 && (NMIDI > 0 || NMIDIBUS > 0)
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/audioio.h>
#include <dev/audio_if.h>
#endif
#if NAUDIO > 0 || (NMIDI > 0 || NMIDIBUS > 0)
int
audioprint(void *aux, const char *pnp)
{
struct audio_attach_args *arg = aux;
const char *type;
if (pnp != NULL) {
switch (arg->type) {
case AUDIODEV_TYPE_AUDIO:
type = "audio";
break;
case AUDIODEV_TYPE_MIDI:
type = "midi";
break;
case AUDIODEV_TYPE_OPL:
type = "opl";
break;
case AUDIODEV_TYPE_MPU:
type = "mpu";
break;
default:
panic("audioprint: unknown type %d", arg->type);
}
aprint_normal("%s at %s", type, pnp);
}
return (UNCONF);
}
#endif /* NAUDIO > 0 || (NMIDI > 0 || NMIDIBUS > 0) */