12.3-5Sv32, Sv39, Sv48: Page Tables & the Translation Algorithm

Part II Linux boot: required Vol. II (Privileged) pp. 129–137 · ~6 min read

  • pte

All the Sv schemes are one design — a radix tree of 4 KiB page-table nodes, walked by hardware — differing only in depth. Sv32 (SXLEN=32): 2 levels, 4-byte PTEs . Sv39/48/57 (SXLEN=64): 3/4/5 levels, 8-byte PTEs, 512 entries per node. Every table is exactly one page, page-aligned; the root’s physical page number lives in satp.

Address formats

VPN[1]3122VPN[0]2112page offset110Sv32 VA
Click a field for its role.
VPN[2]3830VPN[1]2921VPN[0]2012page offset110Sv39 VA
Click a field for its role.

Sv48 prepends VPN[3] (bits 47:39, canonical above 47); Sv57 prepends VPN[4] (bits 56:48). The translated PPN is 22 bits on Sv32 (34-bit physical addresses — RV32 reaches beyond 4 GiB) and 44 bits on all the RV64 schemes (56-bit physical). Canonicality is sign-based, not zero-based: the entrenched OS convention of user-low/kernel-high falls out of one bit test.

The PTE

N63PBMT6261reserved6054PPN[2]5328PPN[1]2719PPN[0]1810RSW98D7A6G5U4X3W2R1V0Sv39 PTE
Click a field for its role.

Sv32’s PTE is the 4-byte tail of this (PPN[1] 12b, PPN[0] 10b, bits 9:0 identical). Any level may hold a leaf: Sv32 offers 4 MiB megapages; Sv39 adds 2 MiB megapages + 1 GiB gigapages; Sv48 adds 512 GiB terapages — each virtually and physically aligned to its size, or page fault. Fetch from X=0, load from R=0 (MXR relaxes to R∨X), store to W=0 → the corresponding page fault; AMOs raise only store faults. For non-leaf PTEs, D/A/U are reserved-zero. With A and paging, the LR/SC reservation set must fit in one 4 KiB page.

Accessed & Dirty

Two conformant schemes, all harts alike:

  • Svade: touching a page with A=0 (or storing with D=0) page faults; the OS maintains A/D in the handler. Radically simpler hardware.
  • Hardware update (Svadu, gated by menvcfg.ADUE): the walker sets A (and D on stores) itself — atomically compare-and-swap against the value it walked, re-running all checks, restarting the walk on mismatch. A may be set speculatively (prefetcher-friendly); D must be exact and program-ordered, and the PTE update must hit the global memory order before the store that caused it. Page tables must live in RsrvEventual PMA memory.

The algorithm

  1. a = satp.ppn × 4096, i = LEVELS−1 (satp must be active).
  2. Read pte at a + va.vpn[i] × PTESIZE — one atomic PTESIZE-wide access; PMA/PMP violation → access fault.
  3. pte.v=0, or R=0∧W=1, or any reserved bit set → page fault.
  4. R∨X → leaf (step 5). Else descend: i−− (i<0 → page fault), a = pte.ppn × 4096, back to 2.
  5. Leaf at i>0 with ppn[i−1:0] ≠ 0 → misaligned superpage, page fault.
  6. Privilege check (U bit vs mode, SUM, MXR) → page fault if denied.
  7. Shadow-stack rules (Zicfiss) → access fault if violated.
  8. R/W/X permission check → page fault.
  9. A=0, or store with D=0 → Svade: page fault; else the CAS update (PMP/PMA-checked as a store now; mismatch → step 2).
  10. Done: pa.pgoff = va.pgoff; superpage: pa.ppn[i−1:0] = va.vpn[i−1:0]; pa.ppn[LEVELS−1:i] = pte.ppn[LEVELS−1:i].

Step-2 reads may be served by the SFENCE.VMA -governed translation cache — hart-private, read-only, incoherent, arbitrarily many entries per (VA, ASID). Step 9 never uses the cache: A/D updates go to memory. Speculative walks may pre-warm the cache any time satp is active, but must not set D, fault, or install entries an intervening fence would have killed.

Hardware Designer Notes

The walker is roughly: a 3-state FSM (issue read, check, descend/finish)

  • one PTESIZE register + the permission-check combinational block, with its memory port arbitrated into the L2 or D-cache. Latency is LEVELS × memory-round-trip on a TLB miss — which is why walk caches holding non-leaf PTEs matter long before associativity tuning does.

Minimal Linux-boot hart MUST

  • Build the walker as the 10-step state machine: PTESIZE-atomic reads, PMP/PMA check on every step-2 access, reserved-bit and W-without-R rejection, superpage alignment check, and the U/SUM/MXR permission mux
  • Pick an A/D scheme: Svade (trap, no write path in the walker) or Svadu (CAS loop + RsrvEventual requirement) — all harts identical
  • Enforce canonical-VA checks at effective-address generation (a wide AND/NAND tree on the high bits) raising PAGE faults, not access faults

MAY simplify / trap-and-emulate

  • Implement Sv39 only — the Linux-boot floor; add Sv48/57 as market demands (same PTE format, one more index mux level each)
  • Cache non-leaf PTEs ("walk caches") and leaves in split TLBs — all invisible under the translation-cache abstraction
  • Start with Svade: Linux supports it, and it deletes the walker’s entire store path

Check yourself — Sv32/Sv39/Sv48 translation

1.During a walk, the walker reads a PTE with V=1, R=0, W=1. What happens?

2.A leaf is found at level i=1 of an Sv39 walk with pte.ppn[0] ≠ 0. What happens, and why does the rule exist?

3.How does hardware A/D-bit updating (the non-Svade scheme) interact with a concurrent PTE modification by another hart?

4.Why does Sv39 demand VA bits 63:39 equal bit 38 (canonical form) instead of zero-extension?

5.An Sv48 walker wants to read a level-2 PTE with two 4-byte reads. Legal?

5 questions