ARM64 DMA Cache Maintenance: Clean vs Invalidate Before Device Access
Deciding between DC CVAC and DC IVAC for ARM64 DMA buffers, with a worked example and alignment pitfalls.
10 Jul 2026, 19:35 UTC

Problem: the device sees stale memory
You fill a transmit buffer in CPU memory, tell a DMA engine to send it, and the wire transmits old data. Or a network card writes a receive buffer, your driver reads it, and the contents are from the previous packet. On ARM64, the likely culprit is the CPU's write-back cache. Stores from the CPU may sit in cache lines and never reach RAM, while the DMA engine reads RAM directly. Conversely, the device may update RAM while the CPU still holds an older copy in its cache. The takeaway is concrete: explicitly maintain cache lines around DMA, and choose the operation by direction of data flow.
How ARM64 write-back caches break DMA
ARM64 cores typically use write-back caches: a CPU store updates the cache and marks the line dirty, but RAM is not updated until the line is evicted or cleaned. A DMA engine does not snoop the CPU cache on most systems; it reads and writes system RAM. This creates two failure modes. If the CPU wrote data and the device reads RAM, the device sees stale or zeroed memory because the dirty line has not been flushed. If the device wrote RAM and the CPU reads, the CPU may return a cached copy that predates the device write. ARM64 does not keep DMA and CPU caches coherent by default for normal memory, so software must issue cache maintenance instructions.
Clean versus invalidate: pick by data direction
The two instructions are DC CVAC (clean by virtual address to point of coherency) and DC IVAC (invalidate by virtual address to point of coherency). Clean pushes dirty cache lines from the CPU to RAM so an external reader sees the latest data. Invalidate drops cache lines so the next CPU read fetches from RAM. Use clean before the device reads CPU-produced data. Use invalidate before the CPU reads device-produced data. Never invalidate a line that the CPU may have modified without cleaning it first, or you will discard unwritten data. Both instructions require a data synchronization barrier (DSB SY) before and after to guarantee ordering relative to the DMA start or CPU read.
| Operation | Instruction | Use when | Risk if skipped |
|---|---|---|---|
| Clean | DC CVAC, dma_buf | CPU wrote buffer, device reads RAM | Device reads stale or zeroed data |
| Invalidate | DC IVAC, dma_buf | Device wrote buffer, CPU reads RAM | CPU reads stale cached copy |
Worked example: cache maintenance around a DMA buffer
This snippet runs in privileged kernel context, such as a device driver, because DC CVAC and DC IVAC are inaccessible from EL0 and trap if executed in userspace. Replace dma_buf with your buffer pointer and size with the buffer length. The buffer should be cache-line aligned; on many ARM64 implementations the line size is 64 bytes, but read CTR_EL0 to confirm for your core.
/* ARM64 assembly, kernel/EL1 context only */
/* x0 = buffer virtual address, x1 = size in bytes */
dsb sy
1: dc cvac, x0 // clean cache line to RAM
add x0, x0, #64 // advance by one cache line (verify actual line size)
sub x1, x1, #64
cmp x1, #64
b.ge 1b
dc cvac, x0 // clean final partial line
dsb sy // ensure clean completes before device starts
/* start DMA here */
For the receive path, replace dc cvac with dc ivac and place the barrier after the device signals completion but before the CPU reads the buffer. In C, the equivalent is typically wrapped by the DMA mapping API; for example, dma_sync_single_for_device performs the clean, while dma_sync_single_for_cpu performs the invalidate. The assembly above shows what those helpers do underneath.
Limitation: alignment and dirty-line hazards
Cache maintenance operates on whole cache lines. If your buffer is not aligned or straddles lines, cleaning or invalidating can touch adjacent memory. Invalidating a dirty line discards CPU modifications that have not reached RAM, silently losing data. The cost is also non-zero: walking a large buffer line by line adds latency before every DMA transfer. For small control structures, prefer coherent allocations such as dma_alloc_coherent, which the kernel maps with cache attributes that avoid explicit maintenance, at the expense of potentially higher memory usage or slower CPU access.
Practical verification
Fill the buffer with a known pattern, run the clean sequence, start the device, and read RAM through a non-cached alias or after invalidating to confirm the device observed the pattern. For the receive path, have the device write a marker, invalidate, then read from the CPU and compare. If the values mismatch, check that you used the correct instruction for the direction, added DSB SY barriers, and aligned the buffer to the cache line size reported by CTR_EL0. Do not assume the operation succeeded merely because no exception occurred; a missing barrier can still leave stale data visible to the device.
Actionable closing
- Confirm your buffer is aligned to the ARM64 cache line size (read CTR_EL0).
- Use DC CVAC followed by DSB SY before the device reads CPU-written RAM.
- Use DC IVAC followed by DSB SY before the CPU reads device-written RAM.
- Never invalidate a potentially dirty line without cleaning it first.
- Use dma_alloc_coherent for small control blocks when explicit maintenance is error-prone.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.