Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 19 Jul 1997 16:37:50 +0200 (CEST)
From:      Luigi Rizzo <luigi@iet.unipi.it>
To:        hackers@freebsd.org, multimedia@freebsd.org
Subject:   dma handling in the sound driver
Message-ID:  <199707191437.QAA01053@prova.iet.unipi.it>

next in thread | raw e-mail | index | archive | help
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/
====================================================================



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199707191437.QAA01053>