freebsd-dev/sys/i386/isa/sound/dmabuf.c
Steven Wallace 1a46116d9d Bug fix for SNDCTL_DSP_GETOSPACE ioctl. It would report 0 bytes
available in buffer when buffer was completely empty.
It now correctly reports the total buffer space available.

Reviewed by:	jkh, davidg
Obtained from:  Linux 1.3.20's sound driver code
1997-02-12 06:56:57 +00:00

1154 lines
25 KiB
C

/*
* sound/dmabuf.c
*
* The DMA buffer manager for digitized voice applications
*
* Copyright by Hannu Savolainen 1993, 1994, 1995
*
* 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 CONFIGURE_SOUNDCARD
#if !defined(EXCLUDE_AUDIO) || !defined(EXCLUDE_GUS)
DEFINE_WAIT_QUEUES (dev_sleeper[MAX_AUDIO_DEV], dev_sleep_flag[MAX_AUDIO_DEV]);
static struct dma_buffparms dmaps[MAX_AUDIO_DEV] =
{
{0}}; /*
* Primitive way to allocate
* such a large array.
* Needs dynamic run-time alloction.
*/
static int space_in_queue (int);
static void reorganize_buffers (int);
static void dma_init_buffers (int);
static void
reorganize_buffers (int dev)
{
/*
* This routine breaks the physical device buffers to logical ones.
*/
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
struct audio_operations *dsp_dev = audio_devs[dev];
unsigned i, p, n;
unsigned sr, nc, sz, bsz;
if (dmap->fragment_size == 0)
{ /* Compute the fragment size using the default algorithm */
sr = dsp_dev->ioctl (dev, SOUND_PCM_READ_RATE, 0, 1);
nc = dsp_dev->ioctl (dev, SOUND_PCM_READ_CHANNELS, 0, 1);
sz = dsp_dev->ioctl (dev, SOUND_PCM_READ_BITS, 0, 1);
if (sr < 1 || nc < 1 || sz < 1)
{
printk ("Warning: Invalid PCM parameters[%d] sr=%d, nc=%d, sz=%d\n",
dev, sr, nc, sz);
sr = DSP_DEFAULT_SPEED;
nc = 1;
sz = 8;
}
sz = sr * nc * sz;
sz /= 8; /* #bits -> #bytes */
/*
* Compute a buffer size for time not exeeding 1 second.
* Usually this algorithm gives a buffer size for 0.5 to 1.0 seconds
* of sound (using the current speed, sample size and #channels).
*/
bsz = dsp_dev->buffsize;
while (bsz > sz)
bsz /= 2;
if (dsp_dev->buffcount == 1 && bsz == dsp_dev->buffsize)
bsz /= 2; /* Needs at least 2 buffers */
if (dmap->subdivision == 0) /* Not already set */
dmap->subdivision = 1; /* Init to default value */
else
bsz /= dmap->subdivision;
if (bsz < 16)
bsz = 16; /* Just a sanity check */
while ((dsp_dev->buffsize * dsp_dev->buffcount) / bsz > MAX_SUB_BUFFERS)
bsz *= 2;
dmap->fragment_size = bsz;
}
else
{
/*
* The process has specified the buffer sice with SNDCTL_DSP_SETFRAGMENT or
* the buffer sice computation has already been done.
*/
if (dmap->fragment_size > (audio_devs[dev]->buffsize / 2))
dmap->fragment_size = (audio_devs[dev]->buffsize / 2);
bsz = dmap->fragment_size;
}
bsz &= ~0x03; /* Force size which is multiple of 4 bytes */
/*
* Now computing addresses for the logical buffers
*/
n = 0;
for (i = 0; i < dmap->raw_count &&
n < dmap->max_fragments &&
n < MAX_SUB_BUFFERS; i++)
{
p = 0;
while ((p + bsz) <= dsp_dev->buffsize &&
n < dmap->max_fragments &&
n < MAX_SUB_BUFFERS)
{
dmap->buf[n] = dmap->raw_buf[i] + p;
dmap->buf_phys[n] = dmap->raw_buf_phys[i] + p;
p += bsz;
n++;
}
}
dmap->nbufs = n;
dmap->bytes_in_use = n * bsz;
for (i = 0; i < dmap->nbufs; i++)
{
dmap->counts[i] = 0;
}
dmap->flags |= DMA_ALLOC_DONE;
}
static void
dma_init_buffers (int dev)
{
struct dma_buffparms *dmap = audio_devs[dev]->dmap = &dmaps[dev];
RESET_WAIT_QUEUE (dev_sleeper[dev], dev_sleep_flag[dev]);
dmap->flags = DMA_BUSY; /* Other flags off */
dmap->qlen = dmap->qhead = dmap->qtail = 0;
dmap->nbufs = 1;
dmap->bytes_in_use = audio_devs[dev]->buffsize;
dmap->dma_mode = DMODE_NONE;
}
int
DMAbuf_open (int dev, int mode)
{
int retval;
struct dma_buffparms *dmap = NULL;
if (dev >= num_audiodevs)
{
printk ("PCM device %d not installed.\n", dev);
return RET_ERROR (ENXIO);
}
if (!audio_devs[dev])
{
printk ("PCM device %d not initialized\n", dev);
return RET_ERROR (ENXIO);
}
dmap = audio_devs[dev]->dmap = &dmaps[dev];
if (dmap->flags & DMA_BUSY)
return RET_ERROR (EBUSY);
#ifdef USE_RUNTIME_DMAMEM
dmap->raw_buf[0] = NULL;
sound_dma_malloc (dev);
#endif
if (dmap->raw_buf[0] == NULL)
return RET_ERROR (ENOSPC); /* Memory allocation failed during boot */
if ((retval = audio_devs[dev]->open (dev, mode)) < 0)
return retval;
dmap->open_mode = mode;
dmap->subdivision = dmap->underrun_count = 0;
dmap->fragment_size = 0;
dmap->max_fragments = 65536; /* Just a large value */
dma_init_buffers (dev);
audio_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_BITS, 8, 1);
audio_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_CHANNELS, 1, 1);
audio_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_RATE, DSP_DEFAULT_SPEED, 1);
return 0;
}
static void
dma_reset (int dev)
{
int retval;
unsigned long flags;
DISABLE_INTR (flags);
audio_devs[dev]->reset (dev);
audio_devs[dev]->close (dev);
if ((retval = audio_devs[dev]->open (dev, audio_devs[dev]->dmap->open_mode)) < 0)
printk ("Sound: Reset failed - Can't reopen device\n");
RESTORE_INTR (flags);
dma_init_buffers (dev);
reorganize_buffers (dev);
}
static int
dma_sync (int dev)
{
unsigned long flags;
if (audio_devs[dev]->dmap->dma_mode == DMODE_OUTPUT)
{
DISABLE_INTR (flags);
while (!PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev])
&& audio_devs[dev]->dmap->qlen)
{
DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], 10 * HZ);
if (TIMED_OUT (dev_sleeper[dev], dev_sleep_flag[dev]))
{
RESTORE_INTR (flags);
return audio_devs[dev]->dmap->qlen;
}
}
RESTORE_INTR (flags);
/*
* Some devices such as GUS have huge amount of on board RAM for the
* audio data. We have to wait until the device has finished playing.
*/
DISABLE_INTR (flags);
if (audio_devs[dev]->local_qlen) /* Device has hidden buffers */
{
while (!(PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev]))
&& audio_devs[dev]->local_qlen (dev))
{
DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], HZ);
}
}
RESTORE_INTR (flags);
}
return audio_devs[dev]->dmap->qlen;
}
int
DMAbuf_release (int dev, int mode)
{
unsigned long flags;
if (!(PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev]))
&& (audio_devs[dev]->dmap->dma_mode == DMODE_OUTPUT))
{
dma_sync (dev);
}
#ifdef USE_RUNTIME_DMAMEM
sound_dma_free (dev);
#endif
DISABLE_INTR (flags);
audio_devs[dev]->reset (dev);
audio_devs[dev]->close (dev);
audio_devs[dev]->dmap->dma_mode = DMODE_NONE;
audio_devs[dev]->dmap->flags &= ~DMA_BUSY;
RESTORE_INTR (flags);
return 0;
}
int
DMAbuf_getrdbuffer (int dev, char **buf, int *len, int dontblock)
{
unsigned long flags;
int err = EIO;
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
DISABLE_INTR (flags);
if (!dmap->qlen)
{
if (dmap->flags & DMA_RESTART)
{
dma_reset (dev);
dmap->flags &= ~DMA_RESTART;
}
if (dmap->dma_mode == DMODE_OUTPUT) /* Direction change */
{
dma_sync (dev);
dma_reset (dev);
dmap->dma_mode = DMODE_NONE;
}
if (!(dmap->flags & DMA_ALLOC_DONE))
reorganize_buffers (dev);
if (!dmap->dma_mode)
{
int err;
if ((err = audio_devs[dev]->prepare_for_input (dev,
dmap->fragment_size, dmap->nbufs)) < 0)
{
RESTORE_INTR (flags);
return err;
}
dmap->dma_mode = DMODE_INPUT;
}
if (!(dmap->flags & DMA_ACTIVE))
{
audio_devs[dev]->start_input (dev, dmap->buf_phys[dmap->qtail],
dmap->fragment_size, 0,
!(audio_devs[dev]->flags & DMA_AUTOMODE) ||
!(dmap->flags & DMA_STARTED));
dmap->flags |= DMA_ACTIVE | DMA_STARTED;
}
if (dontblock)
{
RESTORE_INTR (flags);
#if defined(__FreeBSD__)
return RET_ERROR (EWOULDBLOCK);
#else
return RET_ERROR (EAGAIN);
#endif
}
/* Wait for the next block */
DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], 2 * HZ);
if (TIMED_OUT (dev_sleeper[dev], dev_sleep_flag[dev]))
{
printk ("Sound: DMA timed out - IRQ/DRQ config error?\n");
dma_reset (dev);
err = EIO;
SET_ABORT_FLAG (dev_sleeper[dev], dev_sleep_flag[dev]);
}
else
err = EINTR;
}
RESTORE_INTR (flags);
if (!dmap->qlen)
return RET_ERROR (err);
*buf = &dmap->buf[dmap->qhead][dmap->counts[dmap->qhead]];
*len = dmap->fragment_size - dmap->counts[dmap->qhead];
return dmap->qhead;
}
int
DMAbuf_rmchars (int dev, int buff_no, int c)
{
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
int p = dmap->counts[dmap->qhead] + c;
if (p >= dmap->fragment_size)
{ /* This buffer is completely empty */
dmap->counts[dmap->qhead] = 0;
if (dmap->qlen <= 0 || dmap->qlen > dmap->nbufs)
printk ("\nSound: Audio queue1 corrupted for dev%d (%d/%d)\n",
dev, dmap->qlen, dmap->nbufs);
dmap->qlen--;
dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
}
else
dmap->counts[dmap->qhead] = p;
return 0;
}
int
DMAbuf_ioctl (int dev, unsigned int cmd, unsigned int arg, int local)
{
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
switch (cmd)
{
case SNDCTL_DSP_RESET:
dma_reset (dev);
return 0;
break;
case SNDCTL_DSP_SYNC:
dma_sync (dev);
dma_reset (dev);
return 0;
break;
case SNDCTL_DSP_GETBLKSIZE:
if (!(dmap->flags & DMA_ALLOC_DONE))
reorganize_buffers (dev);
return IOCTL_OUT (arg, dmap->fragment_size);
break;
case SNDCTL_DSP_SETBLKSIZE:
{
int size = IOCTL_IN(arg);
if(!(dmap->flags & DMA_ALLOC_DONE) && size)
{
dmap->fragment_size = size;
return 0;
}
else
return RET_ERROR (EINVAL); /* Too late to change */
}
break;
case SNDCTL_DSP_SUBDIVIDE:
{
int fact = IOCTL_IN (arg);
if (fact == 0)
{
fact = dmap->subdivision;
if (fact == 0)
fact = 1;
return IOCTL_OUT (arg, fact);
}
if (dmap->subdivision != 0 ||
dmap->fragment_size) /* Loo late to change */
return RET_ERROR (EINVAL);
if (fact > MAX_REALTIME_FACTOR)
return RET_ERROR (EINVAL);
if (fact != 1 && fact != 2 && fact != 4 && fact != 8 && fact != 16)
return RET_ERROR (EINVAL);
dmap->subdivision = fact;
return IOCTL_OUT (arg, fact);
}
break;
case SNDCTL_DSP_SETFRAGMENT:
{
int fact = IOCTL_IN (arg);
int bytes, count;
if (fact == 0)
return RET_ERROR (EIO);
if (dmap->subdivision != 0 ||
dmap->fragment_size) /* Loo late to change */
return RET_ERROR (EINVAL);
bytes = fact & 0xffff;
count = (fact >> 16) & 0xffff;
if (count == 0)
count = MAX_SUB_BUFFERS;
if (bytes < 7 || bytes > 17) /* <64 || > 128k */
return RET_ERROR (EINVAL);
if (count < 2)
return RET_ERROR (EINVAL);
dmap->fragment_size = (1 << bytes);
dmap->max_fragments = count;
if (dmap->fragment_size > audio_devs[dev]->buffsize)
dmap->fragment_size = audio_devs[dev]->buffsize;
if (dmap->fragment_size == audio_devs[dev]->buffsize &&
audio_devs[dev]->flags & DMA_AUTOMODE)
dmap->fragment_size /= 2; /* Needs at least 2 buffers */
dmap->subdivision = 1; /* Disable SNDCTL_DSP_SUBDIVIDE */
return IOCTL_OUT (arg, bytes | (count << 16));
}
break;
case SNDCTL_DSP_GETISPACE:
case SNDCTL_DSP_GETOSPACE:
if (!local)
return RET_ERROR (EINVAL);
else
{
audio_buf_info *info = (audio_buf_info *) arg;
if (!(dmap->flags & DMA_ALLOC_DONE))
reorganize_buffers (dev);
info->fragstotal = dmap->nbufs;
if (cmd == SNDCTL_DSP_GETISPACE)
info->fragments = dmap->qlen;
else
{
if (!space_in_queue (dev))
info->fragments = 0;
else
{
info->fragments = dmap->nbufs - dmap->qlen;
if (audio_devs[dev]->local_qlen)
{
int tmp = audio_devs[dev]->local_qlen (dev);
if (tmp & info->fragments)
tmp--; /*
* This buffer has been counted twice
*/
info->fragments -= tmp;
}
}
}
if (info->fragments < 0)
info->fragments = 0;
else if (info->fragments > dmap->nbufs)
info->fragments = dmap->nbufs;
info->fragsize = dmap->fragment_size;
info->bytes = info->fragments * dmap->fragment_size;
if (cmd == SNDCTL_DSP_GETISPACE && dmap->qlen)
info->bytes -= dmap->counts[dmap->qhead];
}
return 0;
default:
return audio_devs[dev]->ioctl (dev, cmd, arg, local);
}
}
static int
space_in_queue (int dev)
{
int len, max, tmp;
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
if (dmap->qlen >= dmap->nbufs) /* No space at all */
return 0;
/*
* Verify that there are no more pending buffers than the limit
* defined by the process.
*/
max = dmap->max_fragments;
len = dmap->qlen;
if (audio_devs[dev]->local_qlen)
{
tmp = audio_devs[dev]->local_qlen (dev);
if (tmp & len)
tmp--; /*
* This buffer has been counted twice
*/
len += tmp;
}
if (len >= max)
return 0;
return 1;
}
int
DMAbuf_getwrbuffer (int dev, char **buf, int *size, int dontblock)
{
unsigned long flags;
int abort, err = EIO;
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
if (dmap->dma_mode == DMODE_INPUT) /* Direction change */
{
dma_reset (dev);
dmap->dma_mode = DMODE_NONE;
}
else if (dmap->flags & DMA_RESTART) /* Restart buffering */
{
dma_sync (dev);
dma_reset (dev);
}
dmap->flags &= ~DMA_RESTART;
if (!(dmap->flags & DMA_ALLOC_DONE))
reorganize_buffers (dev);
if (!dmap->dma_mode)
{
int err;
dmap->dma_mode = DMODE_OUTPUT;
if ((err = audio_devs[dev]->prepare_for_output (dev,
dmap->fragment_size, dmap->nbufs)) < 0)
return err;
}
DISABLE_INTR (flags);
abort = 0;
while (!space_in_queue (dev) &&
!abort)
{
if (dontblock)
{
RESTORE_INTR (flags);
return RET_ERROR (EAGAIN);
}
/*
* Wait for free space
*/
DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], 2 * HZ);
if (TIMED_OUT (dev_sleeper[dev], dev_sleep_flag[dev]))
{
printk ("Sound: DMA timed out - IRQ/DRQ config error?\n");
dma_reset (dev);
err = EIO;
abort = 1;
SET_ABORT_FLAG (dev_sleeper[dev], dev_sleep_flag[dev]);
}
else if (PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev]))
{
err = EINTR;
abort = 1;
}
}
RESTORE_INTR (flags);
if (!space_in_queue (dev))
{
return RET_ERROR (err); /* Caught a signal ? */
}
*buf = dmap->buf[dmap->qtail];
*size = dmap->fragment_size;
dmap->counts[dmap->qtail] = 0;
return dmap->qtail;
}
int
DMAbuf_start_output (int dev, int buff_no, int l)
{
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
if (buff_no != dmap->qtail)
printk ("Sound warning: DMA buffers out of sync %d != %d\n", buff_no, dmap->qtail);
dmap->qlen++;
if (dmap->qlen <= 0 || dmap->qlen > dmap->nbufs)
printk ("\nSound: Audio queue2 corrupted for dev%d (%d/%d)\n",
dev, dmap->qlen, dmap->nbufs);
dmap->counts[dmap->qtail] = l;
if ((l != dmap->fragment_size) &&
((audio_devs[dev]->flags & DMA_AUTOMODE) &&
audio_devs[dev]->flags & NEEDS_RESTART))
dmap->flags |= DMA_RESTART;
else
dmap->flags &= ~DMA_RESTART;
dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
if (!(dmap->flags & DMA_ACTIVE))
{
dmap->flags |= DMA_ACTIVE;
audio_devs[dev]->output_block (dev, dmap->buf_phys[dmap->qhead],
dmap->counts[dmap->qhead], 0,
!(audio_devs[dev]->flags & DMA_AUTOMODE) ||
!(dmap->flags & DMA_STARTED));
dmap->flags |= DMA_STARTED;
}
return 0;
}
int
DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode)
{
int chan = audio_devs[dev]->dmachan;
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
#ifdef linux
unsigned long flags;
#endif
/*
* This function is not as portable as it should be.
*/
/*
* The count must be one less than the actual size. This is handled by
* set_dma_addr()
*/
if (audio_devs[dev]->flags & DMA_AUTOMODE)
{ /*
* Auto restart mode. Transfer the whole *
* buffer
*/
#ifdef linux
DISABLE_INTR (flags);
disable_dma (chan);
clear_dma_ff (chan);
set_dma_mode (chan, dma_mode | DMA_AUTOINIT);
set_dma_addr (chan, dmap->raw_buf_phys[0]);
set_dma_count (chan, dmap->bytes_in_use);
enable_dma (chan);
RESTORE_INTR (flags);
#else
#if defined(__FreeBSD__)
isa_dmastart (B_RAW | ((dma_mode == DMA_MODE_READ) ? B_READ : B_WRITE),
(caddr_t)dmap->raw_buf_phys[0],
dmap->bytes_in_use,
chan);
#else /* else __FreeBSD__ */
#if defined(GENERIC_SYSV)
#ifndef DMAMODE_AUTO
printk ("sound: Invalid DMA mode for device %d\n", dev);
#endif
#if defined(SVR42)
/*
** send full count to snd_dma_prog, it will take care of subtracting
** one if it is required.
*/
snd_dma_prog (chan, dmap->raw_buf_phys[0], dmap->bytes_in_use,
dma_mode, TRUE);
#else /* !SVR42 */
dma_param (chan, ((dma_mode == DMA_MODE_READ) ? DMA_Rdmode : DMA_Wrmode)
#ifdef DMAMODE_AUTO
| DMAMODE_AUTO
#endif
,
dmap->raw_buf_phys[0], dmap->bytes_in_use - 1);
dma_enable (chan);
#endif /* ! SVR42 */
#else
#error This routine is not valid for this OS.
#endif
#endif
#endif
}
else
{
#ifdef linux
DISABLE_INTR (flags);
disable_dma (chan);
clear_dma_ff (chan);
set_dma_mode (chan, dma_mode);
set_dma_addr (chan, physaddr);
set_dma_count (chan, count);
enable_dma (chan);
RESTORE_INTR (flags);
#else
#if defined(__FreeBSD__)
isa_dmastart ((dma_mode == DMA_MODE_READ) ? B_READ : B_WRITE,
(caddr_t)physaddr,
count,
chan);
#else /* FreeBSD */
#if defined(GENERIC_SYSV)
#if defined(SVR42)
snd_dma_prog (chan, physaddr, count, dma_mode, FALSE);
#else /* ! SVR42 */
dma_param (chan, ((dma_mode == DMA_MODE_READ) ? DMA_Rdmode : DMA_Wrmode),
physaddr, count);
dma_enable (chan);
#endif /* SVR42 */
#else
#error This routine is not valid for this OS.
#endif /* GENERIC_SYSV */
#endif
#endif
}
return count;
}
long
DMAbuf_init (long mem_start)
{
int dev;
#if defined(SVR42)
snd_dma_init ();
#endif /* SVR42 */
/*
* NOTE! This routine could be called several times.
*/
for (dev = 0; dev < num_audiodevs; dev++)
audio_devs[dev]->dmap = &dmaps[dev];
return mem_start;
}
void
DMAbuf_outputintr (int dev, int event_type)
{
/*
* Event types:
* 0 = DMA transfer done. Device still has more data in the local
* buffer.
* 1 = DMA transfer done. Device doesn't have local buffer or it's
* empty now.
* 2 = No DMA transfer but the device has now more space in it's local
* buffer.
*/
unsigned long flags;
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
#if defined(SVR42)
snd_dma_intr (audio_devs[dev]->dmachan);
#endif /* SVR42 */
if (event_type != 2)
{
if (dmap->qlen <= 0 || dmap->qlen > dmap->nbufs)
{
printk ("\nSound: Audio queue3 corrupted for dev%d (%d/%d)\n",
dev, dmap->qlen, dmap->nbufs);
return;
}
dmap->qlen--;
dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
dmap->flags &= ~DMA_ACTIVE;
#ifdef __FreeBSD__
isa_dmadone(0, 0, 0, audio_devs[dev]->dmachan);
#endif
if (dmap->qlen)
{
audio_devs[dev]->output_block (dev, dmap->buf_phys[dmap->qhead],
dmap->counts[dmap->qhead], 1,
!(audio_devs[dev]->flags & DMA_AUTOMODE));
dmap->flags |= DMA_ACTIVE;
}
else if (event_type == 1)
{
dmap->underrun_count++;
audio_devs[dev]->halt_xfer (dev);
if ((audio_devs[dev]->flags & DMA_AUTOMODE) &&
audio_devs[dev]->flags & NEEDS_RESTART)
dmap->flags |= DMA_RESTART;
else
dmap->flags &= ~DMA_RESTART;
}
} /* event_type != 2 */
DISABLE_INTR (flags);
if (SOMEONE_WAITING (dev_sleeper[dev], dev_sleep_flag[dev]))
{
WAKE_UP (dev_sleeper[dev], dev_sleep_flag[dev]);
}
RESTORE_INTR (flags);
#if defined(__FreeBSD__)
if(selinfo[dev].si_pid)
selwakeup(&selinfo[dev]);
#endif
}
void
DMAbuf_inputintr (int dev)
{
unsigned long flags;
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
#if defined(SVR42)
snd_dma_intr (audio_devs[dev]->dmachan);
#endif /* SVR42 */
#ifdef __FreeBSD__
isa_dmadone(0, 0, 0, audio_devs[dev]->dmachan);
#endif
if (dmap->qlen == (dmap->nbufs - 1))
{
#if !defined(__FreeBSD__) /* ignore console message. */
printk ("Sound: Recording overrun\n");
#endif
dmap->underrun_count++;
audio_devs[dev]->halt_xfer (dev);
dmap->flags &= ~DMA_ACTIVE;
if (audio_devs[dev]->flags & DMA_AUTOMODE)
dmap->flags |= DMA_RESTART;
else
dmap->flags &= ~DMA_RESTART;
}
else
{
dmap->qlen++;
if (dmap->qlen <= 0 || dmap->qlen > dmap->nbufs)
printk ("\nSound: Audio queue4 corrupted for dev%d (%d/%d)\n",
dev, dmap->qlen, dmap->nbufs);
dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
audio_devs[dev]->start_input (dev, dmap->buf_phys[dmap->qtail],
dmap->fragment_size, 1,
!(audio_devs[dev]->flags & DMA_AUTOMODE));
dmap->flags |= DMA_ACTIVE;
}
DISABLE_INTR (flags);
if (SOMEONE_WAITING (dev_sleeper[dev], dev_sleep_flag[dev]))
{
WAKE_UP (dev_sleeper[dev], dev_sleep_flag[dev]);
}
RESTORE_INTR (flags);
#if defined(__FreeBSD__)
if(selinfo[dev].si_pid)
selwakeup(&selinfo[dev]);
#endif
}
int
DMAbuf_open_dma (int dev)
{
unsigned long flags;
int chan = audio_devs[dev]->dmachan;
if (ALLOC_DMA_CHN (chan, audio_devs[dev]->name))
{
#if 0
/* Enough already! This error is reported twice elsewhere */
printk ("Unable to grab DMA%d for the audio driver\n", chan);
#endif
return RET_ERROR (EBUSY);
}
DISABLE_INTR (flags);
#ifdef linux
disable_dma (chan);
clear_dma_ff (chan);
#endif
RESTORE_INTR (flags);
return 0;
}
void
DMAbuf_close_dma (int dev)
{
int chan = audio_devs[dev]->dmachan;
DMAbuf_reset_dma (dev);
RELEASE_DMA_CHN (chan);
}
void
DMAbuf_reset_dma (int dev)
{
}
#ifdef ALLOW_SELECT
int
DMAbuf_select (int dev, struct fileinfo *file, int sel_type, select_table * wait)
{
struct dma_buffparms *dmap = audio_devs[dev]->dmap;
unsigned long flags;
switch (sel_type)
{
case SEL_IN:
if (dmap->dma_mode != DMODE_INPUT)
return 0;
DISABLE_INTR (flags);
if (!dmap->qlen)
{
#if defined(__FreeBSD__)
selrecord(wait, &selinfo[dev]);
#else
dev_sleep_flag[dev].mode = WK_SLEEP;
select_wait (&dev_sleeper[dev], wait);
#endif
RESTORE_INTR (flags);
return 0;
}
RESTORE_INTR (flags);
return 1;
break;
case SEL_OUT:
if (dmap->dma_mode == DMODE_INPUT)
return 0;
if (dmap->dma_mode == DMODE_NONE)
return 1;
DISABLE_INTR (flags);
if (!space_in_queue (dev))
{
#if defined(__FreeBSD__)
selrecord(wait, &selinfo[dev]);
#else
dev_sleep_flag[dev].mode = WK_SLEEP;
select_wait (&dev_sleeper[dev], wait);
#endif
RESTORE_INTR (flags);
return 0;
}
RESTORE_INTR (flags);
return 1;
break;
case SEL_EX:
return 0;
}
return 0;
}
#endif /* ALLOW_SELECT */
#else /* EXCLUDE_AUDIO */
/*
* Stub versions if audio services not included
*/
int
DMAbuf_open (int dev, int mode)
{
return RET_ERROR (ENXIO);
}
int
DMAbuf_release (int dev, int mode)
{
return 0;
}
int
DMAbuf_getwrbuffer (int dev, char **buf, int *size, int dontblock)
{
return RET_ERROR (EIO);
}
int
DMAbuf_getrdbuffer (int dev, char **buf, int *len, int dontblock)
{
return RET_ERROR (EIO);
}
int
DMAbuf_rmchars (int dev, int buff_no, int c)
{
return RET_ERROR (EIO);
}
int
DMAbuf_start_output (int dev, int buff_no, int l)
{
return RET_ERROR (EIO);
}
int
DMAbuf_ioctl (int dev, unsigned int cmd, unsigned int arg, int local)
{
return RET_ERROR (EIO);
}
long
DMAbuf_init (long mem_start)
{
return mem_start;
}
int
DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode)
{
return RET_ERROR (EIO);
}
int
DMAbuf_open_dma (int dev)
{
return RET_ERROR (ENXIO);
}
void
DMAbuf_close_dma (int dev)
{
return;
}
void
DMAbuf_reset_dma (int dev)
{
return;
}
void
DMAbuf_inputintr (int dev)
{
return;
}
void
DMAbuf_outputintr (int dev, int underrun_flag)
{
return;
}
#endif
#endif