Re: [PATCH v6 22/38] KVM: nSVM: hyper-v: Enable L2 TLB flush

From: Maxim Levitsky
Date: Tue Jun 07 2022 - 06:03:07 EST


On Mon, 2022-06-06 at 10:36 +0200, Vitaly Kuznetsov wrote:
> Implement Hyper-V L2 TLB flush for nSVM. The feature needs to be enabled
> both in extended 'nested controls' in VMCB and VP assist page.
> According to Hyper-V TLFS, synthetic vmexit to L1 is performed with
> - HV_SVM_EXITCODE_ENL exit_code.
> - HV_SVM_ENL_EXITCODE_TRAP_AFTER_FLUSH exit_info_1.
>
> Note: VP assist page is cached in 'struct kvm_vcpu_hv' so
> recalc_intercepts() doesn't need to read from guest's memory. KVM
> needs to update the case upon each VMRUN and after svm_set_nested_state
> (svm_get_nested_state_pages()) to handle the case when the guest got
> migrated while L2 was running.
>
> Signed-off-by: Vitaly Kuznetsov <vkuznets@xxxxxxxxxx>
> ---
>  arch/x86/kvm/svm/hyperv.c |  7 +++++++
>  arch/x86/kvm/svm/hyperv.h | 30 ++++++++++++++++++++++++++++++
>  arch/x86/kvm/svm/nested.c | 36 ++++++++++++++++++++++++++++++++++--
>  3 files changed, 71 insertions(+), 2 deletions(-)
>
> diff --git a/arch/x86/kvm/svm/hyperv.c b/arch/x86/kvm/svm/hyperv.c
> index 911f51021af1..088f6429b24c 100644
> --- a/arch/x86/kvm/svm/hyperv.c
> +++ b/arch/x86/kvm/svm/hyperv.c
> @@ -8,4 +8,11 @@
>  
>  void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu)
>  {
> +       struct vcpu_svm *svm = to_svm(vcpu);
> +
> +       svm->vmcb->control.exit_code = HV_SVM_EXITCODE_ENL;
> +       svm->vmcb->control.exit_code_hi = 0;
> +       svm->vmcb->control.exit_info_1 = HV_SVM_ENL_EXITCODE_TRAP_AFTER_FLUSH;
> +       svm->vmcb->control.exit_info_2 = 0;
> +       nested_svm_vmexit(svm);
>  }
> diff --git a/arch/x86/kvm/svm/hyperv.h b/arch/x86/kvm/svm/hyperv.h
> index dd2e393f84a0..7b01722838bf 100644
> --- a/arch/x86/kvm/svm/hyperv.h
> +++ b/arch/x86/kvm/svm/hyperv.h
> @@ -33,6 +33,9 @@ struct hv_enlightenments {
>   */
>  #define VMCB_HV_NESTED_ENLIGHTENMENTS VMCB_SW
>  
> +#define HV_SVM_EXITCODE_ENL 0xF0000000
> +#define HV_SVM_ENL_EXITCODE_TRAP_AFTER_FLUSH   (1)
> +
>  static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu)
>  {
>         struct vcpu_svm *svm = to_svm(vcpu);
> @@ -48,6 +51,33 @@ static inline void nested_svm_hv_update_vm_vp_ids(struct kvm_vcpu *vcpu)
>         hv_vcpu->nested.vp_id = hve->hv_vp_id;
>  }
>  
> +static inline bool nested_svm_hv_update_vp_assist(struct kvm_vcpu *vcpu)
> +{
> +       if (!to_hv_vcpu(vcpu))
> +               return true;
> +
> +       if (!kvm_hv_assist_page_enabled(vcpu))
> +               return true;
> +
> +       return kvm_hv_get_assist_page(vcpu);
> +}
> +
> +static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
> +{
> +       struct vcpu_svm *svm = to_svm(vcpu);
> +       struct hv_enlightenments *hve =
> +               (struct hv_enlightenments *)svm->nested.ctl.reserved_sw;
> +       struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu);
> +
> +       if (!hv_vcpu)
> +               return false;
> +
> +       if (!hve->hv_enlightenments_control.nested_flush_hypercall)
> +               return false;
> +
> +       return hv_vcpu->vp_assist_page.nested_control.features.directhypercall;
> +}
> +
>  void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu);
>  
>  #endif /* __ARCH_X86_KVM_SVM_HYPERV_H__ */
> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index 28b63663e1d9..369b92aaf1ad 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -171,8 +171,12 @@ void recalc_intercepts(struct vcpu_svm *svm)
>                 vmcb_clr_intercept(c, INTERCEPT_VINTR);
>         }
>  
> -       /* We don't want to see VMMCALLs from a nested guest */
> -       vmcb_clr_intercept(c, INTERCEPT_VMMCALL);
> +       /*
> +        * We want to see VMMCALLs from a nested guest only when Hyper-V L2 TLB
> +        * flush feature is enabled.
> +        */
> +       if (!nested_svm_l2_tlb_flush_enabled(&svm->vcpu))
> +               vmcb_clr_intercept(c, INTERCEPT_VMMCALL);
>  
>         for (i = 0; i < MAX_INTERCEPT; i++)
>                 c->intercepts[i] |= g->intercepts[i];
> @@ -489,6 +493,17 @@ static void nested_save_pending_event_to_vmcb12(struct vcpu_svm *svm,
>  
>  static void nested_svm_transition_tlb_flush(struct kvm_vcpu *vcpu)
>  {
> +       /*
> +        * KVM_REQ_HV_TLB_FLUSH flushes entries from either L1's VP_ID or
> +        * L2's VP_ID upon request from the guest. Make sure we check for
> +        * pending entries for the case when the request got misplaced (e.g.
> +        * a transition from L2->L1 happened while processing L2 TLB flush
> +        * request or vice versa). kvm_hv_vcpu_flush_tlb() will not flush
> +        * anything if there are no requests in the corresponding buffer.
> +        */
> +       if (to_hv_vcpu(vcpu))
> +               kvm_make_request(KVM_REQ_HV_TLB_FLUSH, vcpu);
> +
>         /*
>          * TODO: optimize unconditional TLB flush/MMU sync.  A partial list of
>          * things to fix before this can be conditional:
> @@ -835,6 +850,12 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu)
>                 return 1;
>         }
>  
> +       /* This fails when VP assist page is enabled but the supplied GPA is bogus */
> +       if (!nested_svm_hv_update_vp_assist(vcpu)) {
> +               kvm_inject_gp(vcpu, 0);
> +               return 1;
> +       }
> +
>         vmcb12_gpa = svm->vmcb->save.rax;
>         ret = kvm_vcpu_map(vcpu, gpa_to_gfn(vmcb12_gpa), &map);
>         if (ret == -EINVAL) {
> @@ -1412,6 +1433,7 @@ static int svm_check_nested_events(struct kvm_vcpu *vcpu)
>  int nested_svm_exit_special(struct vcpu_svm *svm)
>  {
>         u32 exit_code = svm->vmcb->control.exit_code;
> +       struct kvm_vcpu *vcpu = &svm->vcpu;
>  
>         switch (exit_code) {
>         case SVM_EXIT_INTR:
> @@ -1430,6 +1452,13 @@ int nested_svm_exit_special(struct vcpu_svm *svm)
>                         return NESTED_EXIT_HOST;
>                 break;
>         }
> +       case SVM_EXIT_VMMCALL:
> +               /* Hyper-V L2 TLB flush hypercall is handled by L0 */
> +               if (guest_hv_cpuid_has_l2_tlb_flush(vcpu) &&
> +                   nested_svm_l2_tlb_flush_enabled(vcpu) &&
> +                   kvm_hv_is_tlb_flush_hcall(vcpu))
> +                       return NESTED_EXIT_HOST;
> +               break;
>         default:
>                 break;
>         }
> @@ -1710,6 +1739,9 @@ static bool svm_get_nested_state_pages(struct kvm_vcpu *vcpu)
>                 return false;
>         }
>  
> +       if (!nested_svm_hv_update_vp_assist(vcpu))
> +               return false;
> +
>         return true;
>  }
>  
Reviewed-by: Maxim Levitsky <mlevitsk@xxxxxxxxxx>

Best regards,
Maxim Levitsky