KVM: nVMX: Add helper to handle TLB flushes on nested VM-Enter/VM-Exit
authorSean Christopherson <sean.j.christopherson@intel.com>
Fri, 20 Mar 2020 21:28:19 +0000 (14:28 -0700)
committerPaolo Bonzini <pbonzini@redhat.com>
Tue, 21 Apr 2020 13:12:52 +0000 (09:12 -0400)
Add a helper to determine whether or not a full TLB flush needs to be
performed on nested VM-Enter/VM-Exit, as the logic is identical for both
flows and needs a fairly beefy comment to boot.  This also provides a
common point to make future adjustments to the logic.

Handle vpid12 changes the new helper as well even though it is specific
to VM-Enter.  The vpid12 logic is an extension of the flushing logic,
and it's worth the extra bool parameter to provide a single location for
the flushing logic.

Cc: Liran Alon <liran.alon@oracle.com>
Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
Message-Id: <20200320212833.3507-24-sean.j.christopherson@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/kvm/vmx/nested.c

index b18dc7bc3be71da6ed2e0cc9a2a1dca1b03a91ee..ba1aedb535efd1532bbb526c8fbc61c89da1c8fe 100644 (file)
@@ -1132,6 +1132,48 @@ static bool nested_has_guest_tlb_tag(struct kvm_vcpu *vcpu)
               (nested_cpu_has_vpid(vmcs12) && to_vmx(vcpu)->nested.vpid02);
 }
 
+static void nested_vmx_transition_tlb_flush(struct kvm_vcpu *vcpu,
+                                           struct vmcs12 *vmcs12,
+                                           bool is_vmenter)
+{
+       struct vcpu_vmx *vmx = to_vmx(vcpu);
+
+       /*
+        * If VPID is disabled, linear and combined mappings are flushed on
+        * VM-Enter/VM-Exit, and guest-physical mappings are valid only for
+        * their associated EPTP.
+        */
+       if (!enable_vpid)
+               return;
+
+       /*
+        * If vmcs12 doesn't use VPID, L1 expects linear and combined mappings
+        * for *all* contexts to be flushed on VM-Enter/VM-Exit.
+        *
+        * If VPID is enabled and used by vmc12, but L2 does not have a unique
+        * TLB tag (ASID), i.e. EPT is disabled and KVM was unable to allocate
+        * a VPID for L2, flush the TLB as the effective ASID is common to both
+        * L1 and L2.
+        *
+        * Defer the flush so that it runs after vmcs02.EPTP has been set by
+        * KVM_REQ_LOAD_MMU_PGD (if nested EPT is enabled) and to avoid
+        * redundant flushes further down the nested pipeline.
+        *
+        * If a TLB flush isn't required due to any of the above, and vpid12 is
+        * changing then the new "virtual" VPID (vpid12) will reuse the same
+        * "real" VPID (vpid02), and so needs to be sync'd.  There is no direct
+        * mapping between vpid02 and vpid12, vpid02 is per-vCPU and reused for
+        * all nested vCPUs.
+        */
+       if (!nested_cpu_has_vpid(vmcs12) || !nested_has_guest_tlb_tag(vcpu)) {
+               kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu);
+       } else if (is_vmenter &&
+                  vmcs12->virtual_processor_id != vmx->nested.last_vpid) {
+               vmx->nested.last_vpid = vmcs12->virtual_processor_id;
+               vpid_sync_context(nested_get_vpid02(vcpu));
+       }
+}
+
 static bool is_bitwise_subset(u64 superset, u64 subset, u64 mask)
 {
        superset &= mask;
@@ -2440,32 +2482,7 @@ static int prepare_vmcs02(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
        if (kvm_has_tsc_control)
                decache_tsc_multiplier(vmx);
 
-       if (enable_vpid) {
-               /*
-                * There is no direct mapping between vpid02 and vpid12, the
-                * vpid02 is per-vCPU for L0 and reused while the value of
-                * vpid12 is changed w/ one invvpid during nested vmentry.
-                * The vpid12 is allocated by L1 for L2, so it will not
-                * influence global bitmap(for vpid01 and vpid02 allocation)
-                * even if spawn a lot of nested vCPUs.
-                */
-               if (nested_cpu_has_vpid(vmcs12) && nested_has_guest_tlb_tag(vcpu)) {
-                       if (vmcs12->virtual_processor_id != vmx->nested.last_vpid) {
-                               vmx->nested.last_vpid = vmcs12->virtual_processor_id;
-                               vpid_sync_context(nested_get_vpid02(vcpu));
-                       }
-               } else {
-                       /*
-                        * If L1 use EPT, then L0 needs to execute INVEPT on
-                        * EPTP02 instead of EPTP01. Therefore, delay TLB
-                        * flush until vmcs02->eptp is fully updated by
-                        * KVM_REQ_LOAD_MMU_PGD. Note that this assumes
-                        * KVM_REQ_TLB_FLUSH is evaluated after
-                        * KVM_REQ_LOAD_MMU_PGD in vcpu_enter_guest().
-                        */
-                       kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu);
-               }
-       }
+       nested_vmx_transition_tlb_flush(vcpu, vmcs12, true);
 
        if (nested_cpu_has_ept(vmcs12))
                nested_ept_init_mmu_context(vcpu);
@@ -4033,24 +4050,7 @@ static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
        if (!enable_ept)
                vcpu->arch.walk_mmu->inject_page_fault = kvm_inject_page_fault;
 
-       /*
-        * If vmcs01 doesn't use VPID, CPU flushes TLB on every
-        * VMEntry/VMExit. Thus, no need to flush TLB.
-        *
-        * If vmcs12 doesn't use VPID, L1 expects TLB to be
-        * flushed on every VMEntry/VMExit.
-        *
-        * Otherwise, we can preserve TLB entries as long as we are
-        * able to tag L1 TLB entries differently than L2 TLB entries.
-        *
-        * If vmcs12 uses EPT, we need to execute this flush on EPTP01
-        * and therefore we request the TLB flush to happen only after VMCS EPTP
-        * has been set by KVM_REQ_LOAD_MMU_PGD.
-        */
-       if (enable_vpid &&
-           (!nested_cpu_has_vpid(vmcs12) || !nested_has_guest_tlb_tag(vcpu))) {
-               kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu);
-       }
+       nested_vmx_transition_tlb_flush(vcpu, vmcs12, false);
 
        vmcs_write32(GUEST_SYSENTER_CS, vmcs12->host_ia32_sysenter_cs);
        vmcs_writel(GUEST_SYSENTER_ESP, vmcs12->host_ia32_sysenter_esp);