[PATCH v2] RISC-V: KVM: Add minimal guest_memfd support

Bingyu.Xian shanbeeyoo at gmail.com
Wed Jul 29 19:45:10 PDT 2026


Add minimal guest_memfd support for RISC-V KVM, following the same

pattern as x86 and arm64.

1. Select KVM_GUEST_MEMFD in Kconfig

2. In kvm_riscv_mmu_map(), check kvm_slot_has_gmem() early, before

   taking mmap_read_lock(). For gmem memslots, resolve the PFN via

   kvm_gmem_get_pfn(), then acquire kvm->mmu_lock and jump to G-stage

   mapping, bypassing vma_lookup()/__kvm_faultin_pfn(). The gmem PFN

   is resolved before mmap_read_lock() so the mmu_lock taken for the

   mapping is never held while waiting for mmap_lock, which would

   deadlock against mmu_notifier invalidation (mmu_lock -> mmap_lock).

3. Skip kvm_release_faultin_page() for gmem pages; their lifetime is

   managed by the gmem inode and invalidation notifier.

4. Skip hva lookup and validity check in gstage_page_fault() for gmem,

   which does not have a userspace VMA backing it.

Changes since RFC v1:

  - Add vcpu_exit.c hva lookup bypass for gmem slots

  - Call kvm_prepare_memory_fault_exit() on gmem fault failure

  - Respect KVM_MEM_READONLY for gmem slot writability

  - Skip THP adjustment for gmem (only PAGE_SIZE supported)

  - Skip dirty tracking for gmem (handled by gmem notifier)

  - Replace goto with is_gmem variable for cleaner flow


Assisted-by: YuanSheng: deepseek-v4-pro
Co-developed-by: Quan Zhou <zhouquan at iscas.ac.cn>
Signed-off-by: Quan Zhou <zhouquan at iscas.ac.cn>
Signed-off-by: Bingyu.Xian <shanbeeyoo at gmail.com>
---
 arch/riscv/kvm/Kconfig     |   1 +
 arch/riscv/kvm/mmu.c       | 134 +++++++++++++++++++++++--------------
 arch/riscv/kvm/vcpu_exit.c |  17 ++++-
 3 files changed, 99 insertions(+), 53 deletions(-)

diff --git a/arch/riscv/kvm/Kconfig b/arch/riscv/kvm/Kconfig
index ec2cee0a39e0..77121100579f 100644
--- a/arch/riscv/kvm/Kconfig
+++ b/arch/riscv/kvm/Kconfig
@@ -29,6 +29,7 @@ config KVM
 	select KVM_GENERIC_DIRTYLOG_READ_PROTECT
 	select KVM_GENERIC_HARDWARE_ENABLING
 	select KVM_MMIO
+	select KVM_GUEST_MEMFD
 	select VIRT_XFER_TO_GUEST_WORK
 	select SCHED_INFO
 	select GUEST_PERF_EVENTS if PERF_EVENTS
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index d2a06a54be17..5d37d8a93a1d 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -551,6 +551,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 	unsigned long vma_pagesize, mmu_seq;
 	struct kvm_gstage gstage;
 	struct page *page;
+	int max_order;
+	bool is_gmem;
 
 	kvm_riscv_gstage_init(&gstage, kvm);
 
@@ -568,60 +570,79 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 		return ret;
 	}
 
