b2aa114118
version and the asm version are inlined, and everything is cached, the asm version is 1.75 times slower than the C version on P5's. On K6's, it is only 1.25 times slower.
441 lines
11 KiB
C
441 lines
11 KiB
C
/*
|
|
* sound/audio.c
|
|
*
|
|
* Device file manager for /dev/audio
|
|
*
|
|
* Copyright by Hannu Savolainen 1993
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met: 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer. 2.
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <i386/isa/sound/sound_config.h>
|
|
|
|
#ifdef CONFIG_AUDIO
|
|
|
|
#include <i386/isa/sound/ulaw.h>
|
|
#include <i386/isa/sound/coproc.h>
|
|
|
|
#define ON 1
|
|
#define OFF 0
|
|
int
|
|
DMAbuf_poll(int dev, struct fileinfo * file, int events, select_table * wait);
|
|
|
|
int
|
|
audio_poll(int dev, struct fileinfo * file, int events, select_table * wait);
|
|
|
|
static int wr_buff_no[MAX_AUDIO_DEV];
|
|
/* != -1, if there is a incomplete output block in the queue. */
|
|
static int wr_buff_size[MAX_AUDIO_DEV], wr_buff_ptr[MAX_AUDIO_DEV];
|
|
|
|
static int audio_mode[MAX_AUDIO_DEV];
|
|
static int dev_nblock[MAX_AUDIO_DEV]; /* 1 if in noblocking mode */
|
|
|
|
#define AM_NONE 0
|
|
#define AM_WRITE 1
|
|
#define AM_READ 2
|
|
|
|
static char *wr_dma_buf[MAX_AUDIO_DEV];
|
|
static int audio_format[MAX_AUDIO_DEV];
|
|
static int local_conversion[MAX_AUDIO_DEV];
|
|
|
|
static int
|
|
set_format(int dev, int fmt)
|
|
{
|
|
if (fmt != AFMT_QUERY) {
|
|
|
|
local_conversion[dev] = 0;
|
|
|
|
if (!(audio_devs[dev]->format_mask & fmt)) /* Not supported */
|
|
if (fmt == AFMT_MU_LAW) {
|
|
fmt = AFMT_U8;
|
|
local_conversion[dev] = AFMT_MU_LAW;
|
|
} else
|
|
fmt = AFMT_U8; /* This is always supported */
|
|
|
|
audio_format[dev] = DMAbuf_ioctl(dev, SNDCTL_DSP_SETFMT,
|
|
(ioctl_arg) fmt, 1);
|
|
}
|
|
if (local_conversion[dev]) /* This shadows the HW format */
|
|
return local_conversion[dev];
|
|
|
|
return audio_format[dev];
|
|
}
|
|
|
|
int
|
|
audio_open(int dev, struct fileinfo * file)
|
|
{
|
|
int ret;
|
|
int bits;
|
|
int dev_type = dev & 0x0f;
|
|
int mode = file->mode & O_ACCMODE;
|
|
|
|
dev = dev >> 4;
|
|
|
|
bits = (dev_type == SND_DEV_DSP16) ? 16 : 8 ;
|
|
|
|
if ((ret = DMAbuf_open(dev, mode)) < 0)
|
|
return ret;
|
|
|
|
if (audio_devs[dev]->coproc)
|
|
if ((ret = audio_devs[dev]->coproc->
|
|
open(audio_devs[dev]->coproc->devc, COPR_PCM)) < 0) {
|
|
audio_release(dev, file);
|
|
printf("Sound: Can't access coprocessor device\n");
|
|
|
|
return ret;
|
|
}
|
|
local_conversion[dev] = 0;
|
|
|
|
if (DMAbuf_ioctl(dev, SNDCTL_DSP_SETFMT, (ioctl_arg) bits, 1) != bits) {
|
|
audio_release(dev, file);
|
|
return -(ENXIO);
|
|
}
|
|
|
|
set_format(dev, (dev_type == SND_DEV_AUDIO) ? AFMT_MU_LAW : bits ) ;
|
|
|
|
wr_buff_no[dev] = -1;
|
|
audio_mode[dev] = AM_NONE;
|
|
wr_buff_size[dev] = wr_buff_ptr[dev] = 0;
|
|
dev_nblock[dev] = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
audio_release(int dev, struct fileinfo * file)
|
|
{
|
|
int mode;
|
|
|
|
dev = dev >> 4;
|
|
mode = file->mode & O_ACCMODE;
|
|
|
|
if (wr_buff_no[dev] >= 0) {
|
|
DMAbuf_start_output(dev, wr_buff_no[dev], wr_buff_ptr[dev]);
|
|
wr_buff_no[dev] = -1;
|
|
}
|
|
if (audio_devs[dev]->coproc)
|
|
audio_devs[dev]->coproc->close(audio_devs[dev]->coproc->devc,COPR_PCM);
|
|
DMAbuf_release(dev, mode);
|
|
audio_devs[dev]->dmap_out->mapping_flags &= ~DMA_MAP_MAPPED ;
|
|
|
|
}
|
|
|
|
static void
|
|
translate_bytes(const u_char *table, u_char *buff, int n)
|
|
{
|
|
u_long i;
|
|
|
|
if (n <= 0)
|
|
return;
|
|
|
|
for (i = 0; i < n; ++i)
|
|
buff[i] = table[buff[i]];
|
|
}
|
|
|
|
int
|
|
audio_write(int dev, struct fileinfo * file, snd_rw_buf * buf, int count)
|
|
{
|
|
int c, p, l;
|
|
int err;
|
|
|
|
dev = dev >> 4;
|
|
|
|
p = 0;
|
|
c = count;
|
|
if ((audio_mode[dev] & AM_READ) &&
|
|
!(audio_devs[dev]->flags & DMA_DUPLEX)) { /* Direction change */
|
|
wr_buff_no[dev] = -1;
|
|
}
|
|
if (audio_devs[dev]->flags & DMA_DUPLEX)
|
|
audio_mode[dev] |= AM_WRITE;
|
|
else
|
|
audio_mode[dev] = AM_WRITE;
|
|
|
|
if (!count) { /* Flush output */
|
|
if (wr_buff_no[dev] >= 0) {
|
|
DMAbuf_start_output(dev, wr_buff_no[dev], wr_buff_ptr[dev]);
|
|
wr_buff_no[dev] = -1;
|
|
}
|
|
return 0;
|
|
}
|
|
while (c) { /* Perform output blocking */
|
|
if (wr_buff_no[dev] < 0) {
|
|
/* There is no incomplete buffers */
|
|
if ((wr_buff_no[dev] = DMAbuf_getwrbuffer(dev,
|
|
&wr_dma_buf[dev], &wr_buff_size[dev],
|
|
dev_nblock[dev])) < 0) {
|
|
/* Handle nonblocking mode */
|
|
if (dev_nblock[dev] && wr_buff_no[dev] == -(EAGAIN))
|
|
return p; /* No more space. Return # of accepted bytes */
|
|
return wr_buff_no[dev];
|
|
}
|
|
wr_buff_ptr[dev] = 0;
|
|
}
|
|
l = c;
|
|
if (l > (wr_buff_size[dev] - wr_buff_ptr[dev]))
|
|
l = (wr_buff_size[dev] - wr_buff_ptr[dev]);
|
|
|
|
if (!audio_devs[dev]->copy_from_user) { /* No device specific
|
|
* copy routine */
|
|
|
|
if (uiomove(&wr_dma_buf[dev][wr_buff_ptr[dev]], l, buf)) {
|
|
printf("sb: Bad copyin()!\n");
|
|
};
|
|
} else
|
|
audio_devs[dev]->copy_from_user(dev,
|
|
wr_dma_buf[dev], wr_buff_ptr[dev], buf, p, l);
|
|
|
|
|
|
/*
|
|
* Insert local processing here
|
|
*/
|
|
|
|
if (local_conversion[dev] == AFMT_MU_LAW) {
|
|
translate_bytes(ulaw_dsp,
|
|
(u_char *) &wr_dma_buf[dev][wr_buff_ptr[dev]], l);
|
|
}
|
|
c -= l;
|
|
p += l;
|
|
wr_buff_ptr[dev] += l;
|
|
|
|
if (wr_buff_ptr[dev] >= wr_buff_size[dev]) {
|
|
if ((err = DMAbuf_start_output(dev, wr_buff_no[dev],
|
|
wr_buff_ptr[dev])) < 0) {
|
|
return err;
|
|
}
|
|
wr_buff_no[dev] = -1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int
|
|
audio_read(int dev, struct fileinfo * file, snd_rw_buf * buf, int count)
|
|
{
|
|
int c, p, l;
|
|
char *dmabuf;
|
|
int buff_no;
|
|
|
|
dev = dev >> 4;
|
|
p = 0;
|
|
c = count;
|
|
if ((audio_mode[dev] & AM_WRITE) &&
|
|
!(audio_devs[dev]->flags & DMA_DUPLEX)) {
|
|
if (wr_buff_no[dev] >= 0) {
|
|
DMAbuf_start_output(dev, wr_buff_no[dev], wr_buff_ptr[dev]);
|
|
|
|
if (!(audio_devs[dev]->flags & DMA_DUPLEX))
|
|
wr_buff_no[dev] = -1;
|
|
}
|
|
}
|
|
if (audio_devs[dev]->flags & DMA_DUPLEX)
|
|
audio_mode[dev] |= AM_READ;
|
|
else
|
|
audio_mode[dev] = AM_READ;
|
|
|
|
while (c) {
|
|
if ((buff_no = DMAbuf_getrdbuffer(dev, &dmabuf, &l,
|
|
dev_nblock[dev])) < 0) {
|
|
/*
|
|
* Nonblocking mode handling. Return current # of bytes
|
|
*/
|
|
|
|
if (dev_nblock[dev] && buff_no == -(EAGAIN))
|
|
return p;
|
|
|
|
return buff_no;
|
|
}
|
|
if (l > c)
|
|
l = c;
|
|
|
|
/*
|
|
* Insert any local processing here.
|
|
*/
|
|
|
|
if (local_conversion[dev] == AFMT_MU_LAW) {
|
|
translate_bytes(dsp_ulaw, (u_char *) dmabuf, l);
|
|
}
|
|
if (uiomove(dmabuf, l, buf)) {
|
|
printf("sb: Bad copyout()!\n");
|
|
};
|
|
|
|
DMAbuf_rmchars(dev, buff_no, l);
|
|
|
|
p += l;
|
|
c -= l;
|
|
}
|
|
return count - c;
|
|
}
|
|
|
|
int
|
|
audio_ioctl(int dev, struct fileinfo * file, u_int cmd, ioctl_arg arg)
|
|
{
|
|
dev = dev >> 4;
|
|
if (((cmd >> 8) & 0xff) == 'C') {
|
|
if (audio_devs[dev]->coproc) /* Coprocessor ioctl */
|
|
return audio_devs[dev]->coproc->ioctl(audio_devs[dev]->coproc->devc, cmd, arg, 0);
|
|
else
|
|
printf("/dev/dsp%d: No coprocessor for this device\n", dev);
|
|
|
|
return -(ENXIO);
|
|
} else
|
|
switch (cmd) {
|
|
|
|
case SNDCTL_DSP_SYNC:
|
|
if (wr_buff_no[dev] >= 0) {
|
|
DMAbuf_start_output(dev, wr_buff_no[dev], wr_buff_ptr[dev]);
|
|
wr_buff_no[dev] = -1;
|
|
}
|
|
return DMAbuf_ioctl(dev, cmd, arg, 0);
|
|
break;
|
|
|
|
case SNDCTL_DSP_POST:
|
|
if (wr_buff_no[dev] >= 0) {
|
|
DMAbuf_start_output(dev, wr_buff_no[dev], wr_buff_ptr[dev]);
|
|
wr_buff_no[dev] = -1;
|
|
}
|
|
return 0;
|
|
break;
|
|
|
|
case SNDCTL_DSP_RESET:
|
|
wr_buff_no[dev] = -1;
|
|
audio_mode[dev] = AM_NONE;
|
|
return DMAbuf_ioctl(dev, cmd, arg, 0);
|
|
break;
|
|
|
|
case SNDCTL_DSP_GETFMTS:
|
|
return *(int *) arg = audio_devs[dev]->format_mask;
|
|
break;
|
|
|
|
case SNDCTL_DSP_SETFMT:
|
|
return *(int *) arg = set_format(dev, (*(int *) arg));
|
|
|
|
case SNDCTL_DSP_GETISPACE:
|
|
if ((audio_mode[dev] & AM_WRITE) &&
|
|
!(audio_devs[dev]->flags & DMA_DUPLEX))
|
|
return -(EBUSY);
|
|
|
|
else {
|
|
audio_buf_info info;
|
|
int err = DMAbuf_ioctl(dev, cmd, (ioctl_arg) & info, 1);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
bcopy((char *) &info, &(((char *) arg)[0]), sizeof(info));
|
|
return 0;
|
|
}
|
|
|
|
case SNDCTL_DSP_GETOSPACE:
|
|
if ((audio_mode[dev] & AM_READ) &&
|
|
!(audio_devs[dev]->flags & DMA_DUPLEX))
|
|
return -(EBUSY);
|
|
else {
|
|
audio_buf_info info;
|
|
int err = DMAbuf_ioctl(dev, cmd, (ioctl_arg) & info, 1);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (wr_buff_no[dev] != -1)
|
|
info.bytes += wr_buff_size[dev] - wr_buff_ptr[dev];
|
|
|
|
bcopy((char *) &info, &(((char *) arg)[0]), sizeof(info));
|
|
return 0;
|
|
}
|
|
|
|
case SNDCTL_DSP_NONBLOCK:
|
|
dev_nblock[dev] = 1;
|
|
return 0;
|
|
break;
|
|
|
|
case SNDCTL_DSP_GETCAPS:
|
|
{
|
|
int info = 1; /* Revision level of this ioctl() */
|
|
|
|
if (audio_devs[dev]->flags & DMA_DUPLEX)
|
|
info |= DSP_CAP_DUPLEX;
|
|
|
|
if (audio_devs[dev]->coproc)
|
|
info |= DSP_CAP_COPROC;
|
|
|
|
if (audio_devs[dev]->local_qlen) /* Dev. has hidden buffs */
|
|
info |= DSP_CAP_BATCH;
|
|
|
|
if (audio_devs[dev]->trigger) /* Supports SETTRIGGER */
|
|
info |= DSP_CAP_TRIGGER;
|
|
|
|
info |= DSP_CAP_MMAP;
|
|
bcopy((char *) &info, &(((char *) arg)[0]), sizeof(info));
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
|
|
case FIOASYNC:
|
|
return *(int *) arg = 1;
|
|
|
|
case FIONBIO:
|
|
return *(int *) arg = 1;
|
|
|
|
default:
|
|
return DMAbuf_ioctl(dev, cmd, arg, 0);
|
|
}
|
|
}
|
|
|
|
#ifdef ALLOW_POLL
|
|
/*
|
|
* XXX should we use spltty() in the select calls ? - lr970714
|
|
*
|
|
*/
|
|
|
|
int
|
|
audio_poll(int dev, struct fileinfo * file, int events, select_table * wait)
|
|
{
|
|
dev = dev >> 4;
|
|
|
|
if (events & (POLLIN | POLLRDNORM)) {
|
|
if ((audio_mode[dev] & AM_WRITE) &&
|
|
!(audio_devs[dev]->flags & DMA_DUPLEX))
|
|
return 0; /* Not recording */
|
|
|
|
|
|
return (DMAbuf_poll(dev, file, events, wait));
|
|
}
|
|
|
|
if (events & (POLLOUT | POLLWRNORM)) {
|
|
if ((audio_mode[dev] & AM_READ) &&
|
|
!(audio_devs[dev]->flags & DMA_DUPLEX))
|
|
return 0; /* Wrong direction */
|
|
|
|
if (wr_buff_no[dev] != -1)
|
|
return 1; /* There is space in the current buffer */
|
|
|
|
return ( DMAbuf_poll(dev, file, events, wait) );
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* ALLOW_POLL */
|
|
|
|
#endif
|