📄 dmabuf.c
字号:
the other case, with a preferred constant transfer size equal torec_blocksize, and fallback to smaller sizes if no space is available. * *//* * dsp_rd_dmadone moves bytes in the input buffer from DMA region to * READY region. We assume it is called at spltty() and with dl>0 */static voiddsp_rd_dmadone(snddev_info *d){ snd_dbuf *b = & (d->dbuf_in) ; dsp_rd_dmaupdate(b); wakeup(b) ; /* wakeup possibly sleeping processes */ if (b->sel.si_pid && ( !(d->flags & SND_F_HAS_SIZE) || b->rl >= d->rec_blocksize ) ) selwakeup( & b->sel );}/* * The following function tracks the status of a (read) dma transfer, * and moves the boundary between the READY and the DMA regions. * It works under the following assumptions: * - the DMA engine is running; * - the function is called with interrupts blocked. */voiddsp_rd_dmaupdate(snd_dbuf *b){ int delta, tmp ; tmp = b->bufsize - isa_dmastatus1(b->chan) ; tmp &= DMA_ALIGN_MASK; /* align... */ delta = tmp - b->fp; if (delta < 0) /* wrapped */ delta += b->bufsize ; b->fp = tmp; b->fl -= delta ; b->rl += delta ; b->total += delta ;}/* * read interrupt routine. Must be called with interrupts blocked. */voiddsp_rdintr(snddev_info *d){ snd_dbuf *b = & (d->dbuf_in) ; if (b->dl) { /* dma was active */ b->int_count++; dsp_rd_dmadone(d); } 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)); /* * Restart if have enough free space to absorb overruns; */ if ( b->fl > 0x200 && (d->flags & (SND_F_ABORTING|SND_F_CLOSING)) == 0 && ( FULL_DUPLEX(d) || (d->flags & SND_F_WRITING) == 0 ) ) { int l = min(b->fl - 0x100, d->rec_blocksize); l &= DMA_ALIGN_MASK ; /* realign sizes */ DEB(printf("rdintr: dl %d -> %d\n", b->dl, l);) if (l != b->dl) { /* for any reason, size has changed. Stop and restart */ if (b->dl > 0 ) d->callback(d, SND_CB_RD | SND_CB_STOP ); b->dl = l ; d->callback(d, SND_CB_RD | SND_CB_START ); } } else { 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 ( ! FULL_DUPLEX(d) && (d->flags & SND_F_WRITING) ) dsp_wrintr(d) ; DEB(printf("cannot start rd-dma rl %d fl %d\n", b->rl, b->fl)); }}/* * body of user-read routine * * Start DMA if not active; wait for READY not empty. * Transfer data from READY region using uiomove(), advance boundary * between FREE and READY. Repeat until transfer is complete. * * To avoid excessive latency in freeing up space for the DMA * engine, transfers are done in blocks of increasing size, so that * the latency is proportional to the size of the smallest block, but * we have a low overhead and are able to feed the dma engine with * large blocks. * * When we enter this routine, we assume that d->flags |= SND_F_READING * was done before. * * NOTE: in the current version, read will not return more than * blocksize bytes at once (unless more are already available), to * avoid that requests using very large buffers block for too long. */intdsp_read_body(snddev_info *d, struct uio *buf){ int limit, l, n, bsz, ret = 0 ; long s; snd_dbuf *b = & (d->dbuf_in) ; /* * "limit" serves to return after at most one blocksize of data * (unless more are already available). Otherwise, things like * cat /dev/audio would use a 64K buffer and would start returning * data after a _very_ long time... * Note -- some applications depend on reads not returning short * blocks. But I believe these apps are broken, since interrupted * system calls might return short reads anyways, and the * application should better check that. */ if (buf->uio_resid > d->rec_blocksize) limit = buf->uio_resid - d->rec_blocksize; else limit = 0; 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)); l = min (n, bsz); s = spltty(); /* no interrupts here ! */ dsp_rd_dmaupdate(b); l = min( l, b->rl ); /* no more than avail. data */ if (l == 0) { int timeout; /* * 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; } 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); if (ret == EINTR || ret == ERESTART) break ; continue; } splx(s); /* * 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 (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 -= b->bufsize ; splx(s) ; bsz = min(b->bufsize, bsz*2); } s = spltty(); /* no interrupts here ... */ d->flags &= ~SND_F_READING ; if (d->flags & SND_F_ABORTING) { d->flags &= ~SND_F_ABORTING; /* XXX */ splx(s); dsp_rdabort(d, 1 /* restart */); /* XXX return EINTR ? */ } splx(s) ; return ret ;}/* * 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 that a 16-bit dma channel * is used (hence the buffer must be properly aligned). */voidalloc_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 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). */voidreset_dbuf(snd_dbuf *b, int chan){ 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);}/* * snd_sync waits until the space in the given channel goes above * a threshold. chan = 1 : play, 2: capture. The threshold is * checked against fl or rl respectively. * Assume that the condition can become true, do not check here... */intsnd_sync(snddev_info *d, int chan, int threshold){ u_long s; int ret; snd_dbuf *b; b = (chan == 1) ? &(d->dbuf_out ) : &(d->dbuf_in ) ; for (;;) { s=spltty(); if ( chan==1 ) dsp_wr_dmaupdate(b); else dsp_rd_dmaupdate(b); if ( (chan == 1 && b->fl <= threshold) || (chan == 2 && b->rl <= threshold) ) { ret = tsleep((caddr_t)b, PRIBIO|PCATCH, "sndsyn", 1); splx(s); if (ret == ERESTART || ret == EINTR) { printf("tsleep returns %d\n", ret); return -1 ; } } else break; } splx(s); return 0 ;}/* * 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. */intdsp_wrabort(snddev_info *d, int restart){ long s; int missing = 0; snd_dbuf *b = & (d->dbuf_out) ; s = spltty(); if ( b->dl ) { b->dl = 0 ; d->flags &= ~ SND_F_WRITING ; if (d->callback) d->callback(d, SND_CB_WR | SND_CB_ABORT); isa_dmastop(b->chan) ; dsp_wr_dmadone(d); DEB(printf("dsp_wrabort: stopped, %d bytes left\n", b->rl)); } 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;}intdsp_rdabort(snddev_info *d, int restart){ long s; int missing = 0; snd_dbuf *b = & (d->dbuf_in) ; s = spltty(); if ( b->dl ) { b->dl = 0 ; d->flags &= ~ SND_F_READING ; if (d->callback) d->callback(d, SND_CB_RD | SND_CB_ABORT); isa_dmastop(b->chan) ; dsp_rd_dmadone(d); } 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;}/* * this routine tries to flush the dma transfer. It is called * on a close. The caller must set SND_F_CLOSING, and insure that * interrupts are enabled. We immediately abort any read DMA * operation, and then wait for the play buffer to drain. */intsnd_flush(snddev_info *d){ int ret, count=10; u_long s; snd_dbuf *b = &(d->dbuf_out) ; 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 %d cnt 0x%x flags 0x%08lx\n", b->chan, b->rl, d->flags); break; } } s = spltty(); /* should not be necessary... */ d->flags &= ~SND_F_CLOSING ; dsp_wrabort(d, 0 /* no restart */); splx(s); return 0 ;}/* * end of new code for dma buffer handling */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -