summaryrefslogtreecommitdiffstats
path: root/sys/i386/isa/snd/dmabuf.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/i386/isa/snd/dmabuf.c')
-rw-r--r--sys/i386/isa/snd/dmabuf.c773
1 files changed, 339 insertions, 434 deletions
diff --git a/sys/i386/isa/snd/dmabuf.c b/sys/i386/isa/snd/dmabuf.c
index e01ddd6..d9a5f37 100644
--- a/sys/i386/isa/snd/dmabuf.c
+++ b/sys/i386/isa/snd/dmabuf.c
@@ -1,56 +1,58 @@
/*
* snd/dmabuf.c
*
- * New DMA routines -- Luigi Rizzo, 19 jul 97
* This file implements the new DMA routines for the sound driver.
+ * AUTO DMA MODE (ISA DMA SIDE).
*
* Copyright by Luigi Rizzo - 1997
*
* 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.
- *
+ * 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/snd/sound.h>
#include <i386/isa/snd/ulaw.h>
#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */
-#define DMA_ALIGN_BITS 2 /* i.e. 4 bytes */
-#define DMA_ALIGN_THRESHOLD (1<< DMA_ALIGN_BITS)
+#define DMA_ALIGN_THRESHOLD 4
#define DMA_ALIGN_MASK (~ (DMA_ALIGN_THRESHOLD - 1))
static void dsp_wr_dmadone(snddev_info *d);
static void dsp_rd_dmadone(snddev_info *d);
/*
- *
-
-SOUND OUTPUT
+ * SOUND OUTPUT
We use a circular buffer to store samples directed to the DAC.
-The buffer is split into three variable-size regions, each identified
-by an offset in the buffer (dp,rp,fp) and a lenght (dl,rl,fl).
+The buffer is split into two variable-size regions, each identified
+by an offset in the buffer (rp,fp) and a lenght (rl,fl). dl contains
+the length of the current DMA transfer, dl=0 means that the dma is
+idle.
- 0 dp,dl rp,rl fp,fl bufsize
- |__________|_____>______|_____________|________|
- FREE DMA READY FREE
+ 0 rp,rl fp,fl bufsize
+ |__________>____________>________|
+ FREE d READY w FREE
READY region: contains data written from the process and ready
to be sent to the DAC;
@@ -58,190 +60,149 @@ by an offset in the buffer (dp,rp,fp) and a lenght (dl,rl,fl).
FREE region: is the empty region of the buffer, where a process
can write new data.
- DMA region: contains data being sent to the DAC by the DMA engine.
- the actual boundary between the FREE and READY regions is in
- fact within the DMA region (indicated by the > symbol above),
- and advances as the DMA engine makes progress.
-
Both the "READY" and "FREE" regions can wrap around the end of the
-buffer. The "DMA" region can only wrap if AUTO DMA is used, otherwise
-it cannot cross the end of the buffer.
-
-Since dl can be updated on the fly, dl0 marks the value used when the
-operation was started. When using AUTO DMA, bufsize-(count in the ISA DMA
-register) directly reflects the position of dp.
-
-At initialization, DMA and READY are empty, and FREE takes all the
-available space:
+buffer. At initialization, READY is empty, FREE takes all the
+available space, and dma is idle.
- dp = rp = fp = 0 ; -- beginning of buffer
- dl0 = dl = 0 ; -- meaning no dma activity
- rl = 0 ; -- meaning no data ready
- fl = bufsize ;
+The two boundaries (rp,fp) in the buffers are advanced by DMA [d]
+and write() [w] operations. The first block of the READY region
+is used for DMA transfers. The transfer is started at rp and with
+chunks of length dl. During DMA operations, rp advances and rl,fl
+are updated by dsp_wr_dmaupdate(). When a new block is written,
+fp advances and rl,fl are updated accordingly.
-The DMA region is also empty whenever there is no DMA activity, for
-whatever reason (e.g. no ready data, or output is paused).
The code works as follows: the user write routine dsp_write_body()
fills up the READY region with new data (reclaiming space from the
-FREE region) and starts the write DMA engine if inactive (activity
-is indicated by d->flags & SND_F_WR_DMA ). The size of each
-DMA transfer is chosen according to a series of rules which will be
-discussed later. When a DMA transfer is complete, an interrupt causes
-dsp_wrintr() to be called which empties the DMA region, extends
-the FREE region and possibly starts the next transfer.
+FREE region) and starts the write DMA engine if inactive. When a
+DMA transfer is complete, an interrupt causes dsp_wrintr() to be
+called which extends the FREE region and possibly starts the next
+transfer.
In some cases, the code tries to track the current status of DMA
-operations by calling isa_dmastatus() and advancing the boundary
-between FREE and DMA regions accordingly.
+operations by calling dsp_wr_dmaupdate() which changes rp, rl and fl.
-The size of a DMA transfer is selected according to the following
-rules:
+The sistem tries to make all DMA transfers use the same size,
+play_blocksize or rec_blocksize. The size is either selected
+by the user, or computed by the system to correspond to about .25s of
+audio. The blocksize must be within a range which is currently:
- 1. when not using AUTO DMA, do not wrap around the end of the
- buffer, and do not let fp move too close to the end of the
- buffer;
+ min(5ms, 40 bytes) ... 1/2 buffer size.
- 2. do not use more than half of the buffer size.
- This serves to allow room for a next write operation concurrent
- with the dma transfer, and to reduce the time which is necessary
- to wait before a pending dma will end (optionally, the max
- size could be further limited to a fixed amount of play time,
- depending on number of channels, sample size and sample speed);
+When there aren't enough data (write) or space (read), a transfer
+is started with a reduced size.
- 3. use the current blocksize (either specified by the user, or
- corresponding roughly to 0.25s of data);
+To reduce problems in case of overruns, the routine which fills up the
+buffer should initialize (e.g. by repeating the last value) a
+reasonably long area after the last block so that no noise is
+produced on overruns.
*
*/
/*
- * dsp_wr_dmadone moves the write DMA region into the FREE region.
- * It is assumed to be called at spltty() and with a write dma
- * previously started.
+ * dsp_wr_dmadone() updates pointers and wakes up any process sleeping
+ * or waiting on a select().
+ * Must be called at spltty().
*/
static void
dsp_wr_dmadone(snddev_info *d)
{
snd_dbuf *b = & (d->dbuf_out) ;
- isa_dmadone(B_WRITE, b->buf + b->dp, b->dl, d->dma1);
- b->fl += b->dl ; /* make dl bytes free */
+ dsp_wr_dmaupdate(b);
/*
* XXX here it would be more efficient to record if there
* actually is a sleeping process, but this should still work.
*/
wakeup(b); /* wakeup possible sleepers */
- if (d->wsel.si_pid &&
+ if (b->sel.si_pid &&
( !(d->flags & SND_F_HAS_SIZE) || b->fl >= d->play_blocksize ) )
- selwakeup( & d->wsel );
- b->dp = b->rp ;
- b->dl0 = b->dl = 0 ;
+ selwakeup( & b->sel );
}
/*
- * The following function tracks the status of a (write) dma transfer,
- * and moves the boundary between the FREE and the DMA regions.
- * It works under the following assumptions:
- * - the DMA engine is running;
- * - the routine is called with interrupts blocked.
- * BEWARE: when using AUTO DMA, dl can go negative! We assume that it
- * does not wrap!
+ * dsp_wr_dmaupdate() tracks the status of a (write) dma transfer,
+ * updating pointers. It must be called at spltty() and the ISA DMA must
+ * have been started.
+ *
+ * NOTE: when we are using auto dma in the device, rl might become
+ * negative.
*/
void
-dsp_wr_dmaupdate(snddev_info *d)
+dsp_wr_dmaupdate(snd_dbuf *b)
{
- snd_dbuf *b = & (d->dbuf_out) ;
- int tmp;
+ int tmp, delta;
- tmp = b->dl - isa_dmastatus1(d->dma1) ;
+ tmp = b->bufsize - isa_dmastatus1(b->chan) ;
tmp &= DMA_ALIGN_MASK; /* align... */
- if (tmp > 0) { /* we have some new data */
- b->dp += tmp;
- if (b->dp >= b->bufsize)
- b->dp -= b->bufsize;
- b->dl -= tmp ;
- b->fl += tmp ;
- }
+ delta = tmp - b->rp;
+ if (delta < 0) /* wrapped */
+ delta += b->bufsize ;
+ b->rp = tmp;
+ b->rl -= delta ;
+ b->fl += delta ;
}
/*
- * Write interrupt routine. Can be called from other places, but
- * with interrupts disabled.
+ * Write interrupt routine. Can be called from other places (e.g.
+ * to start a paused transfer), but with interrupts disabled.
*/
void
dsp_wrintr(snddev_info *d)
{
snd_dbuf *b = & (d->dbuf_out) ;
- int cb_reason = SND_CB_WR ;
- DEB(printf("dsp_wrintr: start on dl %d, rl %d, fl %d\n",
- b->dl, b->rl, b->fl));
- if (d->flags & SND_F_WR_DMA) { /* dma was active */
+ if (b->dl) { /* dma was active */
b->int_count++;
- d->flags &= ~SND_F_WR_DMA;
- cb_reason = SND_CB_WR | SND_CB_RESTART ;
dsp_wr_dmadone(d);
- } else
- cb_reason = SND_CB_WR | SND_CB_START ;
+ }
+ DEB(if (b->rl < 0)
+ printf("dsp_wrintr: dl %d, rp:rl %d:%d, fp:fl %d:%d\n",
+ b->dl, b->rp, b->rl, b->fp, b->fl));
/*
- * start another dma operation only if have ready data in the
- * buffer, there is no pending abort, have a full-duplex device
- * (dma1 != dma2) or have half duplex device and there is no
- * pending op on the other side.
+ * start another dma operation only if have ready data in the buffer,
+ * there is no pending abort, have a full-duplex device
+ * (dbuf_out.chan != dbuf_in.chan) or have half duplex device
+ * and there is no * pending op on the other side.
*
- * Force transfer to be aligned to a boundary of 4, which is
+ * Force transfers to be aligned to a boundary of 4, which is
* needed when doing stereo and 16-bit. We could make this
* adaptive, but why bother for now...
*/
if ( b->rl >= DMA_ALIGN_THRESHOLD &&
! (d->flags & SND_F_ABORTING) &&
- ( (d->dma1 != d->dma2) || ! (d->flags & SND_F_READING) ) ) {
- b->dl = min(b->rl, b->bufsize - b->rp ) ; /* do not wrap */
- b->dl = min(b->dl, d->play_blocksize ); /* avoid too large transfer */
- b->dl &= DMA_ALIGN_MASK ; /* realign things */
- b->rl -= b->dl ;
- b->rp += b->dl ;
- if (b->rp == b->bufsize)
- b->rp = 0 ;
- /*
- * now try to avoid too small dma transfers in the near future.
- * This can happen if I let rp start too close to the end of
- * the buffer. If this happens, and have enough data, try to
- * split the available block in two approx. equal parts.
- * XXX this code can go when we use auto dma.
- */
- if (b->bufsize - b->rp < MIN_CHUNK_SIZE &&
- b->bufsize - b->dp > 2*MIN_CHUNK_SIZE) {
- b->dl = (b->bufsize - b->dp) / 2;
- b->dl &= ~3 ; /* align to a boundary of 4 */
- b->rl += (b->rp - (b->dp + b->dl) ) ;
- b->rp = b->dp + b->dl ; /* no need to check for wraps */
- }
- /*
- * how important is the order of operations ?
- */
- if (b->dl == 0) {
- printf("ouch... want to start for 0 bytes!\n");
- goto ferma;
+ ( (d->dbuf_out.chan != d->dbuf_in.chan) ||
+ ! (d->flags & SND_F_READING) ) ) {
+ int l = min(b->rl, d->play_blocksize ); /* avoid too large transfer */
+ l &= DMA_ALIGN_MASK ; /* realign things */
+
+ if (l != b->dl) {
+ /* for any reason, size has changed. Stop and restart */
+ DEB(printf("wrintr: bsz change from %d to %d, rp %d rl %d\n",
+ b->dl, l, b->rp, b->rl));
+ b->dl = l; /* record previous transfer size */
+ d->callback(d, SND_CB_WR | SND_CB_STOP );
+ d->callback(d, SND_CB_WR | SND_CB_START );
}
- b->dl0 = b->dl ; /* XXX */
- if (d->callback)
- d->callback(d, cb_reason ); /* start/restart dma */
- isa_dmastart( B_WRITE , b->buf + b->dp, b->dl, d->dma1);
- d->flags |= SND_F_WR_DMA;
} else {
-ferma:
- if (d->callback && (cb_reason & SND_CB_REASON_MASK) == SND_CB_RESTART )
+ /* cannot start a new dma transfer */
+ DEB(printf("cannot start wr-dma flags 0x%08x rp %d rl %d\n",
+ d->flags, b->rp, b->rl));
+ if (b->dl > 0) { /* was active */
+ b->dl = 0;
d->callback(d, SND_CB_WR | SND_CB_STOP ); /* stop dma */
+ if (d->flags & SND_F_WRITING)
+ DEB(printf("Race! got wrint while reloading...\n"));
+ else
+ reset_dbuf(b, SND_CHAN_WR);
+ }
/*
* if switching to read, should start the read dma...
*/
- if ( d->dma1 == d->dma2 && (d->flags & SND_F_READING) )
+ if ( d->dbuf_out.chan == d->dbuf_in.chan && (d->flags & SND_F_READING) )
dsp_rdintr(d);
- DEB(printf("cannot start wr-dma flags 0x%08lx dma_dl %d rl %d\n",
- d->flags, isa_dmastatus1(d->dma1), b->rl));
}
}
@@ -259,65 +220,55 @@ ferma:
* data, we reduce the latency to something proportional to the length
* of the first piece, while keeping the overhead low and being able
* to feed the DMA with large blocks.
+ *
+ * assume d->flags |= SND_F_WRITING ; has been done before
*/
int
dsp_write_body(snddev_info *d, struct uio *buf)
{
- int timeout = 1, n, l, bsz, ret = 0 ;
+ int n, l, bsz, ret = 0 ;
long s;
snd_dbuf *b = & (d->dbuf_out) ;
- /* assume d->flags |= SND_F_WRITING ; has been done before */
/*
- * bsz is the max size for the next transfer. If the dma was
- * idle, we want it as large as possible. Otherwise, start with
+ * bsz is the max size for the next transfer. If the dma was idle
+ * (dl == 0), we want it as large as possible. Otherwise, start with
* a small block to avoid underruns if we are close to the end of
* the previous operation.
*/
- bsz = (d->flags & SND_F_WR_DMA) ? MIN_CHUNK_SIZE : b->bufsize ;
+ bsz = b->dl ? MIN_CHUNK_SIZE : b->bufsize ;
while (( n = buf->uio_resid )) {
l = min (n, bsz); /* at most n bytes ... */
s = spltty(); /* no interrupts here ... */
- /*
- * if i) the dma engine is running, ii) we do not have enough space
- * in the FREE region, and iii) the current DMA transfer might let
- * us complete the _whole_ transfer without sleeping, or we are doing
- * non-blocking I/O, then try to extend the FREE region.
- * Otherwise do not bother, we will need to sleep anyways, and
- * make the timeout longer.
- */
-
- if ( d->flags & SND_F_WR_DMA && b->fl < l &&
- ( b->fl + b->dl >= n || d->flags & SND_F_NBIO ) )
- dsp_wr_dmaupdate(d); /* should really change timeout... */
- else
- timeout = hz;
+ dsp_wr_dmaupdate(b);
l = min( l, b->fl ); /* no more than avail. space */
- l = min( l, b->bufsize - b->fp ); /* do not wrap ... */
DEB(printf("dsp_write_body: prepare %d bytes out of %d\n", l,n));
/*
* at this point, we assume that if l==0 the dma engine
- * must (or will, in cause it is paused) be running.
+ * must be running.
*/
if (l == 0) { /* no space, must sleep */
+ int timeout;
if (d->flags & SND_F_NBIO) {
/* unless of course we are doing non-blocking i/o */
splx(s);
break;
}
DEB(printf("dsp_write_body: l=0, (fl %d) sleeping\n", b->fl));
+ if ( b->fl < n )
+ timeout = hz;
+ else
+ timeout = 1 ;
ret = tsleep( (caddr_t)b, PRIBIO|PCATCH, "dspwr", timeout);
if (ret == EINTR)
d->flags |= SND_F_ABORTING ;
splx(s);
- if (ret == ERESTART || ret == EINTR)
+ if (ret == EINTR)
break ;
- timeout = min(2*timeout, hz);
continue;
}
splx(s);
- timeout = 1 ; /* we got some data... */
/*
* copy data to the buffer, and possibly do format
@@ -325,13 +276,19 @@ dsp_write_body(snddev_info *d, struct uio *buf)
* NOTE: I can use fp here since it is not modified by the
* interrupt routines.
*/
- ret = uiomove(b->buf + b->fp, l, buf) ;
- if (ret !=0 ) { /* an error occurred ... */
- printf("uiomove error %d\n", ret);
- break ;
+ if (b->fp + l > b->bufsize) {
+ int l1 = b->bufsize - b->fp ;
+ uiomove(b->buf + b->fp, l1, buf) ;
+ uiomove(b->buf, l - l1, buf) ;
+ if (d->flags & SND_F_XLAT8) {
+ translate_bytes(ulaw_dsp, b->buf + b->fp, l1);
+ translate_bytes(ulaw_dsp, b->buf , l - l1);
+ }
+ } else {
+ uiomove(b->buf + b->fp, l, buf) ;
+ if (d->flags & SND_F_XLAT8)
+ translate_bytes(ulaw_dsp, b->buf + b->fp, l);
}
- if (d->flags & SND_F_XLAT8)
- translate_bytes(ulaw_dsp, b->buf + b->fp, l);
s = spltty(); /* no interrupts here ... */
b->rl += l ; /* this more ready bytes */
@@ -339,17 +296,51 @@ dsp_write_body(snddev_info *d, struct uio *buf)
b->fp += l ;
if (b->fp >= b->bufsize) /* handle wraps */
b->fp -= b->bufsize ;
- if ( !(d->flags & SND_F_WR_DMA) ) {/* dma was idle, restart it */
- if ( (d->flags & (SND_F_INIT|SND_F_WR_DMA|SND_F_RD_DMA)) ==
- SND_F_INIT) {
- /* want to init but no pending DMA activity */
- splx(s);
- d->callback(d, SND_CB_INIT); /* this is slow! */
- s = spltty();
- }
+ if ( b->dl == 0 ) /* dma was idle, restart it */
dsp_wrintr(d) ;
- }
splx(s) ;
+ if (buf->uio_resid == 0 && (b->fp & (b->sample_size - 1)) == 0) {
+ /*
+ * If data is correctly aligned, pad the region with
+ * replicas of the last sample. l0 goes from current to
+ * the buffer end, l1 is the portion which wraps around.
+ */
+ int l0, l1, i;
+
+ l1 = min(/* b->dl */ d->play_blocksize, b->fl);
+ l0 = min (l1, b->bufsize - b->fp);
+ l1 = l1 - l0 ;
+
+ i = b->fp - b->sample_size;
+ if (i < 0 ) i += b->bufsize ;
+ if (b->sample_size == 1) {
+ u_char *p= (u_char *)(b->buf + i), sample = *p;
+
+ for ( ; l0 ; l0--)
+ *p++ = sample ;
+ for (p= (u_char *)(b->buf) ; l1 ; l1--)
+ *p++ = sample ;
+ } else if (b->sample_size == 2) {
+ u_short *p= (u_short *)(b->buf + i), sample = *p;
+
+ l1 /= 2 ;
+ l0 /= 2 ;
+ for ( ; l0 ; l0--)
+ *p++ = sample ;
+ for (p= (u_short *)(b->buf) ; l1 ; l1--)
+ *p++ = sample ;
+ } else { /* must be 4 ... */
+ u_long *p= (u_long *)(b->buf + i), sample = *p;
+
+ l1 /= 4 ;
+ l0 /= 4 ;
+ for ( ; l0 ; l0--)
+ *p++ = sample ;
+ for (p= (u_long *)(b->buf) ; l1 ; l1--)
+ *p++ = sample ;
+ }
+
+ }
bsz = min(b->bufsize, bsz*2);
}
s = spltty(); /* no interrupts here ... */
@@ -357,7 +348,8 @@ dsp_write_body(snddev_info *d, struct uio *buf)
if (d->flags & SND_F_ABORTING) {
d->flags &= ~SND_F_ABORTING;
splx(s);
- dsp_wrabort(d);
+ dsp_wrabort(d, 1 /* restart */);
+ /* XXX return EINTR ? */
}
splx(s) ;
return ret ;
@@ -367,45 +359,29 @@ dsp_write_body(snddev_info *d, struct uio *buf)
* SOUND INPUT
*
-The input part is similar to the output one. The only difference is in
-the ordering of regions, which is the following:
-
- 0 rp,rl dp,dl fp,fl bufsize
- |__________|____________|______>______|________|
- FREE READY DMA FREE
-
-and the fact that input data are in the READY region.
+The input part is similar to the output one, with a circular buffer
+split in two regions, and boundaries advancing because of read() calls
+[r] or dma operation [d]. At initialization, as for the write
+routine, READY is empty, and FREE takes all the space.
-At initialization, as for the write routine, DMA and READY are empty,
-and FREE takes all the space:
-
- dp = rp = fp = 0 ; -- beginning of buffer
- dl0 = dl = 0 ; -- meaning no dma activity
- rl = 0 ; -- meaning no data ready
- fl = bufsize ;
+ 0 rp,rl fp,fl bufsize
+ |__________>____________>________|
+ FREE r READY d FREE
Operation is as follows: upon user read (dsp_read_body()) a DMA read
-is started if not already active (marked by d->flags & SND_F_RD_DMA),
+is started if not already active (marked by b->dl > 0),
then as soon as data are available in the READY region they are
transferred to the user buffer, thus advancing the boundary between FREE
and READY. Upon interrupts, caused by a completion of a DMA transfer,
the READY region is extended and possibly a new transfer is started.
-When necessary, isa_dmastatus() is called to advance the boundary
-between READY and DMA to the real position.
+When necessary, dsp_rd_dmaupdate() is called to advance fp (and update
+rl,fl accordingly). Upon user reads, rp is advanced and rl,fl are
+updated accordingly.
The rules to choose the size of the new DMA area are similar to
-the other case, i.e:
-
- 1. if not using AUTO mode, do not wrap around the end of the
- buffer, and do not let fp move too close to the end of the
- buffer;
-
- 2. do not use more than half the buffer size; this serves to
- leave room for the next dma operation.
-
- 3. use the default blocksize, either user-specified, or
- corresponding to 0.25s of data;
+the other case, with a preferred constant transfer size equal to
+rec_blocksize, and fallback to smaller sizes if no space is available.
*
*/
@@ -419,14 +395,11 @@ dsp_rd_dmadone(snddev_info *d)
{
snd_dbuf *b = & (d->dbuf_in) ;
- isa_dmadone(B_READ, b->buf + b->dp, b->dl, d->dma2);
- b->rl += b->dl ; /* make dl bytes available */
+ dsp_rd_dmaupdate(b);
wakeup(b) ; /* wakeup possibly sleeping processes */
- if (d->rsel.si_pid &&
+ if (b->sel.si_pid &&
( !(d->flags & SND_F_HAS_SIZE) || b->rl >= d->rec_blocksize ) )
- selwakeup( & d->rsel );
- b->dp = b->fp ;
- b->dl0 = b->dl = 0 ;
+ selwakeup( & b->sel );
}
/*
@@ -437,20 +410,18 @@ dsp_rd_dmadone(snddev_info *d)
* - the function is called with interrupts blocked.
*/
void
-dsp_rd_dmaupdate(snddev_info *d)
+dsp_rd_dmaupdate(snd_dbuf *b)
{
- snd_dbuf *b = & (d->dbuf_in) ;
- int tmp ;
+ int delta, tmp ;
- tmp = b->dl - isa_dmastatus1(d->dma2) ;
+ tmp = b->bufsize - isa_dmastatus1(b->chan) ;
tmp &= DMA_ALIGN_MASK; /* align... */
- if (tmp > 0) { /* we have some data */
- b->dp += tmp;
- if (b->dp >= b->bufsize)
- b->dp -= b->bufsize;
- b->dl -= tmp ;
- b->rl += tmp ;
- }
+ delta = tmp - b->fp;
+ if (delta < 0) /* wrapped */
+ delta += b->bufsize ;
+ b->fp = tmp;
+ b->fl -= delta ;
+ b->rl += delta ;
}
/*
@@ -460,64 +431,41 @@ void
dsp_rdintr(snddev_info *d)
{
snd_dbuf *b = & (d->dbuf_in) ;
- int cb_reason = SND_CB_RD ;
- DEB(printf("dsp_rdintr: start dl = %d fp %d blocksize %d\n",
- b->dl, b->fp, d->rec_blocksize));
- if (d->flags & SND_F_RD_DMA) { /* dma was active */
+ if (b->dl) { /* dma was active */
b->int_count++;
- d->flags &= ~SND_F_RD_DMA;
- cb_reason = SND_CB_RD | SND_CB_RESTART ;
dsp_rd_dmadone(d);
- } else
- cb_reason = SND_CB_RD | SND_CB_START ;
+ }
+
+ DEB(printf("dsp_rdintr: start dl %d, rp:rl %d:%d, fp:fl %d:%d\n",
+ b->dl, b->rp, b->rl, b->fp, b->fl));
/*
- * Same checks as in the write case (mutatis mutandis) to decide
- * whether or not to restart a dma transfer.
+ * Restart if have enough free space to absorb overruns;
*/
- if ( b->fl >= DMA_ALIGN_THRESHOLD &&
- ((d->flags & (SND_F_ABORTING|SND_F_CLOSING)) == 0) &&
- ( (d->dma1 != d->dma2) || ( (d->flags & SND_F_WRITING) == 0) ) ) {
- b->dl = min(b->fl, b->bufsize - b->fp ) ; /* do not wrap */
- b->dl = min(b->dl, d->rec_blocksize );
- b->dl &= DMA_ALIGN_MASK ; /* realign sizes */
- b->fl -= b->dl ;
- b->fp += b->dl ;
- if (b->fp == b->bufsize)
- b->fp = 0 ;
- /*
- * now try to avoid too small dma transfers in the near future.
- * This can happen if I let fp start too close to the end of
- * the buffer. If this happens, and have enough data, try to
- * split the available block in two approx. equal parts.
- */
- if (b->bufsize - b->fp < MIN_CHUNK_SIZE &&
- b->bufsize - b->dp > 2*MIN_CHUNK_SIZE) {
- b->dl = (b->bufsize - b->dp) / 2;
- b->dl &= DMA_ALIGN_MASK ; /* align to multiples of 3 */
- b->fl += (b->fp - (b->dp + b->dl) ) ;
- b->fp = b->dp + b->dl ; /* no need to check that fp wraps */
- }
- if (b->dl == 0) {
- printf("ouch! want to read 0 bytes\n");
- goto ferma;
+ if ( b->fl > 0x200 &&
+ (d->flags & (SND_F_ABORTING|SND_F_CLOSING)) == 0 &&
+ ( d->dbuf_out.chan != d->dbuf_in.chan ||
+ (d->flags & SND_F_WRITING) == 0 ) ) {
+ int l = min(b->fl - 0x100, d->rec_blocksize);
+ l &= DMA_ALIGN_MASK ; /* realign sizes */
+ if (l != b->dl) {
+ /* for any reason, size has changed. Stop and restart */
+ b->dl = l ;
+ d->callback(d, SND_CB_RD | SND_CB_STOP );
+ d->callback(d, SND_CB_RD | SND_CB_START );
}
- b->dl0 = b->dl ; /* XXX */
- if (d->callback)
- d->callback(d, cb_reason); /* restart_dma(); */
- isa_dmastart( B_READ , b->buf + b->dp, b->dl, d->dma2);
- d->flags |= SND_F_RD_DMA;
} else {
-ferma:
- if (d->callback && (cb_reason & SND_CB_REASON_MASK) == SND_CB_RESTART)
+ if (b->dl > 0) { /* was active */
+ b->dl = 0;
d->callback(d, SND_CB_RD | SND_CB_STOP);
+ }
/*
* if switching to write, start write dma engine
*/
- if ( d->dma1 == d->dma2 && (d->flags & SND_F_WRITING) )
+ if ( d->dbuf_out.chan == d->dbuf_in.chan && (d->flags & SND_F_WRITING) )
dsp_wrintr(d) ;
- DEB(printf("cannot start rd-dma flags 0x%08lx dma_dl %d fl %d\n",
- d->flags, isa_dmastatus1(d->dma2), b->fl));
+ DEB(printf("cannot start rd-dma rl %d fl %d\n",
+ b->rl, b->fl));
}
}
@@ -549,8 +497,6 @@ dsp_read_body(snddev_info *d, struct uio *buf)
long s;
snd_dbuf *b = & (d->dbuf_in) ;
- int timeout = 1 ; /* counter of how many ticks we sleep */
-
/*
* "limit" serves to return after at most one blocksize of data
* (unless more are already available). Otherwise, things like
@@ -569,116 +515,69 @@ dsp_read_body(snddev_info *d, struct uio *buf)
bsz = MIN_CHUNK_SIZE ; /* the current transfer (doubles at each step) */
while ( (n = buf->uio_resid) > limit ) {
DEB(printf("dsp_read_body: start waiting for %d bytes\n", n));
- /*
- * here compute how many bytes to transfer, enforcing various
- * limitations:
- */
- l = min (n, bsz); /* 1': at most bsz bytes ... */
+ l = min (n, bsz);
s = spltty(); /* no interrupts here ! */
- /*
- * if i) the dma engine is running, ii) we do not have enough
- * ready bytes, and iii) the current DMA transfer could give
- * us what we need, or we are doing non-blocking IO, then try
- * to extend the READY region.
- * Otherwise do not bother, we will need to sleep anyways,
- * and make the timeout longer.
- */
-
- if ( d->flags & SND_F_RD_DMA && b->rl < l &&
- ( d->flags & SND_F_NBIO || b->rl + b->dl >= n - limit ) )
- dsp_rd_dmaupdate(d);
- else
- timeout = hz ;
- l = min( l, b->rl ); /* 2': no more than avail. data */
- l = min( l, b->bufsize - b->rp ); /* 3': do not wrap buffer. */
- /* the above limitation can be removed if we use auto DMA
- * on the ISA controller. But then we have to make a check
- * when doing the uiomove...
- */
- if ( !(d->flags & SND_F_RD_DMA) ) { /* dma was idle, start it */
- /*
- * There are two reasons the dma can be idle: either this
- * is the first read, or the buffer has become full. In
- * the latter case, the dma cannot be restarted until
- * we have removed some data, which will be true at the
- * second round.
- *
- * Call dsp_rdintr to start the dma. It would be nice to
- * have a "need" field in the snd_dbuf, so that we do
- * not start a long operation unnecessarily. However,
- * the restart code will ask for at most d->blocksize
- * bytes, and since we are sure we are the only reader,
- * and the routine is not interrupted, we patch and
- * restore d->blocksize around the call. A bit dirty,
- * but it works, and saves some overhead :)
- */
-
- int old_bs = d->rec_blocksize;
-
- if ( (d->flags & (SND_F_INIT|SND_F_WR_DMA|SND_F_RD_DMA)) ==
- SND_F_INIT) {
- /* want to init but no pending DMA activity */
- splx(s);
- d->callback(d, SND_CB_INIT); /* this is slow! */
- s = spltty();
- }
- if (l < MIN_CHUNK_SIZE)
- d->rec_blocksize = MIN_CHUNK_SIZE ;
- else if (l < d->rec_blocksize)
- d->rec_blocksize = l ;
- dsp_rdintr(d);
- d->rec_blocksize = old_bs ;
- }
-
+ dsp_rd_dmaupdate(b);
+ l = min( l, b->rl ); /* no more than avail. data */
if (l == 0) {
+ int timeout;
/*
- * If, after all these efforts, we still have no data ready,
- * then we must sleep (unless of course we have doing
- * non-blocking i/o. But use exponential delays, starting
- * at 1 tick and doubling each time.
+ * If there is no data ready, then we must sleep (unless
+ * of course we have doing non-blocking i/o). But also
+ * consider restarting the DMA engine.
*/
+ if ( b->dl == 0 ) { /* dma was idle, start it */
+ if ( d->flags & SND_F_INIT && d->dbuf_out.dl == 0 ) {
+ /* want to init and there is no pending DMA activity */
+ splx(s);
+ d->callback(d, SND_CB_INIT); /* this is slow! */
+ s = spltty();
+ }
+ dsp_rdintr(d);
+ }
if (d->flags & SND_F_NBIO) {
splx(s);
break;
}
- DEB(printf("dsp_read_body: sleeping %d waiting for %d bytes\n",
- timeout, buf->uio_resid));
+ if (n-limit > b->dl)
+ timeout = hz; /* we need to wait for an int. */
+ else
+ timeout = 1; /* maybe data will be ready earlier */
ret = tsleep( (caddr_t)b, PRIBIO | PCATCH , "dsprd", timeout ) ;
if (ret == EINTR)
d->flags |= SND_F_ABORTING ;
- splx(s); /* necessary before the goto again... */
- if (ret == ERESTART || ret == EINTR)
+ splx(s);
+ if (ret == EINTR)
break ;
- DEB(printf("woke up, ret %d, rl %d\n", ret, b->rl));
- timeout = min(timeout*2, hz);
continue;
}
splx(s);
- timeout = 1 ; /* we got some data, so reset exp. wait */
- /*
- * if we are using /dev/audio and the device does not
- * support it natively, we should do a format conversion.
- * (in this case from uLAW to natural format).
- * This can be messy in that it can require an intermediate
- * buffer, and also screw up the byte count.
- */
/*
+ * Do any necessary format conversion, and copy to user space.
* NOTE: I _can_ use rp here because it is not modified by the
* interrupt routines.
*/
- if (d->flags & SND_F_XLAT8)
- translate_bytes(dsp_ulaw, b->buf + b->rp, l);
- ret = uiomove(b->buf + b->rp, l, buf) ;
- if (ret !=0 ) /* an error occurred ... */
- break ;
+ if (b->rp + l > b->bufsize) { /* handle wraparounds */
+ int l1 = b->bufsize - b->rp ;
+ if (d->flags & SND_F_XLAT8) {
+ translate_bytes(dsp_ulaw, b->buf + b->rp, l1);
+ translate_bytes(dsp_ulaw, b->buf , l - l1);
+ }
+ uiomove(b->buf + b->rp, l1, buf) ;
+ uiomove(b->buf, l - l1, buf) ;
+ } else {
+ if (d->flags & SND_F_XLAT8)
+ translate_bytes(dsp_ulaw, b->buf + b->rp, l);
+ uiomove(b->buf + b->rp, l, buf) ;
+ }
s = spltty(); /* no interrupts here ... */
b->fl += l ; /* this more free bytes */
b->rl -= l ; /* this less ready bytes */
b->rp += l ; /* advance ready pointer */
- if (b->rp == b->bufsize) /* handle wraps */
- b->rp = 0 ;
+ if (b->rp >= b->bufsize) /* handle wraps */
+ b->rp -= b->bufsize ;
splx(s) ;
bsz = min(b->bufsize, bsz*2);
}
@@ -687,7 +586,8 @@ dsp_read_body(snddev_info *d, struct uio *buf)
if (d->flags & SND_F_ABORTING) {
d->flags |= ~SND_F_ABORTING;
splx(s);
- dsp_rdabort(d);
+ dsp_rdabort(d, 1 /* restart */);
+ /* XXX return EINTR ? */
}
splx(s) ;
return ret ;
@@ -697,30 +597,40 @@ dsp_read_body(snddev_info *d, struct uio *buf)
/*
* short routine to initialize a dma buffer descriptor (usually
* located in the XXX_desc structure). The first parameter is
- * the buffer size, the second one specifies the dma channel in use
- * At the moment we do not support more than 64K, since for some
- * cards I need to switch between dma1 and dma2. The channel is
- * unused.
+ * the buffer size, the second one specifies that a 16-bit dma channel
+ * is used (hence the buffer must be properly aligned).
*/
void
-alloc_dbuf(snd_dbuf *b, int size, int chan)
+alloc_dbuf(snd_dbuf *b, int size)
{
if (size > 0x10000)
panic("max supported size is 64k");
b->buf = contigmalloc(size, M_DEVBUF, M_NOWAIT,
0ul, 0xfffffful, 1ul, 0x10000ul);
- /* should check that it does not fail... */
- b->dp = b->rp = b->fp = 0 ;
- b->dl0 = b->dl = b->rl = 0 ;
+ /* should check that malloc does not fail... */
+ b->rp = b->fp = 0 ;
+ b->dl = b->rl = 0 ;
b->bufsize = b->fl = size ;
}
+/*
+ * this resets a buffer and starts the isa dma on that channel.
+ * Must be called when the dma on the card is disabled (e.g. after init).
+ */
void
-reset_dbuf(snd_dbuf *b)
+reset_dbuf(snd_dbuf *b, int chan)
{
- b->dp = b->rp = b->fp = 0 ;
- b->dl0 = b->dl = b->rl = 0 ;
+ DEB(printf("reset dbuf for chan %d\n", b->chan));
+ b->rp = b->fp = 0 ;
+ b->dl = b->rl = 0 ;
b->fl = b->bufsize ;
+ if (chan == SND_CHAN_NONE)
+ return ;
+ if (chan == SND_CHAN_WR)
+ chan = B_WRITE | B_RAW ;
+ else
+ chan = B_READ | B_RAW ;
+ isa_dmastart( chan , b->buf, b->bufsize, b->chan);
}
/*
@@ -741,9 +651,9 @@ snd_sync(snddev_info *d, int chan, int threshold)
for (;;) {
s=spltty();
if ( chan==1 )
- dsp_wr_dmaupdate(d);
+ dsp_wr_dmaupdate(b);
else
- dsp_rd_dmaupdate(d);
+ dsp_rd_dmaupdate(b);
if ( (chan == 1 && b->fl <= threshold) ||
(chan == 2 && b->rl <= threshold) ) {
ret = tsleep((caddr_t)b, PRIBIO|PCATCH, "sndsyn", 1);
@@ -762,61 +672,53 @@ snd_sync(snddev_info *d, int chan, int threshold)
/*
* dsp_wrabort(d) and dsp_rdabort(d) are non-blocking functions
* which abort a pending DMA transfer and flush the buffers.
+ * They return the number of bytes that has not been transferred.
+ * The second parameter is used to restart the engine if needed.
*/
int
-dsp_wrabort(snddev_info *d)
+dsp_wrabort(snddev_info *d, int restart)
{
long s;
int missing = 0;
snd_dbuf *b = & (d->dbuf_out) ;
s = spltty();
- if ( d->flags & SND_F_WR_DMA ) {
- d->flags &= ~(SND_F_WR_DMA | SND_F_WRITING);
+ if ( b->dl ) {
+ b->dl = 0 ;
+ d->flags &= ~ SND_F_WRITING ;
if (d->callback)
d->callback(d, SND_CB_WR | SND_CB_ABORT);
- missing = isa_dmastop(d->dma1) ; /* this many missing bytes... */
-
- b->rl += missing ; /* which are part of the ready area */
- b->rp -= missing ;
- if (b->rp < 0)
- b->rp += b->bufsize;
- DEB(printf("dsp_wrabort: stopped after %d bytes out of %d\n",
- b->dl - missing, b->dl));
- b->dl -= missing;
+ isa_dmastop(b->chan) ;
dsp_wr_dmadone(d);
- missing = b->rl;
+
+ DEB(printf("dsp_wrabort: stopped, %d bytes left\n", b->rl));
}
- reset_dbuf(b);
+ missing = b->rl;
+ isa_dmadone(B_WRITE, b->buf, b->bufsize, b->chan); /*free chan */
+ reset_dbuf(b, restart ? SND_CHAN_WR : SND_CHAN_NONE);
splx(s);
return missing;
}
int
-dsp_rdabort(snddev_info *d)
+dsp_rdabort(snddev_info *d, int restart)
{
long s;
int missing = 0;
snd_dbuf *b = & (d->dbuf_in) ;
s = spltty();
- if ( d->flags & SND_F_RD_DMA ) {
- d->flags &= ~(SND_F_RD_DMA | SND_F_READING);
+ if ( b->dl ) {
+ b->dl = 0 ;
+ d->flags &= ~ SND_F_READING ;
if (d->callback)
d->callback(d, SND_CB_RD | SND_CB_ABORT);
- missing = isa_dmastop(d->dma2) ; /* this many missing bytes... */
-
- b->fl += missing ; /* which are part of the free area */
- b->fp -= missing ;
- if (b->fp < 0)
- b->fp += b->bufsize;
- DEB(printf("dsp_rdabort: stopped after %d bytes out of %d\n",
- b->dl - missing, b->dl));
- b->dl -= missing;
+ isa_dmastop(b->chan) ;
dsp_rd_dmadone(d);
- missing = b->rl ;
}
- reset_dbuf(b);
+ missing = b->rl ;
+ isa_dmadone(B_READ, b->buf, b->bufsize, b->chan);
+ reset_dbuf(b, restart ? SND_CHAN_RD : SND_CHAN_NONE);
splx(s);
return missing;
}
@@ -831,31 +733,34 @@ dsp_rdabort(snddev_info *d)
int
snd_flush(snddev_info *d)
{
- int ret;
- int count=10;
+ int ret, count=10;
+ u_long s;
+ snd_dbuf *b = &(d->dbuf_out) ;
-DEB(printf("snd_flush d->flags 0x%08lx\n", d->flags));
- dsp_rdabort(d);
- if ( d->flags & SND_F_WR_DMA ) {
- /* close write */
- while ( d->flags & SND_F_WR_DMA ) {
- /*
- * still pending output data.
- */
- ret = tsleep( (caddr_t)&(d->dbuf_out), PRIBIO|PCATCH, "dmafl1", hz);
- if (ret == ERESTART || ret == EINTR) {
- printf("tsleep returns %d\n", ret);
- return -1 ;
- }
- if ( ret && --count == 0) {
- printf("timeout flushing dma1, cnt 0x%x flags 0x%08lx\n",
- isa_dmastatus1(d->dma1), d->flags);
- return -1 ;
- }
+ DEB(printf("snd_flush d->flags 0x%08x\n", d->flags));
+ dsp_rdabort(d, 0 /* no restart */);
+ /* close write */
+ while ( b->dl ) {
+ /*
+ * still pending output data.
+ */
+ ret = tsleep( (caddr_t)b, PRIBIO|PCATCH, "dmafl1", hz);
+ dsp_wr_dmaupdate(b);
+ DEB( printf("snd_sync: now rl : fl %d : %d\n", b->rl, b->fl ) );
+ if (ret == EINTR) {
+ printf("tsleep returns %d\n", ret);
+ return -1 ;
+ }
+ if ( ret && --count == 0) {
+ printf("timeout flushing dbuf_out.chan, cnt 0x%x flags 0x%08lx\n",
+ b->rl, d->flags);
+ break;
}
- d->flags &= ~SND_F_CLOSING ;
}
- reset_dbuf(& (d->dbuf_out) );
+ s = spltty(); /* should not be necessary... */
+ d->flags &= ~SND_F_CLOSING ;
+ dsp_wrabort(d, 0 /* no restart */);
+ splx(s);
return 0 ;
}
OpenPOWER on IntegriCloud