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);
 }
 
        hv_vcpu->nested.vp_id = hve->hv_vp_id;
 }
 
+static inline bool nested_svm_l2_tlb_flush_enabled(struct kvm_vcpu *vcpu)
+{
+       struct vcpu_svm *svm = to_svm(vcpu);
+       struct hv_vmcb_enlightenments *hve = &svm->nested.ctl.hv_enlightenments;
+       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__ */
 
                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];
 
 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 in the right FIFO upon L1/L2 transition as these
+        * requests are put by other vCPUs asynchronously.
+        */
+       if (to_hv_vcpu(vcpu) && npt_enabled)
+               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:
                return 1;
        }
 
+       /* This fails when VP assist page is enabled but the supplied GPA is bogus */
+       ret = kvm_hv_verify_vp_assist(vcpu);
+       if (ret) {
+               kvm_inject_gp(vcpu, 0);
+               return ret;
+       }
+
        vmcb12_gpa = svm->vmcb->save.rax;
        ret = kvm_vcpu_map(vcpu, gpa_to_gfn(vmcb12_gpa), &map);
        if (ret == -EINVAL) {
 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:
                        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;
        }
                return false;
        }
 
+       if (kvm_hv_verify_vp_assist(vcpu))
+               return false;
+
        return true;
 }