[PATCH 1/2] KVM: SVM: Sync TPR from LAPIC into VMCB::V_TPR when setting LAPIC regs
From: Maciej S. Szmigiero
Date: Tue Aug 19 2025 - 10:04:58 EST
From: "Maciej S. Szmigiero" <maciej.szmigiero@xxxxxxxxxx>
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 <maciej.szmigiero@xxxxxxxxxx>
---
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)