-	mmap_read_lock(current->mm);
-
-	vma = vma_lookup(current->mm, hva);
-	if (unlikely(!vma)) {
-		kvm_err("Failed to find VMA for hva 0x%lx\n", hva);
-		mmap_read_unlock(current->mm);
-		return -EFAULT;
-	}
+	is_gmem = kvm_slot_has_gmem(memslot);
+	if (is_gmem) {
+		/*
+		 * For guest_memfd-backed slots, bypass the userspace VMA lookup
+		 * and get the PFN directly from the guest_memfd inode.
+		 */
+		ret = kvm_gmem_get_pfn(kvm, memslot, gfn, &hfn, &page, &max_order);
+		if (ret) {
+			kvm_prepare_memory_fault_exit(vcpu, gpa, PAGE_SIZE,
+						      is_write, false, false);
+			return ret;
+		}
 
-	is_hugetlb = is_vm_hugetlb_page(vma);
-	if (is_hugetlb)
-		vma_pageshift = huge_page_shift(hstate_vma(vma));
-	else
-		vma_pageshift = PAGE_SHIFT;
-	vma_pagesize = 1ULL << vma_pageshift;
-	if (logging || (vma->vm_flags & VM_PFNMAP))
 		vma_pagesize = PAGE_SIZE;
-	else if (is_hugetlb)
-		vma_pagesize = hugetlb_mapping_size(memslot, hva, vma_pagesize);
+		writable = !(memslot->flags & KVM_MEM_READONLY);
+		/* gmem faultin does not go through mmu notifier invalidation */
+		mmu_seq = kvm->mmu_invalidate_seq;
+	} else {
+		mmap_read_lock(current->mm);
 
-	/*
-	 * For hugetlb mappings, vma_pagesize might have been reduced from the
-	 * VMA size to a smaller safe mapping size.
-	 */
-	if (vma_pagesize == PMD_SIZE || vma_pagesize == PUD_SIZE)
-		gfn = ALIGN_DOWN(gpa, vma_pagesize) >> PAGE_SHIFT;
+		vma = vma_lookup(current->mm, hva);
+		if (unlikely(!vma)) {
+			kvm_err("Failed to find VMA for hva 0x%lx\n", hva);
+			mmap_read_unlock(current->mm);
+			return -EFAULT;
+		}
 
-	/*
-	 * Read mmu_invalidate_seq so that KVM can detect if the results of
-	 * vma_lookup() or __kvm_faultin_pfn() become stale prior to acquiring
-	 * kvm->mmu_lock.
-	 *
-	 * Rely on mmap_read_unlock() for an implicit smp_rmb(), which pairs
-	 * with the smp_wmb() in kvm_mmu_invalidate_end().
-	 */
-	mmu_seq = kvm->mmu_invalidate_seq;
-	mmap_read_unlock(current->mm);
+		is_hugetlb = is_vm_hugetlb_page(vma);
+		if (is_hugetlb)
+			vma_pageshift = huge_page_shift(hstate_vma(vma));
+		else
+			vma_pageshift = PAGE_SHIFT;
+		vma_pagesize = 1ULL << vma_pageshift;
+		if (logging || (vma->vm_flags & VM_PFNMAP))
+			vma_pagesize = PAGE_SIZE;
+		else if (is_hugetlb)
+			vma_pagesize = hugetlb_mapping_size(memslot, hva, vma_pagesize);
 
-	if (vma_pagesize != PUD_SIZE &&
-	    vma_pagesize != PMD_SIZE &&
-	    vma_pagesize != PAGE_SIZE) {
-		kvm_err("Invalid VMA page size 0x%lx\n", vma_pagesize);
-		return -EFAULT;
-	}
+		/*
+		 * For hugetlb mappings, vma_pagesize might have been reduced from the
+		 * VMA size to a smaller safe mapping size.
+		 */
+		if (vma_pagesize == PMD_SIZE || vma_pagesize == PUD_SIZE)
+			gfn = ALIGN_DOWN(gpa, vma_pagesize) >> PAGE_SHIFT;
 
-	hfn = __kvm_faultin_pfn(memslot, gfn, is_write ? FOLL_WRITE : 0,
-				&writable, &page);
-	if (hfn == KVM_PFN_ERR_HWPOISON) {
-		send_sig_mceerr(BUS_MCEERR_AR, (void __user *)hva,
-				vma_pageshift, current);
-		return 0;
+		/*
+		 * Read mmu_invalidate_seq so that KVM can detect if the results of
+		 * vma_lookup() or __kvm_faultin_pfn() become stale prior to acquiring
+		 * kvm->mmu_lock.
+		 *
+		 * Rely on mmap_read_unlock() for an implicit smp_rmb(), which pairs
+		 * with the smp_wmb() in kvm_mmu_invalidate_end().
+		 */
+		mmu_seq = kvm->mmu_invalidate_seq;
+		mmap_read_unlock(current->mm);
+
+		if (vma_pagesize != PUD_SIZE &&
+		    vma_pagesize != PMD_SIZE &&
+		    vma_pagesize != PAGE_SIZE) {
+			kvm_err("Invalid VMA page size 0x%lx\n", vma_pagesize);
+			return -EFAULT;
+		}
+
+		hfn = __kvm_faultin_pfn(memslot, gfn, is_write ? FOLL_WRITE : 0,
+					&writable, &page);
+		if (hfn == KVM_PFN_ERR_HWPOISON) {
+			send_sig_mceerr(BUS_MCEERR_AR, (void __user *)hva,
+					vma_pageshift, current);
+			return 0;
+		}
+		if (is_error_noslot_pfn(hfn))
+			return -EFAULT;
 	}
-	if (is_error_noslot_pfn(hfn))
-		return -EFAULT;
 
 	/*
 	 * If logging is active then we allow writable pages only
@@ -639,12 +660,20 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 	 * Check if we are backed by a THP and thus use block mapping if
 	 * possible. Hugetlb mappings already selected their target size above,
 	 * so do not promote them through the THP helper.
+	 *
+	 * Skip THP adjustment for gmem, which only supports PAGE_SIZE mappings
+	 * and does not have a valid hva for follow_pte().
 	 */
-	if (!logging && !is_hugetlb && vma_pagesize == PAGE_SIZE)
+	if (!is_gmem && !logging && !is_hugetlb && vma_pagesize == PAGE_SIZE)
 		vma_pagesize = transparent_hugepage_adjust(kvm, memslot, hva, &hfn, &gpa);
 
 	if (writable) {
-		mark_page_dirty_in_slot(kvm, memslot, gfn);
+		/*
+		 * Skip dirty page tracking for gmem; page dirtying is handled
+		 * via the gmem invalidation notifier and page cache.
+		 */
+		if (!is_gmem)
+			mark_page_dirty_in_slot(kvm, memslot, gfn);
 		ret = kvm_riscv_gstage_map_page(&gstage, pcache, gpa, hfn << PAGE_SHIFT,
 						vma_pagesize, false, true, out_map);
 	} else {
@@ -658,7 +687,12 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 		kvm_err("Failed to map in G-stage\n");
 
 out_unlock:
-	kvm_release_faultin_page(kvm, page, ret, writable);
+	/*
+	 * Skip kvm_release_faultin_page() for gmem pages; their lifetime is
+	 * managed by the gmem inode and invalidation notifier.
+	 */
+	if (!is_gmem)
+		kvm_release_faultin_page(kvm, page, ret, writable);
 	write_unlock(&kvm->mmu_lock);
 	return ret;
 }
diff --git a/arch/riscv/kvm/vcpu_exit.c b/arch/riscv/kvm/vcpu_exit.c
index 28cf9b27bb07..5c381a4f9977 100644
--- a/arch/riscv/kvm/vcpu_exit.c
+++ b/arch/riscv/kvm/vcpu_exit.c
@@ -26,10 +26,21 @@ static int gstage_page_fault(struct kvm_vcpu *vcpu, struct kvm_run *run,
 	fault_addr = ((gpa_t)trap->htval << 2) | (trap->stval & 0x3);
 	gfn = fault_addr >> PAGE_SHIFT;
 	memslot = gfn_to_memslot(vcpu->kvm, gfn);
-	hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
 
-	if (kvm_is_error_hva(hva) ||
-	    (trap->scause == EXC_STORE_GUEST_PAGE_FAULT && !writable)) {
+	/*
+	 * For guest_memfd-backed slots, bypass the hva lookup and validity check
+	 * since gmem does not have a userspace VMA backing it.
+	 */
+	if (kvm_slot_has_gmem(memslot)) {
+		hva = 0;
+		writable = !(memslot->flags & KVM_MEM_READONLY);
+	} else {
+		hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
+	}
+
+	if (!kvm_slot_has_gmem(memslot) &&
+	    (kvm_is_error_hva(hva) ||
+	     (trap->scause == EXC_STORE_GUEST_PAGE_FAULT && !writable))) {
 		switch (trap->scause) {
 		case EXC_LOAD_GUEST_PAGE_FAULT:
 			return kvm_riscv_vcpu_mmio_load(vcpu, run,
-- 
2.54.0




More information about the linux-riscv mailing list