[PATCH 1/2] lib: sbi: Add sbi_va_to_pa() to translate S/U-mode addresses

Vladimir Kondratiev vladimir.kondratiev at mobileye.com
Tue Oct 27 03:44:35 PDT 2026


Trap handlers that act on behalf of a lower privilege mode, such as
platform load/store access fault emulators, receive the faulting
address as a virtual address of the trapped context (mtval). To decide
what was actually accessed they need the physical address, and RISC-V
has no instruction to translate an address on behalf of another mode.

Add sbi_va_to_pa() which walks the page tables referenced by
satp using plain M-mode physical loads, so it is unaffected by the PMP
restrictions applied to S-mode. It supports Sv32 on RV32 and
Sv39/Sv48/Sv57 on RV64, handles superpages, and masks the Svpbmt and
Svnapot bits out of the PPN. Every PTE address is checked against the
current domain before being dereferenced so a corrupt page table
cannot cause a nested M-mode fault. Translation for VS/VU-mode
(two-stage) is not supported and returns SBI_ENOTSUPP.

Add the corresponding satp and PTE field definitions to
riscv_encoding.h.

Signed-off-by: Vladimir Kondratiev <vladimir.kondratiev at mobileye.com>
---
 include/sbi/riscv_encoding.h | 28 ++++++++++++++++
 include/sbi/sbi_unpriv.h     | 23 +++++++++++++
 lib/sbi/sbi_unpriv.c         | 78 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 129 insertions(+)

diff --git a/include/sbi/riscv_encoding.h b/include/sbi/riscv_encoding.h
index ffe7666c176b..367a8eb55649 100644
--- a/include/sbi/riscv_encoding.h
+++ b/include/sbi/riscv_encoding.h
@@ -127,12 +127,32 @@
 #define PRV_M				_UL(3)
 
 #define SATP32_MODE			_UL(0x80000000)
+#define SATP32_MODE_SHIFT		31
 #define SATP32_ASID			_UL(0x7FC00000)
 #define SATP32_PPN			_UL(0x003FFFFF)
 #define SATP64_MODE			_ULL(0xF000000000000000)
+#define SATP64_MODE_SHIFT		60
 #define SATP64_ASID			_ULL(0x0FFFF00000000000)
 #define SATP64_PPN			_ULL(0x00000FFFFFFFFFFF)
 
+/* Page table entry */
+#define PTE_V				_UL(0x001)
+#define PTE_R				_UL(0x002)
+#define PTE_W				_UL(0x004)
+#define PTE_X				_UL(0x008)
+#define PTE_U				_UL(0x010)
+#define PTE_G				_UL(0x020)
+#define PTE_A				_UL(0x040)
+#define PTE_D				_UL(0x080)
+#define PTE_PPN_SHIFT			10
+/* Sv32: PPN is bits 31:10 */
+#define PTE32_PPN_MASK			_UL(0x003FFFFF)
+/* Sv39/48/57: PPN is bits 53:10; excludes Svpbmt (62:61) and Svnapot (63) */
+#define PTE64_PPN_MASK			_ULL(0x00000FFFFFFFFFFF)
+/* VPN field width per page table level */
+#define PTE32_VPN_BITS			10
+#define PTE64_VPN_BITS			9
+
 #define SATP_MODE_OFF			_UL(0)
 #define SATP_MODE_SV32			_UL(1)
 #define SATP_MODE_SV39			_UL(8)
@@ -176,6 +196,10 @@
 #define MSTATUS_SD			MSTATUS64_SD
 #define SSTATUS_SD			SSTATUS64_SD
 #define SATP_MODE			SATP64_MODE
+#define SATP_MODE_SHIFT			SATP64_MODE_SHIFT
+#define SATP_PPN			SATP64_PPN
+#define PTE_PPN_MASK			PTE64_PPN_MASK
+#define PTE_VPN_BITS			PTE64_VPN_BITS
 
 #define HGATP_PPN			HGATP64_PPN
 #define HGATP_VMID_SHIFT		HGATP64_VMID_SHIFT
@@ -185,6 +209,10 @@
 #define MSTATUS_SD			MSTATUS32_SD
 #define SSTATUS_SD			SSTATUS32_SD
 #define SATP_MODE			SATP32_MODE
+#define SATP_MODE_SHIFT			SATP32_MODE_SHIFT
+#define SATP_PPN			SATP32_PPN
+#define PTE_PPN_MASK			PTE32_PPN_MASK
+#define PTE_VPN_BITS			PTE32_VPN_BITS
 
 #define HGATP_PPN			HGATP32_PPN
 #define HGATP_VMID_SHIFT		HGATP32_VMID_SHIFT
diff --git a/include/sbi/sbi_unpriv.h b/include/sbi/sbi_unpriv.h
index be2196267413..245ad4a6e817 100644
--- a/include/sbi/sbi_unpriv.h
+++ b/include/sbi/sbi_unpriv.h
@@ -14,6 +14,7 @@
 
 struct sbi_scratch;
 struct sbi_trap_info;
+struct sbi_trap_regs;
 
 #define DECLARE_UNPRIVILEGED_LOAD_FUNCTION(type)           \
 	type sbi_load_##type(const type *addr,             \
