git: 7a5ef20a977c - main - riscv/pmap.c: Handle spurious kernel page faults in critical sections

From: Bojan Novković <bnovkov_at_FreeBSD.org>
Date: Fri, 10 Jul 2026 16:03:46 UTC
The branch main has been updated by bnovkov:

URL: https://cgit.FreeBSD.org/src/commit/?id=7a5ef20a977c2d1ae5b94e6e6e927ac7a566ff10

commit 7a5ef20a977c2d1ae5b94e6e6e927ac7a566ff10
Author:     Bojan Novković <bnovkov@FreeBSD.org>
AuthorDate: 2026-06-10 19:13:03 +0000
Commit:     Bojan Novković <bnovkov@FreeBSD.org>
CommitDate: 2026-07-10 15:56:57 +0000

    riscv/pmap.c: Handle spurious kernel page faults in critical sections
    
    The Privileged ISA specification permits caching of invalid PTEs
    12.2.1. Supervisor Memory-Management Fence Instruction), which may
    result in a spurious page fault. Such faults are handled by 'pmap_fault'
    which locks the kernel pmap before inspecting and possibly updating the
    offending L2 entry.
    Unfortunately, spurious faults may also occur when we're already holding
    the kernel_pmap lock or running in a critical section, where any attempt
    to grab the pmap lock will result in a kernel panic.
    
    Fix this avoidable panic by performing a lockless lookup to determine
    whether a valid kernel mapping exits and flushing appropriate TLB entry.
    
    Differential Revision:  https://reviews.freebsd.org/D56925
    Reviewed by:    jrtc27, mhorne, markj
---
 sys/riscv/riscv/pmap.c | 46 +++++++++++++++++++++++++++++++++++-----------
 1 file changed, 35 insertions(+), 11 deletions(-)

diff --git a/sys/riscv/riscv/pmap.c b/sys/riscv/riscv/pmap.c
index 35194ebd7c28..67123f9bfcc8 100644
--- a/sys/riscv/riscv/pmap.c
+++ b/sys/riscv/riscv/pmap.c
@@ -2911,28 +2911,52 @@ retryl3:
 	PMAP_UNLOCK(pmap);
 }
 
+static pt_entry_t *
+pmap_fault_lookup(pmap_t pmap, vm_offset_t va)
+{
+	pd_entry_t *l2, l2e;
+
+	l2 = pmap_l2(pmap, va);
+	if (l2 == NULL || ((l2e = pmap_load(l2)) & PTE_V) == 0)
+		return (NULL);
+	if ((l2e & PTE_RWX) == 0)
+		return (pmap_l2_to_l3(l2, va));
+	return (l2);
+}
+
 int
 pmap_fault(pmap_t pmap, vm_offset_t va, vm_prot_t ftype)
 {
-	pd_entry_t *l2, l2e;
 	pt_entry_t bits, *pte, oldpte;
 	int rv;
 
 	KASSERT(VIRT_IS_VALID(va), ("pmap_fault: invalid va %#lx", va));
 
+	if (pmap == kernel_pmap) {
+		/*
+		 * Locking the kernel pmap while processing spurious faults
+		 * may lead to a panic since we might be running a critical section
+		 * or already holding the kernel pmap lock.
+		 * We deal with this by taking advantage of the fact that
+		 * kernel PTPs are never freed and performing a lockless lookup
+		 * to determine whether a valid mapping exits.
+		 */
+		pte = pmap_fault_lookup(pmap, va);
+		if (pte != NULL && (pmap_load(pte) & PTE_KERN) == PTE_KERN) {
+			sfence_vma_page(va);
+			return (1);
+		}
+		/*
+		 * The entry is either not present or missing some bits.
+		 * Fall back to the locked lookup below to handle the fault.
+		 */
+	}
+
 	rv = 0;
 	PMAP_LOCK(pmap);
-	l2 = pmap_l2(pmap, va);
-	if (l2 == NULL || ((l2e = pmap_load(l2)) & PTE_V) == 0)
+	pte = pmap_fault_lookup(pmap, va);
+	if (pte == NULL || ((oldpte = pmap_load(pte)) & PTE_V) == 0)
 		goto done;
-	if ((l2e & PTE_RWX) == 0) {
-		pte = pmap_l2_to_l3(l2, va);
-		if (((oldpte = pmap_load(pte)) & PTE_V) == 0)
-			goto done;
-	} else {
-		pte = l2;
-		oldpte = l2e;
-	}
 
 	if ((pmap != kernel_pmap && (oldpte & PTE_U) == 0) ||
 	    (ftype == VM_PROT_WRITE && (oldpte & PTE_W) == 0) ||