Skip to content

Day 106 — DMA Slave Configuration and dma_slave_config

Today's Goal

Understand how a DMA client configures the peripheral side of a slave DMA channel and how that configuration is later consumed by a DMA controller driver.

The main focus is the relationship between:

  • struct dma_slave_config
  • dmaengine_slave_config()
  • DMA transfer direction
  • Peripheral FIFO/register addresses
  • Transfer width and burst configuration
  • DMA transaction descriptors
  • DMA controller-specific hardware descriptors

1. DMA Slave Configuration

Slave DMA transfers move data between memory and a peripheral endpoint.

Typical examples include:

DMA_MEM_TO_DEV

Memory ──DMA──> Peripheral FIFO


DMA_DEV_TO_MEM

Peripheral FIFO ──DMA──> Memory

Unlike memory-to-memory DMA, the DMA controller must know how to access the peripheral side of the transaction.

Linux represents this information with:

struct dma_slave_config

and applies it with:

dmaengine_slave_config()

2. struct dma_slave_config

Important fields studied today include:

src_addr
dst_addr

src_addr_width
dst_addr_width

src_maxburst
dst_maxburst

device_fc

The source or destination fields used for a transaction depend on the DMA transfer direction.

For DMA_MEM_TO_DEV:

Memory ───────────────> Peripheral
                      dst_addr

The peripheral-side configuration normally uses:

dst_addr
dst_addr_width
dst_maxburst

For DMA_DEV_TO_MEM:

Peripheral ───────────> Memory
 src_addr

The peripheral-side configuration normally uses:

src_addr
src_addr_width
src_maxburst

3. Peripheral Address

The peripheral address normally identifies a DMA-accessible FIFO or register.

Examples include:

UART RX/TX FIFO
SPI FIFO
I2S data register
ADC data register

This address is different from the memory DMA address supplied when preparing a DMA transaction.

Conceptually:

struct dma_slave_config
Peripheral Endpoint


DMA Preparation API
Memory Endpoint

4. CPU MMIO vs DMA Addressing

A peripheral driver may maintain different address representations for CPU and DMA access.

The BCM2835 SPI driver provides a useful example.

In bcm2835_spi_probe():

bs->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem);

bs->phys_addr = iomem->start;

This separates:

bs->regs
    └── CPU virtual MMIO mapping

bs->phys_addr
    └── Peripheral resource address used when building DMA configuration

The CPU accesses peripheral registers through an MMIO mapping.

DMA hardware does not use the CPU virtual address.

On platforms with more complex interconnects, the DMA controller may also require additional address translation before programming the hardware descriptor.


5. BCM2835 SPI DMA Client Example

The following source was studied:

drivers/spi/spi-bcm2835.c

Relevant functions:

bcm2835_spi_probe()
bcm2835_dma_init()

The driver requests TX and RX DMA channels:

ctlr->dma_tx = dma_request_chan(dev, "tx");
ctlr->dma_rx = dma_request_chan(dev, "rx");

The TX channel is configured with the SPI FIFO as its peripheral destination:

slave_config.dst_addr = bs->phys_addr + BCM2835_SPI_FIFO;
slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;

ret = dmaengine_slave_config(ctlr->dma_tx, &slave_config);

The RX configuration contains both source and destination peripheral addresses:

slave_config.src_addr = bs->phys_addr + BCM2835_SPI_FIFO;
slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;

slave_config.dst_addr = bs->phys_addr + BCM2835_SPI_CS;
slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;

This is because the RX DMA channel is used bidirectionally by this driver.

For normal RX:

SPI FIFO ──DMA_DEV_TO_MEM──> Memory

For a TX-only transfer, the driver can use the channel to cyclically write a precalculated value to the SPI CS register to clear the RX FIFO.

This demonstrates an important rule:

A DMA channel name such as "rx" does not itself determine the transaction direction.

The descriptor direction determines which side of dma_slave_config is used.


6. Peripheral Transfer Width

Fields such as:

src_addr_width
dst_addr_width

describe the peripheral-side transfer beat width.

For example:

DMA_SLAVE_BUSWIDTH_4_BYTES

means that the peripheral endpoint is accessed using 4-byte transfer beats.

It does not describe:

  • DMA address width.
  • Total buffer size.
  • Total transaction length.

7. Burst Configuration

The fields:

src_maxburst
dst_maxburst

describe the requested peripheral-side burst behavior.

The DMA client expresses the requirement through the generic DMA Engine interface.

The DMA controller driver later translates that value into controller-specific hardware encoding.


8. Slave Configuration vs Descriptor

One of today's most important distinctions is:

dma_slave_config
Peripheral-Side Channel Configuration


DMA Descriptor
Individual DMA Transaction

Slave configuration may contain:

Peripheral address
Peripheral width
Peripheral burst behavior

A transaction descriptor may contain:

Memory DMA address
Transfer length
Direction
Cyclic buffer information
Callback information

The DMA controller driver eventually combines both sets of information when preparing the hardware transaction.


9. Raspberry Pi 5 RP1 DMA Topology

The Raspberry Pi 5 Device Tree was inspected to understand a real DMA provider relationship.

An RP1 SPI node contains entries such as:

dmas = <&rp1_dma RP1_DMA_SPI4_TX>,
       <&rp1_dma RP1_DMA_SPI4_RX>;

dma-names = "tx", "rx";

This connects the DMA client to the RP1 DMA provider.

Conceptually:

SPI Driver
    │ dma_request_chan("tx" / "rx")
Device Tree DMA Specifier
RP1 DMA Provider

The DMA request specifier identifies the DMA request/resource relationship.

It does not replace src_addr or dst_addr.


10. RP1 DMA Provider

The RP1 DMA controller node contains:

compatible = "snps,axi-dma-1.01a";

The controller provides eight DMA channels and multiple DMA request targets.

This allowed the source trace to continue into the Synopsys AXI DMA controller driver.


11. dmaengine_slave_config() Provider Path

The Synopsys AXI DMA driver registers:

dw->dma.device_config = dw_axi_dma_chan_slave_config;

The callback implementation stores the configuration:

static int dw_axi_dma_chan_slave_config(struct dma_chan *dchan,
                                        struct dma_slave_config *config)
{
    struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);

    memcpy(&chan->config, config, sizeof(*config));

    return 0;
}

This demonstrates that:

dmaengine_slave_config() does not necessarily program the DMA hardware immediately.

In this implementation:

dmaengine_slave_config()
device_config()
dw_axi_dma_chan_slave_config()
chan->config

The configuration is saved in controller-specific channel state.


12. Hardware Descriptor Preparation

The saved configuration is later consumed by:

dw_axi_dma_set_hw_desc()

The function combines:

chan->config
        +
memory DMA address
        +
transfer length
        +
transfer direction

to construct the controller-specific hardware descriptor.

For DMA_MEM_TO_DEV, the driver uses:

dst_addr
dst_addr_width
dst_maxburst

For DMA_DEV_TO_MEM, it uses:

src_addr
src_addr_width
src_maxburst

13. DMA-Visible Peripheral Address

The AXI DMA driver performs address translation before programming the hardware descriptor:

device_addr = phys_to_dma(...);

This reinforces the distinction between:

CPU virtual MMIO address

Peripheral resource address

DMA-visible address

These addresses may refer to the same hardware endpoint but are not necessarily numerically identical.


14. Hardware Descriptor Fields

The controller driver converts the generic DMA Engine configuration into hardware-specific fields.

The studied AXI DMA implementation derives information including:

SAR
DAR
transfer width
burst / MSIZE
INC / NOINC
transfer count

For DMA_MEM_TO_DEV:

SAR = Memory
DAR = Peripheral

Memory     = INC
Peripheral = NOINC

For DMA_DEV_TO_MEM:

SAR = Peripheral
DAR = Memory

Peripheral = NOINC
Memory     = INC

A peripheral FIFO normally remains at a fixed register address while the memory address advances through the DMA buffer.


15. Complete Source Trace

Today's source trace can be summarized as:

DMA Client Driver
        │ fill struct dma_slave_config
dmaengine_slave_config()
struct dma_device::device_config()
dw_axi_dma_chan_slave_config()
        │ memcpy()
chan->config
        │ later
Descriptor Preparation
dw_axi_dma_set_hw_desc()
        ├── saved slave configuration
        ├── memory DMA address
        ├── length
        └── direction
Hardware Descriptor / LLI
        ├── SAR / DAR
        ├── Width
        ├── Burst
        ├── INC / NOINC
        └── Transfer Count

16. Reusable Diagram

A reusable diagram was created to show the relationship between peripheral configuration, transaction information, controller channel state, and hardware descriptor preparation.

DMA Slave Configuration Flow

The diagram intentionally uses a generic DMA client/provider model rather than tying the architecture to BCM2835 SPI, RP1, or Synopsys AXI DMA.


Key Takeaways

  1. Slave DMA transfers data between memory and a peripheral endpoint.
  2. struct dma_slave_config describes the peripheral side of the DMA channel.
  3. DMA_MEM_TO_DEV normally uses the dst_* configuration fields.
  4. DMA_DEV_TO_MEM normally uses the src_* configuration fields.
  5. src_addr_width and dst_addr_width describe peripheral transfer beat width, not address width.
  6. src_maxburst and dst_maxburst express requested peripheral burst behavior.
  7. The DMA channel name does not by itself determine transaction direction.
  8. CPU MMIO virtual addresses must not be used as DMA addresses.
  9. DMA-visible addressing may require platform-specific translation.
  10. dmaengine_slave_config() may save configuration without immediately programming DMA hardware.
  11. The controller driver later combines slave configuration with transaction-specific information.
  12. Peripheral FIFO addresses are normally fixed while memory addresses increment through the DMA buffer.
  13. Generic DMA Engine configuration is translated into controller-specific hardware descriptor fields.