Streaming DMA Mappings Transfer Buffer Ownership Between CPU and Device
A DMA buffer can be valid memory for both a CPU and a device while still requiring a strict handoff between them. Linux streaming DMA mappings express that handoff. The mapping API supplies a device-visible DMA address and gives the DMA layer a point at which architecture-specific cache maintenance, address translation, or bounce buffering can occur.
This matters most on systems where device DMA is not automatically coherent with CPU caches, but the ownership rules are part of the portable DMA API even on machines where cache maintenance becomes a no-op.
A DMA address is not a CPU pointer
dma_map_single() takes a CPU-accessible buffer and returns a dma_addr_t. The returned value is the address the device must use for the transfer. It is not a CPU virtual address and it need not equal the physical address backing the buffer.
An IOMMU can translate the DMA address. A platform DMA layer can also impose addressing constraints or route a transfer through a bounce buffer when the device cannot directly reach the original memory.
CPU virtual address
|
v
Linux DMA mapping layer
|
+--> cache maintenance when required
+--> IOMMU or bounce mapping when required
|
v
device-visible DMA addressDrivers therefore pass the returned DMA address to hardware rather than deriving a bus address from a CPU pointer.
Direction describes data flow and cache obligations
Streaming mappings carry a direction: DMA_TO_DEVICE, DMA_FROM_DEVICE, or DMA_BIDIRECTIONAL. The direction describes the expected data flow from the perspective of memory.
For DMA_TO_DEVICE, software finishes writing the buffer before handing it to the device. For DMA_FROM_DEVICE, the device is expected to write data that software later consumes. DMA_BIDIRECTIONAL covers buffers that can move data in both directions.
The direction is not decorative metadata. DMA implementations can use it to select cache operations and access permissions. Supplying a direction that does not match actual device access can break correctness on platforms that depend on those semantics.
Mapping transfers ownership to the device
After a streaming buffer is mapped for device access, the driver must treat the relevant region according to the DMA ownership rules. CPU access at the wrong time can race with the device and can also defeat cache maintenance performed by the DMA layer.
A typical transmit path has this shape:
CPU fills packet buffer
|
v
dma_map_single(..., DMA_TO_DEVICE)
|
v
device owns transfer interval
|
v
DMA completes
|
v
dma_unmap_single(...)
|
v
CPU may reuse bufferThe mapping call is not a command that starts DMA. Device-specific descriptor writes and doorbells still initiate the hardware operation. The mapping establishes the memory-side conditions needed for that operation.
Reused mappings need explicit synchronization
Some drivers keep a streaming mapping active across multiple ownership changes instead of unmapping it after every transfer. In that case, dma_sync_single_for_cpu() and dma_sync_single_for_device() mark the transitions.
After dma_sync_single_for_cpu(), software may access the region covered by the synchronization. Before the device uses that region again, the driver calls dma_sync_single_for_device() after its final CPU modification.
device phase
|
dma_sync_single_for_cpu()
|
CPU phase
|
dma_sync_single_for_device()
|
device phaseThese calls can translate into cache maintenance on a non-coherent architecture. On a coherent platform they can require little or no cache work, but portable driver logic cannot assume that outcome.
Cache-line sharing can corrupt adjacent data
Cache maintenance operates at cache-line granularity, not arbitrary byte granularity. A small DMA buffer that shares a cache line with unrelated CPU-written data creates a hazardous boundary on a DMA-incoherent system.
Consider a receive buffer occupying part of a line while a CPU-owned field occupies another part. Device writes and CPU cache writeback can then affect different bytes of the same line. Without suitable isolation, one side can overwrite data produced by the other.
Linux DMA documentation therefore places alignment and cache-line sharing constraints on DMA regions, especially for DMA_FROM_DEVICE and DMA_BIDIRECTIONAL use. The safe unit is not merely the C object size; the surrounding cache-line layout matters.
Coherent DMA memory has a different contract
dma_alloc_coherent() provides a different mapping model. CPU and device accesses to coherent DMA memory are arranged so each side can observe the other’s updates without explicit cache flushing for each ownership transition.
That property does not remove ordering requirements. A CPU can reorder stores, so descriptor fields that must become visible in a particular order still need the appropriate memory barrier. Coherency answers whether both agents can observe memory updates; ordering determines the sequence in which those updates become meaningful.
Streaming mappings are commonly suited to payload buffers that move through distinct CPU and device phases. Coherent mappings are commonly suited to long-lived shared control structures such as descriptor rings. The exact choice remains device- and platform-dependent.
Ownership is separate from transfer completion
A DMA mapping does not prove that hardware has finished with the buffer. Completion comes from the device’s protocol: a completion descriptor, status bit, interrupt, polled queue state, or another device-defined signal.
The driver must first establish that the device has completed the relevant access. Only then can it unmap the streaming region or synchronize it back for CPU access.
This separation prevents two different contracts from being collapsed into one operation. The device defines when its transfer is complete. The DMA API defines how memory becomes valid for the next owner across the CPU-device boundary.