[RFC PATCH v3 18/27] KVM: x86: Add a new kvm_x86_op protected_apic_has_injectable_intr()

From: Naveen N Rao (AMD)

Date: Wed Jul 08 2026 - 02:42:06 EST


Secure AVIC does not support posted interrupts (unlike TDX) but requires
interrupts to be injected through the VMCB. To address this, introduce a
new kvm_x86_op protected_apic_has_injectable_intr() and update
kvm_cpu_has_injectable_intr() to use this if the guest APIC is
protected.

Finally, update kvm_check_and_inject_events() to ignore
kvm_cpu_get_interrupt() for a protected APIC guest. For such guests (and
at least in the case of Secure AVIC), KVM has no visibility into IRQ
windows and all pending vectors can be injected at once.

Signed-off-by: Naveen N Rao (AMD) <naveen@xxxxxxxxxx>
---
arch/x86/include/asm/kvm-x86-ops.h | 1 +
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/irq.c | 3 +++
arch/x86/kvm/x86.c | 6 +++++-
4 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index 83dc5086138b..f042af63229b 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -116,6 +116,7 @@ KVM_X86_OP_OPTIONAL(apicv_pre_state_restore)
KVM_X86_OP_OPTIONAL(apicv_post_state_restore)
KVM_X86_OP_OPTIONAL_RET0(dy_apicv_has_pending_interrupt)
KVM_X86_OP_OPTIONAL(protected_apic_has_interrupt)
+KVM_X86_OP_OPTIONAL_RET0(protected_apic_has_injectable_intr)
KVM_X86_OP_OPTIONAL(set_hv_timer)
KVM_X86_OP_OPTIONAL(cancel_hv_timer)
KVM_X86_OP(setup_mce)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index cabb368b7908..31d1fc699e7e 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1861,6 +1861,7 @@ struct kvm_x86_ops {
void (*apicv_post_state_restore)(struct kvm_vcpu *vcpu);
bool (*dy_apicv_has_pending_interrupt)(struct kvm_vcpu *vcpu);
bool (*protected_apic_has_interrupt)(struct kvm_vcpu *vcpu);
+ bool (*protected_apic_has_injectable_intr)(struct kvm_vcpu *vcpu);

int (*set_hv_timer)(struct kvm_vcpu *vcpu, u64 guest_deadline_tsc,
bool *expired);
diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c
index dd776449731f..3f8c4bf834f0 100644
--- a/arch/x86/kvm/irq.c
+++ b/arch/x86/kvm/irq.c
@@ -98,6 +98,9 @@ int kvm_cpu_has_injectable_intr(struct kvm_vcpu *v)
if (kvm_cpu_has_extint(v))
return 1;

+ if (lapic_in_kernel(v) && v->arch.apic->guest_apic_protected)
+ return kvm_x86_call(protected_apic_has_injectable_intr)(v);
+
if (!is_guest_mode(v) && kvm_vcpu_apicv_active(v))
return 0;

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index ed94aa8ebc0d..2609a2972526 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7686,7 +7686,11 @@ static int kvm_check_and_inject_events(struct kvm_vcpu *vcpu,
if (r) {
int irq = kvm_cpu_get_interrupt(vcpu);

- if (!WARN_ON_ONCE(irq == -1)) {
+ /*
+ * Ignore kvm_cpu_get_interrupt() for a protected APIC guest. We know
+ * there are injectable interrupts due to the check above.
+ */
+ if (vcpu->arch.apic->guest_apic_protected || !WARN_ON_ONCE(irq == -1)) {
kvm_queue_interrupt(vcpu, irq, false);
kvm_x86_call(inject_irq)(vcpu, false);
WARN_ON(kvm_x86_call(interrupt_allowed)(vcpu, true) < 0);
--
2.54.0