Linux DMA Mapping¶
Overview¶
Direct Memory Access (DMA) allows a hardware device to transfer data between the device and system memory without requiring the CPU to copy every byte.
The actual transfer is still performed by a hardware DMA engine, DMA controller, or DMA-capable device.
The Linux DMA API does not create DMA capability for hardware that does not support DMA. Instead, it provides a portable interface that allows a driver to prepare CPU memory for access by a DMA-capable device.
The DMA Mapping API is primarily a memory mapping and cache-coherency interface. It does not normally start the hardware DMA transfer.
The driver still programs the device registers or descriptors that start the transfer.
Hardware DMA and the Linux DMA API¶
A DMA transfer requires hardware support.
Examples include:
- A peripheral connected to a system DMA controller
- An Ethernet controller with an integrated DMA engine
- A USB controller with DMA capability
- A storage controller that can directly access system memory
If a device has no DMA capability and no hardware path to a DMA controller, the Linux DMA API cannot make the device perform DMA.
The driver must instead transfer data through CPU-controlled register access.
For DMA-capable hardware, Linux separates two responsibilities:
Linux DMA Mapping API
│
└── Prepares memory and returns a DMA address
Device Driver
│
└── Programs registers or descriptors and starts DMA
For example:
dma_addr = dma_map_single(dev,
buffer,
size,
DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_addr))
return -EIO;
writel(lower_32_bits(dma_addr), DMA_ADDR_REG);
writel(size, DMA_SIZE_REG);
writel(DMA_START, DMA_CONTROL_REG);
dma_map_single() prepares the memory mapping. The final register write starts the hardware transfer.
DMA Address Spaces¶
The CPU and DMA device may not use the same address for the same memory.
Three address types are important:
| Address | Used By | Purpose |
|---|---|---|
| CPU virtual address | CPU and kernel code | Address used by normal C pointers |
| Physical address | Physical memory subsystem | Location of the backing RAM |
| DMA address | DMA-capable device | Address presented to the device |
Conceptually:
A driver must not assume that:
This may appear true on simple systems without an IOMMU or bus translation, but it is not portable.
The driver must use the DMA address returned by the DMA Mapping API.
CPU Address and DMA Address¶
A DMA buffer commonly has two addresses:
CPU View
cpu_addr = 0xffff888012340000
│
▼
+----------------+
| DMA Buffer |
+----------------+
▲
│
Device View
dma_addr = 0x0000000080000000
The CPU uses the virtual address:
The device uses the DMA address:
The DMA address must not be dereferenced as a normal CPU pointer.
Coherent DMA and Streaming DMA¶
Linux DMA mappings are commonly divided into two models:
The main differences are buffer lifetime, ownership, and cache synchronization.
| Characteristic | Coherent DMA | Streaming DMA |
|---|---|---|
| Typical lifetime | Long-lived | Per transfer |
| Allocation | dma_alloc_coherent() |
General memory plus dma_map_single() |
| CPU address | Returned by allocation API | Existing CPU buffer |
| DMA address | Returned by allocation or mapping API | Returned by mapping API |
| Ownership | CPU and device share the buffer | Ownership transfers between CPU and device |
| Cache synchronization | Usually implicit | Managed through DMA mapping and sync APIs |
| Typical use | Descriptor rings, command queues | Packets, frames, transfer buffers |
Coherent DMA¶
Coherent DMA provides a buffer for which the CPU and device observe a consistent view of memory.
The term coherent means that different observers see consistent data.
A coherent DMA allocation returns:
- A CPU virtual address
- A DMA address
Example:
void *cpu_addr;
dma_addr_t dma_addr;
cpu_addr = dma_alloc_coherent(dev,
size,
&dma_addr,
GFP_KERNEL);
if (!cpu_addr)
return -ENOMEM;
The CPU accesses the buffer through cpu_addr.
The device accesses the same buffer through dma_addr.
Coherent DMA is commonly used for long-lived control structures such as:
- DMA descriptor rings
- Command queues
- Completion queues
- Shared control blocks
- Hardware-owned metadata
The allocation is released with:
Streaming DMA¶
Streaming DMA maps an existing CPU buffer for a particular transfer.
Typical flow:
CPU prepares buffer
│
▼
dma_map_single()
│
▼
Device owns mapping
│
▼
DMA transfer
│
▼
dma_unmap_single()
│
▼
CPU owns buffer again
Example:
dma_addr_t dma_addr;
dma_addr = dma_map_single(dev,
buffer,
size,
DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_addr))
return -EIO;
/* Program and start the DMA-capable device. */
/* Wait for or handle DMA completion. */
dma_unmap_single(dev,
dma_addr,
size,
DMA_TO_DEVICE);
Streaming DMA is commonly used for:
- Network packets
- USB transfer buffers
- SPI transfers
- Storage blocks
- Camera frames
- Audio buffers
DMA Direction¶
DMA direction is always described from the DMA device's point of view.
Important
DMA_TO_DEVICEmeans the device reads data prepared by the CPU.
DMA_FROM_DEVICEmeans the device writes data that will later be read by the CPU.
| Direction | CPU Write | CPU Read | Device Write | Device Read | Typical Use |
|---|---|---|---|---|---|
DMA_TO_DEVICE |
Yes | Yes | No | Yes | UART TX, SPI TX, Ethernet TX |
DMA_FROM_DEVICE |
No | Yes, after synchronization | Yes | No | UART RX, camera capture, Ethernet RX |
DMA_BIDIRECTIONAL |
Yes | Yes | Yes | Yes | Shared bidirectional buffers |
DMA_TO_DEVICE¶
Examples:
- Transmit an Ethernet packet
- Send data through SPI
- Send data through UART
- Submit a command block to hardware
DMA_FROM_DEVICE¶
Examples:
- Receive an Ethernet packet
- Capture a camera frame
- Receive SPI data
- Store ADC samples
- Receive UART data
DMA_BIDIRECTIONAL¶
Both the CPU and device may modify the buffer.
This direction may require more cache maintenance and should not be selected merely to avoid deciding the correct direction.
Ownership Transfer¶
Streaming DMA uses an ownership model.
Before mapping:
After dma_map_single():
After dma_unmap_single():
The complete transition is:
CPU owns buffer
│
▼
dma_map_single()
│
▼
Device owns buffer
│
▼
DMA transfer
│
▼
dma_unmap_single()
│
▼
CPU owns buffer
While the device owns a streaming DMA buffer, the CPU should not access it unless the DMA API explicitly transfers access back to the CPU.
Incorrect simultaneous access may cause:
- Partial updates
- Stale data
- Cache corruption
- Device-visible data mismatches
- Platform-dependent failures
Cache Coherency¶
DMA devices access memory independently of the CPU.
On a non-coherent platform, the CPU may modify cached data without immediately updating main memory.
The reverse problem also exists.
A device may write new data into memory while the CPU still has stale data in its cache.
The DMA Mapping API performs the platform-specific cache maintenance required by the mapping operation.
For long-lived streaming mappings that switch between CPU and device access, Linux also provides explicit synchronization APIs.
Synchronization for Device Access¶
dma_sync_single_for_device() makes CPU-written data visible to the DMA device.
Conceptually:
CPU modifies buffer
│
▼
CPU Cache = New
Memory = Old
│
▼
dma_sync_single_for_device()
│
▼
CPU Cache = New
Memory = New
│
▼
Device reads latest data
This is primarily associated with:
DMA_TO_DEVICEDMA_BIDIRECTIONAL
Synchronization for CPU Access¶
dma_sync_single_for_cpu() makes device-written data visible to the CPU.
Conceptually:
Device writes buffer
│
▼
Memory = New
CPU Cache = Old
│
▼
dma_sync_single_for_cpu()
│
▼
Memory = New
CPU Cache = New
│
▼
CPU reads latest data
This is primarily associated with:
DMA_FROM_DEVICEDMA_BIDIRECTIONAL
Memory Barriers and DMA Synchronization¶
Memory barriers and DMA synchronization solve different problems.
Memory Barrier¶
A memory barrier controls ordering.
It guarantees that memory operations become visible in the required order.
DMA Synchronization¶
DMA synchronization controls CPU and device data visibility.
A memory barrier does not replace cache synchronization.
Cache synchronization does not replace ordering barriers required by a device protocol.
Both may be required in the same driver.
ioremap() and DMA Mapping¶
ioremap() and dma_map_single() operate in opposite directions.
ioremap()¶
Makes device registers accessible to the CPU.
dma_map_single()¶
Makes CPU memory accessible to a DMA-capable device.
This distinction is important:
| API | Resource Being Mapped | Consumer |
|---|---|---|
ioremap() |
Device MMIO region | CPU |
dma_map_single() |
CPU memory buffer | DMA-capable device |
Common DMA Mapping APIs¶
| API | Purpose |
|---|---|
dma_alloc_coherent() |
Allocate a long-lived 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() |
End a streaming DMA mapping |
dma_sync_single_for_device() |
Synchronize a mapped buffer for device access |
dma_sync_single_for_cpu() |
Synchronize a mapped buffer for CPU access |
dma_mapping_error() |
Check whether a DMA mapping failed |
Simulator Design¶
The Day98 simulator focuses on the DMA memory model rather than a real DMA controller.
It models:
The simulator tracks:
- CPU virtual address
- Symbolic DMA address
- Mapping type
- DMA direction
- Buffer owner
- CPU cache validity
- CPU cache dirty state
- Device-visible memory
The simulator does not model:
- A real DMA controller
- PCIe transfers
- IOMMU page tables
- Scatter-gather lists
- Hardware descriptor formats
- DMA Engine framework internals
Summary¶
The Linux DMA API does not replace hardware DMA support.
It provides a portable way for drivers to expose CPU memory to DMA-capable hardware while handling:
- CPU and device address-space differences
- DMA address translation
- Mapping lifetime
- Buffer ownership
- DMA direction
- Cache coherency
- Platform-specific synchronization
The most important distinction is:
DMA Mapping API
Prepares memory for the device
Device Driver
Programs and starts the hardware transfer