fix dma underrun issues

mutate some panics to kasserts
add more spl protection

PR:		kern/14990
Partially Submitted by:	Vladimir N.Silyaev <vns@delta.odessa.ua>
Reviewed by:	dfr
This commit is contained in:
Cameron Grant 1999-12-05 19:09:13 +00:00
parent e306923fc0
commit 0e25481f93
6 changed files with 155 additions and 90 deletions

View File

@ -37,6 +37,7 @@
#define CANCHANGE(c) (!(c)->buffer.dl)
static void chn_stintr(pcm_channel *c);
static void chn_clearbuf(pcm_channel *c, int length);
/*
* SOUND OUTPUT
@ -103,7 +104,7 @@ chn_isadmabounce(pcm_channel *c)
{
if (ISA_DMA(&c->buffer)) {
/* tell isa_dma to bounce data in/out */
} else panic("chn_isadmabounce called on invalid channel");
} else KASSERT(1, ("chn_isadmabounce called on invalid channel"));
}
static int
@ -153,26 +154,52 @@ chn_dmadone(pcm_channel *c)
* NOTE: when we are using auto dma in the device, rl might become
* negative.
*/
DEB (static int chn_updatecount=0);
void
chn_dmaupdate(pcm_channel *c)
{
snd_dbuf *b = &c->buffer;
int delta, hwptr = chn_getptr(c);
int delta, hwptr;
DEB (int b_rl=b->rl; int b_fl=b->fl; int b_rp=b->rp; int b_fp=b->fp);
hwptr = chn_getptr(c);
if (c->direction == PCMDIR_PLAY) {
delta = (b->bufsize + hwptr - b->rp) % b->bufsize;
b->rp = hwptr;
b->rl -= delta;
b->fl += delta;
DEB(if (b->rl<0) printf("OUCH!(%d) rl %d(%d) delta %d bufsize %d hwptr %d rp %d(%d)\n", chn_updatecount++, b->rl, b_rl, delta, b->bufsize, hwptr, b->rp, b_rp));
} else {
delta = (b->bufsize + hwptr - b->fp) % b->bufsize;
b->fp = hwptr;
b->rl += delta;
b->fl -= delta;
DEB(if (b->fl<0) printf("OUCH!(%d) fl %d(%d) delta %d bufsize %d hwptr %d fp %d(%d)\n", chn_updatecount++, b->fl, b_fl, delta, b->bufsize, hwptr, b->fp, b_fp));
}
b->total += delta;
}
/*
* Check channel for underflow occured, reset DMA buffer in case of
* underflow. It must be called at spltty().
*/
static void
chn_checkunderflow(pcm_channel *c)
{
snd_dbuf *b = &c->buffer;
if (b->underflow) {
DEB(printf("Clear underflow condition\n"));
b->rp = b->fp = chn_getptr(c);
b->rl = 0;
b->fl = b->bufsize;
b->underflow=0;
} else {
chn_dmaupdate(c);
}
}
/*
* Write interrupt routine. Can be called from other places (e.g.
* to start a paused transfer), but with interrupts disabled.
@ -183,7 +210,8 @@ chn_wrintr(pcm_channel *c)
snd_dbuf *b = &c->buffer;
int start;
if (b->dl) chn_dmadone(c);
if (b->underflow) return; /* nothing new happened */
if (b->dl) chn_dmadone(c);
/*
* start another dma operation only if have ready data in the buffer,
@ -198,40 +226,31 @@ chn_wrintr(pcm_channel *c)
if (start) {
int l;
chn_dmaupdate(c);
l = min(b->rl, c->blocksize) & DMA_ALIGN_MASK;
if (c->flags & CHN_F_MAPPED) l = c->blocksize;
else l = min(b->rl, c->blocksize) & DMA_ALIGN_MASK;
/*
* check if we need to reprogram the DMA on the sound card.
* This happens if the size has changed _and_ the new size
* is smaller, or it matches the blocksize.
* This happens if the size has changed from zero
*
* 0 <= l <= blocksize
* 0 <= dl <= blocksize
* reprog if (dl == 0 || l != dl)
* was:
* l != b->dl && (b->dl == 0 || l < b->dl || l == c->blocksize)
*/
if (b->dl == 0 || l != b->dl) {
/* size has changed. Stop and restart */
DEB(printf("wrintr: bsz %d -> %d, rp %d rl %d\n",
b->dl, l, b->rp, b->rl));
if (b->dl) chn_trigger(c, PCMTRIG_STOP);
b->dl = l; /* record new transfer size */
if (b->dl == 0) {
/* Start DMA operation */
b->dl = c->blocksize ; /* record new transfer size */
chn_trigger(c, PCMTRIG_START);
} else if (b->dl != l) {
/*
* we are near to underflow condition, so to prevent
* audio 'clicks' clear next 1.5*dl bytes
*/
chn_clearbuf(c, (b->dl*3)/2);
}
} else {
/* cannot start a new dma transfer */
DEB(printf("cannot start wr-dma flags 0x%08x rp %d rl %d\n",
c->flags, b->rp, b->rl));
if (b->dl) { /* was active */
b->dl = 0;
chn_trigger(c, PCMTRIG_STOP);
#if 0
if (c->flags & CHN_F_WRITING)
DEB(printf("got wrint while reloading\n"));
else if (b->rl <= 0) /* XXX added 980110 lr */
chn_resetbuf(c);
#endif
if (b->dl) { /* DMA was active */
b->underflow = 1; /* set underflow flag */
chn_clearbuf(c, b->bufsize); /* and clear all DMA buffer */
}
}
}
@ -255,9 +274,10 @@ chn_wrintr(pcm_channel *c)
int
chn_write(pcm_channel *c, struct uio *buf)
{
int a, l, w, timeout, ret = 0;
int a, l, w, timeout, ret = 0, rc;
long s;
snd_dbuf *b = &c->buffer;
int threshold, maxthreshold, minthreshold;
if (c->flags & CHN_F_WRITING) {
/* This shouldn't happen and is actually silly
@ -267,40 +287,54 @@ chn_write(pcm_channel *c, struct uio *buf)
return EBUSY;
}
a = (1 << c->align) - 1;
maxthreshold = (b->dl / 4 + a) & ~a;
minthreshold = a;
c->flags |= CHN_F_WRITING;
while ((c->smegcnt + buf->uio_resid) > a) {
s = spltty();
chn_dmaupdate(c);
splx(s);
if (b->fl < DMA_ALIGN_THRESHOLD) {
s = spltty();
chn_checkunderflow(c);
splx(s);
while ((buf->uio_resid + c->smegcnt) > minthreshold ) { /* Don't allow write unaligned data */
threshold = min((buf->uio_resid + c->smegcnt), maxthreshold);
if (b->fl < threshold) {
if (c->flags & CHN_F_NBIO) {
ret = -1;
break;
}
timeout = (buf->uio_resid >= b->dl)? hz : 1;
ret = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout);
if (ret == EINTR) chn_abort(c);
if (ret == EINTR || ret == ERESTART) break;
ret = 0;
continue;
rc = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout);
if (rc == 0 || rc == EWOULDBLOCK) {
s = spltty();
chn_checkunderflow(c);
splx(s);
if (b->fl < minthreshold) continue; /* write only alligned chunk of data */
} else {
#if 0
if (ret == EINTR) chn_abort(c);
#endif
ret = rc;
break;
}
}
/* ensure we always have a whole number of samples */
l = min(b->fl, b->bufsize - b->fp) & ~a;
if (l == 0) continue;
w = c->feeder->feed(c->feeder, c, b->buf + b->fp, l, buf);
if (w == 0) panic("no feed");
KASSERT(w, ("chn_write: no feed"));
s = spltty();
b->rl += w;
b->fl -= w;
b->fp = (b->fp + w) % b->bufsize;
splx(s);
if (b->rl && !b->dl) chn_stintr(c);
DEB(if(1) printf("write %d bytes fp %d rl %d\n",w ,b->fp, b->rl));
if (!b->dl) chn_stintr(c);
}
if ((ret == 0) && (buf->uio_resid > 0)) {
s = spltty();
l = buf->uio_resid;
if ((c->smegcnt + l) >= SMEGBUFSZ) panic("resid overflow %d", l);
KASSERT( (c->smegcnt + l) < SMEGBUFSZ, ("resid overflow %d", l));
uiomove(c->smegbuf + c->smegcnt, l, buf);
c->smegcnt += l;
splx(s);
}
c->flags &= ~CHN_F_WRITING;
return (ret > 0)? ret : 0;
@ -475,44 +509,61 @@ chn_allocbuf(snd_dbuf *b, bus_dma_tag_t parent_dmat)
return 0;
}
static void
chn_clearbuf(pcm_channel *c, int length)
{
int i;
u_int16_t data, *p;
snd_dbuf *b = &c->buffer;
/* rely on length & DMA_ALIGN_MASK == 0 */
length&=DMA_ALIGN_MASK;
if (c->hwfmt & AFMT_SIGNED) data = 0x00; else data = 0x80;
if (c->hwfmt & AFMT_16BIT) data <<= 8; else data |= data << 8;
if (c->hwfmt & AFMT_BIGENDIAN)
data = ((data >> 8) & 0x00ff) | ((data << 8) & 0xff00);
for (i = b->fp, p=(u_int16_t*)(b->buf+b->fp) ; i < b->bufsize && length; i += 2, length-=2)
*p++ = data;
for (i = 0, p=(u_int16_t*)b->buf; i < b->bufsize && length; i += 2, length-=2)
*p++ = data;
return;
}
void
chn_resetbuf(pcm_channel *c)
{
snd_dbuf *b = &c->buffer;
u_int16_t data, *p;
u_int32_t i;
c->smegcnt = 0;
c->buffer.sample_size = 1;
c->buffer.sample_size <<= (c->hwfmt & AFMT_STEREO)? 1 : 0;
c->buffer.sample_size <<= (c->hwfmt & AFMT_16BIT)? 1 : 0;
/* rely on bufsize & 3 == 0 */
if (c->hwfmt & AFMT_SIGNED) data = 0x00; else data = 0x80;
if (c->hwfmt & AFMT_16BIT) data <<= 8; else data |= data << 8;
if (c->hwfmt & AFMT_BIGENDIAN)
data = ((data >> 8) & 0x00ff) | ((data << 8) & 0xff00);
for (i = 0, p = (u_int16_t *)b->buf; i < b->bufsize; i += 2)
*p++ = data;
b->rp = b->fp = 0;
b->dl = b->rl = 0;
b->fl = b->bufsize;
chn_clearbuf(c, b->bufsize);
b->prev_total = b->total = 0;
b->prev_int_count = b->int_count = 0;
b->first_poll = 1;
b->fl = b->bufsize;
b->underflow=0;
}
void
buf_isadma(snd_dbuf *b, int go)
{
if (ISA_DMA(b)) {
if (go == PCMTRIG_START) isa_dmastart(b->dir | B_RAW, b->buf,
b->bufsize, b->chan);
else {
if (go == PCMTRIG_START) {
DEB(printf("buf 0x%p ISA DMA started\n", b));
isa_dmastart(b->dir | B_RAW, b->buf,
b->bufsize, b->chan);
} else {
DEB(printf("buf 0x%p ISA DMA stopped\n", b));
isa_dmastop(b->chan);
isa_dmadone(b->dir | B_RAW, b->buf, b->bufsize,
b->chan);
}
} else panic("buf_isadma called on invalid channel");
} else KASSERT(1, ("buf_isadma called on invalid channel"));
}
int
@ -522,7 +573,7 @@ buf_isadmaptr(snd_dbuf *b)
int i = b->dl? isa_dmastatus(b->chan) : b->bufsize;
if (i < 0) i = 0;
return b->bufsize - i;
} else panic("buf_isadmaptr called on invalid channel");
} else KASSERT(1, ("buf_isadmaptr called on invalid channel"));
return -1;
}
@ -546,7 +597,7 @@ chn_sync(pcm_channel *c, int threshold)
ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmsyn", 1);
splx(s);
if (ret == ERESTART || ret == EINTR) {
printf("tsleep returns %d\n", ret);
DEB(printf("chn_sync: tsleep returns %d\n", ret));
return -1;
}
} else break;
@ -608,18 +659,18 @@ chn_flush(pcm_channel *c)
DEB(printf("snd_flush c->flags 0x%08x\n", c->flags));
c->flags |= CHN_F_CLOSING;
if (c->direction != PCMDIR_PLAY) chn_abort(c);
else while (b->dl) {
else if (b->dl) while (!b->underflow) {
/* still pending output data. */
ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmflu", hz);
chn_dmaupdate(c);
DEB(chn_dmaupdate(c));
DEB(printf("snd_sync: now rl : fl %d : %d\n", b->rl, b->fl));
if (ret == EINTR) {
printf("tsleep returns %d\n", ret);
if (ret == EINTR || ret == ERESTART) {
DEB(printf("chn_flush: tsleep returns %d\n", ret));
return -1;
}
if (ret && --count == 0) {
printf("timeout flushing dbuf_out, cnt 0x%x flags 0x%x\n",
b->rl, c->flags);
DEB(printf("chn_flush: timeout flushing dbuf_out, cnt 0x%x flags 0x%x\n",\
b->rl, c->flags));
break;
}
}
@ -644,11 +695,13 @@ chn_reinit(pcm_channel *c)
if ((c->flags & CHN_F_INIT) && CANCHANGE(c)) {
chn_setformat(c, c->format);
chn_setspeed(c, c->speed);
chn_setblocksize(c, c->blocksize);
chn_setblocksize(c, (c->flags & CHN_F_HAS_SIZE) ? c->blocksize : 0);
chn_setvolume(c, (c->volume >> 8) & 0xff, c->volume & 0xff);
c->flags &= ~CHN_F_INIT;
return 1;
}
if (CANCHANGE(c) && !(c->flags & CHN_F_HAS_SIZE) )
chn_setblocksize(c, 0); /* Apply new block size */
return 0;
}
@ -719,10 +772,10 @@ chn_setblocksize(pcm_channel *c, int blksz)
c->flags &= ~CHN_F_HAS_SIZE;
if (blksz >= 2) c->flags |= CHN_F_HAS_SIZE;
if (blksz < 0) blksz = -blksz;
if (blksz < 2) blksz = (c->buffer.sample_size * c->speed) >> 2;
if (blksz < 2) blksz = c->buffer.sample_size * (c->speed >> 2);
RANGE(blksz, 1024, c->buffer.bufsize / 4);
blksz &= ~3;
c->blocksize = c->setblocksize(c->devinfo, blksz);
blksz &= DMA_ALIGN_MASK;
c->blocksize = c->setblocksize(c->devinfo, blksz) & DMA_ALIGN_MASK;
return c->blocksize;
}
c->blocksize = blksz;
@ -739,7 +792,14 @@ chn_trigger(pcm_channel *c, int go)
int
chn_getptr(pcm_channel *c)
{
return c->getptr(c->devinfo);
int hwptr;
int a = (1 << c->align) - 1;
hwptr=c->getptr(c->devinfo);
/* don't allow unaligned values in the hwa ptr */
hwptr &= ~a ; /* Apply channel align mask */
hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */
return hwptr;
}
pcmchan_caps *

