* [PATCH RFC 0/1] KVM: riscv: G-stage PMD block mappings for VM_PFNMAP
@ 2026-07-09 13:00 Bingyu.Xian
2026-07-09 13:00 ` [PATCH RFC 1/1] KVM: riscv: Allow safe " Bingyu.Xian
0 siblings, 1 reply; 2+ messages in thread
From: Bingyu.Xian @ 2026-07-09 13:00 UTC (permalink / raw)
To: anup; +Cc: atish.patra, kvm, kvm-riscv, linux-riscv, Bingyu.Xian
From: "Bingyu.Xian" <Shanbeeyoo@gmail.com>
This RFC adapts the arm64 "stage-2 block mapping for host device MMIO"
idea (commit 2aa53d68cee6) to RISC-V KVM, allowing VM_PFNMAP regions
such as VFIO-assigned PCI BARs to use 2 MB G-stage PMD block mappings
instead of being forced to 4 KB PTEs.
=== Why this matters for RISC-V ===
1. RISC-V virtualization is maturing rapidly. The H-extension was
ratified in 2021 and KVM/riscv has been upstream since v5.15, but
many performance optimizations that exist on arm64 and x86 are still
missing. This patch closes one of those gaps.
2. Server-class RISC-V platforms are on the roadmap. Device passthrough
(VFIO) for GPUs, NPUs, and high-speed NICs is essential for
data-center workloads. These devices have BARs ranging from tens of
MB to multiple GB. Without this patch, a single 256 MB GPU BAR
requires 65 536 G-stage PTEs (512 KB of page-table memory and 65 536
G-stage faults), compared to 128 PMD blocks with this optimization.
3. RISC-V IOMMU specification was ratified in March 2023 and Linux
driver support is under active development. Once RISC-V IOMMU
hardware becomes available, VFIO passthrough will be a first-class
use case. This KVM G-stage optimization is a prerequisite for
acceptable passthrough performance.
4. RISC-V has a natural advantage over arm64 here: the memory type is
determined by the physical address' PMA (Physical Memory Attribute),
not by the G-stage PTE attributes. So promoting 4 KB -> 2 MB does
not alter memory-type semantics, unlike arm64 where the stage-2 PTE
MemAttr field requires careful handling.
=== Design choices ===
Unlike arm64's original implementation which derives the host physical
address from vma->vm_pgoff, this patch walks the host page tables via
get_hva_mapping_size(). This is more conservative but safer: if the
host mm itself has not installed a PMD leaf, KVM falls back to 4 KB
exactly as before. The vm_pgoff approach is unreliable since the VFIO
unmap_mapping_range() changes discussed on the RISC-V list in 2025.
The first version is deliberately 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
(pfnmap-huge-test.ko) that installs a PMD leaf in the host page table
for a VM_PFNMAP VMA, simulating a VFIO BAR with a 2 MB host mapping.
Tracepoint kvm_mmu_map confirms all three scenarios:
- anonymous memory: size=4KB pfnmap=0 (control group)
- /dev/mem (4KB host): size=4KB pfnmap=1 (safe fallback works)
- PMD leaf module: size=2048KB pfnmap=1 (block mapping works!)
I welcome feedback on:
- Whether the host-page-table-walk approach is preferred over the
arm64 vm_pgoff approach
- Whether PUD (1 GB) block support should be added now or deferred
- Whether the tracepoint should be kept or removed before non-RFC
submission
Bingyu.Xian (1):
KVM: riscv: Allow safe G-stage PMD block mappings for VM_PFNMAP
arch/riscv/kvm/mmu.c | 45 ++++++++++++++++++++++++++++++++++++------
arch/riscv/kvm/trace.h | 29 +++++++++++++++++++++++++++
2 files changed, 68 insertions(+), 6 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH RFC 1/1] KVM: riscv: Allow safe G-stage PMD block mappings for VM_PFNMAP
2026-07-09 13:00 [PATCH RFC 0/1] KVM: riscv: G-stage PMD block mappings for VM_PFNMAP Bingyu.Xian
@ 2026-07-09 13:00 ` Bingyu.Xian
0 siblings, 0 replies; 2+ messages in thread
From: Bingyu.Xian @ 2026-07-09 13:00 UTC (permalink / raw)
To: anup; +Cc: atish.patra, kvm, kvm-riscv, linux-riscv, Bingyu.Xian, Quan Zhou
From: "Bingyu.Xian" <Shanbeeyoo@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@iscas.ac.cn>
Signed-off-by: Bingyu.Xian <Shanbeeyoo@gmail.com>
Cc: Anup Patel <anup@brainfault.org>
Cc: Atish Patra <atish.patra@linux.dev>
Cc: kvm@vger.kernel.org
Cc: kvm-riscv@lists.infradead.org
Cc: linux-riscv@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
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-09 13:00 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 13:00 [PATCH RFC 0/1] KVM: riscv: G-stage PMD block mappings for VM_PFNMAP Bingyu.Xian
2026-07-09 13:00 ` [PATCH RFC 1/1] KVM: riscv: Allow safe " Bingyu.Xian
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox