Skip to content

DMA Slave Transfer

DMA slave transfers move data between system memory and a peripheral endpoint such as a UART, SPI controller, I2C controller, or audio interface.

After a DMA channel has been requested and its peripheral-side parameters have been configured with dmaengine_slave_config(), the DMA client prepares individual transactions using DMA-mapped memory.

A typical one-shot slave DMA transaction combines:

Peripheral Configuration
        +
DMA-Mapped Memory
        +
Transfer Length
        +
Transfer Direction
        +
Transaction Flags
DMA Transaction Descriptor

The DMA controller driver translates this generic transaction into controller-specific hardware descriptors.


Slave DMA Lifecycle

A DMA client normally separates long-lived channel setup from per-transfer operations.

Channel Setup

Channel setup is typically performed during driver initialization:

dma_request_chan()
dmaengine_slave_config()
DMA channel ready

The slave configuration describes the peripheral side of the DMA channel, including:

  • Peripheral FIFO or register address.
  • Transfer width.
  • Burst configuration.
  • Other controller-supported slave parameters.

See DMA Slave Configuration for the configuration model.

Per-Transfer Lifecycle

A one-shot streaming DMA transaction typically follows:

CPU Buffer
DMA Mapping
DMA Address
Prepare Descriptor
Set Callback
Submit
Issue Pending
DMA Execution
Completion
Unmap
CPU Buffer

The following diagram summarizes the lifecycle and its cleanup boundaries.

DMA Slave Transfer Lifecycle


DMA Mapping

A normal CPU virtual address must not be passed directly to DMA Engine as if it were a DMA address.

For a streaming DMA mapping, the client first maps the buffer with the DMA mapping API.

TX example:

dma_addr_t dma_addr;

dma_addr = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_addr))
    return -EIO;

RX example:

dma_addr = dma_map_single(dev, buf, len, DMA_FROM_DEVICE);
if (dma_mapping_error(dev, dma_addr))
    return -EIO;

The returned dma_addr_t is the DMA-visible address used when preparing the DMA Engine transaction.

Conceptually:

CPU Virtual Address
        │ dma_map_single()
DMA Address
DMA Engine Transaction

DMA mapping is a separate operation from DMA transaction preparation.


Two Different DMA Directions

Slave DMA involves two direction abstractions that must not be confused.

Transfer DMA Mapping API DMA Engine API
TX DMA_TO_DEVICE DMA_MEM_TO_DEV
RX DMA_FROM_DEVICE DMA_DEV_TO_MEM

DMA Mapping Direction

The DMA mapping API uses:

DMA_TO_DEVICE
DMA_FROM_DEVICE

These values describe how the device accesses system memory.

For example:

DMA_TO_DEVICE
    =
Device reads memory

DMA_FROM_DEVICE
    =
Device writes memory

This information is used by the DMA mapping layer for DMA address translation, ownership, and architecture-specific cache handling.

DMA Engine Direction

DMA Engine uses:

DMA_MEM_TO_DEV
DMA_DEV_TO_MEM

These values describe the topology of the DMA transaction.

For TX:

Memory
Peripheral

DMA_MEM_TO_DEV

For RX:

Peripheral
Memory

DMA_DEV_TO_MEM

The two enum families describe related operations at different abstraction layers.


Preparing a Single-Buffer Transfer

For a DMA-mapped contiguous DMA address range, a client can use:

dmaengine_prep_slave_single()

A typical TX preparation is:

desc = dmaengine_prep_slave_single(chan,
                                   dma_addr,
                                   len,
                                   DMA_MEM_TO_DEV,
                                   DMA_PREP_INTERRUPT |
                                   DMA_CTRL_ACK);
if (!desc) {
    /* Handle preparation failure. */
}

The important parameters are:

Parameter Purpose
chan DMA channel used for the transaction
dma_addr DMA-mapped memory address
len Transfer length
direction DMA_MEM_TO_DEV or DMA_DEV_TO_MEM
flags Descriptor preparation flags

The memory address must already be suitable for DMA access.

dmaengine_prep_slave_single() does not perform dma_map_single() on behalf of the client.


Single Is a Client-Side Representation

dmaengine_prep_slave_single() is implemented as a convenience helper around the slave scatter-gather provider interface.

Conceptually:

dma_addr_t
    +
length
One DMA SG Entry
device_prep_slave_sg()

Therefore, single means:

The DMA client describes the memory side as one DMA address range.

It does not mean:

  • One physical page.
  • One hardware transfer block.
  • One controller descriptor.
  • One hardware LLI.

The DMA controller driver may split the transaction further according to hardware limitations.

For example:

Client

One 64 KiB DMA Range
One SG Entry
DMA Controller Driver
        ├── HW Descriptor 0
        ├── HW Descriptor 1
        ├── HW Descriptor 2
        └── HW Descriptor 3

The exact split is controller-specific.


Scatter-Gather Slave Transfer

When the memory side consists of multiple DMA segments, the client can use:

dmaengine_prep_slave_sg()

Conceptually:

DMA-Mapped SG
dmaengine_prep_slave_sg()
device_prep_slave_sg()
DMA Controller Driver

Each mapped SG entry provides DMA-visible address and length information.

The provider consumes information equivalent to:

DMA address
DMA length
DMA direction
transaction flags

A software SG entry must not be confused with a hardware descriptor.

SG Entry
Hardware Descriptor / LLI

A provider may:

  • Combine information from the SG list and slave configuration.
  • Split an SG entry because of maximum hardware block sizes.
  • Generate multiple hardware descriptors.
  • Link hardware descriptors using controller-specific mechanisms.

DMA Mapping and SG Segmentation

DMA mapping may change how memory is represented to the device.

Conceptually:

Original Memory Regions
Scatterlist
DMA Mapping
DMA-Visible Segments

The number of original scatterlist entries and the number of DMA-visible segments are not necessarily identical.

DMA controller drivers should consume the mapped DMA representation rather than reconstructing DMA addresses from CPU-side memory information.


vmalloc() and Slave DMA

vmalloc() provides virtually contiguous memory but does not guarantee physically contiguous backing pages.

Conceptually:

Virtual Address Space

+------+------+------+------+
| P0   | P1   | P2   | P3   |
+------+------+------+------+

        virtually contiguous

The physical pages may be scattered:

Physical Memory

P0        P2              P1        P3

Therefore, a vmalloc() range cannot generally be treated as one physically contiguous DMA range.

A suitable driver or subsystem may instead represent the backing pages using scatter-gather DMA:

vmalloc() Range
Backing Pages
Scatterlist
DMA Mapping
DMA-Visible SG
DMA Engine

Whether this is appropriate depends on the subsystem, DMA device, and hardware restrictions.

The important distinction is:

Lack of physical continuity does not by itself imply that DMA is impossible; it changes how the memory must be represented and mapped.


Configuring the Completion Callback

After descriptor preparation succeeds, the client can configure completion handling:

desc->callback = my_dma_complete;
desc->callback_param = request;

The callback is associated with the prepared descriptor, so callback configuration must occur after successful descriptor preparation.

Conceptually:

Prepare
Descriptor Exists
Set Callback
Submit

The callback context must remain valid for as long as the completion path may reference it.


Callback Context Lifetime

Asynchronous completion introduces an important lifetime requirement.

The following pattern is unsafe if the function returns before DMA completion:

struct my_request request;

desc->callback_param = &request;

dmaengine_submit(desc);
dma_async_issue_pending(chan);

return 0;

Once the function returns, the stack object no longer has a valid lifetime, while the callback may still execute later.

The required relationship is:

Callback Context Lifetime
Possible Callback Lifetime

This rule also applies to other resources referenced by asynchronous completion code.


Submitting the Descriptor

After callback configuration, the descriptor is submitted:

dma_cookie_t cookie;
int ret;

cookie = dmaengine_submit(desc);

ret = dma_submit_error(cookie);
if (ret)
    /* Handle submission failure. */

Submission transfers the prepared transaction into the DMA Engine/provider lifecycle.

Conceptually:

Prepared
dmaengine_submit()
Submitted

Submission does not itself mean that hardware execution has started.


Issuing Pending Work

The client starts pending DMA work with:

dma_async_issue_pending(chan);

The lifecycle becomes:

Prepared
Submitted
Issued
Active
Completed

The exact transition from issued to active is controller-specific.