View File

@ -58,7 +58,7 @@ struct _snd_mixer {
*/
struct _snd_dbuf {
char *buf;
u_int8_t *buf;
int bufsize;
volatile int rp, fp; /* pointers to the ready and free area */
volatile int dl; /* transfer size */
@ -71,6 +71,7 @@ struct _snd_dbuf {
u_long prev_total; /* copy of the above when GETxPTR called */
int first_poll;
bus_dmamap_t dmamap;
int underflow;
};
typedef int (pcmfeed_init_t)(pcm_feeder *feeder);

View File

@ -51,8 +51,9 @@ allocchn(snddev_info *d, int direction)
static int
getchns(snddev_info *d, int chan, pcm_channel **rdch, pcm_channel **wrch)
{
if ((d->flags & SD_F_PRIO_SET) == SD_F_PRIO_SET)
panic("read and write both prioritised");
KASSERT((d->flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \
("getchns: read and write both prioritised"));
if (d->flags & SD_F_SIMPLEX) {
*rdch = (d->flags & SD_F_PRIO_RD)? d->arec[chan] : &d->fakechan;
*wrch = (d->flags & SD_F_PRIO_WR)? d->aplay[chan] : &d->fakechan;
@ -66,8 +67,8 @@ getchns(snddev_info *d, int chan, pcm_channel **rdch, pcm_channel **wrch)
static void
setchns(snddev_info *d, int chan)
{
if ((d->flags & SD_F_PRIO_SET) == SD_F_PRIO_SET)
panic("read and write both prioritised");
KASSERT((d->flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \
("getchns: read and write both prioritised"));
d->flags |= SD_F_DIR_SET;
if (d->flags & SD_F_EVILSB16) {
if ((d->flags & SD_F_PRIO_RD) && (d->aplay[chan])) {
@ -159,7 +160,10 @@ dsp_close(snddev_info *d, int chan, int devtype)
chn_abort(rdch);
rdch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED);
}
if (wrch) wrch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED);
if (wrch) {
chn_flush(wrch);
wrch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED);
}
d->aplay[chan] = NULL;
d->arec[chan] = NULL;
return 0;
@ -173,8 +177,8 @@ dsp_read(snddev_info *d, int chan, struct uio *buf, int flag)
if (!(d->flags & SD_F_PRIO_SET)) d->flags |= SD_F_PRIO_RD;
if (!(d->flags & SD_F_DIR_SET)) setchns(d, chan);
getchns(d, chan, &rdch, &wrch);
if (!rdch || !(rdch->flags & CHN_F_BUSY))
panic("dsp_read: non%s channel", rdch? "busy" : "existant");
KASSERT(wrch, ("dsp_read: nonexistant channel"));
KASSERT(wrch->flags & CHN_F_BUSY, ("dsp_read: nonbusy channel"));
if (rdch->flags & CHN_F_MAPPED) return EINVAL;
if (!(rdch->flags & CHN_F_RUNNING)) {
rdch->flags |= CHN_F_RUNNING;
@ -191,8 +195,8 @@ dsp_write(snddev_info *d, int chan, struct uio *buf, int flag)
if (!(d->flags & SD_F_PRIO_SET)) d->flags |= SD_F_PRIO_WR;
if (!(d->flags & SD_F_DIR_SET)) setchns(d, chan);
getchns(d, chan, &rdch, &wrch);
if (!wrch || !(wrch->flags & CHN_F_BUSY))
panic("dsp_write: non%s channel", wrch? "busy" : "existant");
KASSERT(wrch, ("dsp_write: nonexistant channel"));
KASSERT(wrch->flags & CHN_F_BUSY, ("dsp_write: nonbusy channel"));
if (wrch->flags & CHN_F_MAPPED) return EINVAL;
if (!(wrch->flags & CHN_F_RUNNING)) {
wrch->flags |= CHN_F_RUNNING;
@ -529,7 +533,7 @@ dsp_ioctl(snddev_info *d, int chan, u_long cmd, caddr_t arg)
case SOUND_PCM_READ_FILTER:
/* dunno what these do, don't sound important */
default:
DEB(printf("default ioctl snd%d fn 0x%08x fail\n", unit, cmd));
DEB(printf("default ioctl chan%d fn 0x%08lx fail\n", chan, cmd));
ret = EINVAL;
break;
}

View File

@ -108,24 +108,23 @@ static unsigned char u8_to_ulaw[] = {
static int
feed_root(pcm_feeder *feeder, pcm_channel *ch, u_int8_t *buffer, u_int32_t count, struct uio *stream)
{
int ret, tmp = 0, c = 0;
if (!count) panic("feed_root: count == 0");
int ret, c = 0, s;
KASSERT(count, ("feed_root: count == 0"));
count &= ~((1 << ch->align) - 1);
if (!count) panic("feed_root: aligned count == 0");
KASSERT(count, ("feed_root: aligned count == 0"));
s = spltty();
if (ch->smegcnt > 0) {
c = min(ch->smegcnt, count);
bcopy(ch->smegbuf, buffer, c);
ch->smegcnt -= c;
}
while ((stream->uio_resid > 0) && (c < count)) {
tmp = stream->uio_resid;
ret = uiomove(buffer + c, count - c, stream);
if (ret) panic("feed_root: uiomove failed");
tmp -= stream->uio_resid;
c += tmp;
count = min(count, stream->uio_resid);
if (count) {
ret = uiomove(buffer, count, stream);
KASSERT(ret == 0, ("feed_root: uiomove failed"));
}
if (!c) panic("feed_root: uiomove didn't");
return c;
splx(s);
return c + count;
}
pcm_feeder feeder_root = { "root", 0, NULL, NULL, feed_root };

View File

@ -352,7 +352,7 @@ sndpoll(dev_t i_dev, int events, struct proc *p)
int dev, chan;
snddev_info *d = get_snddev_info(i_dev, NULL, &dev, &chan);
DEB(printf("sndpoll dev 0x%04x events 0x%08x\n", i_dev, events));
DEB(printf("sndpoll d 0x%p dev 0x%04x events 0x%08x\n", d, dev, events));
if (d == NULL) return ENXIO;

View File

@ -98,6 +98,7 @@ struct isa_device { int dummy; };
#define SD_F_SIMPLEX 0x00000001
#define SD_F_EVILSB16 0x00000002
#define SD_F_EVILERSB16X 0x00000004
#define SD_F_PRIO_RD 0x10000000
#define SD_F_PRIO_WR 0x20000000
#define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR)