* [PATCH v4 1/5] KVM: riscv: Avoid overwriting G-stage tables with huge leaves
[not found] <20260815095548.860018-1-jinyu.tang@linux.dev>
@ 2026-08-15 10:00 ` Jinyu Tang
2026-08-15 10:06 ` [PATCH v4 3/5] KVM: riscv: Implement KVM_PRE_FAULT_MEMORY Jinyu Tang
1 sibling, 0 replies; 2+ messages in thread
From: Jinyu Tang @ 2026-08-15 10:00 UTC (permalink / raw)
To: Anup Patel, Anup Patel, Atish Patra
Cc: kvm, kvm-riscv, linux-riscv, linux-kernel, linux-kselftest,
Paolo Bonzini, Sean Christopherson, Shuah Khan, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Andrew Jones,
Conor Dooley, Yong-Xuan Wang, Nutty Liu, Yifei Jiang, Jinyu Tang,
Jinyu Tang, Sashiko
RISC-V KVM can overwrite an existing G-stage table entry when
installing a huge leaf mapping. If the target huge range already has a
lower-level page table, kvm_riscv_gstage_set_pte() can replace the
non-leaf entry with a leaf PTE and disconnect the lower-level page
table.
Reject replacing a valid table entry with a leaf PTE. If huge-page
installation hits such a conflict, fall back to a 4K mapping for the
original faulting GPA in the MMU fault path, where the original GPA and
HFN are still available.
Suggested-by: Sashiko <sashiko-bot@kernel.org>
Fixes: 9d05c1fee837 ("RISC-V: KVM: Implement stage2 page table programming")
Signed-off-by: Jinyu Tang <jinyu.tang@linux.dev>
---
arch/riscv/kvm/gstage.c | 6 ++++++
arch/riscv/kvm/mmu.c | 24 +++++++++++++++++++++++-
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
index e5002cb9cbef..54d45addf18f 100644
--- a/arch/riscv/kvm/gstage.c
+++ b/arch/riscv/kvm/gstage.c
@@ -174,6 +174,12 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
if (pte_val(*ptep) != pte_val(map->pte)) {
bool was_invalid = !pte_val(*ptep);
+
+ /* Avoid replacing an existing lower-level table with a leaf mapping. */
+ if (!gstage_pte_leaf(ptep) && !was_invalid &&
+ gstage_pte_leaf(&map->pte))
+ return -EEXIST;
+
set_pte(ptep, map->pte);
if (gstage_pte_leaf(ptep) &&
!(was_invalid && riscv_has_extension_unlikely(RISCV_ISA_EXT_SVVPTC)))
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 6035b5ec9503..bfd6168ebe30 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -625,10 +625,11 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
struct kvm_gstage_mapping *out_map)
{
int ret;
- kvm_pfn_t hfn;
+ kvm_pfn_t fault_hfn, hfn;
bool is_hugetlb;
bool writable;
unsigned int vma_pageshift;
+ gpa_t fault_gpa = gpa;
gfn_t gfn = gpa >> PAGE_SHIFT;
struct vm_area_struct *vma;
struct kvm *kvm = vcpu->kvm;
@@ -709,6 +710,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
}
if (is_error_noslot_pfn(hfn))
return -EFAULT;
+ fault_hfn = hfn + ((fault_gpa >> PAGE_SHIFT) - gfn);
/*
* If logging is active then we allow writable pages only
@@ -734,9 +736,29 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
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);
+ if (ret == -EEXIST) {
+ /*
+ * Retry at 4K granularity for the original faulting GPA
+ * when a huge leaf cannot replace an existing table.
+ */
+ ret = kvm_riscv_gstage_map_page(&gstage, pcache, fault_gpa,
+ fault_hfn << PAGE_SHIFT,
+ PAGE_SIZE, false, true,
+ out_map);
+ }
} else {
ret = kvm_riscv_gstage_map_page(&gstage, pcache, gpa, hfn << PAGE_SHIFT,
vma_pagesize, true, true, out_map);
+ if (ret == -EEXIST) {
+ /*
+ * Retry at 4K granularity for the original faulting GPA
+ * when a huge leaf cannot replace an existing table.
+ */
+ ret = kvm_riscv_gstage_map_page(&gstage, pcache, fault_gpa,
+ fault_hfn << PAGE_SHIFT,
+ PAGE_SIZE, true, true,
+ out_map);
+ }
}
if (ret)
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 2+ messages in thread* [PATCH v4 3/5] KVM: riscv: Implement KVM_PRE_FAULT_MEMORY
[not found] <20260815095548.860018-1-jinyu.tang@linux.dev>
2026-08-15 10:00 ` [PATCH v4 1/5] KVM: riscv: Avoid overwriting G-stage tables with huge leaves Jinyu Tang
@ 2026-08-15 10:06 ` Jinyu Tang
1 sibling, 0 replies; 2+ messages in thread
From: Jinyu Tang @ 2026-08-15 10:06 UTC (permalink / raw)
To: Anup Patel, Anup Patel, Atish Patra
Cc: kvm, kvm-riscv, linux-riscv, linux-kernel, linux-kselftest,
Paolo Bonzini, Sean Christopherson, Shuah Khan, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Andrew Jones,
Conor Dooley, Yong-Xuan Wang, Nutty Liu, Yifei Jiang, Jinyu Tang,
Jinyu Tang
The generic KVM_PRE_FAULT_MEMORY ioctl lets userspace populate KVM page
tables before running a vCPU over a GPA range. x86 already supports the
ioctl, but RISC-V does not expose the capability and has no arch hook.
Add the RISC-V arch hook and reuse the existing G-stage fault mapping
path with a read access. Report progress using the G-stage mapping
returned by the map path, so the ioctl can advance by the actual leaf
size that covers the requested GPA. Retry until a mapping is installed
or a signal, VM-dead request, or real error is observed.
Signed-off-by: Jinyu Tang <jinyu.tang@linux.dev>
---
arch/riscv/kvm/Kconfig | 1 +
arch/riscv/kvm/gstage.c | 3 +++
arch/riscv/kvm/mmu.c | 45 ++++++++++++++++++++++++++++++++++++++++-
arch/riscv/kvm/vm.c | 1 +
4 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/arch/riscv/kvm/Kconfig b/arch/riscv/kvm/Kconfig
index ec2cee0a39e0..8ac209e8ac87 100644
--- a/arch/riscv/kvm/Kconfig
+++ b/arch/riscv/kvm/Kconfig
@@ -28,6 +28,7 @@ config KVM
select KVM_COMMON
select KVM_GENERIC_DIRTYLOG_READ_PROTECT
select KVM_GENERIC_HARDWARE_ENABLING
+ select KVM_GENERIC_PRE_FAULT_MEMORY
select KVM_MMIO
select VIRT_XFER_TO_GUEST_WORK
select SCHED_INFO
diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
index 54d45addf18f..dff315dfd24e 100644
--- a/arch/riscv/kvm/gstage.c
+++ b/arch/riscv/kvm/gstage.c
@@ -286,6 +286,9 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
out_map->level, true);
} else if (ALIGN_DOWN(PFN_PHYS(pte_pfn(ptep_get(ptep))), page_size) == hpa) {
kvm_riscv_gstage_update_pte_prot(gstage, ptep_level, gpa, ptep, prot);
+ out_map->addr = ALIGN_DOWN(gpa, page_size);
+ out_map->level = ptep_level;
+ out_map->pte = ptep_get(ptep);
return 0;
}
}
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 2fabcd409991..f6ca86da53eb 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -717,7 +717,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
if (hfn == KVM_PFN_ERR_HWPOISON) {
send_sig_mceerr(BUS_MCEERR_AR, (void __user *)hva,
vma_pageshift, current);
- return 0;
+ return -EFAULT;
}
if (is_error_noslot_pfn(hfn))
return -EFAULT;
@@ -782,6 +782,49 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
return ret;
}
+long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
+ struct kvm_pre_fault_memory *range)
+{
+ struct kvm_gstage_mapping out_map = { 0 };
+ struct kvm_memory_slot *memslot;
+ unsigned long map_size;
+ unsigned long hva;
+ gpa_t end;
+ gfn_t gfn;
+ int ret;
+
+ gfn = gpa_to_gfn(range->gpa);
+ memslot = kvm_vcpu_gfn_to_memslot(vcpu, gfn);
+ if (!memslot)
+ return -ENOENT;
+
+ hva = gfn_to_hva_memslot_prot(memslot, gfn, NULL);
+ if (kvm_is_error_hva(hva))
+ return -ENOENT;
+
+ for (;;) {
+ if (signal_pending(current))
+ return -EINTR;
+
+ if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu))
+ return -EIO;
+
+ cond_resched();
+ ret = kvm_riscv_mmu_map(vcpu, memslot, range->gpa, hva, false, &out_map);
+ if (ret)
+ return ret;
+
+ if (!pte_val(out_map.pte))
+ continue;
+
+ map_size = PAGE_SIZE << (out_map.level * kvm_riscv_gstage_index_bits);
+ end = out_map.addr + map_size;
+ break;
+ }
+
+ return min_t(u64, range->size, end - range->gpa);
+}
+
int kvm_riscv_mmu_alloc_pgd(struct kvm *kvm)
{
struct page *pgd_page;
diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
index a9f083feeb76..58500a19b33b 100644
--- a/arch/riscv/kvm/vm.c
+++ b/arch/riscv/kvm/vm.c
@@ -187,6 +187,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_MP_STATE:
case KVM_CAP_IMMEDIATE_EXIT:
case KVM_CAP_SET_GUEST_DEBUG:
+ case KVM_CAP_PRE_FAULT_MEMORY:
r = 1;
break;
case KVM_CAP_NR_VCPUS:
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 2+ messages in thread