After dma_async_issue_pending(), the client must assume that DMA hardware may access the mapped buffer.

This becomes an important resource-lifetime boundary.


DMA Completion vs Peripheral Completion

DMA completion describes completion of the DMA transaction.

It does not necessarily mean that the peripheral has completed its own operation.

For example:

Memory
    │ DMA transfer
SPI TX FIFO
    │ SPI controller
Shift Register
Wire

The DMA transaction may complete after the final memory data has been delivered to the peripheral FIFO while the peripheral is still transmitting data.

Therefore:

DMA Completion
Peripheral Completion

If peripheral completion matters, the peripheral driver must verify the appropriate hardware state.

DMA Engine cannot provide this peripheral-specific guarantee.


One-Shot TX Lifecycle

A simple blocking TX transfer can be represented as:

CPU TX Buffer
dma_map_single()
DMA_TO_DEVICE
DMA Address
dmaengine_prep_slave_single()
DMA_MEM_TO_DEV
Set Callback
dmaengine_submit()
dma_async_issue_pending()
DMA Execution
Completion Callback
Wake Waiting Context
dma_unmap_single()
DMA_TO_DEVICE
Buffer Reusable by CPU

The callback can be kept small by using it only to signal a completion object.


One-Shot RX Lifecycle

The corresponding RX lifecycle is:

CPU RX Buffer
dma_map_single()
DMA_FROM_DEVICE
DMA Address
dmaengine_prep_slave_single()
DMA_DEV_TO_MEM
Set Callback
dmaengine_submit()
dma_async_issue_pending()
DMA Writes Buffer
Completion Callback
Wake Waiting Context
dma_unmap_single()
DMA_FROM_DEVICE
CPU Reads Received Data

For a one-shot streaming mapping that is unmapped after completion, the CPU can access the RX data after dma_unmap_single() completes.

An additional dma_sync_single_for_cpu() is not required merely to access the buffer after that mapping has been released.


Persistent Mapping and DMA Synchronization

Not every DMA mapping is immediately unmapped after one transfer.

A persistent mapping may remain active while CPU and device ownership alternates.

In that case, DMA synchronization APIs may be required:

Device Owns Buffer
dma_sync_*_for_cpu()
CPU Owns / Accesses Buffer
dma_sync_*_for_device()
Device Owns Buffer Again

These synchronization operations address DMA memory visibility and ownership.

They do not stop or pause the DMA controller.

This distinction is especially important for cyclic DMA.


Cyclic DMA Buffer Access

Consider a 4096-byte cyclic buffer divided into periods:

+----------+----------+----------+----------+
| Period 0 | Period 1 | Period 2 | Period 3 |
+----------+----------+----------+----------+

DMA may continuously process:

P0 → P1 → P2 → P3 → P0 → ...

Calling a DMA synchronization API does not prevent DMA from progressing to another period or eventually wrapping around.

The driver must coordinate which period is safe for CPU processing.

Conceptually:

DMA
    =
Producer

CPU
    =
Consumer

The CPU must consume completed regions before DMA reuses them.

If DMA catches up with the CPU, the problem is a ring-buffer overrun rather than a cache synchronization problem.


Buffer Ownership

DMA mapping ownership and buffer allocation ownership are separate.

Consider:

my_dma_tx(mdev, buf, len);

The caller may own the allocation:

Caller
    └── Buffer Allocation

while the transfer function temporarily owns:

DMA Transfer Function
    └── Streaming DMA Mapping

Therefore:

dma_unmap_single(...)

does not imply that the same function should:

kfree(buf);

A cleanup path must release only resources that the current owner is responsible for.


Cleanup Before DMA Issue

Suppose DMA mapping succeeds but descriptor preparation fails:

Map
Prepare
Unmap

The mapping must be released because it was successfully acquired.

However, no active DMA transaction exists that needs to be stopped.

The same principle applies to other pre-issue failures:

Cleanup should correspond to the resources and lifecycle states that were actually reached.


Cleanup After DMA Issue

The situation changes after:

dma_async_issue_pending(chan);

At this point, hardware may already be accessing the mapped buffer.

A timeout or driver-removal path must not immediately unmap or free resources.

A typical safe sequence is:

