[PATCH RFC 1/1] KVM: riscv: Allow safe G-stage PMD block mappings for VM_PFNMAP
Bingyu.Xian
shanbeeyoo at gmail.com
Thu Jul 9 06:00:36 PDT 2026
From: "Bingyu.Xian" <Shanbeeyoo at gmail.com>
RISC-V KVM currently forces all VM_PFNMAP (device MMIO) faults to
PAGE_SIZE, preventing G-stage block mappings for VFIO-assigned device
BARs:
if (logging || (vma->vm_flags & VM_PFNMAP))
vma_pagesize = PAGE_SIZE;
A 256 MB VFIO BAR therefore requires 65 536 G-stage PTEs instead of
128 PMD blocks, incurring excessive G-stage faults, page-table memory
and TLB pressure.
The arm64 patch "Try stage2 block mapping for host device MMIO"
(commit 2aa53d68cee6) addressed this by deriving the host physical
address from vma->vm_pgoff. However, vma->vm_pgoff is unreliable for
VM_PFNMAP since the VFIO unmap_mapping_range() changes discussed on the
RISC-V list in 2025 ("Remove automatic I/O mapping for VM_PFNMAP").
This patch takes a different, more conservative approach:
pfnmap_mapping_size() walks the host page tables via the existing
get_hva_mapping_size(). If the host mm itself installed a PMD leaf,
physical contiguity within that 2 MB block is already guaranteed and
KVM can safely use a G-stage PMD block. Otherwise it falls back to
PAGE_SIZE, exactly as before.
RISC-V has a natural advantage here: the memory type is derived from
the physical address' PMA (Physical Memory Attribute), independent of
the G-stage PTE size, so promoting 4 KB -> 2 MB does not alter the
memory-type semantics. arm64 requires extra care with the stage-2
PTE MemAttr field in this situation.
The eligibility helper fault_supports_gstage_huge_mapping() is
generalized to accept a map_size parameter (previously hardcoded to
PMD_SIZE) so it can be reused for both THP and PFNMAP checks.
The gfn alignment calculation is fixed to use vma_pagesize instead of
huge_page_mask(hstate_vma(vma)), since PFNMAP VMAs are not hugetlb.
A new tracepoint kvm_mmu_map is added to aid debugging and performance
analysis, recording GPA, HVA, HFN, mapping size and whether the fault
was a VM_PFNMAP region.
This first version is conservative:
- Only PMD (2 MB) blocks, not PUD (1 GB)
- dirty logging still forces PAGE_SIZE
- falls back to PAGE_SIZE whenever contiguity/alignment cannot be
proven
Testing: verified on QEMU (rv64, h=true, sstc=true) with a custom
kernel module that installs a PMD leaf in the host page table for a
VM_PFNMAP VMA. Tracepoint confirms:
- anonymous memory: size=4KB pfnmap=0 (control)
- /dev/mem (4KB PTE): size=4KB pfnmap=1 (safe fallback)
- PMD leaf module: size=2048KB pfnmap=1 (block mapping success)
Signed-off-by: Quan Zhou <zhouquan at iscas.ac.cn>
Signed-off-by: Bingyu.Xian <Shanbeeyoo at gmail.com>
Cc: Anup Patel <anup at brainfault.org>
Cc: Atish Patra <atish.patra at linux.dev>
Cc: kvm at vger.kernel.org
Cc: kvm-riscv at lists.infradead.org
Cc: linux-riscv at lists.infradead.org
---
arch/riscv/kvm/mmu.c | 45 ++++++++++++++++++++++++++++++++++++------
arch/riscv/kvm/trace.h | 29 +++++++++++++++++++++++++++
2 files changed, 68 insertions(+), 6 deletions(-)
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 2d3def024270..53fb34d4b76d 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -16,6 +16,8 @@
#include <asm/kvm_mmu.h>
#include <asm/kvm_nacl.h>
+#include "trace.h"
+
static void mmu_wp_memory_region(struct kvm *kvm, int slot)
{
struct kvm_memslots *slots = kvm_memslots(kvm);
@@ -286,7 +288,7 @@ bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
}
static bool fault_supports_gstage_huge_mapping(struct kvm_memory_slot *memslot,
- unsigned long hva)
+ unsigned long hva, unsigned long map_size)
{
hva_t uaddr_start, uaddr_end;
gpa_t gpa_start;
@@ -321,7 +323,7 @@ static bool fault_supports_gstage_huge_mapping(struct kvm_memory_slot *memslot,
* e -> g
* f -> h
*/
- if ((gpa_start & (PMD_SIZE - 1)) != (uaddr_start & (PMD_SIZE - 1)))
+ if ((gpa_start & (map_size - 1)) != (uaddr_start & (map_size - 1)))
return false;
/*
@@ -336,7 +338,7 @@ static bool fault_supports_gstage_huge_mapping(struct kvm_memory_slot *memslot,
* userspace_addr or the base_gfn, as both are equally aligned (per
* the check above) and equally sized.
*/
- return (hva >= ALIGN(uaddr_start, PMD_SIZE)) && (hva < ALIGN_DOWN(uaddr_end, PMD_SIZE));
+ return (hva >= ALIGN(uaddr_start, map_size)) && (hva < ALIGN_DOWN(uaddr_end, map_size));
}
static int get_hva_mapping_size(struct kvm *kvm,
@@ -404,7 +406,7 @@ static unsigned long transparent_hugepage_adjust(struct kvm *kvm,
* sure that the HVA and GPA are sufficiently aligned and that the
* block map is contained within the memslot.
*/
- if (fault_supports_gstage_huge_mapping(memslot, hva)) {
+ if (fault_supports_gstage_huge_mapping(memslot, hva, PMD_SIZE)) {
int sz;
sz = get_hva_mapping_size(kvm, hva);
@@ -421,6 +423,31 @@ static unsigned long transparent_hugepage_adjust(struct kvm *kvm,
return PAGE_SIZE;
}
+/*
+ * Determine the G-stage mapping size for a VM_PFNMAP (e.g. host device
+ * MMIO) fault. Unlike arm64's original implementation, we never derive the
+ * host physical address from vma->vm_pgoff (which is unreliable since the
+ * VFIO unmap_mapping_range() changes). Instead we walk the host page tables
+ * via get_hva_mapping_size(): if the host itself installed a leaf block for
+ * this address, physical contiguity within that block is already guaranteed
+ * by the host mm. The memory type stays correct because RISC-V derives it
+ * from the physical address' PMA, independent of the G-stage PTE size.
+ *
+ * Be conservative for now and only promote to PMD-sized blocks; PUD-sized
+ * device blocks are left as future work. Fall back to PAGE_SIZE whenever
+ * contiguity or HVA/GPA alignment cannot be proven.
+ */
+static unsigned long pfnmap_mapping_size(struct kvm *kvm,
+ struct kvm_memory_slot *memslot,
+ unsigned long hva)
+{
+ if (get_hva_mapping_size(kvm, hva) >= PMD_SIZE &&
+ fault_supports_gstage_huge_mapping(memslot, hva, PMD_SIZE))
+ return PMD_SIZE;
+
+ return PAGE_SIZE;
+}
+
int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
gpa_t gpa, unsigned long hva, bool is_write,
struct kvm_gstage_mapping *out_map)
@@ -465,11 +492,14 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
else
vma_pageshift = PAGE_SHIFT;
vma_pagesize = 1ULL << vma_pageshift;
- if (logging || (vma->vm_flags & VM_PFNMAP))
+
+ if (logging)
vma_pagesize = PAGE_SIZE;
+ else if (vma->vm_flags & VM_PFNMAP)
+ vma_pagesize = pfnmap_mapping_size(kvm, memslot, hva);
if (vma_pagesize == PMD_SIZE || vma_pagesize == PUD_SIZE)
- gfn = (gpa & huge_page_mask(hstate_vma(vma))) >> PAGE_SHIFT;
+ gfn = (gpa & ~(vma_pagesize - 1)) >> PAGE_SHIFT;
/*
* Read mmu_invalidate_seq so that KVM can detect if the results of
@@ -515,6 +545,9 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
if (!logging && (vma_pagesize == PAGE_SIZE))
vma_pagesize = transparent_hugepage_adjust(kvm, memslot, hva, &hfn, &gpa);
+ trace_kvm_mmu_map(gpa, hva, hfn, vma_pagesize,
+ !!(vma->vm_flags & VM_PFNMAP));
+
if (writable) {
mark_page_dirty_in_slot(kvm, memslot, gfn);
ret = kvm_riscv_gstage_map_page(&gstage, pcache, gpa, hfn << PAGE_SHIFT,
diff --git a/arch/riscv/kvm/trace.h b/arch/riscv/kvm/trace.h
index 3d54175d805c..db2d28f1d714 100644
--- a/arch/riscv/kvm/trace.h
+++ b/arch/riscv/kvm/trace.h
@@ -56,6 +56,35 @@ TRACE_EVENT(kvm_exit,
__entry->htinst)
);
+TRACE_EVENT(kvm_mmu_map,
+ TP_PROTO(unsigned long gpa, unsigned long hva, unsigned long hfn,
+ unsigned long map_size, bool is_pfnmap),
+ TP_ARGS(gpa, hva, hfn, map_size, is_pfnmap),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, gpa)
+ __field(unsigned long, hva)
+ __field(unsigned long, hfn)
+ __field(unsigned long, map_size)
+ __field(bool, is_pfnmap)
+ ),
+
+ TP_fast_assign(
+ __entry->gpa = gpa;
+ __entry->hva = hva;
+ __entry->hfn = hfn;
+ __entry->map_size = map_size;
+ __entry->is_pfnmap = is_pfnmap;
+ ),
+
+ TP_printk("GPA:0x%lx, HVA:0x%lx, HFN:0x%lx, size:%luKB, pfnmap:%d",
+ __entry->gpa,
+ __entry->hva,
+ __entry->hfn,
+ __entry->map_size >> 10,
+ __entry->is_pfnmap)
+);
+
#endif /* _TRACE_RSICV_KVM_H */
#undef TRACE_INCLUDE_PATH
--
2.54.0
More information about the linux-riscv
mailing list