External Hardware Buffers

Some chips have their own hardware buffers and the DMA transfer from the host memory is not available. In such a case, you need to either 1) copy/set the audio data directly to the external hardware buffer, or 2) make an intermediate buffer and copy/set the data from it to the external hardware buffer in interrupts (or in tasklets, preferably).

The first case works fine if the external hardware buffer is large enough. This method doesn't need any extra buffers and thus is more effective. You need to define the copy and silence callbacks for the data transfer. However, there is a drawback: it cannot be mmapped. The examples are GUS's GF1 PCM or emu8000's wavetable PCM.

The second case allows for mmap on the buffer, although you have to handle an interrupt or a tasklet to transfer the data from the intermediate buffer to the hardware buffer. You can find an example in the vxpocket driver.

Another case is when the chip uses a PCI memory-map region for the buffer instead of the host memory. In this case, mmap is available only on certain architectures like the Intel one. In non-mmap mode, the data cannot be transferred as in the normal way. Thus you need to define the copy and silence callbacks as well, as in the cases above. The examples are found in rme32.c and rme96.c.

The implementation of the copy and silence callbacks depends upon whether the hardware supports interleaved or non-interleaved samples. The copy callback is defined like below, a bit differently depending whether the direction is playback or capture:


  static int playback_copy(struct snd_pcm_substream *substream, int channel,
               snd_pcm_uframes_t pos, void *src, snd_pcm_uframes_t count);
  static int capture_copy(struct snd_pcm_substream *substream, int channel,
               snd_pcm_uframes_t pos, void *dst, snd_pcm_uframes_t count);

          

In the case of interleaved samples, the second argument (channel) is not used. The third argument (pos) points the current position offset in frames.

The meaning of the fourth argument is different between playback and capture. For playback, it holds the source data pointer, and for capture, it's the destination data pointer.

The last argument is the number of frames to be copied.

What you have to do in this callback is again different between playback and capture directions. In the playback case, you copy the given amount of data (count) at the specified pointer (src) to the specified offset (pos) on the hardware buffer. When coded like memcpy-like way, the copy would be like:


  my_memcpy(my_buffer + frames_to_bytes(runtime, pos), src,
            frames_to_bytes(runtime, count));

          

For the capture direction, you copy the given amount of data (count) at the specified offset (pos) on the hardware buffer to the specified pointer (dst).


  my_memcpy(dst, my_buffer + frames_to_bytes(runtime, pos),
            frames_to_bytes(runtime, count));

          

Note that both the position and the amount of data are given in frames.

In the case of non-interleaved samples, the implementation will be a bit more complicated.

You need to check the channel argument, and if it's -1, copy the whole channels. Otherwise, you have to copy only the specified channel. Please check isa/gus/gus_pcm.c as an example.

The silence callback is also implemented in a similar way.


  static int silence(struct snd_pcm_substream *substream, int channel,
                     snd_pcm_uframes_t pos, snd_pcm_uframes_t count);

          

The meanings of arguments are the same as in the copy callback, although there is no src/dst argument. In the case of interleaved samples, the channel argument has no meaning, as well as on copy callback.

The role of silence callback is to set the given amount (count) of silence data at the specified offset (pos) on the hardware buffer. Suppose that the data format is signed (that is, the silent-data is 0), and the implementation using a memset-like function would be like:


  my_memcpy(my_buffer + frames_to_bytes(runtime, pos), 0,
            frames_to_bytes(runtime, count));

          

In the case of non-interleaved samples, again, the implementation becomes a bit more complicated. See, for example, isa/gus/gus_pcm.c.