Skip to content

Linux DMA Mapping APIs

Purpose

The Linux DMA Mapping API provides a portable interface for preparing CPU memory so that it can be accessed by DMA-capable devices.

Unlike ioremap(), which maps device registers into the CPU address space, the DMA Mapping API exposes CPU memory to hardware devices.


#include <linux/dma-mapping.h>

Common APIs

API Purpose
dma_alloc_coherent() Allocate a coherent DMA buffer.
dma_free_coherent() Release a coherent DMA buffer.
dma_map_single() Map an existing CPU buffer for streaming DMA.
dma_unmap_single() Remove a streaming DMA mapping.
dma_sync_single_for_device() Synchronize a mapped buffer before device access.
dma_sync_single_for_cpu() Synchronize a mapped buffer before CPU access.
dma_mapping_error() Check whether a DMA mapping failed.

dma_alloc_coherent()

Purpose

Allocates a coherent DMA buffer.

The returned CPU virtual address and DMA address refer to the same underlying memory and remain valid until released.

Prototype

void *dma_alloc_coherent(struct device *dev,
                         size_t size,
                         dma_addr_t *dma_handle,
                         gfp_t gfp);

Parameters

Parameter Description
dev Device owning the DMA buffer.
size Allocation size in bytes.
dma_handle Returned DMA address.
gfp Allocation flags.

Return Value

Returns the CPU virtual address on success.

Returns NULL on failure.

Example

dma_addr_t dma_addr;
void *cpu_addr;

cpu_addr = dma_alloc_coherent(dev,
                              PAGE_SIZE,
                              &dma_addr,
                              GFP_KERNEL);

if (!cpu_addr)
    return -ENOMEM;

/* Use the DMA buffer. */

dma_free_coherent(dev,
                  PAGE_SIZE,
                  cpu_addr,
                  dma_addr);

dma_free_coherent()

Purpose

Releases a coherent DMA buffer.

Prototype

void dma_free_coherent(struct device *dev,
                       size_t size,
                       void *cpu_addr,
                       dma_addr_t dma_handle);

dma_map_single()

Purpose

Maps an existing CPU buffer for streaming DMA.

A streaming mapping may require explicit ownership synchronization when the CPU and DMA device access the mapped memory at different times.

Prototype

dma_addr_t dma_map_single(struct device *dev,
                          void *cpu_addr,
                          size_t size,
                          enum dma_data_direction direction);

Parameters

Parameter Description
dev DMA-capable device.
cpu_addr CPU virtual address.
size Buffer size.
direction DMA transfer direction.

Return Value

Returns a DMA address on success.

The return value should always be checked with dma_mapping_error().

Example

dma_addr_t dma_addr;

dma_addr = dma_map_single(dev,
                          buffer,
                          length,
                          DMA_TO_DEVICE);

if (dma_mapping_error(dev, dma_addr))
    return -EIO;

dma_unmap_single()

Purpose

Ends a streaming DMA mapping.

The mapping must not be removed while the DMA device may still access the mapped memory.

Prototype

void dma_unmap_single(struct device *dev,
                      dma_addr_t dma_addr,
                      size_t size,
                      enum dma_data_direction direction);

Important Notes

Before unmapping:

  • stop or complete DMA activity
  • synchronize outstanding DMA operations when required
  • ensure the device no longer accesses the mapping
  • ensure higher-level buffer ownership rules are satisfied

For a persistent streaming mapping, mapping lifetime may span multiple DMA completion events.


dma_sync_single_for_device()

Purpose

Synchronizes a streaming DMA mapping before device access.

This API is used when ownership of a mapped region is being returned to the DMA device after CPU access.

Prototype

void dma_sync_single_for_device(struct device *dev,
                                dma_addr_t dma_addr,
                                size_t size,
                                enum dma_data_direction direction);

Important Notes

The API does not create a new DMA mapping.

It operates on memory that is already mapped for streaming DMA.

A useful ownership model is:

CPU owns mapped region
dma_sync_single_for_device()
Device may access mapped region

The exact cache-maintenance behavior is architecture-dependent.


dma_sync_single_for_cpu()

Purpose

Synchronizes a streaming DMA mapping before CPU access.

This API is commonly used after a DMA device has written into a streaming-mapped buffer and the CPU needs to consume the result.

Prototype

void dma_sync_single_for_cpu(struct device *dev,
                             dma_addr_t dma_addr,
                             size_t size,
                             enum dma_data_direction direction);

Important Notes

A useful ownership model is:

Device owns mapped region
DMA completion
dma_sync_single_for_cpu()
CPU may access mapped region

The mapping itself remains valid after synchronization.

Therefore:

mapping lifetime
CPU/device ownership interval

This distinction becomes especially important for persistent streaming DMA buffers.


dma_mapping_error()

Purpose

Checks whether a DMA mapping operation failed.

Prototype

int dma_mapping_error(struct device *dev,
                      dma_addr_t dma_addr);

Return Value

Returns non-zero if the DMA mapping failed.

Returns zero if the mapping succeeded.


Persistent Streaming Mappings

A streaming mapping does not need to correspond to only one DMA transaction.

A client may keep a larger buffer mapped across an entire streaming session.

For example:

Persistent Mapping

┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
└────────┴────────┴────────┴────────┘

DMA may repeatedly operate on different regions of the same mapping.

Conceptually:

