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 <[email protected]> Fixes: 9d05c1fee837 ("RISC-V: KVM: Implement stage2 page table programming") Signed-off-by: Jinyu Tang <[email protected]> --- 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