DMA Issued
Timeout / Cancellation / Removal
Stop Accepting New Work
dmaengine_terminate_sync()
DMA and Completion Activity Resolved
dma_unmap_single()
Release Owned Resources

The key safety question is:

Can DMA hardware or asynchronous completion activity still access this resource?

Cookie state alone is not sufficient to answer this lifetime question.


dmaengine_terminate_sync() as a Lifetime Boundary

dmaengine_terminate_sync() is useful when cleanup must establish that DMA activity and associated completion processing have been synchronized before resources are released.

Conceptually:

DMA may still access buffer
dmaengine_terminate_sync()
No remaining DMA/completion access
Unmap / Free

This is especially important for:

  • Transfer timeout.
  • Driver removal.
  • Request cancellation.
  • Error recovery after DMA has been issued.

The exact controller-specific termination behavior remains the responsibility of the DMA provider.


Slave Configuration and Transaction Information

Slave DMA hardware programming combines two different information sources.

Peripheral-Side Configuration

Configured with:

dmaengine_slave_config()

Examples include:

Peripheral address
Peripheral transfer width
Burst configuration

Per-Transaction Information

Provided during descriptor preparation:

Memory DMA address
Transfer length
Transfer direction
Flags

The provider combines them:

Peripheral Configuration
        ├───────────────┐
        │               │
        ▼               ▼
                  Transaction
                  Information
        ┌───────────────┘
DMA Controller Driver
Hardware Descriptor / LLI

This is the connection between slave configuration and slave transfer preparation.


Provider-Side Translation

A DMA controller driver consumes generic DMA Engine information and translates it into hardware-specific fields.

For a memory-to-device transaction:

Memory DMA Address
SAR
INC


Peripheral Address
DAR
NOINC

For a device-to-memory transaction:

Peripheral Address
SAR
NOINC


Memory DMA Address
DAR
INC

The provider also determines controller-specific details such as:

  • Transfer-width encoding.
  • Burst encoding.
  • Maximum block size.
  • Transfer count representation.
  • Number of hardware descriptors.
  • Hardware descriptor linking.

The DMA client should describe the required transfer rather than construct controller-specific descriptors itself.


DMA Client Implementation Sequence

A practical DMA client can be developed using three groups of operations.

Initialization

Request Channel
Configure Peripheral

Per Transfer

Map Buffer
Prepare Descriptor
Configure Callback
Submit
Issue
Wait / Handle Completion
Unmap

Error and Removal

Prevent New Work
Terminate / Synchronize
Unmap
Release Owned Resources

This sequence is more important than memorizing individual API prototypes.

Once the lifecycle and ownership boundaries are understood, the exact API details can be verified while implementing a real driver.


Implementation Checklist

When implementing a one-shot DMA slave transfer, verify:

  • The correct DMA channel has been requested.
  • Peripheral-side slave configuration has already been applied.
  • The CPU buffer is mapped for the correct DMA mapping direction.
  • The DMA mapping succeeded.
  • The DMA address rather than the CPU virtual address is passed to DMA Engine.
  • The correct DMA Engine transfer direction is used.
  • Descriptor preparation succeeded.
  • Callback context remains valid until completion can no longer occur.
  • dmaengine_submit() is checked with dma_submit_error().
  • Pending DMA work is explicitly issued.
  • The mapping remains valid while DMA may access the buffer.
  • Timeout and removal paths terminate and synchronize active DMA before unmapping.
  • Streaming mappings are eventually unmapped.
  • Buffer allocation ownership is handled independently from DMA mapping ownership.
  • Peripheral completion is checked separately when DMA completion alone is insufficient.

Mental Model

The complete one-shot slave DMA model is:

                Channel Setup

dma_request_chan()
dmaengine_slave_config()
Peripheral Configuration
        └─────────────────────────┐
                Transaction       │
CPU Buffer                        │
    ↓                             │
DMA Mapping                       │
    ↓                             │
DMA Address                       │
    ↓                             │
Prepare Descriptor                │
    │                             │
    └──────────────┬──────────────┘
          DMA Controller Driver
          Hardware Descriptor
                 Submit
                 Issue
             DMA Execution
              Completion
                 Unmap
              CPU Buffer

The central rule is:

Keep every DMA resource valid until neither the DMA hardware nor asynchronous completion processing can access it.