Map Complete Buffer
DMA Slot 0
CPU Consumes Slot 0
DMA Slot 1
CPU Consumes Slot 1
...
Terminate Stream
Unmap Complete Buffer

This separates:

mapping lifetime
        from
individual DMA-region ownership

A client must still obey the DMA Mapping API synchronization requirements for each region it allows the CPU or device to access.


Mapping Granularity and Buffer Ownership

A persistent mapping may contain multiple logical buffer regions.

For example:

┌─────────────┬─────────────┬─────────────┬─────────────┐
│   Region 0  │   Region 1  │   Region 2  │   Region 3  │
└─────────────┴─────────────┴─────────────┴─────────────┘

Region 0 → CPU consumption
Region 1 → DMA active
Region 2 → available for later DMA
Region 3 → available for later DMA

The complete mapping can remain valid even though the logical ownership of individual regions changes over time.

This is common in continuous acquisition architectures such as cyclic DMA.

The exact synchronization APIs and allowed ranges must follow the Linux DMA API requirements for the architecture and mapping type being used.


Lab Simulator Extensions

The Embedded Linux Learning DMA simulator includes additional helper APIs used by the Day110 continuous DMA lab.

These helpers are not Linux kernel DMA API functions.

They exist only to model partial ownership transitions inside one persistent simulated DMA mapping.

The simulator extensions are:

Simulator API Purpose
dma_sync_single_range_for_cpu() Synchronize one subrange of a persistent simulated mapping for CPU access.
dma_sync_single_range_for_device() Synchronize one subrange of a persistent simulated mapping for device access.

They complement the simulator's existing whole-mapping synchronization APIs.


dma_sync_single_range_for_cpu()

Purpose

Synchronizes a subrange of an existing simulated streaming DMA mapping for CPU access.

The base DMA address remains the DMA address originally returned when the complete buffer was mapped.

The requested synchronization region is specified using an offset and size.

Simulator Prototype

bool dma_sync_single_range_for_cpu(
    dma_addr_t dma_addr,
    size_t offset,
    size_t size,
    enum dma_data_direction direction);

Parameters

Parameter Description
dma_addr Base DMA address of the existing simulated streaming mapping.
offset Byte offset from the beginning of the mapping.
size Number of bytes to synchronize.
direction DMA direction used when the mapping was created.

Return Value

Returns true when the requested range is valid and the simulated synchronization succeeds.

Returns false when:

  • the mapping does not exist
  • the mapping type is invalid
  • the DMA direction does not match
  • the offset is outside the mapping
  • the requested range exceeds the mapping

Example

Assume one 64-byte persistent mapping is divided into four 16-byte slots:

DMA Mapping

offset 0
┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
│ 16 B   │ 16 B   │ 16 B   │ 16 B   │
└────────┴────────┴────────┴────────┘

To synchronize Slot 2:

dma_sync_single_range_for_cpu(dma_addr,
                              32U,
                              16U,
                              DMA_FROM_DEVICE);

The simulator copies only that requested device-visible subrange into the simulated CPU-visible view.


dma_sync_single_range_for_device()

Purpose

Synchronizes a subrange of an existing simulated streaming DMA mapping before that region is returned to device access.

Simulator Prototype

bool dma_sync_single_range_for_device(
    dma_addr_t dma_addr,
    size_t offset,
    size_t size,
    enum dma_data_direction direction);

Parameters

Parameter Description
dma_addr Base DMA address of the existing simulated streaming mapping.
offset Byte offset from the beginning of the mapping.
size Number of bytes to synchronize.
direction DMA direction used when the mapping was created.

Return Value

Returns true when the requested range is valid and synchronization succeeds.

Returns false when the mapping or requested range is invalid.

Example

dma_sync_single_range_for_device(dma_addr,
                                 slot->offset,
                                 slot->length,
                                 DMA_FROM_DEVICE);

The Day110 simulator uses this operation when a CPU-owned slot has been consumed and is being returned to the reusable DMA buffer pool.


Simulator Range Validation

The simulator validates a subrange using the equivalent of:

offset <= mapping size

and

size <= mapping size - offset

This avoids allowing:

offset + size

to extend beyond the persistent mapping.

It also avoids integer-overflow-prone range validation.


Simulator Ownership Model

The original simulator's whole-buffer synchronization functions model one ownership state for the complete mapping.

Day110 introduces a different requirement.

One persistent mapping may simultaneously contain logical slots in different states:

Slot 0 → READY
Slot 1 → CPU_OWNED
Slot 2 → DMA_OWNED
Slot 3 → FREE

Therefore the Day110 stream simulator tracks slot ownership separately using its DMA ring metadata.

The range synchronization helpers update simulated CPU/device visibility for the requested range but do not redefine the Linux DMA API itself.


Linux API vs Simulator API

Keep the distinction explicit:

Linux DMA Mapping API Lab Simulator Extension
dma_sync_single_for_cpu() dma_sync_single_range_for_cpu()
dma_sync_single_for_device() dma_sync_single_range_for_device()
Real kernel API Project-specific simulator helper
Uses Linux DMA API semantics Models per-slot synchronization for the learning simulator

The range helper names should not be copied into a real Linux driver as if they were kernel APIs.

When implementing a real driver, use the synchronization interfaces and range semantics provided by the Linux DMA Mapping API for the target kernel and architecture.