@@ -44,4 +45,26 @@ void sbi_store_loop(u8 *buffer, ulong addr, ulong len,
 
 ulong sbi_get_insn(ulong mepc, struct sbi_trap_info *trap);
 
+/**
+ * Translate a virtual address of the trapped S/U-mode context into a
+ * physical address by walking the page tables referenced by satp.
+ *
+ * Intended for trap handlers (e.g. load/store access fault emulation)
+ * that need to know which physical address a lower privilege mode
+ * tried to access. The walk is done with plain M-mode physical loads,
+ * so it is not affected by the PMP restrictions applied to S-mode.
+ *
+ * @param regs trap registers of the trapped context, used to determine
+ *             the previous privilege mode
+ * @param va virtual address in the trapped context
+ * @param pa output physical address
+ *
+ * @return 0 on success,
+ *         SBI_EINVAL if the trapped context was M-mode,
+ *         SBI_ENOTSUPP for VS/VU-mode (two-stage translation) or an
+ *         unsupported satp mode,
+ *         SBI_EINVALID_ADDR if the walk hits an invalid or unreadable PTE
+ */
+int sbi_va_to_pa(const struct sbi_trap_regs *regs, ulong va, ulong *pa);
+
 #endif
diff --git a/lib/sbi/sbi_unpriv.c b/lib/sbi/sbi_unpriv.c
index 60becedc2ea4..028d5074fbb8 100644
--- a/lib/sbi/sbi_unpriv.c
+++ b/lib/sbi/sbi_unpriv.c
@@ -9,6 +9,8 @@
 
 #include <sbi/riscv_encoding.h>
 #include <sbi/sbi_bitops.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_error.h>
 #include <sbi/sbi_hart.h>
 #include <sbi/sbi_scratch.h>
 #include <sbi/sbi_string.h>
@@ -253,3 +255,79 @@ ulong sbi_get_insn(ulong mepc, struct sbi_trap_info *trap)
 
 	return insn;
 }
+
+int sbi_va_to_pa(const struct sbi_trap_regs *regs, ulong va, ulong *pa)
+{
+	const struct sbi_domain *dom = sbi_domain_thishart_ptr();
+	ulong satp, ppn, pte, pte_pa, mask;
+	int levels, lvl;
+
+	if (sbi_mstatus_prev_mode(regs->mstatus) == PRV_M)
+		return SBI_EINVAL;
+	/* VS/VU-mode would need a two-stage (vsatp + hgatp) walk */
+	if (sbi_regs_from_virt(regs))
+		return SBI_ENOTSUPP;
+
+	satp = csr_read(CSR_SATP);
+	switch ((satp & SATP_MODE) >> SATP_MODE_SHIFT) {
+	case SATP_MODE_OFF:
+		*pa = va;
+		return SBI_OK;
+#if __riscv_xlen == 32
+	case SATP_MODE_SV32:
+		levels = 2;
+		break;
+#else
+	case SATP_MODE_SV39:
+		levels = 3;
+		break;
+	case SATP_MODE_SV48:
+		levels = 4;
+		break;
+	case SATP_MODE_SV57:
+		levels = 5;
+		break;
+#endif
+	default:
+		return SBI_ENOTSUPP;
+	}
+
+	/*
+	 * M-mode runs with MPRV=0 so the dereferences below are physical
+	 * loads. The hardware has already walked these tables to produce
+	 * the trap, so the PTEs we read are the ones it used.
+	 */
+	ppn = satp & SATP_PPN;
+	for (lvl = levels - 1; lvl >= 0; lvl--) {
+		pte_pa = (ppn << PAGE_SHIFT) +
+			 (((va >> (PAGE_SHIFT + PTE_VPN_BITS * lvl)) &
+			   ((1UL << PTE_VPN_BITS) - 1)) * sizeof(ulong));
+
+		/*
+		 * Never dereference memory the trapped mode itself could not
+		 * read: a bogus pointer here would be a nested M-mode fault.
+		 */
+		if (!sbi_domain_check_addr(dom, pte_pa, PRV_S, SBI_DOMAIN_READ))
+			return SBI_EINVALID_ADDR;
+
+		pte = *(volatile ulong *)pte_pa;
+		if (!(pte & PTE_V) || ((pte & PTE_W) && !(pte & PTE_R)))
+			return SBI_EINVALID_ADDR;
+
+		ppn = (pte >> PTE_PPN_SHIFT) & PTE_PPN_MASK;
+		if (!(pte & (PTE_R | PTE_X)))
+			continue;
+
+		/* Leaf PTE. For a superpage the low PPN bits come from VA. */
+		mask = (1UL << (PAGE_SHIFT + PTE_VPN_BITS * lvl)) - 1;
+		if (ppn & (mask >> PAGE_SHIFT))
+			return SBI_EINVALID_ADDR;
+		/* Physical address must fit in ulong (matters for Sv32) */
+		if (ppn >> (__riscv_xlen - PAGE_SHIFT))
+			return SBI_ENOTSUPP;
+		*pa = ((ppn << PAGE_SHIFT) & ~mask) | (va & mask);
+		return SBI_OK;
+	}
+
+	return SBI_EINVALID_ADDR;
+}

-- 
2.43.0




More information about the opensbi mailing list