diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index b3e99eeab82f1..f9d26c620f089 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -1169,29 +1169,6 @@ static void drop_spte(struct kvm *kvm, u64 *sptep) rmap_remove(kvm, sptep); } - -static bool __drop_large_spte(struct kvm *kvm, u64 *sptep) -{ - if (is_large_pte(*sptep)) { - WARN_ON(sptep_to_sp(sptep)->role.level == PG_LEVEL_4K); - drop_spte(kvm, sptep); - --kvm->stat.lpages; - return true; - } - - return false; -} - -static void drop_large_spte(struct kvm_vcpu *vcpu, u64 *sptep) -{ - if (__drop_large_spte(vcpu->kvm, sptep)) { - struct kvm_mmu_page *sp = sptep_to_sp(sptep); - - kvm_flush_remote_tlbs_with_address(vcpu->kvm, sp->gfn, - KVM_PAGES_PER_HPAGE(sp->role.level)); - } -} - /* * Write-protect on the specified @sptep, @pt_protect indicates whether * spte write-protection is caused by protecting shadow page table. @@ -2076,20 +2053,13 @@ static void clear_sp_write_flooding_count(u64 *spte) __clear_sp_write_flooding_count(sptep_to_sp(spte)); } -static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, - gfn_t gfn, - gva_t gaddr, - unsigned level, - int direct, - unsigned int access) +static union kvm_mmu_page_role +kvm_mmu_child_role(struct kvm_vcpu *vcpu, gva_t gaddr, unsigned int level, + int direct, unsigned int access) { bool direct_mmu = vcpu->arch.mmu->direct_map; union kvm_mmu_page_role role; - struct hlist_head *sp_list; unsigned quadrant; - struct kvm_mmu_page *sp; - int collisions = 0; - LIST_HEAD(invalid_list); role = vcpu->arch.mmu->mmu_role.base; role.level = level; @@ -2103,6 +2073,25 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, role.quadrant = quadrant; } + return role; +} + +static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, + gfn_t gfn, + gva_t gaddr, + unsigned level, + int direct, + unsigned int access) +{ + bool direct_mmu = vcpu->arch.mmu->direct_map; + union kvm_mmu_page_role role; + struct hlist_head *sp_list; + struct kvm_mmu_page *sp; + int collisions = 0; + LIST_HEAD(invalid_list); + + role = kvm_mmu_child_role(vcpu, gaddr, level, direct, access); + sp_list = &vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)]; for_each_valid_sp(vcpu->kvm, sp, sp_list) { if (sp->gfn != gfn) { @@ -2312,6 +2301,40 @@ static int mmu_page_zap_pte(struct kvm *kvm, struct kvm_mmu_page *sp, return 0; } +static bool kvm_mmu_child_sp_exists(struct kvm_vcpu *vcpu, u64 *sptep, gfn_t gfn, + gva_t gaddr, unsigned int level, int direct, + unsigned int access) +{ + union kvm_mmu_page_role role; + struct kvm_mmu_page *child; + + if (!is_shadow_present_pte(*sptep) || is_large_pte(*sptep)) + return false; + + child = to_shadow_page(*sptep & PT64_BASE_ADDR_MASK); + if (!child) + return false; + + role = kvm_mmu_child_role(vcpu, gaddr, level, direct, access); + return child->gfn == gfn && child->role.word == role.word; +} + +static void drop_large_spte(struct kvm_vcpu *vcpu, u64 *sptep) +{ + struct kvm *kvm = vcpu->kvm; + struct kvm_mmu_page *parent_sp; + LIST_HEAD(invalid_list); + + if (!is_shadow_present_pte(*sptep)) + return; + + parent_sp = sptep_to_sp(sptep); + WARN_ON_ONCE(parent_sp->role.level == PG_LEVEL_4K); + + mmu_page_zap_pte(kvm, parent_sp, sptep, &invalid_list); + kvm_mmu_remote_flush_or_zap(kvm, &invalid_list, true); +} + static int kvm_mmu_page_unlink_children(struct kvm *kvm, struct kvm_mmu_page *sp, struct list_head *invalid_list) @@ -2997,10 +3020,11 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t gpa, u32 error_code, if (it.level == level) break; - drop_large_spte(vcpu, it.sptep); - if (is_shadow_present_pte(*it.sptep)) + if (kvm_mmu_child_sp_exists(vcpu, it.sptep, base_gfn, it.addr, + it.level - 1, true, ACC_ALL)) continue; + drop_large_spte(vcpu, it.sptep); sp = kvm_mmu_get_page(vcpu, base_gfn, it.addr, it.level - 1, true, ACC_ALL); diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h index 6dbe890122d6e..714aceb173074 100644 --- a/arch/x86/kvm/mmu/paging_tmpl.h +++ b/arch/x86/kvm/mmu/paging_tmpl.h @@ -659,12 +659,13 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr, gfn_t table_gfn; clear_sp_write_flooding_count(it.sptep); - drop_large_spte(vcpu, it.sptep); + table_gfn = gw->table_gfn[it.level - 2]; + access = gw->pt_access[it.level - 2]; sp = NULL; - if (!is_shadow_present_pte(*it.sptep)) { - table_gfn = gw->table_gfn[it.level - 2]; - access = gw->pt_access[it.level - 2]; + if (!kvm_mmu_child_sp_exists(vcpu, it.sptep, table_gfn, addr, + it.level - 1, false, access)) { + drop_large_spte(vcpu, it.sptep); sp = kvm_mmu_get_page(vcpu, table_gfn, addr, it.level-1, false, access); /* @@ -721,9 +722,9 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr, validate_direct_spte(vcpu, it.sptep, direct_access); - drop_large_spte(vcpu, it.sptep); - - if (!is_shadow_present_pte(*it.sptep)) { + if (!kvm_mmu_child_sp_exists(vcpu, it.sptep, base_gfn, addr, + it.level - 1, true, direct_access)) { + drop_large_spte(vcpu, it.sptep); sp = kvm_mmu_get_page(vcpu, base_gfn, addr, it.level - 1, true, direct_access); link_shadow_page(vcpu, it.sptep, sp);