Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

KVM: SVM: Fix IRQ window inhibit handling across multiple vCPUs

IRQ window inhibits can be requested by multiple vCPUs at the same time
for injecting interrupts meant for different vCPUs. However, AVIC
inhibition is VM-wide and hence it is possible for the inhibition to be
cleared prematurely by the first vCPU that obtains the IRQ window even
though a second vCPU is still waiting for its IRQ window. This is likely
not a functional issue since the other vCPU will again see that
interrupts are pending to be injected (due to KVM_REQ_EVENT), and will
again request for an IRQ window inhibition. However, this can result in
AVIC being rapidly toggled resulting in high contention on
apicv_update_lock and degrading performance of the guest.

Address this by maintaining a VM-wide count of the number of vCPUs that
have requested for an IRQ window. Set/clear the inhibit reason when the
count transitions between 0 and 1. This ensures that the inhibit reason
is not cleared as long as there are some vCPUs still waiting for an IRQ
window.

Co-developed-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Co-developed-by: Naveen N Rao (AMD) <naveen@kernel.org>
Signed-off-by: Naveen N Rao (AMD) <naveen@kernel.org>
Tested-by: Naveen N Rao (AMD) <naveen@kernel.org>
Link: https://patch.msgid.link/20260123224514.2509129-3-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>

