KVM: x86: nSVM: support PAUSE filtering when L0 doesn't intercept PAUSE
authorMaxim Levitsky <mlevitsk@redhat.com>
Tue, 22 Mar 2022 17:40:47 +0000 (19:40 +0200)
committerPaolo Bonzini <pbonzini@redhat.com>
Sat, 2 Apr 2022 09:41:23 +0000 (05:41 -0400)
Expose the pause filtering and threshold in the guest CPUID
and support PAUSE filtering when possible:

- If the L0 doesn't intercept PAUSE (cpu_pm=on), then allow L1 to
  have full control over PAUSE filtering.

- if the L1 doesn't intercept PAUSE, use host values and update
  the adaptive count/threshold even when running nested.

- Otherwise always exit to L1; it is not really possible to merge
  the fields correctly.  It is expected that in this case, userspace
  will not enable this feature in the guest CPUID, to avoid having the
  guest update both fields pointlessly.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Message-Id: <20220322174050.241850-4-mlevitsk@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/kvm/svm/nested.c
arch/x86/kvm/svm/svm.c
arch/x86/kvm/svm/svm.h

index f1332d8..fe00117 100644 (file)
@@ -668,6 +668,29 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
        if (!nested_vmcb_needs_vls_intercept(svm))
                vmcb02->control.virt_ext |= VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
 
+       if (kvm_pause_in_guest(svm->vcpu.kvm)) {
+               /* use guest values since host doesn't use them */
+               vmcb02->control.pause_filter_count =
+                               svm->pause_filter_enabled ?
+                               svm->nested.ctl.pause_filter_count : 0;
+
+               vmcb02->control.pause_filter_thresh =
+                               svm->pause_threshold_enabled ?
+                               svm->nested.ctl.pause_filter_thresh : 0;
+
+       } else if (!vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_PAUSE)) {
+               /* use host values when guest doesn't use them */
+               vmcb02->control.pause_filter_count = vmcb01->control.pause_filter_count;
+               vmcb02->control.pause_filter_thresh = vmcb01->control.pause_filter_thresh;
+       } else {
+               /*
+                * Intercept every PAUSE otherwise and
+                * ignore both host and guest values
+                */
+               vmcb02->control.pause_filter_count = 0;
+               vmcb02->control.pause_filter_thresh = 0;
+       }
+
        nested_svm_transition_tlb_flush(vcpu);
 
        /* Enter Guest-Mode */
@@ -928,6 +951,9 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
        vmcb12->control.event_inj         = svm->nested.ctl.event_inj;
        vmcb12->control.event_inj_err     = svm->nested.ctl.event_inj_err;
 
+       if (!kvm_pause_in_guest(vcpu->kvm) && vmcb02->control.pause_filter_count)
+               vmcb01->control.pause_filter_count = vmcb02->control.pause_filter_count;
+
        nested_svm_copy_common_state(svm->nested.vmcb02.ptr, svm->vmcb01.ptr);
 
        svm_switch_vmcb(svm, &svm->vmcb01);
index a6282be..30b00a8 100644 (file)
@@ -908,6 +908,9 @@ static void grow_ple_window(struct kvm_vcpu *vcpu)
        struct vmcb_control_area *control = &svm->vmcb->control;
        int old = control->pause_filter_count;
 
+       if (kvm_pause_in_guest(vcpu->kvm) || !old)
+               return;
+
        control->pause_filter_count = __grow_ple_window(old,
                                                        pause_filter_count,
                                                        pause_filter_count_grow,
@@ -926,6 +929,9 @@ static void shrink_ple_window(struct kvm_vcpu *vcpu)
        struct vmcb_control_area *control = &svm->vmcb->control;
        int old = control->pause_filter_count;
 
+       if (kvm_pause_in_guest(vcpu->kvm) || !old)
+               return;
+
        control->pause_filter_count =
                                __shrink_ple_window(old,
                                                    pause_filter_count,
@@ -2982,7 +2988,6 @@ static int interrupt_window_interception(struct kvm_vcpu *vcpu)
 static int pause_interception(struct kvm_vcpu *vcpu)
 {
        bool in_kernel;
-
        /*
         * CPL is not made available for an SEV-ES guest, therefore
         * vcpu->arch.preempted_in_kernel can never be true.  Just
@@ -2990,8 +2995,7 @@ static int pause_interception(struct kvm_vcpu *vcpu)
         */
        in_kernel = !sev_es_guest(vcpu->kvm) && svm_get_cpl(vcpu) == 0;
 
-       if (!kvm_pause_in_guest(vcpu->kvm))
-               grow_ple_window(vcpu);
+       grow_ple_window(vcpu);
 
        kvm_vcpu_on_spin(vcpu, in_kernel);
        return kvm_skip_emulated_instruction(vcpu);
@@ -4018,6 +4022,12 @@ static void svm_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
 
        svm->v_vmload_vmsave_enabled = vls && guest_cpuid_has(vcpu, X86_FEATURE_V_VMSAVE_VMLOAD);
 
+       svm->pause_filter_enabled = kvm_cpu_cap_has(X86_FEATURE_PAUSEFILTER) &&
+                       guest_cpuid_has(vcpu, X86_FEATURE_PAUSEFILTER);
+
+       svm->pause_threshold_enabled = kvm_cpu_cap_has(X86_FEATURE_PFTHRESHOLD) &&
+                       guest_cpuid_has(vcpu, X86_FEATURE_PFTHRESHOLD);
+
        svm_recalc_instruction_intercepts(vcpu, svm);
 
        /* For sev guests, the memory encryption bit is not reserved in CR3.  */
@@ -4771,6 +4781,12 @@ static __init void svm_set_cpu_caps(void)
                if (lbrv)
                        kvm_cpu_cap_set(X86_FEATURE_LBRV);
 
+               if (boot_cpu_has(X86_FEATURE_PAUSEFILTER))
+                       kvm_cpu_cap_set(X86_FEATURE_PAUSEFILTER);
+
+               if (boot_cpu_has(X86_FEATURE_PFTHRESHOLD))
+                       kvm_cpu_cap_set(X86_FEATURE_PFTHRESHOLD);
+
                /* Nested VM can receive #VMEXIT instead of triggering #GP */
                kvm_cpu_cap_set(X86_FEATURE_SVME_ADDR_CHK);
        }
index b687393..50b95e8 100644 (file)
@@ -237,6 +237,8 @@ struct vcpu_svm {
        bool tsc_scaling_enabled          : 1;
        bool v_vmload_vmsave_enabled      : 1;
        bool lbrv_enabled                 : 1;
+       bool pause_filter_enabled         : 1;
+       bool pause_threshold_enabled      : 1;
 
        u32 ldr_reg;
        u32 dfr_reg;