From owner-freebsd-multimedia Sat Jul 19 07:36:59 1997 Return-Path: Received: (from root@localhost) by hub.freebsd.org (8.8.5/8.8.5) id HAA27781 for multimedia-outgoing; Sat, 19 Jul 1997 07:36:59 -0700 (PDT) Received: from prova.iet.unipi.it (prova1.iet.unipi.it [131.114.9.11]) by hub.freebsd.org (8.8.5/8.8.5) with ESMTP id HAA27773; Sat, 19 Jul 1997 07:36:50 -0700 (PDT) Received: (from luigi@localhost) by prova.iet.unipi.it (8.8.5/8.8.5) id QAA01053; Sat, 19 Jul 1997 16:37:50 +0200 (CEST) From: Luigi Rizzo Message-Id: <199707191437.QAA01053@prova.iet.unipi.it> Subject: dma handling in the sound driver To: hackers@freebsd.org, multimedia@freebsd.org Date: Sat, 19 Jul 1997 16:37:50 +0200 (CEST) X-Mailer: ELM [version 2.4ME+ PL31 (25)] MIME-Version: 1.0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Sender: owner-multimedia@freebsd.org X-Loop: FreeBSD.org Precedence: bulk I have planned to rewrite the dma buffer handling routines for the sound driver as follows. To me this seems reasonably simple and efficient. Can you please have a look at this high-level description of the problem (including some pseudo-code, very close to the actual implementation) and tell if this looks reasonable ? (this description will also go into the source files, so hopefully people will not have a hard time in understanding how it works...) Thanks Luigi ------- The main problem with the DMA in the sound driver is to avoid that the flow of data from/to the codec is interrupted, resulting in missing samples or clicks. The problem is dealt with at several levels. 1) within the sound card, there is some amount of buffering (e.g. a FIFO, or on-board memory, etc.) so as to overcome occasional periods when the user process is slow. 2) some amount of buffering must be supplied in the driver as well, for those boards (many) which do not have enough buffering. Even in presence of buffering, there might be problems when the current DMA operation terminates and a new one is restarted. In fact: - if we use a single DMA buffer, we need the time to refill it before starting the next op. The largest the buffer (and the size of the block being transferred), the longest is the idle time; - with two DMA buffers, we can refill one buffer while the other one is in use by the DMA engine. We can still have troubles if we start a long refill near the end of operation of DMA on the other buffer, but this problem can be minimized (but not avoided; if we are late, we are late, no matter how many buffers we have!) In our implementation, we use a single memory block structured as three logical, variable-size, buffers: one in use by the dma engine, the next one ready for use by the dma (already filled up), the last one free for refills. dp,dl rp,rl fp,fl +-------+-------------+---------------+------+ | free | used by dma | ready for use | free | +-------+-------------+---------------+------+ Both the "ready" and "free" areas can wrap around the end of the buffer. The "dma" are Three pointers (dp, rp, fp) and three length (dl, rl, fl) are used for the purpose. At initialization: dp = rp = fp = 0 ; /* beginning of buffer */ dl = 0 ; /* meaning no dma activity) */ rl = 0 ; /* meaning no data ready */ fl = bufsize ; Upon dma_interrupt(), we try to use the next buffer if possible: s=splhigh(); fl += dl ; dp = rp ; dl = 0 ; if (rl > 0) { dl = min(rl, bufsize - rp ) ; /* do not wrap */ rl -= dl ; rp += dl ; if (rp == bufsize) rp = 0; /* * now try to avoid too small dma transfers in the near future. * This can happen if I let rp start too close to the end of * the buffer. If this happens, and have enough data, try to * split the available block in two approx. equal parts. */ if (bufsize - rp < MIN_DMA_SIZE && bufsize - dp > 2*MIN_DMA_SIZE) { dl = (bufsize - dp) / 2; rl += (rp - (dp + dl) ) ; rp = dp + dl ; } restart_dma(); } splx(s) Upon user_write() for n bytes we copy data into the free buffer and then extend the ready block. Instead of doing all the copy at once, we start with small pieces in order to minimize the chance of a starvation. The size of the smallest chunk is chosen in a way that the execution time of uiomove is still dominated by the constant part. bsz = 64 ; /* min block size */ while (n>0) { int l = min (n, bsz); l = min (l, fl); l = min (l, bufsize - fp ); uiomove(buf, .. , l); s=splhigh(); rl += l ; fl -= l ; fp += l ; if (fp == bufsize) fp = 0 ; if (rl == l) { dl = rl ; restart_dma(); } splx(s); if (fl == 0) { /* buffer full, must sleep */ tsleep( ... ); /* handle errors etc. */ } bsz = min(bufsize, bsz*2); } ==================================================================== Luigi Rizzo Dip. di Ingegneria dell'Informazione email: luigi@iet.unipi.it Universita' di Pisa tel: +39-50-568533 via Diotisalvi 2, 56126 PISA (Italy) fax: +39-50-568522 http://www.iet.unipi.it/~luigi/ ====================================================================