Host only
Give a device its own DMA address space. Host tables map a device GPA / IOVA to RAM and enforce isolation. No PASID guest lookup is needed.
V=1 · TV=1 · GV=0 · Mode=1–6
Pick any colour. The displayed accent adjusts for readable contrast in light or dark mode.
IOMMU / INTERACTIVE WORKING MODEL
Trace a device request from its address space to memory—or to a fault.
AMD-style semantic model · up to four guest levels · configurable host levels · 4 KiB / 2 MiB / 1 GiB leaves · a synthetic Ethernet NIC and selected raw entry encodings.
An IOMMU controls which memory a device may access. Begin with the default mapping, select Translate, then step through DMA. Compare the two fault examples to see why an invalid page copies no bytes.
START HERE
A device sends a memory address. The IOMMU checks a device entry (DTE), follows a set of lookup tables, and either finds an allowed memory location or stops the request. A page is a mapped block of 4 KiB, 2 MiB or 1 GiB; a page entry is an 8-byte record pointing to another table or the final block.
Give a device its own DMA address space. Host tables map a device GPA / IOVA to RAM and enforce isolation. No PASID guest lookup is needed.
V=1 · TV=1 · GV=0 · Mode=1–6
Use a PASID-selected address space without a second physical remap. Guest tables map GVA to GPA, and GPA already is SPA.
V=1 · TV=1 · GV=1 · Mode=0
Give a process inside a VM a virtual address space, while the host controls which RAM the VM may use. Guest-table memory also needs host translation.
V=1 · TV=1 · GV=1 · Mode=1–6
These presets assume the required hardware features and global enables. “Guest” names the first translation stage; it does not mean a VM is required for every use. Effective PASID width is the smallest of the PCIe device capability, the IOMMU’s PASmax and its GCR3 capacity. The device capability is separate from the DTE.
Choose up to eight PASID contexts. Each maps the chosen device virtual page to a separate GPA and system physical page, illustrating separate address spaces. With mixed page sizes, only the region covered by both stages is accessible. This demonstration is not a hardware isolation test or proof.
Mapped frame bases must align to their leaf size. A DMA address may include any byte offset within that mapping; it does not need to be page aligned. Table pages in this model remain 4 KiB aligned.
Skipped levels retain the original address bit positions; they are not shifted or packed together. A data leaf cannot be skipped. Nested Mode 1 is not offered by this lab because its guest tables are allocated above 2 MiB; the architecture itself permits Mode 1.
Each PASID has one guest data leaf and one host data leaf. If the host page is smaller, only the host-sized region containing the configured guest address is mapped. Page-table storage always uses 4 KiB blocks. Guest Only bypasses the host stage; Host Only bypasses the guest stage.
The PCIe PASID capability describes the device’s maximum PASID width. GLX selects the IOMMU’s GCR3 table capacity. This demo uses the smaller range as a software guard against aliases. With a 16-bit NIC and GLX=1, the effective capacity is 65,536 PASIDs (IDs 0–65,535), even though the two-level GCR3 structure can represent more.
GLX capacity: 0 → 9 bits; 1 → 18 bits; 2 → 20 bits. The demo assumes the IOMMU hardware supports the selected GLX and a 20-bit PASID ceiling. A platform may impose further limits. AMD 48882 §2.2.6.3 specifies that GLX=0 ignores PASID bits above bit 8, and GLX=1 ignores bits above bit 17. Software must choose enough levels for the PASIDs it uses. This demo rejects such aliases before issuing a request; that rejection is not an emulated hardware IO_PAGE_FAULT. These mock capabilities do not read your hardware.
Linux PCIe PASID capability handling · AMD GCR3 table capacityIn this example, the System Physical Address (SPA) of the backing memory is given, and the addresses are synthetic. The C++ linear address is a separate CPU-side buffer association, not a pointer cast or a CPU page-table walk. Values are synthetic; no buffer is allocated at these addresses.
Capabilities describe what the NIC supports; control registers enable those features. “+” means a bit is set and “−” means it is clear, as in lspci output. These controls apply immediately; the device width above changes when you apply the mapping.
Offsets are relative to each extended capability: PASID ID 0x001B and ATS ID 0x000F. The Command register is at configuration-space offset 0x04. Unused bits are zero in this selected-field example.
Linux PCIe register definitions · Linux PASID/ATS enablement · pciutils decoding
This query uses the current request below and returns a translation with read/write permissions. It copies no packet bytes and does not add an IO_PAGE_FAULT entry for a failed page lookup. This is a standalone probe; it does not warm the DMA cache. Choose ATS / pretranslated DMA in section 05 to observe the full query, cache and transfer sequence.
A selected DTE with V=1 and TV=0 is not a page-table lookup failure. AMD 48882 rev 3.11 Table 64 classifies that ATS request as INVALID_DEVICE_REQUEST with TR=1; this model stops without a response or event. AMD IOMMU Table 64.
Enable ATSCtl and DTE.I, then issue a query.
Guest walk: PML4 / 48-bit teaching profile · independent of host Mode
A receive operation (RX) writes packet bytes from the NIC into RAM. A transmit operation (TX) reads RAM into the NIC. Both use the request address, PASID, access and fault settings above.
| Operation | AT[1:0] | Address and effect |
|---|---|---|
| Untranslated memory DMA | 00 |
NIC sends the original GVA or GPA/IOVA. The IOMMU translates it and checks access before memory is touched. |
| ATS translation request | 01 |
NIC asks the translation agent for an address and permissions. This query copies no payload bytes. |
| Translated / pretranslated memory DMA | 10 |
NIC sends an SPA obtained through ATS and enforces the cached permissions. The IOMMU does not repeat the page-table walk for this permitted device. |
| Reserved | 11 |
Not selectable in this demonstration. |
AT is a two-bit field. AT[1]=1 in the translated
encoding 10; the value 01 means a
translation request. AMD calls the device-translated address
“pretranslated”—it is not a fourth kind of memory DMA. An address
calculated by software is not automatically an ATS translation.
Untranslated DMA and ATS can use the same guest and host tables. What changes is when translation occurs: during the memory request, or earlier so the NIC can cache the result. Cache entries must be invalidated when mappings or permissions change. This demo starts with an empty cache on each prepare/configuration change.
AMD PCIe AT encodings · AMD 48882: pretranslated address, Definitions and §1.1
Byte preview: first 64 bytes (offsets 0–63). The counter covers the full descriptor. Each page needs a valid translation and permission before its bytes move. ATS mode uses the NIC’s cached translation for AT=10 requests. A failed page copies zero bytes; earlier completed pages are not rolled back.
Hex bytes correspond to consecutive request offsets. “—” means no backing address has been resolved for that byte. RAM starts with byte[offset] = offset mod 256, or your repeating pattern when patterned reads are selected. Highlighted cells were written or read. Each run starts from the same mock memory. Other PASID buffers are separate allocations.
These examples select untranslated DMA (AT=00), enable the NIC and choose a mapped PASID. Your page sizes and read/write choice stay selected. Use the ATS examples above to compare recovery.
The first page fails its presence check. 0 of 16 bytes copied.
Start 8 bytes before a valid page ends. Copy those 8 bytes, then reject the next page. 8 of 16 bytes copied; 0 from the invalid page.
The breakdown predicts the configured descriptor. The progress bar and byte display below show only the events reached during playback. An earlier successful page is not rolled back when the next page faults.
Head and Tail are byte offsets. Eight slots (seven usable) demonstrate pointer movement; these are not hardware-valid buffer allocations. Command fetch advances Head; COMPLETION_WAIT establishes completion.
The PPR log is separate from the IO_PAGE_FAULT event buffer below. An incoming PRI request creates one PPR entry. Consuming it advances Head; COMPLETE_PPR_REQUEST sends the response and does not append another PPR entry. These are semantic records, not packed hardware words. The mock OS restores only an injected missing data leaf in an existing allocation; it refuses unmapped neighbouring pages and permission upgrades.
The fault selector describes the initial state. Page-in changes this transfer’s private table copy; inspect an ATS response to see those entries. Reset restores the initial fault. ATS replies are conservatively cached as 4 KiB entries even for larger leaves. Transfers use synthetic bursts of at most 256 bytes to make cache reuse visible.
Logging enabled · DTE SA=0 / SE=0 · memory requests, not ATS queries. The eight slots are a compact visual teaching buffer, not a hardware-valid event-buffer size. A page fault appends a 16-byte IO_PAGE_FAULT record. This is the IOMMU’s event log, separate from the NIC’s interrupt-remapping table. Successful DMA does not create a fault event.
Teaching model: one descriptor, chunks of at most 256 bytes that never cross a 4 KiB boundary or a mapping, and no real packets or hardware. Earlier successful chunks remain copied if a later chunk faults; this is not a model of PCIe timing, descriptor rings or a particular NIC.
The Requester ID chooses this record. It supplies the roots and permissions; it does not contain the page mapping itself.
A PASID identifies an address space. The GCR3 tables act like a directory: the PASID selects a guest page-table root. They do not translate the data address yet.
0x180000 SPA
↓ selected GCR3 depth
↓ selected entry contains a GPA (equals SPA in Guest Only)
0x100000 SPA
↓ Mode=6
The selected Host Mode chooses the root level. NextLevel points to the next directory or leaf, allowing the configured zero-index levels to be skipped. A 4 KiB data leaf is at level 1; 2 MiB and 1 GiB leaves end at levels 2 and 3. In Guest + Host, guest-table storage uses separate 4 KiB host mappings.
FOLLOW THE POINTERS
You do not need to memorize the hexadecimal numbers. Follow the named tables, select a card, and see which slot points to the next table or data frame. These cards use the same walk as the detailed trace below.
Choose a step to inspect its entries, or step through the request. In Guest + Host, guest and subordinate GCR3 table fetches also need host translation. In Guest Only, GPA already equals SPA. Expand any entry to inspect its bits and mocked memory.
A page fault means the lookup or permission check could not complete. It does not automatically mean the hardware is broken. The failing entry tells software what is missing or disallowed.
The next 4 KiB page has no entry. The original page remains accessible.
A read succeeds; a write is rejected. Present and writable are different checks.
In a nested walk, the host must map the guest table itself before its entry can even be read.
Examples reset to a known mapping so their outcomes are reproducible.
Real software may install a mapping, adjust authorized permissions, invalidate cached translations and retry through an appropriate protocol. The address walk shows a teaching diagnosis. Running DMA also encodes the selected IO_PAGE_FAULT record; neither action models automatic hardware recovery.
An IRTE belongs to an interrupt-remapping table. It does not translate the DMA address and is not another guest or host page-table level. This simplified legacy-style table uses 4-byte entries. The independent probe below previews routing; the DMA demonstration delivers a completion interrupt only after a successful transfer. Its generated C++ packs and decodes the selected IRTE fields.
0x1C0000 SPAIRTE[index]Vector + APIC destination
Disabled or absent entries stop this educational interrupt path. Other delivery modes, guest virtual interrupts, interrupt-fault event records and real MSI message decoding are outside this model.
Validity of this model: this is an executable teaching model of selected AMD IOMMU behaviours, not a device emulator or conformance test. The generated C++ and browser walker are compared for translation paths, page sizes, permission faults, skipped levels and ATS/PRI scenarios. Agreement between two implementations is useful regression evidence; it does not prove agreement with silicon. To program an emulated one by hand, open the QEMU labs: QEMU's AMD, Arm and RISC-V IOMMUs in your browser.
The C++ rejects inconsistent packed/decoded fields, misaligned supported leaves and a guest walk without a leaf. Host entries are limited to read-enabled pages; raw entry editing is not a complete architectural parser. Command execution is serial, PRI handles one group, and timing, coherency, A/D updates and feature-dependent errors are outside its scope. Read the validation scope and reuse guide.
The guest uses canonical 48-bit virtual addresses and four 9-bit indices (PML4E, PDPE, PDE, PTE). Host Mode selects 1–6 levels; their shifts are 12, 21, 30, 39, 48 and 57; only seven top-index bits are available in a 64-bit input. The mapping builder limits physical page bases to 52 bits.
The GCR3 root is an SPA in this model; a valid Level 1 GCR3 entry holds the GPA of the guest PML4. Subordinate GCR3 pointers are GPAs; the example identity-maps their backing pages when host translation is active. GLX=0/1/2 selects one/two/three GCR3 levels. Index slices are PASID[8:0], [17:9], and [19:18]. This demo combines the PCIe device’s configured PASID width with the GLX capacity and uses the smaller range as a software guard against aliases. A 16-bit PASID fits in two levels; GLX=2 can still be used with a 16-bit width, with a zero top index. Only explicitly mapped PASIDs have valid leaves. Guest table fetches pass through host translation in the nested path; Guest Only uses GPA = SPA. ATS mode uses a small semantic IOMMU translation cache for permission responses; microarchitectural TLB behaviour is not modelled. ATS mode adds a small NIC address translation cache (ATC).
All pointers, BDFs, domains and tables are synthetic. Raw words encode a selected page-entry subset of AMD 48882 rev 3.11. Host IR/IW/PR/NextLevel and guest P/RW/US use different bit positions. Guest A/D are pre-set, host HAD is zero, and unmodelled optional features remain off. PCIe and ATS controls shown on this page are configurable. The walker uses decoded teaching fields alongside the displayed packed words; it does not parse arbitrary hardware memory. This is not a complete hardware implementation. The address walker uses teaching diagnostics; mock DMA adds the selected IO_PAGE_FAULT encoding. The model omits other architectural events, access/dirty updates, privileged access details, nonstandard page sizes, real PCIe invalidation packet timing and memory coherency protocols. The mock verifies ATC provenance before translated DMA; this is a teaching safeguard, not ordinary AMD hardware revalidation of an SPA (§2.11). ATS mode models a per-descriptor 4 KiB ATC and semantic PRI/PPR page-in handshake; packed command/PPR words, real PRI registers, timeouts, concurrent requests and secure ATS are omitted. Command and PPR rings model ownership, fetch, completion and wraparound. A per-session IOMMU cache is also invalidated; invalidation scope is conservatively the whole mock session. Mapping changes discard the mock cache. Control/DTE protocol errors are stopped by this teaching model; their full architectural event handling is not emulated. The address inspector validates one address. The mock DMA demo splits a 1–65,536 byte transfer at the active guest/host page boundaries, in requests of at most 256 bytes, and checks each chunk; it is not a hardware protocol implementation. Arm SMMU uses different structures and is not represented by these AMD-specific names.
Generated from the applied mappings, GCR3 tables, request, faults, NIC, DMA length/payload and interrupt settings. Run it to reproduce the complete mock transfer from its initial memory state. Edit manually to explore further, or regenerate to return to the playground state.
Edit the source, then run the tests. Compilation provided by Compiler Explorer. Clicking Run sends the code to that external service. Use only public, non-confidential code.
Ready. No code has been sent.
Run the example to see actual output here.
If the external service is unavailable, copy the source and
open Compiler Explorer, or compile locally with
g++ -std=c++23 example.cpp -o example and run
./example.