diff options
author | Cameron Grant <cg@FreeBSD.org> | 1999-12-05 19:09:13 +0000 |
---|---|---|
committer | Cameron Grant <cg@FreeBSD.org> | 1999-12-05 19:09:13 +0000 |
commit | 0e25481f93e5a763af382768f99088b1de537405 (patch) | |
tree | e5aecbdb22731782c9d2d597873542399fba3378 /sys | |
parent | e306923fc0563d4ceeba371e9f78de378ba5b83f (diff) | |
download | src-0e25481f93e5a763af382768f99088b1de537405.tar.gz src-0e25481f93e5a763af382768f99088b1de537405.zip |
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
Notes
Notes:
svn path=/head/; revision=54155
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 194 | ||||
-rw-r--r-- | sys/dev/sound/pcm/datatypes.h | 3 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 24 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder.c | 21 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.c | 2 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.h | 1 |
6 files changed, 155 insertions, 90 deletions
diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index 146770e588c6..4fdf0f7dafec 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -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,27 +154,53 @@ 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 * diff --git a/sys/dev/sound/pcm/datatypes.h b/sys/dev/sound/pcm/datatypes.h index a4af384a1c01..a163d123222b 100644 --- a/sys/dev/sound/pcm/datatypes.h +++ b/sys/dev/sound/pcm/datatypes.h @@ -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); diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index c7d4850776f1..395515534b1b 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -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; } diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c index a545c1fe91f7..cc45a5c9734e 100644 --- a/sys/dev/sound/pcm/feeder.c +++ b/sys/dev/sound/pcm/feeder.c @@ -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 }; diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index 4d8b64e9bc09..894f56aec112 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -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; diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 40a5c139b41f..2b0adcd74489 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -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) |