Index: head/sys/dev/sound/pcm/channel.c =================================================================== --- head/sys/dev/sound/pcm/channel.c (revision 60960) +++ head/sys/dev/sound/pcm/channel.c (revision 60961) @@ -1,1222 +1,1240 @@ /* * Copyright (c) 1999 Cameron Grant * Portions Copyright by Luigi Rizzo - 1997-99 * All rights reserved. * * 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. * * $FreeBSD$ */ #include #define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ #define DMA_ALIGN_THRESHOLD 4 #define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) #define ISA_DMA(b) (((b)->chan >= 0 && (b)->chan != 4 && (b)->chan < 8)) #define CANCHANGE(c) (!(c)->buffer.dl) /* #define DEB(x) x */ static void buf_clear(snd_dbuf *b, u_int32_t fmt, int length); static void chn_dmaupdate(pcm_channel *c); static void chn_wrintr(pcm_channel *c); static void chn_rdintr(pcm_channel *c); +static u_int32_t chn_start(pcm_channel *c); /* * SOUND OUTPUT We use a circular buffer to store samples directed to the DAC. The buffer is split into two variable-size regions, each identified by an offset in the buffer (rp,fp) and a length (rl,fl): 0 rp,rl fp,fl bufsize |__________>____________>________| FREE d READY w FREE READY: data written from the process and ready to be sent to the DAC; FREE: free part of the buffer. Both regions can wrap around the end of the buffer. At initialization, READY is empty, FREE takes all the available space, and dma is idle. dl contains the length of the current DMA transfer, dl=0 means that the dma is idle. The two boundaries (rp,fp) in the buffers are advanced by DMA [d] and write() [w] operations. The first portion of the READY region is used for DMA transfers. The transfer is started at rp and with chunks of length dl. During DMA operations, dsp_wr_dmaupdate() updates rp, rl and fl tracking the ISA DMA engine as the transfer makes progress. When a new block is written, fp advances and rl,fl are updated accordingly. 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. 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 dsp_wr_dmaupdate() which changes rp, rl and fl. The system 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: min(5ms, 40 bytes) ... 1/2 buffer size. When there aren't enough data (write) or space (read), a transfer is started with a reduced size. 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. * */ /* XXX this is broken: in the event a bounce buffer is used, data never * gets copied in or out of the real buffer. fix requires mods to isa_dma.c * and possibly fixes to other autodma mode clients */ static void chn_isadmabounce(pcm_channel *c) { if (ISA_DMA(&c->buffer)) { /* tell isa_dma to bounce data in/out */ } else KASSERT(1, ("chn_isadmabounce called on invalid channel")); } static int chn_polltrigger(pcm_channel *c) { snd_dbuf *bs = &c->buffer2nd; - unsigned lim = (c->flags & CHN_F_HAS_SIZE)? c->blocksize2nd : 0; + unsigned lim = (c->flags & CHN_F_HAS_SIZE)? bs->blksz : 0; int trig = 0; if (c->flags & CHN_F_MAPPED) - trig = ((bs->int_count > bs->prev_int_count) || bs->first_poll); + trig = ((bs->int_count > bs->prev_int_count) || bs->prev_int_count == 0); else trig = (((c->direction == PCMDIR_PLAY)? bs->fl : bs->rl) > lim); return trig; } static int chn_pollreset(pcm_channel *c) { snd_dbuf *bs = &c->buffer2nd; - if (c->flags & CHN_F_MAPPED) bs->prev_int_count = bs->int_count; - bs->first_poll = 0; + if (c->flags & CHN_F_MAPPED) + bs->prev_int_count = bs->int_count; return 1; } /* * chn_dmadone() updates pointers and wakes up any process waiting * on a select(). Must be called at spltty(). */ static void chn_dmadone(pcm_channel *c) { snd_dbuf *b = &c->buffer; if (c->direction == PCMDIR_PLAY) chn_checkunderflow(c); else chn_dmaupdate(c); - if (ISA_DMA(b)) chn_isadmabounce(c); /* sync bounce buffer */ + if (ISA_DMA(b)) + chn_isadmabounce(c); /* sync bounce buffer */ b->int_count++; } /* * chn_dmawakeup() wakes up any process sleeping. Separated from * chn_dmadone() so that wakeup occurs only when feed from a * secondary buffer to a DMA buffer takes place. Must be called * at spltty(). */ static void chn_dmawakeup(pcm_channel *c) { snd_dbuf *b = &c->buffer; wakeup(b); } /* * chn_dmaupdate() tracks the status of a dma transfer, * updating pointers. It must be called at spltty(). * * NOTE: when we are using auto dma in the device, rl might become * negative. */ DEB (static int chn_updatecount=0); static void chn_dmaupdate(pcm_channel *c) { snd_dbuf *b = &c->buffer; 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; if (b->rl < 0) { DEB(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; if (b->fl < 0) { DEB(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, so that new data can go into the buffer. It must be * called at spltty(). */ void chn_checkunderflow(pcm_channel *c) { snd_dbuf *b = &c->buffer; if (b->underflow) { DEB(printf("Clear underflow condition\n")); /* * The DMA keeps running even after underflow occurs. * Hence the value returned by chn_getptr() here soon * gets a lag when we get back to chn_write(). Although * there are no easy and precise methods to figure out * the lag, a quarter of b->bufsize would be a fair * choice, provided that a DMA interrupt generates upon * each transfer of a half b->bufsize. */ b->rp = chn_getptr(c); b->fp = (b->rp + b->bufsize / 4) % b->bufsize; b->rl = b->bufsize / 4; b->fl = b->bufsize - b->rl; b->underflow = 0; } else { chn_dmaupdate(c); } } /* * Feeds new data to the write dma buffer. Can be called in the bottom half. * Hence must be called at spltty. */ int chn_wrfeed(pcm_channel *c) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; int a, l, lacc; /* ensure we always have a whole number of samples */ a = (1 << c->align) - 1; lacc = 0; if (c->flags & CHN_F_MAPPED) { - bs->rl = min(c->blocksize, b->fl); + bs->rl = min(b->blksz, b->fl); bs->fl = 0; a = 0; } /* printf("b: [rl: %d, rp %d, fl %d, fp %d]; bs: [rl: %d, rp %d, fl %d, fp %d]\n", b->rl, b->rp, b->fl, b->fp, bs->rl, bs->rp, bs->fl, bs->fp); */ /* Don't allow write unaligned data */ while (bs->rl > a && b->fl > a) { /* ensure we always have a whole number of samples */ l = min(min(bs->rl, bs->bufsize - bs->rp), min(b->fl, b->bufsize - b->fp)) & ~a; if (l == 0) return lacc; /* Move the samples, update the markers and pointers. */ bcopy(bs->buf + bs->rp, b->buf + b->fp, l); bs->fl += l; bs->rl -= l; bs->rp = (bs->rp + l) % bs->bufsize; b->rl += l; b->fl -= l; b->fp = (b->fp + l) % b->bufsize; /* Clear the new space in the secondary buffer. */ - buf_clear(bs, c->hwfmt, l); + buf_clear(bs, bs->fmt, l); /* Accumulate the total bytes of the moved samples. */ lacc += l; /* A feed to the DMA buffer is equivalent to an interrupt. */ bs->total += l; if (c->flags & CHN_F_MAPPED) { - if (bs->total - bs->prev_total >= c->blocksize2nd) { + if (bs->total - bs->prev_total >= bs->blksz) { bs->prev_total = bs->total; bs->int_count++; c->blocks++; } } else bs->int_count++; - if (bs->sel.si_pid && chn_polltrigger(c)) selwakeup(&bs->sel); + if (bs->sel.si_pid && chn_polltrigger(c)) + selwakeup(&bs->sel); } return lacc; } /* Feeds new data to the secondary write buffer. */ static int chn_wrfeed2nd(pcm_channel *c, struct uio *buf) { snd_dbuf *bs = &c->buffer2nd; int l, w, wacc; /* The DMA buffer may have some space. */ while (chn_wrfeed(c) > 0); /* ensure we always have a whole number of samples */ wacc = 0; while (buf->uio_resid > 0 && bs->fl > 0) { /* * The size of the data to move here does not have to be * aligned. We take care of it upon moving the data to a * DMA buffer. */ l = min(bs->fl, bs->bufsize - bs->fp); /* Move the samples, update the markers and pointers. */ w = c->feeder->feed(c->feeder, c, bs->buf + bs->fp, l, buf); - if (w == 0) panic("no feed"); + if (w == 0) + panic("no feed"); bs->rl += w; bs->fl -= w; bs->fp = (bs->fp + w) % bs->bufsize; /* Accumulate the total bytes of the moved samples. */ - bs->total += w; wacc += w; /* If any pcm data gets moved, push it to the DMA buffer. */ if (w > 0) while (chn_wrfeed(c) > 0); } return wacc; } /* * Write interrupt routine. Can be called from other places (e.g. * to start a paused transfer), but with interrupts disabled. */ static void chn_wrintr(pcm_channel *c) { snd_dbuf *b = &c->buffer; - int start, l; if (b->underflow && !(c->flags & CHN_F_MAPPED)) { /* printf("underflow return\n"); */ return; /* nothing new happened */ } - if (b->dl) chn_dmadone(c); + if (b->dl) + chn_dmadone(c); /* * start another dma operation only if have ready data in the buffer, * there is no pending abort, have a full-duplex device, or have a * half duplex device and there is no pending op on the other side. * * Force transfers to be aligned to a boundary of 4, which is * needed when doing stereo and 16-bit. */ - /* - * Prepare new space of at least c->blocksize in the DMA - * buffer for mmap. - */ - /* - if (c->flags & CHN_F_MAPPED && b->fl < c->blocksize) { - dl = c->blocksize - b->fl; - b->fl += dl; - b->rl -= dl; - b->rp = (b->rp + dl) % b->bufsize; - buf_clear(b, c->hwfmt, dl); - } - */ /* Check underflow and update the pointers. */ chn_checkunderflow(c); /* * Fill up the DMA buffer, followed by waking up the top half. * If some of the pcm data in uio are still left, the top half * goes to sleep by itself. */ if (c->flags & CHN_F_MAPPED) chn_wrfeed(c); else { while (chn_wrfeed(c) > 0); - buf_clear(b, c->hwfmt, b->fl); + buf_clear(b, b->fmt, b->fl); } chn_dmawakeup(c); - if (c->flags & CHN_F_MAPPED) - start = c->flags & CHN_F_TRIGGERED; - else { -/* printf("%d >= %d && !(%x & %x)\n", b->rl, DMA_ALIGN_THRESHOLD, c->flags, CHN_F_ABORTING | CHN_F_CLOSING); -*/ start = (b->rl >= DMA_ALIGN_THRESHOLD && !(c->flags & CHN_F_ABORTING)); - } - if (start & !(c->flags & CHN_F_NOTRIGGER)) { + if (c->flags & CHN_F_TRIGGERED) { chn_dmaupdate(c); - 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 from zero */ if (b->dl == 0) { /* Start DMA operation */ - b->dl = c->blocksize; /* record new transfer size */ + b->dl = b->blksz; /* record new transfer size */ chn_trigger(c, PCMTRIG_START); } /* * Emulate writing by DMA, i.e. transfer the pcm data from * the emulated-DMA buffer to the device itself. */ chn_trigger(c, PCMTRIG_EMLDMAWR); - if (b->dl != l) { - DEB(printf("near underflow %d, %d, %d\n", l, b->dl, b->fl)); + if (b->rl < b->dl) { + DEB(printf("near underflow (%d < %d), %d\n", b->rl, b->dl, b->fl)); /* * we are near to underflow condition, so to prevent * audio 'clicks' clear next b->fl bytes */ - buf_clear(b, c->hwfmt, b->fl); + buf_clear(b, b->fmt, b->fl); + if (b->rl < DMA_ALIGN_THRESHOLD) + b->underflow = 1; } } else { /* cannot start a new dma transfer */ DEB(printf("underflow, flags 0x%08x rp %d rl %d\n", c->flags, b->rp, b->rl)); if (b->dl) { /* DMA was active */ b->underflow = 1; /* set underflow flag */ - buf_clear(b, c->hwfmt, b->bufsize); + buf_clear(b, b->fmt, b->bufsize); } } } /* * user write routine * * advance the boundary between READY and FREE, fill the space with * uiomove(), and possibly start DMA. Do the above until the transfer * is complete. * * To minimize latency in case a pending DMA transfer is about to end, * we do the transfer in pieces of increasing sizes, extending the * READY area at every checkpoint. In the (necessary) assumption that * memory bandwidth is larger than the rate at which the dma consumes * 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. */ int chn_write(pcm_channel *c, struct uio *buf) { int ret = 0, timeout, res, newsize; long s; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; if (c->flags & CHN_F_WRITING) { /* This shouldn't happen and is actually silly * - will never wake up, just timeout; why not sleep on b? */ tsleep(&s, PZERO, "pcmwrW", hz); return EBUSY; } c->flags |= CHN_F_WRITING; c->flags &= ~CHN_F_ABORTING; s = spltty(); /* * XXX Certain applications attempt to write larger size * of pcm data than c->blocksize2nd without blocking, * resulting partial write. Expand the block size so that * the write operation avoids blocking. */ - if ((c->flags & CHN_F_NBIO) && buf->uio_resid > c->blocksize2nd) { + if ((c->flags & CHN_F_NBIO) && buf->uio_resid > bs->blksz) { DEB(printf("pcm warning: broken app, nbio and tried to write %d bytes with fragsz %d\n", - buf->uio_resid, c->blocksize2nd)); + buf->uio_resid, bs->blksz)); newsize = 16; while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2)) newsize <<= 1; - chn_setblocksize(c, c->fragments, newsize); - DEB(printf("pcm warning: frags reset to %d x %d\n", c->fragments, c->blocksize2nd)); + chn_setblocksize(c, bs->blkcnt, newsize); + DEB(printf("pcm warning: frags reset to %d x %d\n", bs->blkcnt, bs->blksz)); } /* Store the initial size in the uio. */ res = buf->uio_resid; /* * Fill up the secondary and DMA buffer. * chn_wrfeed*() takes care of the alignment. */ /* Check for underflow before writing into the buffers. */ chn_checkunderflow(c); while (chn_wrfeed2nd(c, buf) > 0); - if (c->flags & CHN_F_NBIO && buf->uio_resid > 0) + if ((c->flags & CHN_F_NBIO) && (buf->uio_resid > 0)) ret = EAGAIN; /* Start playing if not yet. */ - if ((bs->rl || b->rl) && !b->dl) - chn_intr(c); + if (!b->dl) + chn_start(c); if (ret == 0) { /* Wait until all samples are played in blocking mode. */ while (buf->uio_resid > 0) { /* Check for underflow before writing into the buffers. */ chn_checkunderflow(c); /* Fill up the buffers with new pcm data. */ while (chn_wrfeed2nd(c, buf) > 0); - /* Start playing if necessary. */ - if ((bs->rl || b->rl) && !b->dl) - chn_intr(c); - /* Have we finished to feed the secondary buffer? */ if (buf->uio_resid == 0) break; /* Wait for new free space to write new pcm samples. */ - splx(s); + /* splx(s); */ timeout = 1; /*(buf->uio_resid >= b->dl)? hz / 20 : 1; */ ret = tsleep(b, PRIBIO | PCATCH, "pcmwr", timeout); - s = spltty(); + /* s = spltty(); */ /* if (ret == EINTR) chn_abort(c); */ if (ret == EINTR || ret == ERESTART) break; } } else ret = 0; c->flags &= ~CHN_F_WRITING; splx(s); return ret; } /* * SOUND INPUT * 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. 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 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, 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, with a preferred constant transfer size equal to rec_blocksize, and fallback to smaller sizes if no space is available. */ +static int +chn_rddump(pcm_channel *c, int cnt) +{ + snd_dbuf *b = &c->buffer; + + b->rl -= cnt; + b->fl += cnt; + b->rp = (b->rp + cnt) % b->bufsize; + return cnt; +} + /* * Feed new data from the read buffer. Can be called in the bottom half. * Hence must be called at spltty. */ int chn_rdfeed(pcm_channel *c) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; int l, lacc; /* ensure we always have a whole number of samples */ lacc = 0; while (bs->fl >= DMA_ALIGN_THRESHOLD && b->rl >= DMA_ALIGN_THRESHOLD) { l = min(min(bs->fl, bs->bufsize - bs->fp), min(b->rl, b->bufsize - b->rp)) & DMA_ALIGN_MASK; /* Move the samples, update the markers and pointers. */ bcopy(b->buf + b->rp, bs->buf + bs->fp, l); bs->fl -= l; bs->rl += l; bs->fp = (bs->fp + l) % bs->bufsize; b->rl -= l; b->fl += l; b->rp = (b->rp + l) % b->bufsize; - /* Clear the new space in the DMA buffer. */ - buf_clear(b, c->hwfmt, l); /* Accumulate the total bytes of the moved samples. */ lacc += l; /* A feed from the DMA buffer is equivalent to an interrupt. */ bs->int_count++; - if (bs->sel.si_pid && chn_polltrigger(c)) selwakeup(&bs->sel); + if (bs->sel.si_pid && chn_polltrigger(c)) + selwakeup(&bs->sel); } return lacc; } /* Feeds new data from the secondary read buffer. */ static int chn_rdfeed2nd(pcm_channel *c, struct uio *buf) { snd_dbuf *bs = &c->buffer2nd; int l, w, wacc; /* The DMA buffer may have pcm data. */ while(chn_rdfeed(c) > 0); /* ensure we always have a whole number of samples */ wacc = 0; while (buf->uio_resid > 0 && bs->rl > 0) { /* * The size of the data to move here does not have to be * aligned. We take care of it upon moving the data to a * DMA buffer. */ l = min(bs->rl, bs->bufsize - bs->rp); /* Move the samples, update the markers and pointers. */ w = c->feeder->feed(c->feeder, c, bs->buf + bs->rp, l, buf); if (w == 0) panic("no feed"); bs->fl += w; bs->rl -= w; bs->rp = (bs->rp + w) % bs->bufsize; /* Clear the new space in the secondary buffer. */ - buf_clear(bs, c->hwfmt, l); + buf_clear(bs, bs->fmt, l); /* Accumulate the total bytes of the moved samples. */ bs->total += w; wacc += w; /* If any pcm data gets moved, suck up the DMA buffer. */ if (w > 0) while (chn_rdfeed(c) > 0); } return wacc; } /* read interrupt routine. Must be called with interrupts blocked. */ static void chn_rdintr(pcm_channel *c) { snd_dbuf *b = &c->buffer; - snd_dbuf *bs = &c->buffer2nd; - int start, dl; if (b->dl) chn_dmadone(c); DEB(printf("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 */ - /* - * Prepare new space of at least c->blocksize in the secondary - * buffer for mmap. - */ - if (c->flags & CHN_F_MAPPED && bs->fl < c->blocksize) { - dl = c->blocksize - bs->fl; - bs->fl += dl; - bs->rl -= dl; - bs->rp = (bs->rp + dl) % bs->bufsize; - buf_clear(bs, c->hwfmt, dl); - } - /* Update the pointers. */ chn_dmaupdate(c); /* * Suck up the DMA buffer, followed by waking up the top half. * If some of the pcm data in the secondary buffer are still left, * the top half goes to sleep by itself. */ while(chn_rdfeed(c) > 0); chn_dmawakeup(c); - if (c->flags & CHN_F_MAPPED) - start = c->flags & CHN_F_TRIGGERED; - else - start = (b->fl > 0x200 && !(c->flags & CHN_F_ABORTING)); - if (start) { - int l = min(b->fl - 0x100, c->blocksize); - if (c->flags & CHN_F_MAPPED) l = c->blocksize; - l &= DMA_ALIGN_MASK ; /* realign sizes */ - DEB(printf("rdintr: dl %d -> %d\n", b->dl, l);) - if (l != b->dl) { - /* size has changed. Stop and restart */ - if (b->dl) { - chn_trigger(c, PCMTRIG_STOP); - chn_dmaupdate(c); - l = min(b->fl - 0x100, c->blocksize); - l &= DMA_ALIGN_MASK ; /* realign sizes */ - } - b->dl = l; + if (b->fl < b->dl) { + DEB(printf("near overflow (%d < %d), %d\n", b->fl, b->dl, b->rl)); + chn_rddump(c, b->blksz - b->fl); + } + + if (c->flags & CHN_F_TRIGGERED) { + /* + * check if we need to reprogram the DMA on the sound card. + * This happens if the size has changed from zero + */ + if (b->dl == 0) { + /* Start DMA operation */ + b->dl = b->blksz; /* record new transfer size */ chn_trigger(c, PCMTRIG_START); } + /* + * Emulate writing by DMA, i.e. transfer the pcm data from + * the emulated-DMA buffer to the device itself. + */ + chn_trigger(c, PCMTRIG_EMLDMARD); } else { if (b->dl) { /* was active */ b->dl = 0; chn_trigger(c, PCMTRIG_STOP); chn_dmaupdate(c); } } } /* * 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. * * 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. */ int chn_read(pcm_channel *c, struct uio *buf) { int ret = 0, timeout, limit, res; long s; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; if (c->flags & CHN_F_READING) { /* This shouldn't happen and is actually silly */ tsleep(&s, PZERO, "pcmrdR", hz); return (EBUSY); } s = spltty(); /* Store the initial size in the uio. */ res = buf->uio_resid; c->flags |= CHN_F_READING; c->flags &= ~CHN_F_ABORTING; - limit = buf->uio_resid - c->blocksize; - if (limit < 0) limit = 0; + limit = buf->uio_resid - b->blksz; + if (limit < 0) + limit = 0; /* Update the pointers and suck up the DMA and secondary buffers. */ chn_dmaupdate(c); while (chn_rdfeed2nd(c, buf) > 0); /* Start capturing if not yet. */ - if ((!bs->rl || !b->rl) && !b->dl) chn_intr(c); + if ((!bs->rl || !b->rl) && !b->dl) + chn_start(c); if (!(c->flags & CHN_F_NBIO)) { /* Wait until all samples are captured. */ while (buf->uio_resid > 0) { /* Suck up the DMA and secondary buffers. */ chn_dmaupdate(c); while (chn_rdfeed2nd(c, buf) > 0); - /* Start capturing if necessary. */ - if ((!bs->rl || !b->rl) && !b->dl) chn_intr(c); - /* Have we finished to feed the uio? */ if (buf->uio_resid == 0) break; /* Wait for new pcm samples. */ - splx(s); + /* splx(s); */ timeout = (buf->uio_resid - limit >= b->dl)? hz / 20 : 1; ret = tsleep(b, PRIBIO | PCATCH, "pcmrd", timeout); - s = spltty(); - if (ret == EINTR) chn_abort(c); - if (ret == EINTR || ret == ERESTART) break; + /* s = spltty(); */ + /* if (ret == EINTR) chn_abort(c); */ + if (ret == EINTR || ret == ERESTART) + break; } } else { /* If no pcm data was read on nonblocking, return EAGAIN. */ if (buf->uio_resid == res) ret = EAGAIN; } c->flags &= ~CHN_F_READING; splx(s); return ret; } void chn_intr(pcm_channel *c) { if (c->flags & CHN_F_INIT) chn_reinit(c); if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); } +u_int32_t +chn_start(pcm_channel *c) +{ + u_int32_t r, s; + snd_dbuf *b = &c->buffer; + + r = 0; + s = spltty(); + if (b->dl == 0 && !(c->flags & (CHN_F_MAPPED | CHN_F_NOTRIGGER))) { + if (c->direction == PCMDIR_PLAY) { + /* Fill up the DMA buffer. */ + while (chn_wrfeed(c) > 0); + if (b->rl >= b->blksz) + r = CHN_F_TRIGGERED; + } else { + /* Suck up the DMA buffer. */ + while (chn_rdfeed(c) > 0); + if (b->fl >= b->blksz) + r = CHN_F_TRIGGERED; + } + c->flags |= r; + chn_intr(c); + } + splx(s); + return r; +} + static void chn_dma_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { snd_dbuf *b = (snd_dbuf *)arg; if (bootverbose) { printf("pcm: setmap %lx, %lx; ", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len); printf("%p -> %lx\n", b->buf, (unsigned long)vtophys(b->buf)); } } /* * Allocate memory for DMA buffer. If the device do not perform DMA transfer, * the drvier can call malloc(9) by its own. */ int chn_allocbuf(snd_dbuf *b, bus_dma_tag_t parent_dmat) { if (bus_dmamem_alloc(parent_dmat, (void **)&b->buf, BUS_DMA_NOWAIT, &b->dmamap)) return -1; if (bus_dmamap_load(parent_dmat, b->dmamap, b->buf, b->bufsize, chn_dma_setmap, b, 0)) return -1; return 0; } static void buf_clear(snd_dbuf *b, u_int32_t fmt, int length) { int i; u_int16_t data, *p; /* rely on length & DMA_ALIGN_MASK == 0 */ length &= DMA_ALIGN_MASK; if (length == 0) return; if (fmt & AFMT_SIGNED) data = 0x00; else data = 0x80; if (fmt & AFMT_16BIT) data <<= 8; else data |= data << 8; if (fmt & AFMT_BIGENDIAN) data = ((data >> 8) & 0x00ff) | ((data << 8) & 0xff00); i = b->fp; p = (u_int16_t *)(b->buf + b->fp); while (length > 0) { *p++ = data; length -= 2; i += 2; if (i >= b->bufsize) { p = (u_int16_t *)b->buf; i = 0; } } } void chn_resetbuf(pcm_channel *c) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; c->blocks = 0; - b->sample_size = 1; - b->sample_size <<= (c->hwfmt & AFMT_STEREO)? 1 : 0; - b->sample_size <<= (c->hwfmt & AFMT_16BIT)? 1 : 0; - b->rp = b->fp = 0; b->dl = b->rl = 0; b->fl = b->bufsize; b->prev_total = b->total = 0; b->prev_int_count = b->int_count = 0; - b->first_poll = 1; b->underflow = 0; if (b->buf && b->bufsize > 0) - buf_clear(b, c->hwfmt, b->bufsize); + buf_clear(b, b->fmt, b->bufsize); bs->rp = bs->fp = 0; bs->dl = bs->rl = 0; bs->fl = bs->bufsize; bs->prev_total = bs->total = 0; - b->prev_int_count = b->int_count = 0; - b->first_poll = 1; - b->underflow = 0; + bs->prev_int_count = bs->int_count = 0; + bs->underflow = 0; if (bs->buf && bs->bufsize > 0) - buf_clear(bs, c->hwfmt, bs->bufsize); + buf_clear(bs, bs->fmt, bs->bufsize); } void buf_isadma(snd_dbuf *b, int go) { if (ISA_DMA(b)) { switch (go) { case PCMTRIG_START: DEB(printf("buf 0x%p ISA DMA started\n", b)); isa_dmastart(b->dir | ISADMA_RAW, b->buf, b->bufsize, b->chan); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: DEB(printf("buf 0x%p ISA DMA stopped\n", b)); isa_dmastop(b->chan); isa_dmadone(b->dir | ISADMA_RAW, b->buf, b->bufsize, b->chan); break; } } else KASSERT(1, ("buf_isadma called on invalid channel")); } int buf_isadmaptr(snd_dbuf *b) { if (ISA_DMA(b)) { int i = b->dl? isa_dmastatus(b->chan) : b->bufsize; - if (i < 0) i = 0; + if (i < 0) + i = 0; return b->bufsize - i; } else KASSERT(1, ("buf_isadmaptr called on invalid channel")); return -1; } /* * chn_sync waits until the space in the given channel goes above * a threshold. The threshold is checked against fl or rl respectively. * Assume that the condition can become true, do not check here... */ int chn_sync(pcm_channel *c, int threshold) { u_long s, rdy; int ret; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; for (;;) { s = spltty(); chn_checkunderflow(c); while (chn_wrfeed(c) > 0); rdy = (c->direction == PCMDIR_PLAY)? bs->fl : bs->rl; if (rdy <= threshold) { ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmsyn", 1); splx(s); if (ret == ERESTART || ret == EINTR) { DEB(printf("chn_sync: tsleep returns %d\n", ret)); return -1; } } else break; } splx(s); return 0; } int chn_poll(pcm_channel *c, int ev, struct proc *p) { snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; u_long s; int ret; s = spltty(); if (!(c->flags & CHN_F_MAPPED)) { if (c->direction == PCMDIR_PLAY) { /* Fill up the DMA buffer. */ chn_checkunderflow(c); while (chn_wrfeed(c) > 0); } else { /* Suck up the DMA buffer. */ chn_dmaupdate(c); while (chn_rdfeed(c) > 0); } if (!b->dl) - chn_intr(c); + chn_start(c); } ret = 0; if (chn_polltrigger(c) && chn_pollreset(c)) ret = ev; else selrecord(p, &bs->sel); splx(s); return ret; } /* * chn_abort is a non-blocking function which aborts a pending * DMA transfer and flushes the buffers. * It returns the number of bytes that have not been transferred. */ int chn_abort(pcm_channel *c) { int missing = 0, cnt = 0; snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; if (!b->dl) return 0; c->flags |= CHN_F_ABORTING; + c->flags &= ~CHN_F_TRIGGERED; while (!b->underflow && (b->dl > 0) && (cnt < 20)) { tsleep((caddr_t)b, PRIBIO, "pcmabr", hz / 20); cnt++; } chn_trigger(c, PCMTRIG_ABORT); b->dl = 0; if (c->direction == PCMDIR_PLAY) chn_checkunderflow(c); else chn_dmaupdate(c); missing = bs->rl; return missing; } /* * this routine tries to flush the dma transfer. It is called * on a close. We immediately abort any read DMA * operation, and then wait for the play buffer to drain. */ int chn_flush(pcm_channel *c) { int ret, count = 50, s; snd_dbuf *b = &c->buffer; DEB(printf("chn_flush c->flags 0x%08x\n", c->flags)); c->flags |= CHN_F_CLOSING; c->flags &= ~CHN_F_TRIGGERED; if (c->direction == PCMDIR_REC) chn_abort(c); else if (b->dl) { while ((b->rl > 0) && !b->underflow && (count-- > 0)) { /* still pending output data. */ ret = tsleep((caddr_t)b, PRIBIO | PCATCH, "pcmflu", hz / 10); s = spltty(); chn_dmaupdate(c); splx(s); DEB(printf("chn_flush: now rl = %d, fl = %d\n", b->rl, b->fl)); if (ret == EINTR || ret == ERESTART) { DEB(printf("chn_flush: tsleep returns %d\n", ret)); return ret; } } } if (count == 0) DEB(printf("chn_flush: timeout flushing dbuf_out, cnt 0x%x flags 0x%x\n", b->rl, c->flags)); c->flags &= ~CHN_F_CLOSING; - if (c->direction == PCMDIR_PLAY && b->dl) chn_abort(c); + if (c->direction == PCMDIR_PLAY && b->dl) + chn_abort(c); return 0; } int chn_reset(pcm_channel *c, u_int32_t fmt) { - int r; + int r = 0; chn_abort(c); c->flags &= CHN_F_RESET; r = chn_setblocksize(c, CHN_2NDBUFBLKNUM, CHN_2NDBUFBLKSIZE); if (r) return r; if (fmt) { - c->format = fmt; - c->speed = DSP_DEFAULT_SPEED; - c->volume = (100 << 8) | 100; + r = chn_setformat(c, fmt); + if (r == 0) + r = chn_setspeed(c, DSP_DEFAULT_SPEED); + if (r == 0) + r = chn_setvolume(c, 100, 100); } chn_resetbuf(c); - c->flags |= CHN_F_INIT; + /* c->flags |= CHN_F_INIT; */ return 0; } int chn_reinit(pcm_channel *c) { if ((c->flags & CHN_F_INIT) && CANCHANGE(c)) { chn_setformat(c, c->format); chn_setspeed(c, c->speed); chn_setvolume(c, (c->volume >> 8) & 0xff, c->volume & 0xff); c->flags &= ~CHN_F_INIT; return 1; } return 0; } int chn_init(pcm_channel *c, void *devinfo, int dir) { snd_dbuf *bs = &c->buffer2nd; /* Initialize the hardware and DMA buffer first. */ c->flags = 0; c->feeder = &feeder_root; c->buffer.chan = -1; c->devinfo = c->init(devinfo, &c->buffer, c, dir); if (c->devinfo == NULL || c->buffer.bufsize == 0) return 1; chn_setdir(c, dir); /* And the secondary buffer. */ bs->buf = NULL; bs->bufsize = 0; return 0; } int chn_setdir(pcm_channel *c, int dir) { int r; c->direction = dir; r = c->setdir(c->devinfo, c->direction); if (!r && ISA_DMA(&c->buffer)) c->buffer.dir = (dir == PCMDIR_PLAY)? ISADMA_WRITE : ISADMA_READ; return r; } int chn_setvolume(pcm_channel *c, int left, int right) { /* could add a feeder for volume changing if channel returns -1 */ if (CANCHANGE(c)) { - return -1; + c->volume = (left << 8) | right; + return 0; } c->volume = (left << 8) | right; c->flags |= CHN_F_INIT; return 0; } int chn_setspeed(pcm_channel *c, int speed) { + if (speed <= 0) + return EINVAL; /* could add a feeder for rate conversion */ if (CANCHANGE(c)) { c->speed = c->setspeed(c->devinfo, speed); - return c->speed; + return 0; } c->speed = speed; c->flags |= CHN_F_INIT; return 0; } int chn_setformat(pcm_channel *c, u_int32_t fmt) { + snd_dbuf *b = &c->buffer; + snd_dbuf *bs = &c->buffer2nd; + + u_int32_t hwfmt; if (CANCHANGE(c)) { - c->hwfmt = c->format = fmt; - c->hwfmt = chn_feedchain(c); + c->format = fmt; + hwfmt = chn_feedchain(c); + if ((c->flags & CHN_F_MAPPED) && c->format != hwfmt) + return EINVAL; + b->fmt = hwfmt; + bs->fmt = hwfmt; chn_resetbuf(c); - c->setformat(c->devinfo, c->hwfmt); - return fmt; + c->setformat(c->devinfo, hwfmt); + return 0; } c->format = fmt; c->flags |= CHN_F_INIT; return 0; } int chn_setblocksize(pcm_channel *c, int blkcnt, int blksz) { + snd_dbuf *b = &c->buffer; snd_dbuf *bs = &c->buffer2nd; - int s, bufsz; + int s, ss, bufsz; - if (c->fragments == blkcnt && c->blocksize2nd == blksz) + if (bs->blkcnt == blkcnt && bs->blksz == blksz) return 0; if (c->flags & CHN_F_MAPPED) { DEB(printf("chn_setblocksize: can't work on mapped channel")); return EINVAL; } c->flags &= ~CHN_F_HAS_SIZE; + + ss = 1; + ss <<= (bs->fmt & AFMT_STEREO)? 1 : 0; + ss <<= (bs->fmt & AFMT_16BIT)? 1 : 0; + if (blksz >= 2) c->flags |= CHN_F_HAS_SIZE; /* let us specify blksz without setting CHN_F_HAS_SIZE */ if (blksz < 0) blksz = -blksz; /* default to blksz = ~0.25s */ if (blksz < 16) - blksz = (c->buffer.sample_size * c->speed) >> 2; + blksz = (ss * c->speed) >> 2; if (blkcnt * blksz > CHN_2NDBUFMAXSIZE) blkcnt = CHN_2NDBUFMAXSIZE / blksz; bufsz = blkcnt * blksz; s = spltty(); if (bs->buf != NULL) free(bs->buf, M_DEVBUF); bs->buf = malloc(bufsz, M_DEVBUF, M_WAITOK); if (bs->buf == NULL) { splx(s); DEB(printf("chn_setblocksize: out of memory.")); return ENOSPC; } bs->bufsize = bufsz; bs->rl = bs->rp = bs->fp = 0; bs->fl = bs->bufsize; - buf_clear(bs, c->hwfmt, bs->bufsize); - c->fragments = blkcnt; - c->blocksize2nd = blksz; - RANGE(blksz, 16, c->buffer.bufsize / 2); - c->blocksize = c->setblocksize(c->devinfo, blksz); + buf_clear(bs, bs->fmt, bs->bufsize); + bs->blkcnt = blkcnt; + bs->blksz = blksz; + RANGE(blksz, 16, b->bufsize / 2); + b->blksz = c->setblocksize(c->devinfo, blksz); splx(s); return 0; } int chn_trigger(pcm_channel *c, int go) { return c->trigger(c->devinfo, go); } int chn_getptr(pcm_channel *c) { int hwptr; int a = (1 << c->align) - 1; snd_dbuf *b = &c->buffer; hwptr = b->dl? c->getptr(c->devinfo) : 0; /* 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 * chn_getcaps(pcm_channel *c) { return c->getcaps(c->devinfo); } Index: head/sys/dev/sound/pcm/datatypes.h =================================================================== --- head/sys/dev/sound/pcm/datatypes.h (revision 60960) +++ head/sys/dev/sound/pcm/datatypes.h (revision 60961) @@ -1,164 +1,161 @@ /* * Copyright (c) 1999 Cameron Grant * All rights reserved. * * 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. * * $FreeBSD$ */ typedef struct _snd_mixer snd_mixer; typedef struct _snd_dbuf snd_dbuf; typedef struct _snddev_info snddev_info; typedef struct _pcmchan_caps pcmchan_caps; typedef struct _pcm_feeder pcm_feeder; typedef struct _pcm_channel pcm_channel; typedef int (mix_set_t)(snd_mixer *m, unsigned dev, unsigned left, unsigned right); typedef int (mix_recsrc_t)(snd_mixer *m, u_int32_t src); typedef int (mix_init_t)(snd_mixer *m); struct _snd_mixer { char name[64]; mix_init_t *init; mix_set_t *set; mix_recsrc_t *setrecsrc; void *devinfo; u_int32_t devs; u_int32_t recdevs; u_int32_t recsrc; u_int16_t level[32]; }; /* * descriptor of a dma buffer. See dmabuf.c for documentation. * (rp,rl) and (fp,fl) identify the READY and FREE regions of the * buffer. dl contains the length used for dma transfer, dl>0 also * means that the channel is busy and there is a DMA transfer in progress. */ struct _snd_dbuf { u_int8_t *buf; int bufsize; - volatile int rp, fp; /* pointers to the ready and free area */ volatile int dl; /* transfer size */ + volatile int rp, fp; /* pointers to the ready and free area */ volatile int rl, fl; /* lenght of ready and free areas. */ volatile u_int32_t int_count, prev_int_count; + volatile u_int32_t total, prev_total; int chan, dir; /* dma channel */ - int sample_size; /* 1, 2, 4 */ - struct selinfo sel; - u_long total; /* total bytes processed */ - u_long prev_total; /* copy of the above when GETxPTR called */ - int first_poll; - bus_dmamap_t dmamap; + int fmt, blksz, blkcnt; int underflow; + bus_dmamap_t dmamap; + struct selinfo sel; }; typedef int (pcmfeed_init_t)(pcm_feeder *feeder); typedef int (pcmfeed_free_t)(pcm_feeder *feeder); typedef int (pcmfeed_feed_t)(pcm_feeder *feeder, pcm_channel *c, u_int8_t *buffer, u_int32_t count, struct uio *stream); struct _pcm_feeder { char name[16]; int align; pcmfeed_init_t *init; pcmfeed_free_t *free; pcmfeed_feed_t *feed; void *data; pcm_feeder *source; }; struct _pcmchan_caps { u_int32_t minspeed, maxspeed; u_int32_t formats, bestfmt; }; typedef void *(pcmchan_init_t)(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); typedef int (pcmchan_setdir_t)(void *data, int dir); typedef int (pcmchan_setformat_t)(void *data, u_int32_t format); typedef int (pcmchan_setspeed_t)(void *data, u_int32_t speed); typedef int (pcmchan_setblocksize_t)(void *data, u_int32_t blocksize); typedef int (pcmchan_trigger_t)(void *data, int go); typedef int (pcmchan_getptr_t)(void *data); typedef pcmchan_caps *(pcmchan_getcaps_t)(void *data); struct _pcm_channel { pcmchan_init_t *init; pcmchan_setdir_t *setdir; pcmchan_setformat_t *setformat; pcmchan_setspeed_t *setspeed; pcmchan_setblocksize_t *setblocksize; pcmchan_trigger_t *trigger; pcmchan_getptr_t *getptr; pcmchan_getcaps_t *getcaps; pcm_feeder *feeder; u_int32_t align; int volume; u_int32_t speed; u_int32_t flags; - u_int32_t format, hwfmt; - u_int32_t blocksize, blocksize2nd; - u_int32_t fragments, blocks; + u_int32_t format; + u_int32_t blocks; int direction; snd_dbuf buffer, buffer2nd; void *devinfo; }; typedef void (pcm_swap_t)(void *data, int dir); #define SND_STATUSLEN 64 /* descriptor of audio device */ struct _snddev_info { pcm_channel *play, *rec, **aplay, **arec, fakechan; int *ref; unsigned playcount, reccount, chancount; snd_mixer mixer; u_long magic; unsigned flags; void *devinfo; pcm_swap_t *swap; char status[SND_STATUSLEN]; }; /* mixer description structure and macros - these should go away, * only sb.[ch] and mss.[ch] use them */ struct mixer_def { u_int regno:7; u_int polarity:1; /* 1 means reversed */ u_int bitoffs:4; u_int nbits:4; }; typedef struct mixer_def mixer_ent; typedef struct mixer_def mixer_tab[32][2]; #define MIX_ENT(name, reg_l, pol_l, pos_l, len_l, reg_r, pol_r, pos_r, len_r) \ {{reg_l, pol_l, pos_l, len_l}, {reg_r, pol_r, pos_r, len_r}} #define PMIX_ENT(name, reg_l, pos_l, len_l, reg_r, pos_r, len_r) \ {{reg_l, 0, pos_l, len_l}, {reg_r, 0, pos_r, len_r}} #define MIX_NONE(name) MIX_ENT(name, 0,0,0,0, 0,0,0,0) Index: head/sys/dev/sound/pcm/dsp.c =================================================================== --- head/sys/dev/sound/pcm/dsp.c (revision 60960) +++ head/sys/dev/sound/pcm/dsp.c (revision 60961) @@ -1,612 +1,633 @@ /* * Copyright (c) 1999 Cameron Grant * All rights reserved. * * 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. * * $FreeBSD$ */ #include #include #include static int getchns(snddev_info *d, int chan, pcm_channel **rdch, pcm_channel **wrch); static pcm_channel * allocchn(snddev_info *d, int direction) { pcm_channel *chns = (direction == PCMDIR_PLAY)? d->play : d->rec; int i, cnt = (direction == PCMDIR_PLAY)? d->playcount : d->reccount; for (i = 0; i < cnt; i++) { if (!(chns[i].flags & CHN_F_BUSY)) { chns[i].flags |= CHN_F_BUSY; return &chns[i]; } } return NULL; } static int getchns(snddev_info *d, int chan, pcm_channel **rdch, pcm_channel **wrch) { KASSERT((d->flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \ ("getchns: read and write both prioritised")); if ((d->flags & SD_F_SIMPLEX) && (d->flags & SD_F_PRIO_SET)) { *rdch = (d->flags & SD_F_PRIO_RD)? d->arec[chan] : &d->fakechan; *wrch = (d->flags & SD_F_PRIO_WR)? d->aplay[chan] : &d->fakechan; d->fakechan.flags |= CHN_F_BUSY; } else { *rdch = d->arec[chan]; *wrch = d->aplay[chan]; } return 0; } static void setchns(snddev_info *d, int chan) { 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->swap) d->swap(d->devinfo, (d->flags & SD_F_PRIO_WR)? PCMDIR_PLAY : PCMDIR_REC); } int dsp_open(snddev_info *d, int chan, int oflags, int devtype) { pcm_channel *rdch, *wrch; u_int32_t fmt; if (chan >= d->chancount) return ENODEV; if ((d->flags & SD_F_SIMPLEX) && (d->ref[chan] > 0)) return EBUSY; rdch = d->arec[chan]; wrch = d->aplay[chan]; if (oflags & FREAD) { if (rdch == NULL) { rdch = allocchn(d, PCMDIR_REC); if (!rdch) return EBUSY; } else return EBUSY; } if (oflags & FWRITE) { if (wrch == NULL) { wrch = allocchn(d, PCMDIR_PLAY); if (!wrch && (oflags & FREAD)) { rdch->flags &= ~CHN_F_BUSY; return EBUSY; } } else return EBUSY; } d->aplay[chan] = wrch; d->arec[chan] = rdch; d->ref[chan]++; switch (devtype) { case SND_DEV_DSP16: fmt = AFMT_S16_LE; break; case SND_DEV_DSP: fmt = AFMT_U8; break; case SND_DEV_AUDIO: fmt = AFMT_MU_LAW; break; case SND_DEV_NORESET: fmt = 0; break; default: return ENXIO; } if (rdch && (oflags & FREAD)) { chn_reset(rdch, fmt); if (oflags & O_NONBLOCK) rdch->flags |= CHN_F_NBIO; } if (wrch && (oflags & FWRITE)) { chn_reset(wrch, fmt); if (oflags & O_NONBLOCK) wrch->flags |= CHN_F_NBIO; } return 0; } int dsp_close(snddev_info *d, int chan, int devtype) { pcm_channel *rdch, *wrch; d->ref[chan]--; if (d->ref[chan]) return 0; d->flags &= ~SD_F_TRANSIENT; rdch = d->arec[chan]; wrch = d->aplay[chan]; if (rdch) { chn_abort(rdch); rdch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED); chn_reset(rdch, 0); } if (wrch) { chn_flush(wrch); wrch->flags &= ~(CHN_F_BUSY | CHN_F_RUNNING | CHN_F_MAPPED); chn_reset(wrch, 0); } d->aplay[chan] = NULL; d->arec[chan] = NULL; return 0; } int dsp_read(snddev_info *d, int chan, struct uio *buf, int flag) { pcm_channel *rdch, *wrch; 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); KASSERT(rdch, ("dsp_read: nonexistant channel")); KASSERT(rdch->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; return chn_read(rdch, buf); } int dsp_write(snddev_info *d, int chan, struct uio *buf, int flag) { pcm_channel *rdch, *wrch; 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); 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; return chn_write(wrch, buf); } int dsp_ioctl(snddev_info *d, int chan, u_long cmd, caddr_t arg) { int ret = 0, *arg_i = (int *)arg; u_long s; pcm_channel *wrch = NULL, *rdch = NULL; rdch = d->arec[chan]; wrch = d->aplay[chan]; /* * all routines are called with int. blocked. Make sure that * ints are re-enabled when calling slow or blocking functions! */ s = spltty(); switch(cmd) { #ifdef OLDPCM_IOCTL /* * we start with the new ioctl interface. */ case AIONWRITE: /* how many bytes can write ? */ if (wrch && wrch->buffer.dl) while (chn_wrfeed(wrch) > 0); *arg_i = wrch? wrch->buffer2nd.fl : 0; break; case AIOSSIZE: /* set the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; if (wrch) chn_setblocksize(wrch, p->play_size); if (rdch) chn_setblocksize(rdch, p->rec_size); } /* FALLTHROUGH */ case AIOGSIZE: /* get the current blocksize */ { struct snd_size *p = (struct snd_size *)arg; - if (wrch) p->play_size = wrch->blocksize2nd; - if (rdch) p->rec_size = rdch->blocksize2nd; + if (wrch) p->play_size = wrch->buffer2nd.blksz; + if (rdch) p->rec_size = rdch->buffer2nd.blksz; } break; case AIOSFMT: { snd_chan_param *p = (snd_chan_param *)arg; if (wrch) { chn_setformat(wrch, p->play_format); chn_setspeed(wrch, p->play_rate); } if (rdch) { chn_setformat(rdch, p->rec_format); chn_setspeed(rdch, p->rec_rate); } } /* FALLTHROUGH */ case AIOGFMT: { snd_chan_param *p = (snd_chan_param *)arg; p->play_rate = wrch? wrch->speed : 0; p->rec_rate = rdch? rdch->speed : 0; p->play_format = wrch? wrch->format : 0; p->rec_format = rdch? rdch->format : 0; } break; case AIOGCAP: /* get capabilities */ { snd_capabilities *p = (snd_capabilities *)arg; pcmchan_caps *pcaps = NULL, *rcaps = NULL; if (rdch) rcaps = chn_getcaps(rdch); if (wrch) pcaps = chn_getcaps(wrch); p->rate_min = max(rcaps? rcaps->minspeed : 0, pcaps? pcaps->minspeed : 0); p->rate_max = min(rcaps? rcaps->maxspeed : 1000000, pcaps? pcaps->maxspeed : 1000000); p->bufsize = min(rdch? rdch->buffer2nd.bufsize : 1000000, wrch? wrch->buffer2nd.bufsize : 1000000); /* XXX bad on sb16 */ p->formats = (rcaps? rcaps->formats : 0xffffffff) & (pcaps? pcaps->formats : 0xffffffff); p->mixers = 1; /* default: one mixer */ p->inputs = d->mixer.devs; p->left = p->right = 100; } break; case AIOSTOP: if (*arg_i == AIOSYNC_PLAY && wrch) *arg_i = chn_abort(wrch); else if (*arg_i == AIOSYNC_CAPTURE && rdch) *arg_i = chn_abort(rdch); else { printf("AIOSTOP: bad channel 0x%x\n", *arg_i); *arg_i = 0; } break; case AIOSYNC: printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n", ((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos); break; #endif /* * here follow the standard ioctls (filio.h etc.) */ case FIONREAD: /* get # bytes to read */ if (rdch && rdch->buffer.dl) while (chn_rdfeed(rdch) > 0); *arg_i = rdch? rdch->buffer2nd.rl : 0; break; case FIOASYNC: /*set/clear async i/o */ DEB( printf("FIOASYNC\n") ; ) break; case SNDCTL_DSP_NONBLOCK: case FIONBIO: /* set/clear non-blocking i/o */ if (rdch) rdch->flags &= ~CHN_F_NBIO; if (wrch) wrch->flags &= ~CHN_F_NBIO; if (*arg_i) { if (rdch) rdch->flags |= CHN_F_NBIO; if (wrch) wrch->flags |= CHN_F_NBIO; } break; /* * Finally, here is the linux-compatible ioctl interface */ #define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int) case THE_REAL_SNDCTL_DSP_GETBLKSIZE: case SNDCTL_DSP_GETBLKSIZE: if (wrch) - *arg_i = wrch->blocksize2nd; + *arg_i = wrch->buffer2nd.blksz; else if (rdch) - *arg_i = rdch->blocksize2nd; + *arg_i = rdch->buffer2nd.blksz; else *arg_i = 0; break ; case SNDCTL_DSP_SETBLKSIZE: RANGE(*arg_i, 16, 65536); if (wrch) chn_setblocksize(wrch, 2, *arg_i); if (rdch) chn_setblocksize(rdch, 2, *arg_i); break; case SNDCTL_DSP_RESET: DEB(printf("dsp reset\n")); splx(s); if (wrch) chn_abort(wrch); if (rdch) chn_abort(rdch); break; case SNDCTL_DSP_SYNC: DEB(printf("dsp sync\n")); splx(s); if (wrch) chn_sync(wrch, wrch->buffer2nd.bufsize - 4); break; case SNDCTL_DSP_SPEED: splx(s); - if (wrch) chn_setspeed(wrch, *arg_i); - if (rdch) chn_setspeed(rdch, *arg_i); + if (wrch) + ret = chn_setspeed(wrch, *arg_i); + if (rdch && ret == 0) + ret = chn_setspeed(rdch, *arg_i); /* fallthru */ case SOUND_PCM_READ_RATE: *arg_i = wrch? wrch->speed : rdch->speed; break; case SNDCTL_DSP_STEREO: splx(s); - if (wrch) chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | + if (wrch) + ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | ((*arg_i)? AFMT_STEREO : 0)); - if (rdch) chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | + if (rdch && ret == 0) + ret = chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | ((*arg_i)? AFMT_STEREO : 0)); *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_STEREO)? 1 : 0; break; case SOUND_PCM_WRITE_CHANNELS: +/* case SNDCTL_DSP_CHANNELS: ( == SOUND_PCM_WRITE_CHANNELS) */ splx(s); - if (wrch) chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | + if (*arg_i == 1 || *arg_i == 2) { + if (wrch) + ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | ((*arg_i == 2)? AFMT_STEREO : 0)); - if (rdch) chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | - ((*arg_i == 2)? AFMT_STEREO : 0)); - /* fallthru */ + if (rdch && ret == 0) + ret = chn_setformat(rdch, (rdch->format & ~AFMT_STEREO) | + ((*arg_i == 2)? AFMT_STEREO : 0)); + *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_STEREO)? 1 : 0; + } else + *arg_i = 0; + break; case SOUND_PCM_READ_CHANNELS: *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_STEREO)? 2 : 1; break; case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ *arg_i = wrch? chn_getcaps(wrch)->formats : chn_getcaps(rdch)->formats; break ; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ splx(s); - if (wrch) chn_setformat(wrch, (*arg_i) | (wrch->format & AFMT_STEREO)); - if (rdch) chn_setformat(rdch, (*arg_i) | (rdch->format & AFMT_STEREO)); + if (wrch) + ret = chn_setformat(wrch, (*arg_i) | (wrch->format & AFMT_STEREO)); + if (rdch && ret == 0) + ret = chn_setformat(rdch, (*arg_i) | (rdch->format & AFMT_STEREO)); *arg_i = (wrch? wrch->format: rdch->format) & ~AFMT_STEREO; break; case SNDCTL_DSP_SUBDIVIDE: /* XXX watch out, this is RW! */ - DEB(printf("SNDCTL_DSP_SUBDIVIDE unimplemented\n");) + printf("SNDCTL_DSP_SUBDIVIDE unimplemented\n"); break; case SNDCTL_DSP_SETFRAGMENT: - /* XXX watch out, this is RW! */ DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); { pcm_channel *c = wrch? wrch : rdch; u_int32_t fragln = (*arg_i) & 0x0000ffff; u_int32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; u_int32_t fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; if (maxfrags == 0) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; if (maxfrags < 2) { ret = EINVAL; break; } if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE) maxfrags = CHN_2NDBUFMAXSIZE / fragsz; DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); if (rdch) ret = chn_setblocksize(rdch, maxfrags, fragsz); if (wrch && ret == 0) ret = chn_setblocksize(wrch, maxfrags, fragsz); - *arg_i = (c->fragments << 16) | c->blocksize; + fragsz = c->buffer2nd.blksz; + fragln = 0; + while (fragsz > 1) { + fragln++; + fragsz >>= 1; + } + *arg_i = (c->buffer2nd.blkcnt << 16) | fragln; } break; case SNDCTL_DSP_GETISPACE: /* XXX Space for reading? Makes no sense... */ /* return the size of data available in the input queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (rdch) { snd_dbuf *b = &rdch->buffer; snd_dbuf *bs = &rdch->buffer2nd; if (b->dl && !(rdch->flags & CHN_F_MAPPED)) /* * Suck up the secondary and DMA buffer. * chn_rdfeed*() takes care of the alignment. */ while (chn_rdfeed(rdch) > 0); a->bytes = bs->rl; - a->fragments = a->bytes / rdch->blocksize2nd; - a->fragstotal = rdch->fragments; - a->fragsize = rdch->blocksize2nd; + a->fragments = a->bytes / rdch->buffer2nd.blksz; + a->fragstotal = rdch->buffer2nd.blkcnt; + a->fragsize = rdch->buffer2nd.blksz; } } break; case SNDCTL_DSP_GETOSPACE: /* return space available in the output queue */ { audio_buf_info *a = (audio_buf_info *)arg; if (wrch) { snd_dbuf *b = &wrch->buffer; snd_dbuf *bs = &wrch->buffer2nd; if (b->dl && !(wrch->flags & CHN_F_MAPPED)) { /* * Fill up the secondary and DMA buffer. * chn_wrfeed*() takes care of the alignment. * Check for underflow before writing into the buffers. */ chn_checkunderflow(wrch); while (chn_wrfeed(wrch) > 0); } a->bytes = bs->fl; - a->fragments = a->bytes / wrch->blocksize2nd; - a->fragstotal = wrch->fragments; - a->fragsize = wrch->blocksize2nd; + a->fragments = a->bytes / wrch->buffer2nd.blksz; + a->fragstotal = wrch->buffer2nd.blkcnt; + a->fragsize = wrch->buffer2nd.blksz; } } break; case SNDCTL_DSP_GETIPTR: { count_info *a = (count_info *)arg; if (rdch) { snd_dbuf *b = &rdch->buffer; snd_dbuf *bs = &rdch->buffer2nd; if (b->dl && !(rdch->flags & CHN_F_MAPPED)) /* * Suck up the secondary and DMA buffer. * chn_rdfeed*() takes care of the alignment. */ while (chn_rdfeed(rdch) > 0); a->bytes = bs->total; - a->blocks = bs->rl / rdch->blocksize2nd; - a->ptr = bs->rl % rdch->blocksize2nd; + a->blocks = rdch->blocks; + a->ptr = bs->rp; + rdch->blocks = 0; } else ret = EINVAL; } break; case SNDCTL_DSP_GETOPTR: { count_info *a = (count_info *)arg; if (wrch) { snd_dbuf *b = &wrch->buffer; snd_dbuf *bs = &wrch->buffer2nd; if (b->dl && !(wrch->flags & CHN_F_MAPPED)) { /* * Fill up the secondary and DMA buffer. * chn_wrfeed*() takes care of the alignment. * Check for underflow before writing into the buffers. */ chn_checkunderflow(wrch); while (chn_wrfeed(wrch) > 0); } a->bytes = bs->total; a->blocks = wrch->blocks; a->ptr = bs->rp; wrch->blocks = 0; } else ret = EINVAL; } break; case SNDCTL_DSP_GETCAPS: *arg_i = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; if (rdch && wrch && !(d->flags & SD_F_SIMPLEX)) *arg_i |= DSP_CAP_DUPLEX; break; case SOUND_PCM_READ_BITS: *arg_i = ((wrch? wrch->format : rdch->format) & AFMT_16BIT)? 16 : 8; break; case SNDCTL_DSP_SETTRIGGER: if (rdch) { rdch->flags &= ~(CHN_F_TRIGGERED | CHN_F_NOTRIGGER); if (*arg_i & PCM_ENABLE_INPUT) rdch->flags |= CHN_F_TRIGGERED; else rdch->flags |= CHN_F_NOTRIGGER; chn_intr(rdch); } if (wrch) { wrch->flags &= ~(CHN_F_TRIGGERED | CHN_F_NOTRIGGER); if (*arg_i & PCM_ENABLE_OUTPUT) wrch->flags |= CHN_F_TRIGGERED; else wrch->flags |= CHN_F_NOTRIGGER; chn_intr(wrch); } break; case SNDCTL_DSP_GETTRIGGER: *arg_i = 0; if (wrch && wrch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_OUTPUT; if (rdch && rdch->flags & CHN_F_TRIGGERED) *arg_i |= PCM_ENABLE_INPUT; break; case SNDCTL_DSP_GETODELAY: if (wrch) { snd_dbuf *b = &wrch->buffer; if (b->dl) { chn_checkunderflow(wrch); if (!(wrch->flags & CHN_F_MAPPED)) while (chn_wrfeed(wrch) > 0); } *arg = b->total; } else ret = EINVAL; break; case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: /* undocumented */ case SNDCTL_DSP_POST: case SOUND_PCM_WRITE_FILTER: case SOUND_PCM_READ_FILTER: /* dunno what these do, don't sound important */ default: DEB(printf("default ioctl chan%d fn 0x%08lx fail\n", chan, cmd)); ret = EINVAL; break; } splx(s); return ret; } int dsp_poll(snddev_info *d, int chan, int events, struct proc *p) { int ret = 0, e; pcm_channel *wrch = NULL, *rdch = NULL; getchns(d, chan, &rdch, &wrch); e = events & (POLLOUT | POLLWRNORM); if (wrch && e) ret |= chn_poll(wrch, e, p); e = events & (POLLIN | POLLRDNORM); if (rdch && e) ret |= chn_poll(rdch, e, p); return ret; } int dsp_mmap(snddev_info *d, int chan, vm_offset_t offset, int nprot) { pcm_channel *wrch = NULL, *rdch = NULL, *c = NULL; getchns(d, chan, &rdch, &wrch); /* XXX this is broken by line 204 of vm/device_pager.c, so force write buffer */ - if (1 || (wrch && (nprot & PROT_WRITE))) c = wrch; - else if (rdch && (nprot & PROT_READ)) c = rdch; - if (c) { + if (1 || (wrch && (nprot & PROT_WRITE))) + c = wrch; + else if (rdch && (nprot & PROT_READ)) + c = rdch; + if (c && (c->format == c->buffer.fmt)) { c->flags |= CHN_F_MAPPED; return atop(vtophys(c->buffer2nd.buf + offset)); - } - return -1; + } else + return -1; }