From cdb4ada469a5dd0fd3bbe93e3380ab5a627ddf4d Mon Sep 17 00:00:00 2001 From: "Maciej S. Szmigiero" Date: Mon, 25 Aug 2025 18:44:28 +0200 Subject: [PATCH 1/3] KVM: SVM: Sync TPR from LAPIC into VMCB::V_TPR even if AVIC is active ANBZ: #45496 commit d02e48830e3fce9701265f6c5a58d9bdaf906a76 upstream. Commit 3bbf3565f48c ("svm: Do not intercept CR8 when enable AVIC") inhibited pre-VMRUN sync of TPR from LAPIC into VMCB::V_TPR in sync_lapic_to_cr8() when AVIC is active. AVIC does automatically 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 meant that when AVIC is enabled host changes to TPR in the LAPIC state might not get automatically copied into the V_TPR field of VMCB. 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 doing pre-VMRUN TPR sync from LAPIC into VMCB::V_TPR even when AVIC is enabled. Hygon-SIG: commit d02e48830e3f upstream KVM: SVM: Sync TPR from LAPIC into VMCB::V_TPR even if AVIC is active Fixes: 3bbf3565f48c ("svm: Do not intercept CR8 when enable AVIC") Cc: stable@vger.kernel.org Signed-off-by: Maciej S. Szmigiero Reviewed-by: Naveen N Rao (AMD) Link: https://lore.kernel.org/r/c231be64280b1461e854e1ce3595d70cde3a2e9d.1756139678.git.maciej.szmigiero@oracle.com [sean: tag for stable@] Signed-off-by: Sean Christopherson Signed-off-by: Tina Zhang Signed-off-by: Qiming Deng Cc: hygon-arch@list.openanolis.cn --- arch/x86/kvm/svm/svm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 3d0f2d700efc..6a3710884b64 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -3782,8 +3782,7 @@ static inline void sync_lapic_to_cr8(struct kvm_vcpu *vcpu) struct vcpu_svm *svm = to_svm(vcpu); u64 cr8; - if (nested_svm_virtualize_tpr(vcpu) || - kvm_vcpu_apicv_active(vcpu)) + if (nested_svm_virtualize_tpr(vcpu)) return; cr8 = kvm_get_cr8(vcpu); -- Gitee From 4032a3e8134313f3ec33ee2ecf7a8ed766457fdd Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 3 Feb 2026 11:07:09 -0800 Subject: [PATCH 2/3] KVM: SVM: Initialize AVIC VMCB fields if AVIC is enabled with in-kernel APIC ANBZ: #45496 commit 3989a6d036c8ec82c0de3614bed23a1dacd45de5 upstream. Initialize all per-vCPU AVIC control fields in the VMCB if AVIC is enabled in KVM and the VM has an in-kernel local APIC, i.e. if it's _possible_ the vCPU could activate AVIC at any point in its lifecycle. Configuring the VMCB if and only if AVIC is active "works" purely because of optimizations in kvm_create_lapic() to speculatively set apicv_active if AVIC is enabled *and* to defer updates until the first KVM_RUN. In quotes because KVM likely won't do the right thing if kvm_apicv_activated() is false, i.e. if a vCPU is created while APICv is inhibited at the VM level for whatever reason. E.g. if the inhibit is *removed* before KVM_REQ_APICV_UPDATE is handled in KVM_RUN, then __kvm_vcpu_update_apicv() will elide calls to vendor code due to seeing "apicv_active == activate". Cleaning up the initialization code will also allow fixing a bug where KVM incorrectly leaves CR8 interception enabled when AVIC is activated without creating a mess with respect to whether AVIC is activated or not. Hygon-SIG: commit 3989a6d036c8 upstream KVM: SVM: Initialize AVIC VMCB fields if AVIC is enabled with in-kernel APIC Cc: stable@vger.kernel.org Fixes: 67034bb9dd5e ("KVM: SVM: Add irqchip_split() checks before enabling AVIC") Fixes: 6c3e4422dd20 ("svm: Add support for dynamic APICv") Reviewed-by: Naveen N Rao (AMD) Reviewed-by: Jim Mattson Link: https://patch.msgid.link/20260203190711.458413-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Paolo Bonzini [Tina: Adapt this change to the Linux 5.10 SVM/AVIC implementation. Keep the existing direct AVIC_ENABLE_MASK update because this tree does not have avic_activate_vmcb() or avic_deactivate_vmcb(), use the older avic_init_vmcb() signature, and use avic instead of enable_apicv.] Signed-off-by: Tina Zhang Signed-off-by: Qiming Deng Cc: hygon-arch@list.openanolis.cn --- arch/x86/kvm/svm/avic.c | 2 +- arch/x86/kvm/svm/svm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c index a199a4890a3e..28e6d3ddf8eb 100644 --- a/arch/x86/kvm/svm/avic.c +++ b/arch/x86/kvm/svm/avic.c @@ -204,7 +204,7 @@ void avic_init_vmcb(struct vcpu_svm *svm) vmcb->control.avic_logical_id = lpa & AVIC_HPA_MASK; vmcb->control.avic_physical_id = ppa & AVIC_HPA_MASK; vmcb->control.avic_physical_id |= AVIC_MAX_PHYSICAL_ID_COUNT; - if (kvm_apicv_activated(svm->vcpu.kvm)) + if (kvm_vcpu_apicv_active(&svm->vcpu)) vmcb->control.int_ctl |= AVIC_ENABLE_MASK; else vmcb->control.int_ctl &= ~AVIC_ENABLE_MASK; diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 6a3710884b64..efdc1a4839c2 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -1300,7 +1300,7 @@ static void init_vmcb(struct vcpu_svm *svm) svm_check_invpcid(svm); - if (kvm_vcpu_apicv_active(&svm->vcpu)) + if (avic && irqchip_in_kernel(svm->vcpu.kvm)) avic_init_vmcb(svm); /* -- Gitee From d7480cc3dd4ddffe78d77ae645f92d69c3a48b10 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 3 Feb 2026 11:07:10 -0800 Subject: [PATCH 3/3] KVM: SVM: Set/clear CR8 write interception when AVIC is (de)activated ANBZ: #45496 commit 87d0f901a9bd8ae6be57249c737f20ac0cace93d upstream. Explicitly set/clear CR8 write interception when AVIC is (de)activated to fix a bug where KVM leaves the interception enabled after AVIC is activated. E.g. if KVM emulates INIT=>WFS while AVIC is deactivated, CR8 will remain intercepted in perpetuity. On its own, the dangling CR8 intercept is "just" a performance issue, but combined with the TPR sync bug fixed by commit d02e48830e3f ("KVM: SVM: Sync TPR from LAPIC into VMCB::V_TPR even if AVIC is active"), the dangling intercept is fatal to Windows guests as the TPR seen by hardware gets wildly out of sync with reality. Note, VMX isn't affected by the bug as TPR_THRESHOLD is explicitly ignored when Virtual Interrupt Delivery is enabled, i.e. when APICv is active in KVM's world. I.e. there's no need to trigger update_cr8_intercept(), this is firmly an SVM implementation flaw/detail. WARN if KVM gets a CR8 write #VMEXIT while AVIC is active, as KVM should never enter the guest with AVIC enabled and CR8 writes intercepted. Hygon-SIG: commit 87d0f901a9bd upstream KVM: SVM: Set/clear CR8 write interception when AVIC is (de)activated Fixes: 3bbf3565f48c ("svm: Do not intercept CR8 when enable AVIC") Cc: stable@vger.kernel.org Cc: Jim Mattson Cc: Naveen N Rao (AMD) Cc: Maciej S. Szmigiero Reviewed-by: Naveen N Rao (AMD) Reviewed-by: Jim Mattson Link: https://patch.msgid.link/20260203190711.458413-3-seanjc@google.com Signed-off-by: Sean Christopherson [Squash fix to avic_deactivate_vmcb. - Paolo] Signed-off-by: Paolo Bonzini [Tina: Adapt this change to Linux 5.10, which does not have avic_activate_vmcb() or avic_deactivate_vmcb(). Update CR8 interception in avic_init_vmcb() and svm_refresh_apicv_exec_ctrl() instead, and retain the older cr8_write_interception() interface.] Signed-off-by: Tina Zhang Signed-off-by: Qiming Deng Cc: hygon-arch@list.openanolis.cn --- arch/x86/kvm/svm/avic.c | 10 ++++++++-- arch/x86/kvm/svm/svm.c | 7 ++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c index 28e6d3ddf8eb..3fd6c10e0e54 100644 --- a/arch/x86/kvm/svm/avic.c +++ b/arch/x86/kvm/svm/avic.c @@ -204,10 +204,12 @@ void avic_init_vmcb(struct vcpu_svm *svm) vmcb->control.avic_logical_id = lpa & AVIC_HPA_MASK; vmcb->control.avic_physical_id = ppa & AVIC_HPA_MASK; vmcb->control.avic_physical_id |= AVIC_MAX_PHYSICAL_ID_COUNT; - if (kvm_vcpu_apicv_active(&svm->vcpu)) + if (kvm_vcpu_apicv_active(&svm->vcpu)) { vmcb->control.int_ctl |= AVIC_ENABLE_MASK; - else + svm_clr_intercept(svm, INTERCEPT_CR8_WRITE); + } else { vmcb->control.int_ctl &= ~AVIC_ENABLE_MASK; + } } static u64 *avic_get_physical_id_entry(struct kvm_vcpu *vcpu, @@ -677,8 +679,12 @@ void svm_refresh_apicv_exec_ctrl(struct kvm_vcpu *vcpu) */ avic_post_state_restore(vcpu); vmcb->control.int_ctl |= AVIC_ENABLE_MASK; + svm_clr_intercept(svm, INTERCEPT_CR8_WRITE); } else { vmcb->control.int_ctl &= ~AVIC_ENABLE_MASK; + + if (!sev_es_guest(vcpu->kvm)) + svm_set_intercept(svm, INTERCEPT_CR8_WRITE); } vmcb_mark_dirty(vmcb, VMCB_AVIC); diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index efdc1a4839c2..885250662ce7 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -1176,8 +1176,7 @@ static void init_vmcb(struct vcpu_svm *svm) svm_set_intercept(svm, INTERCEPT_CR0_WRITE); svm_set_intercept(svm, INTERCEPT_CR3_WRITE); svm_set_intercept(svm, INTERCEPT_CR4_WRITE); - if (!kvm_vcpu_apicv_active(&svm->vcpu)) - svm_set_intercept(svm, INTERCEPT_CR8_WRITE); + svm_set_intercept(svm, INTERCEPT_CR8_WRITE); set_dr_intercepts(svm); @@ -2666,9 +2665,11 @@ static int dr_interception(struct vcpu_svm *svm) static int cr8_write_interception(struct vcpu_svm *svm) { struct kvm_run *kvm_run = svm->vcpu.run; + u8 cr8_prev = kvm_get_cr8(&svm->vcpu); int r; - u8 cr8_prev = kvm_get_cr8(&svm->vcpu); + WARN_ON_ONCE(kvm_vcpu_apicv_active(&svm->vcpu)); + /* instruction emulation calls kvm_set_cr8() */ r = cr_interception(svm); if (lapic_in_kernel(&svm->vcpu)) -- Gitee