DMA API
dma-api provides typed DMA ownership primitives for drivers. The public
surface separates three different concepts that should not be mixed:
CoherentArray<T>/CoherentBox<T>: CPU and device see the same memory without explicit cache maintenance. Use these for descriptor rings, command queues, completion queues, and controller contexts. Coherency does not provide ordering; drivers still need their normal barriers before doorbells and after completion ownership changes.ContiguousArray<T>/ContiguousBox<T>: owned device-address-contiguous DMA memory using normal CPU mapping. Use these for data buffers and buffer pools. CPU-only accessors are named with a_cpusuffix; ownership transfer is explicit viaprepare_for_device(_all)/complete_for_cpu(_all)or the higher-level*_for_device/*_from_devicehelpers.StreamingMap<T>: RAII mapping of an existing caller-owned buffer. Use this for one transfer of a buffer not owned bydma-api. Explicit sync methods handle cache maintenance and bounce-buffer copy, andDropunmaps.
The *_for_device and *_from_device helpers are convenience ownership
transfer APIs. They wrap the same synchronization operations as
prepare_for_device(_all) and complete_for_cpu(_all): CPU writes are made
visible before the device runs, and device writes are made visible before CPU
reads. They do not detect device completion, place MMIO doorbells, or provide
hardware ordering barriers. Drivers still decide when a transfer is submitted
and when it has completed.
DmaAddr is the only portable device-visible address type. Backend-private raw
handles are split into DmaAllocHandle for owned allocations and
DmaMapHandle for streaming mappings.
Constraints
Every allocation and mapping is checked against DmaConstraints:
DeviceDma::new(dma_mask, op) is shorthand for
DmaConstraints::new(dma_mask). Use with_constraints when a specific queue
or transfer has stronger alignment, boundary, or segment-size requirements.
Backends must never hand a driver a DMA address outside the requested mask. For
example, a device created with DeviceDma::new(u32::MAX as u64, op) must only
return 32-bit reachable DMA addresses. Streaming mappings may use a fast path
when the original buffer already satisfies the constraints; otherwise they
should allocate an in-mask bounce buffer.
Backend Contract
Implement DmaOp once for the platform:
use ;
use ;
;
The default sync methods perform cache maintenance and handle bounce-buffer copying. Platforms can override them if the architecture needs a different policy.
Driver Usage
Descriptor/control memory:
let mut ring = dma.?;
ring.set_cpu;
doorbell_after_release_barrier;
Owned data buffers:
let mut tx = dma.?;
tx.copy_to_device_from_slice;
submit;
Device-written owned buffers:
let rx = dma.?;
submit;
wait_complete;
rx.read_from_device;
Streaming mappings:
let map = dma.map_streaming_slice_for_device?;
submit;
wait_complete;
map.complete_for_cpu_all;
Buffer pools use ContiguousBufferPool and return ContiguousBuffer values.
They are intended for repeated owned data buffers such as network RX/TX pools
or block read buffers. Reusing a buffer does not imply that the memory is
zeroed again; callers own the content and the explicit sync points.
Choosing A Primitive
Use Coherent* for hardware metadata whose CPU and device ownership flips
frequently and where per-transfer cache maintenance would be wrong or too
fragile: xHCI contexts and rings, NVMe SQ/CQ, network descriptor rings,
SD/MMC descriptor tables.
Use Contiguous* for owned payload memory that needs a contiguous
device-visible DMA address range but not an uncached CPU mapping: block data,
network pools, NVMe PRP data buffers, and accelerator input/output buffers.
Use StreamingMap for caller-owned buffers whose lifetime is tied to one DMA
operation: USB transfer buffers, SDHCI/DWMMC/Phytium MCI block request slices,
or any buffer allocated outside the DMA API.