Addresses in device descriptors cannot be directly cast from CPU pointers; unmap is not a command to stop hardware. DMA requires managing addresses, direction, visibility, and handover of ownership.
Lookup Target
Reading Location
Addresses and coherent/streaming
Sections 1–2
Direction, completion, and SG counts
Sections 3–4
IOMMU, bounce, and isolation
Sections 5–6
1. Three Types of Addresses Cannot Be Cast to Each Other
Address
Who Uses It
Typical Type
CPU Virtual Address
Kernel code accessing buffers
void *
CPU Physical Address
Memory and resource management
phys_addr_t
DMA Address
Written into device descriptors for device addressing
dma_addr_t
An IOMMU can translate IOVAs issued by devices into physical page addresses; without an IOMMU, DMA addresses may also be affected by host bridge offsets, etc. dma_addr_t cannot be dereferenced directly, nor obtained by arbitrarily casting CPU pointers. Even if the values happen to be identical on a specific machine, this is not a portable guarantee.
Drivers set the DMA mask according to hardware capabilities. The address width of descriptors and data payloads can differ; set the streaming mask and coherent mask separately if necessary. A narrower mask may not be supported on the platform, so handle API return values; do not claim device support for 64-bit DMA just because the CPU is 64-bit.
2. Coherent and Streaming Solve Different Access Patterns
Model
Common Use Case
Driver Responsibilities
Coherent allocation
Long-lived descriptors and status areas accessed frequently by both parties
Access ordering, mutual exclusion, hardware completion, and release timing
Streaming mapping
Bulk transfers like network packets or storage data
Direction, ownership handover, synchronization, and mapping lifetime
dma_alloc_coherent() returns a CPU address and outputs a DMA address for device use. When freeing, use the matching device, size, CPU address, and DMA handle. Coherent memory exempts this region from explicit cache maintenance for normal access, but it does not mean all writes are immediately visible to the device in source code order, nor does it automatically composite operations into atomic ones.
For example, when publishing a descriptor, write the address and length first, execute the appropriate dma_wmb(), and finally set the valid bit according to the device protocol; after the device completes, the CPU may also need dma_rmb() to safely read data published with the completion flag. MMIO doorbell access ordering must also satisfy I/O accessor and hardware requirements. Barriers do not equal waiting for DMA completion.
"Streaming" in streaming mappings does not mean mapping only once, nor does it promise to be faster than coherent; performance depends on the platform, cache, mapping overhead, and access patterns. Concepts and address rules are in the DMA HOWTO, and specific interfaces are in the DMA API.
3. Direction is Based on Data Flow Between Device and Memory
DMA_TO_DEVICE: Device reads from memory, e.g., sending network packets.
DMA_FROM_DEVICE: Device writes to memory, e.g., receiving network packets.
DMA_BIDIRECTIONAL: Both sides write, and ownership and synchronization protocols must still be followed.
Do not just mark "read" or "write," as the CPU and device perspectives are exactly opposite. Use accurate directions when determined; the device, range, and direction used for map, sync, and unmap must match the API's pairing requirements.
A single receive can be understood as: the CPU allocates a suitable buffer and maps it as FROM_DEVICE, submitting the returned DMA address to the device; unmap after confirming the device has truly completed, then the CPU reads. Unmapping is not a command to stop hardware; you must first confirm the device is no longer accessing the memory.
When Mapping is Not Revoked, CPU and Device Must Still Hand Over Access Rights
When retaining a mapping long-term, call dma_sync_single_for_cpu() after confirming device completion before the CPU enters the access phase; call dma_sync_single_for_device() before handing back to the device. These calls may involve cache maintenance or bounce buffer copying, but they do not prevent the other party from accessing simultaneously on behalf of the driver.
In the simplified model of non-coherent platforms, CPU cache and RAM contents are shown separately. Ownership is transferred via synchronization actions; observe which step is missing between device completion and CPU readiness to read.
Preparing the visual
DMA has written to RAM, why can't the CPU use its old cache directly?
Device completion, cache visibility, and mapping lifetime are different conditions. sync does not wait for ongoing DMA, unmap does not stop hardware; CPU VA, physical address, and DMA address are not interchangeable.
{"id":"dma-streaming-ownership","title":"DMA has written to RAM, why can't the CPU use its old cache directly?","summary":"Device completion, cache visibility, and mapping lifetime are different conditions. sync does not wait for ongoing DMA, unmap does not stop hardware; CPU VA, physical address, and DMA address are not interchangeable.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"cpuWrite\" data-i18n=\"cpuWrite\"></button><button id=\"forDevice\" data-i18n=\"forDevice\"></button></div><div class=\"actions\"><button id=\"deviceWrite\" data-i18n=\"deviceWrite\"></button><button id=\"complete\" data-i18n=\"complete\"></button></div><div class=\"actions\"><button id=\"forCPU\" data-i18n=\"forCPU\"></button><button id=\"unmap\" data-i18n=\"unmap\"></button></div><div class=\"actions\"><button id=\"map\" data-i18n=\"map\"></button></div><div class=\"address\">CPU VA 0xffff800000001000<br>PA 0x4000 · DMA 0x8000</div><div class=\"track-row\"><span data-i18n=\"mapping\"></span><div id=\"mapping\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"cpu\"></span><div id=\"cpu\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"ram\"></span><div id=\"ram\" class=\"track\"></div></div><div class=\"card\"><header data-i18n=\"owner\"></header><div id=\"owner\" class=\"readout\"></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.address{font:11px/1.8 var(--viz-font-mono);overflow-wrap:anywhere;margin:15px 0}.readout{min-height:5.3em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let cpu=[0,0,0,0],ram=[0,0,0,0],owner='CPU',mapped=true,active=false,serial=0;function draw(){cells('cpu',cpu.map(n=>({text:String(n),cls:owner==='CPU'?'on':'empty'})));cells('ram',ram.map(n=>({text:String(n),cls:owner==='DEVICE'?'gold':'empty'})));cells('mapping',[{text:mapped?'0x8000 → 0x4000':'0x8000 × 0x4000',cls:mapped?'on':'empty'}]);set('owner',owner+'\\n'+t(active?'active':'idle'));$('#cpuWrite').disabled=owner!=='CPU';$('#forDevice').disabled=!mapped||owner!=='CPU';$('#deviceWrite').disabled=!mapped||owner!=='DEVICE'||active;$('#complete').disabled=!active;$('#forCPU').disabled=!mapped||owner!=='DEVICE'||active;$('#unmap').disabled=!mapped||active;$('#map').disabled=mapped;set('explanation',t('note'));}$('#cpuWrite').onclick=()=>{cpu=[++serial,serial,serial,serial];draw();};$('#forDevice').onclick=()=>{ram=[...cpu];owner='DEVICE';draw();};$('#deviceWrite').onclick=()=>{ram=[++serial,serial,serial,serial];active=true;draw();};$('#complete').onclick=()=>{active=false;draw();};$('#forCPU').onclick=()=>{cpu=[...ram];owner='CPU';draw();};$('#unmap').onclick=()=>{if(owner==='DEVICE')cpu=[...ram];mapped=false;owner='CPU';draw();};$('#map').onclick=()=>{ram=[...cpu];mapped=true;owner='DEVICE';draw();};$('#reset').onclick=()=>{cpu=[0,0,0,0];ram=[0,0,0,0];owner='CPU';mapped=true;active=false;serial=0;draw();};draw();","audio":false,"strings":{"active":"Device request in progress","complete":"Confirm DMA has completed","cpu":"CPU cache view","cpuWrite":"CPU modifies content","deviceWrite":"Device starts writing","forCPU":"sync_for_cpu","forDevice":"sync_for_device","heading":"DMA has written to RAM, why can't the CPU use its old cache directly?","idle":"Device no longer accessing","intro":"Hand over streaming buffers between CPU and device. Modify CPU content first, then hand over for device writing; complete DMA, sync back to CPU, or unmap separately.","map":"Remap for device","mapping":"DMA address → Physical address","note":"After device writes, the CPU line may still hold the old value. Confirm completion first, then sync_for_cpu, to enter the CPU access phase per protocol; the disabled actions in the diagram are precisely these unmet conditions.","owner":"Currently allowed accessing party","ram":"RAM / Device access view","reset":"Reset","scope":"DMA_BIDIRECTIONAL on a non-coherent platform, four-byte exclusive cache line, no bounce. Initial mapping has completed one sync_for_cpu. The diagram shows both parties' copies from an observer's perspective; CPU reading during device ownership is not permitted. Handover folds necessary cache maintenance; do not interpret this as data copying on every coherent platform.","scopeLabel":"Model Scope and Conventions","unmap":"Unmap DMA"}}
What Memory Can Be Mapped Directly
dma_map_single() is suitable for contiguous memory meeting constraints, such as properly allocated kmalloc buffers. Stack memory, kernel image addresses, and virtually contiguous addresses returned by vmalloc cannot be directly used as generic single-segment DMA buffers. vmalloc underlying pages need to be handled via appropriate per-page/SG paths.
You should also avoid sharing cache lines between CPU fields and device write areas; on non-coherent platforms, such sharing can cause data overwrites. dma_mapping_error(dev, addr) checks if mapping failed; a DMA address of zero does not automatically represent failure.
4. Two Counts for Scatter-gather
SG allows expressing a single data transfer using multiple memory fragments. The DMA layer can merge adjacent fragments, so the number of segments seen by the device may be less than the number of input entries.
Below is partial API usage, assuming sgl is correctly established and orig_nents > 0:
int mapped_nents =dma_map_sg(dev, sgl, orig_nents, direction);if(mapped_nents ==0)return-EIO;/* Program hardware DMA segments: use the mapped count and sg_dma_* fields. */struct scatterlist *sg;int i;for_each_sg(sgl, sg, mapped_nents, i){
dma_addr_t addr =sg_dma_address(sg);unsignedint len =sg_dma_len(sg);/* Fill addr/len into actual hardware descriptors; hardware not started here. */dev_dbg(dev,"DMA segment %d: %pad / %u\n", i,&addr, len);}/* The actual driver must confirm hardware has stopped using the buffer before executing the next line. */dma_unmap_sg(dev, sgl, orig_nents, direction);
Usage Location
Which Count to Pass
Setting DMA segments for hardware
mapped_nents
dma_unmap_sg
Original orig_nents
dma_sync_sg_for_*
Original orig_nents
The mapping layer generates segments from the device's perspective and must also retain information needed to restore the original mapping. Do not use the merged count for all interfaces. See the SG API documentation.
5. IOMMU and SWIOTLB Can Coexist
IOMMU provides address translation, permissions, and isolation via page tables, and may also map discrete physical pages into contiguous IOVAs. It does not guarantee that any SG becomes a single segment; it is still constrained by segment length, boundaries, and mapping resources. Interrupt remapping is a related but independent capability provided by the platform; you cannot assume all interrupt safety conditions are met just because DMA translation exists.
SWIOTLB provides bounce buffers: intermediate buffers accessible by device operations, where the DMA layer moves data at corresponding synchronization points. Besides limited device addressing, shared memory constraints in confidential computing may also use bounces; therefore, it is not simply "if IOMMU exists, no copy; if no IOMMU, then SWIOTLB."
The direction of copying relates to the DMA direction: TO_DEVICE requires sending data prepared by the CPU to a region readable by the device, while FROM_DEVICE retrieves device results when the CPU receives data. Whether copying is needed and at which synchronization point it occurs is determined by the mapping and synchronization paths; do not assume bidirectional copying on every map/unmap.
The SWIOTLB pool size parameter has its own units; do not treat slot counts directly as KiB. For example, with common 2 KiB slots, 65536 slots is 128 MiB, not 64 MiB. Specific parameters, dynamic pools, and limits are subject to the target kernel's SWIOTLB documentation.
6. Domain, Group, and VFIO
Object
Question Answered
IOMMU domain
Which address space and mapping policy to use? Multiple devices can share
IOMMU group
What is the smallest set of devices the system can reliably isolate?
Groups are influenced by topology, ACS, and device behavior, and are not fixed as one group per PCI function.
When handing over a function from a group to untrusted user space, you must also control other related devices in the group to prevent driver access that could bypass isolation boundaries. This does not mean every function must actually be presented to the same VM; specific binding and ownership constraints are managed by the VFIO/IOMMU interface.
VFIO allows controlled exposure of device resources, DMA mappings, and interrupts. The underlying guest memory for device access remains host physical pages; the protection goal is to ensure it can only access authorized mappings, not to write "cannot access host memory at all." Special modes without IOMMU cannot be considered to have the same isolation guarantees. See the VFIO documentation.
Reading Order
For address exceptions, first check DMA mask, return values, and addresses written to descriptors; for data corruption, check direction, completion timing, cache line sharing, and barriers; for post-release corruption, check if unmap/free occurred before hardware and callbacks finished. IOMMU faults indicate that a device access violated mapping conditions, but you cannot conclude that the page table implementation is wrong solely based on a fault.