[PATCH 1/2] KVM: x86: ioapic: Clear stale RTC pending EOIs using the delivery record

From: Xiang Zheng

Date: Tue Oct 20 2026 - 02:01:52 EST


Use the vCPU/vector pairs recorded in rtc_status to clear completed RTC
pending EOIs instead of consulting the current redirection entry.

AVIC completes EOIs of edge-triggered interrupts in hardware, without a
VM exit, so KVM cannot decrement the RTC pending-EOI count from an EOI
exit. ioapic_lazy_update_eoi() compensates at the next injection by
clearing the pending EOI of any vCPU that no longer has the vector in
IRR/ISR, but it identifies those vCPUs with the current redirection
entry instead of the delivery record kept in rtc_status.

If the guest rewrites the RTC redirection entry after an interrupt was
delivered and EOI'd without an exit, e.g. because the guest moves the
RTC to another processor, the new route no longer matches the recorded
delivery, so the pending EOI is left set and every later RTC interrupt
is reported as coalesced and dropped. A guest that relies on the RTC
timer interrupt to advance timekeeping, e.g. a Windows guest without
HPET and with an unused APIC timer, then loses its timer and hangs.

Walk the vCPUs present in rtc_status and test the vector stored at
delivery. While at it, clear every completed record instead of bailing
out after the first one, and skip pins other than the RTC, the only pin
with delivery tracking.

Fixes: f458d039db7e ("kvm: ioapic: Lazy update IOAPIC EOI")
Signed-off-by: Xiang Zheng <xiang.zheng@xxxxxxxxxxxxxxxxx>
---
arch/x86/kvm/ioapic.c | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
index 0d59b9c758c2..803fda172db5 100644
--- a/arch/x86/kvm/ioapic.c
+++ b/arch/x86/kvm/ioapic.c
@@ -167,21 +167,18 @@ static void ioapic_lazy_update_eoi(struct kvm_ioapic *ioapic, int irq)
{
unsigned long i;
struct kvm_vcpu *vcpu;
- union kvm_ioapic_redirect_entry *entry = &ioapic->redirtbl[irq];
+ struct rtc_status *status = &ioapic->rtc_status;
+
+ if (irq != RTC_GSI || !status->pending_eoi)
+ return;

+ /* Use the target recorded at delivery; the entry may have changed. */
kvm_for_each_vcpu(i, vcpu, ioapic->kvm) {
- if (!kvm_apic_match_dest(vcpu, NULL, APIC_DEST_NOSHORT,
- entry->fields.dest_id,
- entry->fields.dest_mode) ||
- kvm_apic_pending_eoi(vcpu, entry->fields.vector))
+ if (!test_bit(vcpu->vcpu_id, status->map) ||
+ kvm_apic_pending_eoi(vcpu, status->vectors[vcpu->vcpu_id]))
continue;

- /*
- * If no longer has pending EOI in LAPICs, update
- * EOI for this vector.
- */
- rtc_irq_eoi(ioapic, vcpu, entry->fields.vector);
- break;
+ rtc_irq_eoi(ioapic, vcpu, status->vectors[vcpu->vcpu_id]);
}
}

--
2.50.1 (Apple Git-155)