In plain words
Think of a hotel where guests send in requests for a room by name. The device ID is the guest's badge: the front desk uses it to pull out that guest's folder (the device context). If the guest names a project, the PASID picks one booking sheet inside the folder (the process context). The sheet turns the name the guest used into a room in the building (the first stage); a second desk, run by the hotel, turns that into the real room number (the second stage). The doorman remembers recent answers (the cache). If you change a sheet, the doorman still remembers the old answer until you tell him to forget it, and you only know he forgot once he has said so. Requests to the desk go in a mail slot (the command queue); when something goes wrong the desk writes it in an incident log (the fault queue), and it never fixes the problem itself.
Five ideas to keep before the first button:
- DMA is a device reaching memory by itself. A network card receiving data writes memory; fetching a descriptor reads it. Read and write are named from the memory's side.
- IOVA is just "the address presented for I/O translation". Depending on which stages are on, it is a virtual address, a guest-physical address or a system-physical address, not always a fourth kind.
- Identity picks the configuration; the address picks a spot inside it. A correct address under the wrong valid identity reaches the wrong allowed memory and nothing faults.
- A mapping and its cached copy are different state. Publishing a table value, asking for maintenance, the maintenance finishing and the next DMA request are four separate events.
- Permission, payload and notification are separate evidence. An allowed translation is not a completed transfer. A fault record is not a repair. An MSI is not proof that a handler ran.
For context: the CPU's RISC-V paging does not cover devices, so DMA gets its own specification (ratified 2023; the 2026 release adds four ratified extensions). The CPU instruction set, the IOMMU, the interrupt architecture, firmware description and the operating system are separate contracts.
Find the context
Two identities arrive with a request. device_id is up to 24
bits; over PCIe it usually comes from the 16-bit requester ID (bus,
device, function), and the platform decides the mapping.
02:00.0 is 0x0200 here. process_id is
optional, up to 20 bits, and comes from the PCIe PASID. "PASID
present" and "PASID value" are separate facts: PASID 0 is a PASID,
and no PASID is none. It is not a Unix process ID.
The IOMMU's ddtp register points at the root of a
device directory of one, two or three levels. The pieces of
device_id index it, and the leaf is a device context: 32
bytes in the base format, 64 bytes in the extended format that holds
MSI fields too. Directory depth and page-table depth are independent.
| Format | device_id slices, low to high | Entry |
|---|---|---|
| Base | 7, 9, 8 bits | 32-byte device context; 8-byte directory entries above it |
| Extended | 6, 9, 9 bits | 64-byte device context |
The slices follow how many contexts fit in one 4 KiB leaf page; it
is not a different PCIe format. With PDTV=0 the device
context itself holds the first-stage root and the PSCID, and a
request that carries a PASID is refused (cause 260). With
PDTV=1 it points at a process directory (PD8,
PD17 or PD20 for 8-, 17- or 20-bit process IDs) whose leaf is a
16-byte process context: first-stage root, mode and PSCID. A
request with no PASID follows the context's rule: DPE=1
uses process 0; DPE=0 makes the first stage Bare, and the
request still goes through the second stage. There is no rule that a
missing PASID always faults.
Translate and check
| First stage | Second stage | The device address is a | Result |
|---|---|---|---|
| Bare | Bare | SPA | No translation or page protection from these stages |
| Sv39 (or another paged mode) | Bare | virtual address | First stage gives the SPA |
| Bare | Sv39x4 (or another) | GPA | Second stage gives the SPA |
| Sv39 | Sv39x4 | virtual address | virtual to GPA to SPA, checked at both stages |
The architecture defines Sv32, Sv39, Sv48 and Sv57 for the first stage and an "x4" version of each for the second; which a chip has is discovered, not assumed. Sv39 is three levels of 9-bit indexes plus a 12-bit offset. Sv39x4 widens the root index by two bits: a 16 KiB root, aligned to 16 KiB, taking 41-bit guest-physical addresses. "x4" means the bigger root, not four stages.
A valid entry can still refuse an access. This chapter's fixture uses
the valid, read, write, user, accessed and dirty bits, no execute and
no global mappings, and sets A and D in advance so that a permission
failure is not mixed up with hardware updating those bits. The
second-stage user check treats the request as a non-supervisor one.
Supervisor process requests have more rules (ENS,
SUM). "Permission is read AND write" hides the validity,
privilege, alignment, width and table-read failures that also exist.
The tables are guest memory too. When the second stage is on, the process directory pointer and every first-stage table pointer are guest-physical addresses, so reading them needs a second-stage translation. Only the device directory and the second-stage root sit in the IOMMU's own physical address space. That is the thin dashed loop in the diagram, and the nested walk below.
The worked fixture
All addresses are made up; none comes from a real machine. The request
is a 16-byte untranslated Memory Write from 02:00.0,
PASID 42, user privilege, no execute, to IOVA
0x40203abc, carrying de ad be ef four times.
The device context is the extended format, a two-level directory and
PD8, with Sv39 over Sv39x4, PSCID 0x123 and GSCID 7
(both deliberately unlike 42). Both stages allow the user write, A and
D are set, and caches start cold. Page A starts as aa;
page B as bb. The intended destination is A.
| Object | Address or calculation | Meaning |
|---|---|---|
| Directory root | SPA 0x00100000 | ddtp.MODE = two levels |
Slices of 0x0200 | DDI[2]=0, DDI[1]=8, DDI[0]=0 | 9, 9 and 6 bits from high to low |
| Root entry | 0x00100000 + 8 × 8 = 0x00100040 | Valid pointer to the leaf at SPA 0x00101000 |
| Device context | 0x00101000 + 0 × 64 | PDTV=1, DPE=0, second-stage root SPA 0x00200000, process directory at GPA 0x00400000 |
| Process context 42 | GPA 0x00400000 + 42 × 16 = 0x004002a0 | Second stage maps this read to SPA 0x804002a0; first-stage root GPA 0x00401000 |
The second-stage root occupies SPA 0x00200000 to
0x00203fff; its entry 0 points at a table at
0x00204000. That table's entry 2 (SPA
0x00204010) is one valid 2 MiB leaf that maps GPA
0x00400000 to 0x005fffff onto SPA
0x80400000 to 0x805fffff, which is where the
directories and first-stage tables live. That broad mapping keeps the
fixture small; a real allocator would be tighter. Entry 256 (SPA
0x00204800) leads to a last-level table at
0x00205000, whose entry 0 maps GPA page
0x20000000 to page A at SPA 0x90000000 and
whose entry 1 maps 0x20001000 to page B at
0x91000000.
Sv39 splits 0x40203abc into indexes 1, 1 and 3 and the
offset 0xabc. Every table read below first goes through
the second stage, and none of those reads carries the payload.
| Step | Before the second stage | Actual access | Result |
|---|---|---|---|
| Read process context 42 | GPA 0x004002a0 | SPA 0x804002a0 | First-stage root GPA 0x00401000 |
| Sv39 level 2, entry 1 | GPA 0x00401008 | SPA 0x80401008 | Next table GPA 0x00402000 |
| Sv39 level 1, entry 1 | GPA 0x00402008 | SPA 0x80402008 | Next table GPA 0x00403000 |
| Sv39 level 0, entry 3 | GPA 0x00403018 | SPA 0x80403018 | Leaf: GPA page 0x20000000 |
| Translate the payload address | GPA 0x20000abc | second-stage indexes 0, 256, 0 | SPA 0x90000abc |
| Commit the payload | SPA 0x90000abc to 0x90000acb | 16 bytes | Page A changes; page B does not |
A hit in a translation cache could skip these reads on real hardware; this cold
sequence is a teaching trace, not a required number of memory accesses.
As packed 64-bit words (little-endian) the entries are: a pointer to
page 0x00101000 is 0x40401 (page number
shifted left 10, plus V); the first-stage leaf to GPA
0x20000000 with V, R, W, U, A and D is
0x080000d7; the second-stage leaf to
0x90000000 with the same flags is
0x240000d7. The model uses BigInt for these; JavaScript's
ordinary numbers lose bits above 2^53.
Faults, queues and what proves what
Three queues, three directions. The command queue (CQ) carries software's commands to the IOMMU; the fault queue (FQ) carries fault and event records to software; the optional page-request queue (PQ) carries device page requests. CQ and PQ entries are 16 bytes, FQ records 32. Each is a ring whose head and tail wrap; one slot stays empty, so a four-slot ring holds three entries. Software owns the CQ tail and the FQ and PQ heads. Arm's queues carry a wrap bit; these do not.
| Cause | Name | Where the experiments meet it | Hidden by DTF=1 |
|---|---|---|---|
| 15 | Write page fault | First-stage leaf has W=0 or A=0 | yes |
| 23 | Write guest-page fault | Second stage refuses the payload write, or refuses a table read (then bit 0 of iotval2 is 1) | yes |
| 258 | DDT entry not valid | Invalid device context | no |
| 260 | Transaction type disallowed | A PASID with PDTV=0 | yes |
| 266 | PDT entry not valid | No process context 0 for DPE=1 | yes |
A guest-page fault keeps the original access type even when the failing
access was the IOMMU's own table read. A fault record names the cause,
the transaction type, the device, whether a PASID was present and its
value, the address, and extra information in iotval2.
Some bridges report page offsets as zero. DTF stops
translation faults being reported without granting any
access. A full or unreachable fault queue drops records and sets
fqof or fqmf, and the IOMMU may coalesce
identical faults. So the absence of a record proves neither success
nor the absence of a fault: check the protected bytes too.
The chosen PCIe Memory Write is posted: it has no completion. A denied write can transfer nothing and leave no record at all, for example under DTF. How a refused read is answered depends on the I/O bridge and protocol; the spec's guideline shows one illustrative read response, not a universal data value.
Maintenance is a separate step
| Command | What it maintains |
|---|---|
IODIR.INVAL_DDT, IODIR.INVAL_PDT | Cached directory and context state |
IOTINVAL.VMA | First-stage translation state, in the scope its operands name |
IOTINVAL.GVMA | Second-stage state and applicable MSI-table state |
ATS.INVAL | The device's own cache, by a PCIe message the device must answer |
IOFENCE.C | Ordering: completion of earlier commands |
None of them maintains every other cache, or a CPU's TLB. In
IOTINVAL, GV and PSCV say
whether the GSCID and PSCID operands are valid; an unused selector is
not "ID zero". The scope depends on the command: IOTINVAL.VMA
with GV=0 works on host address spaces (second stage Bare),
so it does not touch an entry that belongs to GSCID 7; IOTINVAL.GVMA
with GV=0 means all VM address spaces. A PSCID-selective
invalidation skips global mappings; the fixture has none.
The head moving past an ordinary command means the IOMMU
fetched it, not that it finished. Only the head moving past a
successful IOFENCE.C says every earlier command is
complete and committed. A fence can also write a value to a
configured address, or raise a wired interrupt, as another way to
see it. Its read and write ordering bits cover requests the IOMMU
already processed, not everything still crossing the interconnect.
The experiments implement exact scopes; real hardware may legally
invalidate more, so a wrong-scope command that appears to work on one
chip is not a recipe.
The architecture forbids caching entries whose valid bit is 0 in the directories, page tables and MSI table. This is why the chapter's stale-mapping story uses a valid-to-valid remap; the not-present cache lesson from the AMD chapters has no counterpart here.
ATS, PRI and MSI: optional branches
With ATS a device asks the IOMMU for a translation and keeps it
in its own cache. With PRI it asks software to bring a page
in; that is not the fault queue repairing a failed request. An ATS
request that cannot complete (a missing page) gets a Success answer
with R=0 and W=0 and no fault record; a page that is merely
read-only gets R=1 and W=0. Configuration errors get Unsupported
Request or Completer Abort and are logged. Software answers a page
request with ATS.PRGR. A Success status says the group
is finished, not that a page is resident, and the response moves no
payload: the device must ask again, receive a usable translation, and
send a new request. Invalid Request and Response Failure statuses
tell it not to bother. Even a Success answer is not proof a page is
resident: when the page-request
queue overflows, a request that needs an answer can still be
auto-answered Success.
With T2GPA, the answer to a translation request is a GPA,
not the final SPA, and later translated requests still pass the second
stage. That is safe only if the physical path really routes those
requests through the IOMMU, including PCIe peer-routing controls; it is
not the same as trusting a device that holds SPAs, and Linux group
numbers do not prove it either.
An MSI is another memory write. After the first stage, a write
whose guest-physical page matches the device context's interrupt-file
mask and pattern is not looked up in the ordinary tables but in the
MSI table. Basic mode redirects it to a real interrupt-file
page. Optional MRIF mode instead sets a pending bit in a
512-byte structure in memory (32 pairs of pending and enable
doublewords) and sends a configured notice MSI. In AIA 1.0
the notice is required even when the identity's enable bit is 0. It
still does not show a guest handler ran. A translation request for
such an address is told "untranslated access only", so a device never
caches a normal address for it. The model assumes atomic MRIF updates
and does not simulate several writers racing. The 20260222 text also
says msiptp.MODE must be Off when the second stage is
Bare; checking implementations are recommended to reject it as
misconfigured (cause 259).
What is supported where
Three labels apply to every claim: the architecture says what the pinned specification defines; the capability and platform says what this chip advertises, enables and routes; the lesson or Linux snapshot says what the synthetic model or the pinned driver does. An optional feature is not a proposal; a ratified feature is not necessarily in a chip; a capability bit does not prove software enabled or used it.
| Facility | Architecture and hardware gate | Linux 7.3-rc5 snapshot | In this chapter |
|---|---|---|---|
| Device contexts and directory | Core; discover directory modes and context format | Implemented | Walked exactly |
| First-stage paging | A subset of modes may be present | Sv39, Sv48 and Sv57 selected from capabilities | Sv39 |
| Second-stage paging | SvNx4 capabilities and iohgatp | Comments say not yet merged | Synthetic |
| Process directory and PASID | PD8, PD17, PD20; device and request enablement | Comments say SVA/PASID not yet merged | PD8, synthetic |
| Command and fault queues | Core | Implemented; the fault handler logs, rate-limited | Bounded model |
| ATS, PRI, device invalidation | ATS capability plus device, platform and context controls | No complete end-to-end path exposed | Synthetic branch |
| T2GPA | Extra capability, routing assumptions | Not a working Linux feature here | Compares GPA and SPA modes |
| MSI translation and MRIF | MSI_FLAT, MSI_MRIF, AMO_MRIF; AIA | The IOMMU's own queue notifications are not device MSI remapping | Synthetic |
| Atomic A/D updates | AMO_HWAD with SADE and GADE | No claim from capability bits alone | A and D preset |
| QoS, non-leaf and range invalidation | Ratified extensions with capability bits | Non-leaf and range handling exists in the driver | Not used |
| Performance counters, translation debug | Optional capabilities | Register definitions are not usage | Not used |
The driver (drivers/iommu/riscv/, first in Linux 6.13)
allocates PSCIDs, manages the directory and the queues, offers paging,
identity and blocked domains, and has platform and PCI integration.
The Device Tree binding describes architectural features; that does not
mean the driver implements all of them. On a real system, check the
capabilities register for address widths, byte order, interrupt
generation and directory depth, and the firmware tables for routing.
Registers are little-endian even where table memory is configured
otherwise; the fixture is little-endian throughout.
The same questions in four architectures
This is a map of roles, not a table of encodings. The editions compared are AMD 48882 rev 3.11 (April 2026), Intel VT-d rev 5.0 (August 2024, not claimed to be the newest publication) and Arm IHI 0070 H.a (6 March 2026).
| Question | RISC-V IOMMU | AMD | Intel VT-d | Arm SMMUv3 |
|---|---|---|---|---|
| What selects the device's configuration? | device_id, device directory, DC | DeviceID, device table, DTE | Requester identity, root and context structures | StreamID, stream table, STE |
| What selects a process context? | process_id (PASID), process directory, PC | PASID, guest-CR3 hierarchy where enabled | PASID, scalable PASID directory and table | SubstreamID, context descriptor; PCIe commonly maps the PASID |
| Where is first-stage root state? | iosatp in the DC or PC | Guest CR3 and guest paging mode | First-stage pointer in the scalable PASID entry | CD translation table base |
| What selects the second stage? | DC iohgatp | Host translation controls in the DTE | Second-stage pointer and controls | Stage-2 configuration in the STE |
| Which tags scope translation maintenance? | PSCID and GSCID | DomainID and PASID scopes | Domain ID and PASID-related scopes | ASID, VMID and translation regime |
| What maintains configuration caches? | IODIR.INVAL_DDT/PDT | INVALIDATE_DEVTAB_ENTRY and guest-root maintenance | Context-cache and PASID-cache invalidation | CFGI_STE, CFGI_CD |
| What maintains page translations? | IOTINVAL.VMA/GVMA | INVALIDATE_IOMMU_PAGES | IOTLB and PASID-based IOTLB invalidation | TLBI commands, or configured broadcast maintenance |
| What proves queued maintenance finished? | Successful IOFENCE.C | COMPLETION_WAIT | Invalidation Wait descriptor | CMD_SYNC |
| How does software see ordinary faults? | Fault queue plus status bits | Event log and status; PPR is separate | Fault-recording and status; page requests separate | Event queue plus global errors; stall and PRI paths differ |
| How is a device's translation cache maintained? | ATS.INVAL, endpoint completion, fence ordering | INVALIDATE_IOTLB_PAGES | Device-TLB invalidation | ATC invalidation commands |
| How are interrupts handled? | Optional MSI table, IMSIC and MRIF through AIA | Interrupt remapping tables, optional guest features | Interrupt remapping table, optional posted interrupts | GIC and ITS, distinct from the SMMU's DMA translation |
| Can CPU and IOMMU share table memory? | Possible; separately configured and maintained | Selected guest and host table-sharing formats | CPU-compatible first-stage formats | Compatible formats subject to regime and maintenance rules |
Where the analogy stops
- PSCID is not PASID. Many request identities can name one shared address space; separately allocated cache tags need consistent scope and lifetime rules.
- GSCID is not AMD's GuestID. GuestID names a virtualization context in AMD's own interface; it replaces neither DomainID nor GSCID.
- Arm's SubstreamID is not an ASID. Stream and substream identity select configuration; the selected ASID, VMID and regime scope the cached translations.
- Intel's scalable mode is not legacy VT-d. A scalable request with no PASID uses RID_PASID or its defined implied value, not RISC-V's DPE.
- Registers, layouts, fault numbers and completion protocols do not carry over. Entries of the same size have unrelated bits.
- There is no universal fault queue. Intel's fault recording is not a renamed FQ, and page-request queues are not fault logs.
- Interrupt translation is not part of the SMMU. On Arm it belongs to the GIC and ITS architecture and its integration.
What claims does this page make? 3 points
- Spec rule: the RISC-V IOMMU Architecture Specification release 20260222 (source commit 054005f4; base architecture version 1.0, shown as v1.0.1 in the official library), the privileged ISA release 20260120 for page tables, and AIA 1.0 §7.1 for MSI tables and memory-resident interrupt files.
- Platform assumption: which features a chip has and which the Linux driver uses. The Linux statements are about mainline commit ce1e0223d8ad (1 October 2026, Makefile says 7.3-rc5), not a distribution kernel.
- Teaching model: one synthetic device, made-up addresses and pages, deterministic. Not a RISC-V CPU, not QEMU, not a hardware conformance claim.
What this model leaves out
- User-privilege requests only: ENS, SUM and supervisor accesses are not modelled, and the model refuses them.
- One 16-byte transfer inside one page; device_id up to 24 bits and PASID up to 20 bits.
- With T2GPA the translated request is executable; the SPA mode is explained but not stepped through.
- Reserved bits in an MSI table entry are not checked (the specification reports cause 263).
- PDTV=0 (first-stage root in the device context) is treated as Bare, and big-endian MSIs are discarded.
- There is no Previous button: use Start again. The model has no Linux driver behind it; it follows the specification.
Sources
- RISC-V IOMMU Architecture Specification, release 20260222, commit 054005f4: preface (the version labels), data structures (directories, contexts, "Process to translate an IOVA", MSI and ATS handling), queues (commands, fault records), registers (capabilities), software guidelines, hardware guidelines, extensions. The specification is published by RISC-V International under CC BY 4.0; the explanation, fixture and drawings here are original. Official library.
- RISC-V privileged ISA, release 20260120: Supervisor (Sv39), Hypervisor (Sv39x4). AIA 1.0 §7.1: MSI page tables, MRIF format and notice rules. RISC-V International.
- Linux, commit ce1e0223d8ad (1 October 2026; Makefile 7.3-rc5):
drivers/iommu/riscv/iommu.c(the "not yet merged" comments are at lines 1171, 1212 and 1242),Kconfig, and the Device Tree binding. The driver first appears in Linux 6.13. - Comparison editions: AMD 48882 rev 3.11 (April 2026), Intel VT-d rev 5.0 (August 2024, D51397-017), Arm IHI 0070 H.a (6 March 2026).
- Not done here: no RISC-V hardware, QEMU guest or Linux boot was run for this chapter, so the model is regression evidence for the rules, not a certification of any chip. The RISC-V browser lab boots QEMU's RISC-V IOMMU in your browser, and its
dmakit run eduprograms it by hand with what this model produced.