+63 -12
+18 -1
arch/x86/include/asm/kvm_host.h
··· 1433 1433 struct kvm_pit *vpit; 1434 1434 #endif 1435 1435 atomic_t vapics_in_nmi_mode; 1436 + 1436 1437 struct mutex apic_map_lock; 1437 1438 struct kvm_apic_map __rcu *apic_map; 1438 1439 atomic_t apic_map_dirty; ··· 1441 1440 bool apic_access_memslot_enabled; 1442 1441 bool apic_access_memslot_inhibited; 1443 1442 1444 - /* Protects apicv_inhibit_reasons */ 1443 + /* 1444 + * Protects apicv_inhibit_reasons and apicv_nr_irq_window_req (with an 1445 + * asterisk, see kvm_inc_or_dec_irq_window_inhibit() for details). 1446 + */ 1445 1447 struct rw_semaphore apicv_update_lock; 1446 1448 unsigned long apicv_inhibit_reasons; 1449 + atomic_t apicv_nr_irq_window_req; 1447 1450 1448 1451 gpa_t wall_clock; 1449 1452 ··· 2319 2314 enum kvm_apicv_inhibit reason) 2320 2315 { 2321 2316 kvm_set_or_clear_apicv_inhibit(kvm, reason, false); 2317 + } 2318 + 2319 + void kvm_inc_or_dec_irq_window_inhibit(struct kvm *kvm, bool inc); 2320 + 2321 + static inline void kvm_inc_apicv_irq_window_req(struct kvm *kvm) 2322 + { 2323 + kvm_inc_or_dec_irq_window_inhibit(kvm, true); 2324 + } 2325 + 2326 + static inline void kvm_dec_apicv_irq_window_req(struct kvm *kvm) 2327 + { 2328 + kvm_inc_or_dec_irq_window_inhibit(kvm, false); 2322 2329 } 2323 2330 2324 2331 int kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 error_code,
+25 -11
arch/x86/kvm/svm/svm.c
··· 3729 3729 * the case in which the interrupt window was requested while L1 was 3730 3730 * active (the vCPU was not running nested). 3731 3731 */ 3732 - if (!kvm_cpu_has_injectable_intr(vcpu) || is_guest_mode(vcpu)) 3733 - kvm_clear_apicv_inhibit(vcpu->kvm, APICV_INHIBIT_REASON_IRQWIN); 3732 + if (svm->avic_irq_window && 3733 + (!kvm_cpu_has_injectable_intr(vcpu) || is_guest_mode(vcpu))) { 3734 + svm->avic_irq_window = false; 3735 + kvm_dec_apicv_irq_window_req(svm->vcpu.kvm); 3736 + } 3734 3737 3735 3738 trace_kvm_inj_virq(intr->nr, intr->soft, reinjected); 3736 3739 ++vcpu->stat.irq_injections; ··· 3935 3932 */ 3936 3933 if (vgif || gif_set(svm)) { 3937 3934 /* 3938 - * IRQ window is not needed when AVIC is enabled, 3939 - * unless we have pending ExtINT since it cannot be injected 3940 - * via AVIC. In such case, KVM needs to temporarily disable AVIC, 3941 - * and fallback to injecting IRQ via V_IRQ. 3935 + * KVM only enables IRQ windows when AVIC is enabled if there's 3936 + * pending ExtINT since it cannot be injected via AVIC (ExtINT 3937 + * bypasses the local APIC). V_IRQ is ignored by hardware when 3938 + * AVIC is enabled, and so KVM needs to temporarily disable 3939 + * AVIC in order to detect when it's ok to inject the ExtINT. 3942 3940 * 3943 - * If running nested, AVIC is already locally inhibited 3944 - * on this vCPU, therefore there is no need to request 3945 - * the VM wide AVIC inhibition. 3941 + * If running nested, AVIC is already locally inhibited on this 3942 + * vCPU (L2 vCPUs use a different MMU that never maps the AVIC 3943 + * backing page), therefore there is no need to increment the 3944 + * VM-wide AVIC inhibit. KVM will re-evaluate events when the 3945 + * vCPU exits to L1 and enable an IRQ window if the ExtINT is 3946 + * still pending. 3947 + * 3948 + * Note, the IRQ window inhibit needs to be updated even if 3949 + * AVIC is inhibited for a different reason, as KVM needs to 3950 + * keep AVIC inhibited if the other reason is cleared and there 3951 + * is still an injectable interrupt pending. 3946 3952 */ 3947 - if (!is_guest_mode(vcpu)) 3948 - kvm_set_apicv_inhibit(vcpu->kvm, APICV_INHIBIT_REASON_IRQWIN); 3953 + if (enable_apicv && !svm->avic_irq_window && !is_guest_mode(vcpu)) { 3954 + svm->avic_irq_window = true; 3955 + kvm_inc_apicv_irq_window_req(vcpu->kvm); 3956 + } 3949 3957 3950 3958 svm_set_vintr(svm); 3951 3959 }
+1
arch/x86/kvm/svm/svm.h
··· 333 333 334 334 bool guest_state_loaded; 335 335 336 + bool avic_irq_window; 336 337 bool x2avic_msrs_intercepted; 337 338 bool lbr_msrs_intercepted; 338 339
+19
arch/x86/kvm/x86.c
··· 11014 11014 } 11015 11015 EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_set_or_clear_apicv_inhibit); 11016 11016 11017 + void kvm_inc_or_dec_irq_window_inhibit(struct kvm *kvm, bool inc) 11018 + { 11019 + int add = inc ? 1 : -1; 11020 + 11021 + if (!enable_apicv) 11022 + return; 11023 + 11024 + /* 11025 + * Strictly speaking, the lock is only needed if going 0->1 or 1->0, 11026 + * a la atomic_dec_and_mutex_lock. However, ExtINTs are rare and 11027 + * only target a single CPU, so that is the common case; do not 11028 + * bother eliding the down_write()/up_write() pair. 11029 + */ 11030 + guard(rwsem_write)(&kvm->arch.apicv_update_lock); 11031 + if (atomic_add_return(add, &kvm->arch.apicv_nr_irq_window_req) == inc) 11032 + __kvm_set_or_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_IRQWIN, inc); 11033 + } 11034 + EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_inc_or_dec_irq_window_inhibit); 11035 + 11017 11036 static void vcpu_scan_ioapic(struct kvm_vcpu *vcpu) 11018 11037 { 11019 11038 if (!kvm_apic_present(vcpu))