2026-07-18
Your CPU has an MMU that translates virtual addresses to physical ones. But when a NIC does DMA, or a GPU reads a buffer, those devices issue raw bus addresses. Without help, they can only see physical memory — and worse, a malicious or buggy device could DMA over your kernel. The IOMMU (Intel VT-d, AMD-Vi, ARM SMMU) is a second MMU sitting on the I/O side of the memory controller, translating device-issued addresses through page tables the OS controls.
An IOMMU serves three jobs:
The mechanism: when a device issues a DMA to address X, the transaction arrives at the root complex tagged with the device's BDF (Bus/Device/Function). The IOMMU uses BDF to look up a context entry, which points to a page table. It walks that table exactly like an MMU walks yours — same 4-level structure on x86, cached in an IOTLB.
Real-world example: Linux's iommu=pt (passthrough) mode is common on servers because full IOMMU translation isn't free. A DMA to an untranslated address hits the IOTLB. On miss, the IOMMU walks page tables — that's four memory accesses per DMA descriptor. On a 100 Gbps NIC pushing 8M packets/sec, an IOTLB miss rate of even 1% can burn measurable memory bandwidth just walking device page tables.
Rule of thumb: IOTLB entries are precious — usually 64 to 512 per unit. If your workload scatters DMAs across more 4K pages than the IOTLB holds, you're page-walking constantly. Fix: use hugepages for DMA buffers. One 2 MB IOTLB entry covers what would otherwise need 512 entries. NVMe drivers doing multi-queue I/O benefit hugely.
Gotcha: IOMMU invalidations are slow. When a driver unmaps a DMA region, the IOMMU must flush IOTLB entries, which requires a serialized command sent over the IOMMU's command queue. High-frequency map/unmap cycles (naive networking code) can bottleneck on invalidation throughput long before CPU or PCIe does.
