From: "Maciej S. Szmigiero" When AVIC is enabled the normal pre-VMRUN sync in sync_lapic_to_cr8() is inhibited so any changed TPR in the LAPIC state would not get copied into the V_TPR field of VMCB. AVIC does sync between these two fields, however it does so only on explicit guest writes to one of these fields, not on a bare VMRUN. This is especially true when it is the userspace setting LAPIC state via KVM_SET_LAPIC ioctl() since userspace does not have access to the guest VMCB. Practice shows that it is the V_TPR that is actually used by the AVIC to decide whether to issue pending interrupts to the CPU (not TPR in TASKPRI), so any leftover value in V_TPR will cause serious interrupt delivery issues in the guest when AVIC is enabled. Fix this issue by explicitly copying LAPIC TPR to VMCB::V_TPR in avic_apicv_post_state_restore(), which gets called from KVM_SET_LAPIC and similar code paths when AVIC is enabled. Fixes: 3bbf3565f48c ("svm: Do not intercept CR8 when enable AVIC") Signed-off-by: Maciej S. Szmigiero --- arch/x86/kvm/svm/avic.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c index a34c5c3b164e..877bc3db2c6e 100644 --- a/arch/x86/kvm/svm/avic.c +++ b/arch/x86/kvm/svm/avic.c @@ -725,8 +725,31 @@ int avic_init_vcpu(struct vcpu_svm *svm) void avic_apicv_post_state_restore(struct kvm_vcpu *vcpu) { + struct vcpu_svm *svm = to_svm(vcpu); + u64 cr8; + avic_handle_dfr_update(vcpu); avic_handle_ldr_update(vcpu); + + /* Running nested should have inhibited AVIC. */ + if (WARN_ON_ONCE(nested_svm_virtualize_tpr(vcpu))) + return; + + /* + * Sync TPR from LAPIC TASKPRI into V_TPR field of the VMCB. + * + * When AVIC is enabled the normal pre-VMRUN sync in sync_lapic_to_cr8() + * is inhibited so any set TPR LAPIC state would not get reflected + * in V_TPR. + * + * Practice shows that it is the V_TPR that is actually used by the + * AVIC to decide whether to issue pending interrupts to the CPU, not + * TPR in TASKPRI. + */ + cr8 = kvm_get_cr8(vcpu); + svm->vmcb->control.int_ctl &= ~V_TPR_MASK; + svm->vmcb->control.int_ctl |= cr8 & V_TPR_MASK; + WARN_ON_ONCE(!vmcb_is_dirty(svm->vmcb, VMCB_INTR)); } static void svm_ir_list_del(struct kvm_kernel_irqfd *irqfd)