Re: [RFC] KVM: x86/mmu: Prefetch forward run of pages on TDP page faults
Sean Christopherson <[email protected]>
| Newsgroups | org.kernel.vger.kvm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
On Tue, Aug 25, 2026, Sean Christopherson wrote: > Somewhat off the cuff and *very* lightly tested, but this seems to do what I want. > If it provides comparable performance, I'll write a changelog (or two? e.g. to > have direct MMUs switch in a separate patch), and let Sashiko and other bots rip > apart my idea. > > Note! This has a hard dependency on in-flight prefaulting fixes[*]. Without > those, prefaulting will hang the vCPU if the root is invalidated. > [*] https://lore.kernel.org/all/[email protected] > > Note #2! The below deliberately ignores A/D-disabled MMUs. I can't think of > any reason why it matters whether or not KVM can precisely detect accessed SPTEs, > all of the aging stuff is already extremely fuzzy. And of course I posted an untested version (I ripped out the direct MMU prefetching as an afterthough, and dropped a printk). This version should actually compile. diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 79c450d677b4..88b6aa1f840f 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -118,6 +118,9 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(tdp_mmu_enabled); bool __read_mostly eager_page_split = true; module_param(eager_page_split, bool, 0644); +unsigned int __read_mostly auto_prefault_nr_pages = KVM_PAGES_PER_HPAGE(PG_LEVEL_2M); +module_param(auto_prefault_nr_pages, uint, 0644); + static int max_huge_page_level __read_mostly; static int tdp_root_level __read_mostly; static int max_tdp_level __read_mostly; @@ -3205,69 +3208,6 @@ static bool kvm_mmu_prefetch_sptes(struct kvm_vcpu *vcpu, gfn_t gfn, u64 *sptep, return true; } -static bool direct_pte_prefetch_many(struct kvm_vcpu *vcpu, - struct kvm_mmu_page *sp, - u64 *start, u64 *end) -{ - gfn_t gfn = kvm_mmu_page_get_gfn(sp, spte_index(start)); - unsigned int access = sp->role.access; - - return kvm_mmu_prefetch_sptes(vcpu, gfn, start, end - start, access); -} - -static void __direct_pte_prefetch(struct kvm_vcpu *vcpu, - struct kvm_mmu_page *sp, u64 *sptep) -{ - u64 *spte, *start = NULL; - int i; - - WARN_ON_ONCE(!sp->role.direct); - - i = spte_index(sptep) & ~(PTE_PREFETCH_NUM - 1); - spte = sp->spt + i; - - for (i = 0; i < PTE_PREFETCH_NUM; i++, spte++) { - if (is_shadow_present_pte(*spte) || spte == sptep) { - if (!start) - continue; - if (!direct_pte_prefetch_many(vcpu, sp, start, spte)) - return; - - start = NULL; - } else if (!start) - start = spte; - } - if (start) - direct_pte_prefetch_many(vcpu, sp, start, spte); -} - -static void direct_pte_prefetch(struct kvm_vcpu *vcpu, u64 *sptep) -{ - struct kvm_mmu_page *sp; - - sp = sptep_to_sp(sptep); - - /* - * Without accessed bits, there's no way to distinguish between - * actually accessed translations and prefetched, so disable pte - * prefetch if accessed bits aren't available. - */ - if (sp_ad_disabled(sp)) - return; - - if (sp->role.level > PG_LEVEL_4K) - return; - - /* - * If addresses are being invalidated, skip prefetching to avoid - * accidentally prefetching those addresses. - */ - if (unlikely(vcpu->kvm->mmu_invalidate_in_progress)) - return; - - __direct_pte_prefetch(vcpu, sp, sptep); -} - /* * Lookup the mapping level for @gfn in the current mm. * @@ -3502,8 +3442,8 @@ static int direct_map(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault) { struct kvm_shadow_walk_iterator it; struct kvm_mmu_page *sp; - int ret, access; gfn_t base_gfn = fault->gfn; + int access; kvm_mmu_hugepage_adjust(vcpu, fault); @@ -3534,13 +3474,8 @@ static int direct_map(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault) if (WARN_ON_ONCE(it.level != fault->goal_level)) return -EFAULT; - ret = mmu_set_spte(vcpu, fault->slot, it.sptep, access, - base_gfn, fault->pfn, fault); - if (ret == RET_PF_SPURIOUS) - return ret; - - direct_pte_prefetch(vcpu, it.sptep); - return ret; + return mmu_set_spte(vcpu, fault->slot, it.sptep, access, base_gfn, + fault->pfn, fault); } static void kvm_send_hwpoison_signal(struct kvm_memory_slot *slot, gfn_t gfn) @@ -6580,11 +6515,38 @@ static int kvm_mmu_write_protect_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, return RET_PF_EMULATE; } +static void kvm_mmu_auto_prefault(struct kvm_vcpu *vcpu, gpa_t start, + u64 error_code, u8 level) +{ + gfn_t nr_pages = READ_ONCE(auto_prefault_nr_pages); + int nr_pages_msb; + gfn_t i; + + if (unlikely(error_code & PFERR_RSVD_MASK)) + return; + + nr_pages = min(nr_pages, KVM_PAGES_PER_HPAGE(PG_LEVEL_1G)); + nr_pages_msb = find_last_bit((unsigned long *)&nr_pages, sizeof(nr_pages)); + + start = ALIGN_DOWN(start, gfn_to_gpa(BIT_ULL(nr_pages_msb))); + + for (i = KVM_PAGES_PER_HPAGE(level); i < nr_pages; i += KVM_PAGES_PER_HPAGE(level)) { + gpa_t gpa = start + gfn_to_gpa(i); + + if (gpa < start || gpa_to_gfn(gpa) > kvm_mmu_max_gfn()) + return; + + if (kvm_tdp_page_prefault(vcpu, gpa, error_code, &level)) + return; + } +} + int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 error_code, void *insn, int insn_len) { int r, emulation_type = EMULTYPE_PF; bool direct = vcpu->arch.mmu->root_role.direct; + u8 level; if (WARN_ON_ONCE(!VALID_PAGE(vcpu->arch.mmu->root.hpa))) return RET_PF_RETRY; @@ -6617,7 +6579,7 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err vcpu->stat.pf_taken++; r = kvm_mmu_do_page_fault(vcpu, cr2_or_gpa, error_code, false, - &emulation_type, NULL); + &emulation_type, &level); if (KVM_BUG_ON(r == RET_PF_INVALID, vcpu->kvm)) return -EIO; } @@ -6628,6 +6590,8 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err if (r == RET_PF_WRITE_PROTECTED) r = kvm_mmu_write_protect_fault(vcpu, cr2_or_gpa, error_code, &emulation_type); + else if (r == RET_PF_FIXED) + kvm_mmu_auto_prefault(vcpu, cr2_or_gpa, error_code, level); if (r == RET_PF_FIXED) vcpu->stat.pf_fixed++; diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h index 27427e7f22fa..b41b4b78cc81 100644 --- a/arch/x86/kvm/mmu/paging_tmpl.h +++ b/arch/x86/kvm/mmu/paging_tmpl.h @@ -617,7 +617,7 @@ static void FNAME(pte_prefetch)(struct kvm_vcpu *vcpu, struct guest_walker *gw, sp = sptep_to_sp(sptep); - if (sp->role.level > PG_LEVEL_4K) + if (sp->role.level > PG_LEVEL_4K || sp->role.direct) return; /* @@ -627,9 +627,6 @@ static void FNAME(pte_prefetch)(struct kvm_vcpu *vcpu, struct guest_walker *gw, if (unlikely(vcpu->kvm->mmu_invalidate_in_progress)) return; - if (sp->role.direct) - return __direct_pte_prefetch(vcpu, sp, sptep); - i = spte_index(sptep) & ~(PTE_PREFETCH_NUM - 1); spte = sp->spt + i;