* [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery
@ 2026-08-11 7:34 Hao Zhang
2026-08-11 7:39 ` [PATCH v4 2/4] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Hao Zhang @ 2026-08-11 7:34 UTC (permalink / raw)
To: seanjc@google.com; +Cc: kvm@vger.kernel.org, pbonzini@redhat.com, Huang, Kai
From: Hao Zhang <zhanghao1@kylinos.cn>
The I/O APIC sets remote_irr for level-triggered interrupts that have
been accepted by a local APIC and are waiting for an EOI.
ioapic_service() currently treats any non-zero return from
kvm_irq_delivery_to_apic() as successful delivery. But
kvm_irq_delivery_to_apic() returns -1 when no destination is found.
Setting remote_irr for that failed delivery leaves the pin blocked
forever waiting for an EOI that will never be generated.
Update remote_irr only when the delivery result is positive, i.e. when
at least one local APIC accepted the interrupt.
Fixes: 4925663a079c ("KVM: Report IRQ injection status to userspace.")
Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
---
arch/x86/kvm/ioapic.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
index 757667fb2bfa..24a7cc3b8b7e 100644
--- a/arch/x86/kvm/ioapic.c
+++ b/arch/x86/kvm/ioapic.c
@@ -491,7 +491,7 @@ static int ioapic_service(struct kvm_ioapic *ioapic, int irq, bool line_status)
} else
ret = kvm_irq_delivery_to_apic(ioapic->kvm, NULL, &irqe);
- if (ret && irqe.trig_mode == IOAPIC_LEVEL_TRIG)
+ if (ret > 0 && irqe.trig_mode == IOAPIC_LEVEL_TRIG)
entry->fields.remote_irr = 1;
return ret;
base-commit: c21bb4193868a8de71fc4693fa741e195fdf5d86
--
2.15.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 2/4] KVM: selftests: Verify failed IOAPIC delivery preserves state
2026-08-11 7:34 [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Hao Zhang
@ 2026-08-11 7:39 ` Hao Zhang
2026-08-11 7:41 ` [PATCH v4 3/4] KVM: x86: ioapic: Preserve irr_delivered for duplicate edge interrupts Hao Zhang
` (2 subsequent siblings)
3 siblings, 0 replies; 7+ messages in thread
From: Hao Zhang @ 2026-08-11 7:39 UTC (permalink / raw)
To: seanjc@google.com; +Cc: kvm@vger.kernel.org, pbonzini@redhat.com
From: Hao Zhang <zhanghao1@kylinos.cn>
Add an x86 selftest for the in-kernel I/O APIC state that is exposed
through KVM_GET_IRQCHIP.
Create a VM with an in-kernel irqchip and no vCPUs, route an unmasked
level-triggered I/O APIC pin to a non-existent APIC ID, and verify that
failed delivery does not set remote_irr.
Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../testing/selftests/kvm/x86/ioapic_state_test.c | 92 ++++++++++++++++++++++
2 files changed, 93 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86/ioapic_state_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 6fc34e9bf8e1..89e3f82e75b0 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -86,6 +86,7 @@ TEST_GEN_PROGS_x86 += x86/hyperv_features
TEST_GEN_PROGS_x86 += x86/hyperv_ipi
TEST_GEN_PROGS_x86 += x86/hyperv_svm_test
TEST_GEN_PROGS_x86 += x86/hyperv_tlb_flush
+TEST_GEN_PROGS_x86 += x86/ioapic_state_test
TEST_GEN_PROGS_x86 += x86/kvm_clock_test
TEST_GEN_PROGS_x86 += x86/kvm_pv_test
TEST_GEN_PROGS_x86 += x86/kvm_buslock_test
diff --git a/tools/testing/selftests/kvm/x86/ioapic_state_test.c b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
new file mode 100644
index 000000000000..be5237f34ad2
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Regression tests for in-kernel I/O APIC state.
+ */
+
+#include "kvm_util.h"
+#include "test_util.h"
+
+#define TEST_IOAPIC_PIN 16
+#define TEST_VECTOR 0x50
+#define NO_SUCH_APIC_ID 0xfe
+#define TEST_IOAPIC_EDGE_TRIG 0
+#define TEST_IOAPIC_LEVEL_TRIG 1
+
+static void get_ioapic(struct kvm_vm *vm, struct kvm_irqchip *irqchip)
+{
+ int r;
+
+ irqchip->chip_id = KVM_IRQCHIP_IOAPIC;
+ r = __vm_ioctl(vm, KVM_GET_IRQCHIP, irqchip);
+ if (r && errno == ENXIO)
+ __TEST_REQUIRE(0, "In-kernel I/O APIC not available");
+
+ TEST_ASSERT(!r, KVM_IOCTL_ERROR(KVM_GET_IRQCHIP, r));
+}
+
+static void set_ioapic(struct kvm_vm *vm, struct kvm_irqchip *irqchip)
+{
+ irqchip->chip_id = KVM_IRQCHIP_IOAPIC;
+ vm_ioctl(vm, KVM_SET_IRQCHIP, irqchip);
+}
+
+static void set_ioapic_entry(struct kvm_vm *vm, bool level_triggered,
+ u32 dest_id)
+{
+ struct kvm_irqchip irqchip;
+
+ get_ioapic(vm, &irqchip);
+
+ irqchip.chip.ioapic.redirtbl[TEST_IOAPIC_PIN].fields.vector =
+ TEST_VECTOR;
+ irqchip.chip.ioapic.redirtbl[TEST_IOAPIC_PIN].fields.dest_id =
+ dest_id;
+ irqchip.chip.ioapic.redirtbl[TEST_IOAPIC_PIN].fields.dest_mode = 0;
+ irqchip.chip.ioapic.redirtbl[TEST_IOAPIC_PIN].fields.trig_mode =
+ level_triggered ? TEST_IOAPIC_LEVEL_TRIG :
+ TEST_IOAPIC_EDGE_TRIG;
+ irqchip.chip.ioapic.redirtbl[TEST_IOAPIC_PIN].fields.mask = 0;
+ irqchip.chip.ioapic.redirtbl[TEST_IOAPIC_PIN].fields.remote_irr = 0;
+
+ set_ioapic(vm, &irqchip);
+}
+
+static int kvm_irq_line_status(struct kvm_vm *vm, int level)
+{
+ struct kvm_irq_level irq = {
+ .irq = TEST_IOAPIC_PIN,
+ .level = level,
+ };
+
+ vm_ioctl(vm, KVM_IRQ_LINE_STATUS, &irq);
+ return irq.status;
+}
+
+static void test_no_remote_irr_for_undelivered_level_interrupt(void)
+{
+ struct kvm_irqchip irqchip;
+ struct kvm_vm *vm;
+ int status;
+
+ vm = vm_create_barebones();
+ vm_create_irqchip(vm);
+
+ set_ioapic_entry(vm, true, NO_SUCH_APIC_ID);
+
+ status = kvm_irq_line_status(vm, 1);
+ TEST_ASSERT(status == -1,
+ "Expected failed interrupt delivery, got %d", status);
+
+ get_ioapic(vm, &irqchip);
+ TEST_ASSERT(!irqchip.chip.ioapic.redirtbl[TEST_IOAPIC_PIN].fields.remote_irr,
+ "KVM set remote_irr for a level-triggered interrupt that wasn't delivered");
+
+ kvm_vm_free(vm);
+}
+
+int main(void)
+{
+ test_no_remote_irr_for_undelivered_level_interrupt();
+
+ return 0;
+}
--
2.15.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 3/4] KVM: x86: ioapic: Preserve irr_delivered for duplicate edge interrupts
2026-08-11 7:34 [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Hao Zhang
2026-08-11 7:39 ` [PATCH v4 2/4] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
@ 2026-08-11 7:41 ` Hao Zhang
2026-08-11 11:27 ` Huang, Kai
2026-08-11 7:43 ` [PATCH v4 4/4] KVM: x86: ioapic: Clear irr_delivered on level-triggered RTE Hao Zhang
2026-08-11 11:11 ` [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Huang, Kai
3 siblings, 1 reply; 7+ messages in thread
From: Hao Zhang @ 2026-08-11 7:41 UTC (permalink / raw)
To: seanjc@google.com; +Cc: kvm@vger.kernel.org, pbonzini@redhat.com
From: Hao Zhang <zhanghao1@kylinos.cn>
For edge-triggered interrupts, ioapic_set_irq() clears irr_delivered
before checking whether the new edge is a duplicate. A duplicate edge
is then coalesced without being serviced, leaving the interrupt visible
in KVM_GET_IRQCHIP state even though the original interrupt was already
delivered.
Move the state update after the duplicate-edge check so duplicate edges
do not make a delivered interrupt appear pending during VM migration.
Add a selftest to verify that a duplicate edge interrupt does not make
an already-delivered interrupt visible in the saved IRR state.
Fixes: 5bda6eed2e36 ("KVM: ioapic: Record edge-triggered interrupts delivery status")
Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
---
arch/x86/kvm/ioapic.c | 2 +-
.../testing/selftests/kvm/x86/ioapic_state_test.c | 47 ++++++++++++++++++++++
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
index 24a7cc3b8b7e..676effd674f9 100644
--- a/arch/x86/kvm/ioapic.c
+++ b/arch/x86/kvm/ioapic.c
@@ -230,11 +230,11 @@ static int ioapic_set_irq(struct kvm_ioapic *ioapic, unsigned int irq,
old_irr = ioapic->irr;
ioapic->irr |= mask;
if (edge) {
- ioapic->irr_delivered &= ~mask;
if (old_irr == ioapic->irr) {
ret = 0;
goto out;
}
+ ioapic->irr_delivered &= ~mask;
}
ret = ioapic_service(ioapic, irq, line_status);
diff --git a/tools/testing/selftests/kvm/x86/ioapic_state_test.c b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
index be5237f34ad2..da0276a1542f 100644
--- a/tools/testing/selftests/kvm/x86/ioapic_state_test.c
+++ b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
@@ -3,7 +3,9 @@
* Regression tests for in-kernel I/O APIC state.
*/
+#include "apic.h"
#include "kvm_util.h"
+#include "processor.h"
#include "test_util.h"
#define TEST_IOAPIC_PIN 16
@@ -62,6 +64,16 @@ static int kvm_irq_line_status(struct kvm_vm *vm, int level)
return irq.status;
}
+static void assert_ioapic_pin_irr(struct kvm_vm *vm, bool expected)
+{
+ struct kvm_irqchip irqchip;
+
+ get_ioapic(vm, &irqchip);
+ TEST_ASSERT(!!(irqchip.chip.ioapic.irr & (1 << TEST_IOAPIC_PIN)) == expected,
+ "Expected IOAPIC IRR for pin %u to be %u, got 0x%x",
+ TEST_IOAPIC_PIN, expected, irqchip.chip.ioapic.irr);
+}
+
static void test_no_remote_irr_for_undelivered_level_interrupt(void)
{
struct kvm_irqchip irqchip;
@@ -84,9 +96,44 @@ static void test_no_remote_irr_for_undelivered_level_interrupt(void)
kvm_vm_free(vm);
}
+static void test_duplicate_edge_interrupt_preserves_delivery_state(void)
+{
+ struct kvm_lapic_state lapic;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ u64 apicbase;
+ int status;
+
+ vm = vm_create_with_one_vcpu(&vcpu, NULL);
+ apicbase = vcpu_get_msr(vcpu, MSR_IA32_APICBASE);
+ vcpu_set_msr(vcpu, MSR_IA32_APICBASE,
+ apicbase | MSR_IA32_APICBASE_ENABLE);
+ vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic);
+ *(u32 *)(lapic.regs + APIC_SPIV) |= APIC_SPIV_APIC_ENABLED;
+ vcpu_ioctl(vcpu, KVM_SET_LAPIC, &lapic);
+
+ set_ioapic_entry(vm, false, vcpu->id);
+
+ status = kvm_irq_line_status(vm, 1);
+ TEST_ASSERT(status > 0,
+ "Expected edge interrupt delivery, got %d", status);
+
+ assert_ioapic_pin_irr(vm, false);
+
+ status = kvm_irq_line_status(vm, 1);
+ TEST_ASSERT(!status,
+ "Expected duplicate edge interrupt to be coalesced, got %d",
+ status);
+
+ assert_ioapic_pin_irr(vm, false);
+
+ kvm_vm_free(vm);
+}
+
int main(void)
{
test_no_remote_irr_for_undelivered_level_interrupt();
+ test_duplicate_edge_interrupt_preserves_delivery_state();
return 0;
}
--
2.15.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 4/4] KVM: x86: ioapic: Clear irr_delivered on level-triggered RTE
2026-08-11 7:34 [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Hao Zhang
2026-08-11 7:39 ` [PATCH v4 2/4] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
2026-08-11 7:41 ` [PATCH v4 3/4] KVM: x86: ioapic: Preserve irr_delivered for duplicate edge interrupts Hao Zhang
@ 2026-08-11 7:43 ` Hao Zhang
2026-08-11 11:43 ` Huang, Kai
2026-08-11 11:11 ` [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Huang, Kai
3 siblings, 1 reply; 7+ messages in thread
From: Hao Zhang @ 2026-08-11 7:43 UTC (permalink / raw)
To: seanjc@google.com; +Cc: kvm@vger.kernel.org, pbonzini@redhat.com
From: Hao Zhang <zhanghao1@kylinos.cn>
irr_delivered hides delivered edge interrupts from KVM_GET_IRQCHIP.
The interrupt remains in IRR until the guest deasserts the line.
The guest can reprogram an IOAPIC RTE from edge to level via MMIO.
If an edge interrupt was delivered before the trigger mode change, a
stale irr_delivered bit can incorrectly mask a subsequent pending level
interrupt from KVM_GET_IRQCHIP.
That can cause userspace to migrate the VM without a pending level
interrupt that is still present in KVM's in-kernel IOAPIC.
Clear irr_delivered when the RTE is configured for level-triggered
interrupts, and add a regression test that reprograms the IOAPIC via
guest MMIO to verify that the pending IRR remains visible.
Fixes: 5bda6eed2e36 ("KVM: ioapic: Record edge-triggered interrupts delivery status")
Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
---
arch/x86/kvm/ioapic.c | 11 +++-
.../testing/selftests/kvm/x86/ioapic_state_test.c | 67 +++++++++++++++++++---
2 files changed, 69 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
index 676effd674f9..81db025d2ac9 100644
--- a/arch/x86/kvm/ioapic.c
+++ b/arch/x86/kvm/ioapic.c
@@ -377,8 +377,17 @@ static void ioapic_write_indirect(struct kvm_ioapic *ioapic, u32 val)
* as edge-triggered. This behavior is used to simulate an
* explicit EOI on IOAPICs that don't have the EOI register.
*/
- if (e->fields.trig_mode == IOAPIC_EDGE_TRIG)
+ if (e->fields.trig_mode == IOAPIC_EDGE_TRIG) {
e->fields.remote_irr = 0;
+ } else {
+ /*
+ * irr_delivered tracks edge-triggered interrupts that
+ * have already been delivered. Clear stale edge-triggered
+ * delivery state when the entry is configured for
+ * level-triggered interrupts.
+ */
+ ioapic->irr_delivered &= ~(1 << index);
+ }
mask_after = e->fields.mask;
if (mask_before != mask_after)
diff --git a/tools/testing/selftests/kvm/x86/ioapic_state_test.c b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
index da0276a1542f..f024f4f01571 100644
--- a/tools/testing/selftests/kvm/x86/ioapic_state_test.c
+++ b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
@@ -7,12 +7,18 @@
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
+#include "ucall_common.h"
#define TEST_IOAPIC_PIN 16
#define TEST_VECTOR 0x50
#define NO_SUCH_APIC_ID 0xfe
#define TEST_IOAPIC_EDGE_TRIG 0
#define TEST_IOAPIC_LEVEL_TRIG 1
+#define IOAPIC_DEFAULT_GPA 0xfec00000ULL
+#define IOAPIC_REG_SELECT 0x00
+#define IOAPIC_REG_WINDOW 0x10
+#define IOAPIC_RTE_LOW(pin) (0x10 + 2 * (pin))
+#define IOAPIC_RTE_TRIG_MODE BIT(15)
static void get_ioapic(struct kvm_vm *vm, struct kvm_irqchip *irqchip)
{
@@ -53,6 +59,19 @@ static void set_ioapic_entry(struct kvm_vm *vm, bool level_triggered,
set_ioapic(vm, &irqchip);
}
+static void enable_lapic(struct kvm_vcpu *vcpu)
+{
+ struct kvm_lapic_state lapic;
+ u64 apicbase;
+
+ apicbase = vcpu_get_msr(vcpu, MSR_IA32_APICBASE);
+ vcpu_set_msr(vcpu, MSR_IA32_APICBASE,
+ apicbase | MSR_IA32_APICBASE_ENABLE);
+ vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic);
+ *(u32 *)(lapic.regs + APIC_SPIV) |= APIC_SPIV_APIC_ENABLED;
+ vcpu_ioctl(vcpu, KVM_SET_LAPIC, &lapic);
+}
+
static int kvm_irq_line_status(struct kvm_vm *vm, int level)
{
struct kvm_irq_level irq = {
@@ -98,19 +117,12 @@ static void test_no_remote_irr_for_undelivered_level_interrupt(void)
static void test_duplicate_edge_interrupt_preserves_delivery_state(void)
{
- struct kvm_lapic_state lapic;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
- u64 apicbase;
int status;
vm = vm_create_with_one_vcpu(&vcpu, NULL);
- apicbase = vcpu_get_msr(vcpu, MSR_IA32_APICBASE);
- vcpu_set_msr(vcpu, MSR_IA32_APICBASE,
- apicbase | MSR_IA32_APICBASE_ENABLE);
- vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic);
- *(u32 *)(lapic.regs + APIC_SPIV) |= APIC_SPIV_APIC_ENABLED;
- vcpu_ioctl(vcpu, KVM_SET_LAPIC, &lapic);
+ enable_lapic(vcpu);
set_ioapic_entry(vm, false, vcpu->id);
@@ -130,10 +142,49 @@ static void test_duplicate_edge_interrupt_preserves_delivery_state(void)
kvm_vm_free(vm);
}
+static void guest_switch_ioapic_pin_to_level(void)
+{
+ u32 *ioapic = (u32 *)IOAPIC_DEFAULT_GPA;
+
+ WRITE_ONCE(ioapic[IOAPIC_REG_SELECT / sizeof(u32)],
+ IOAPIC_RTE_LOW(TEST_IOAPIC_PIN));
+ WRITE_ONCE(ioapic[IOAPIC_REG_WINDOW / sizeof(u32)],
+ TEST_VECTOR | IOAPIC_RTE_TRIG_MODE);
+
+ GUEST_DONE();
+}
+
+static void test_edge_to_level_clears_delivery_state(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ int status;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_switch_ioapic_pin_to_level);
+ enable_lapic(vcpu);
+ virt_map(vm, IOAPIC_DEFAULT_GPA, IOAPIC_DEFAULT_GPA, 1);
+
+ set_ioapic_entry(vm, false, vcpu->id);
+
+ status = kvm_irq_line_status(vm, 1);
+ TEST_ASSERT(status > 0,
+ "Expected edge interrupt delivery, got %d", status);
+
+ assert_ioapic_pin_irr(vm, false);
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
+
+ assert_ioapic_pin_irr(vm, true);
+
+ kvm_vm_free(vm);
+}
+
int main(void)
{
test_no_remote_irr_for_undelivered_level_interrupt();
test_duplicate_edge_interrupt_preserves_delivery_state();
+ test_edge_to_level_clears_delivery_state();
return 0;
}
--
2.15.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery
2026-08-11 7:34 [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Hao Zhang
` (2 preceding siblings ...)
2026-08-11 7:43 ` [PATCH v4 4/4] KVM: x86: ioapic: Clear irr_delivered on level-triggered RTE Hao Zhang
@ 2026-08-11 11:11 ` Huang, Kai
3 siblings, 0 replies; 7+ messages in thread
From: Huang, Kai @ 2026-08-11 11:11 UTC (permalink / raw)
To: seanjc@google.com, hao_zhang_kdev@163.com
Cc: kvm@vger.kernel.org, pbonzini@redhat.com
On Tue, 2026-08-11 at 15:34 +0800, Hao Zhang wrote:
> From: Hao Zhang <zhanghao1@kylinos.cn>
>
> The I/O APIC sets remote_irr for level-triggered interrupts that have
> been accepted by a local APIC and are waiting for an EOI.
>
> ioapic_service() currently treats any non-zero return from
> kvm_irq_delivery_to_apic() as successful delivery. But
> kvm_irq_delivery_to_apic() returns -1 when no destination is found.
> Setting remote_irr for that failed delivery leaves the pin blocked
> forever waiting for an EOI that will never be generated.
>
> Update remote_irr only when the delivery result is positive, i.e. when
> at least one local APIC accepted the interrupt.
>
> Fixes: 4925663a079c ("KVM: Report IRQ injection status to userspace.")
The code LGTM, but I am not sure this is the right commit for the Fixes tag.
Looking at the relevant diff:
+static int ioapic_service(struct kvm_ioapic *ioapic, unsigned int idx)
{
union ioapic_redir_entry *pent;
+ int injected = -1;
pent = &ioapic->redirtbl[idx];
if (!pent->fields.mask) {
- int injected = ioapic_deliver(ioapic, idx);
+ injected = ioapic_deliver(ioapic, idx);
if (injected && pent->fields.trig_mode == IOAPIC_LEVEL_TRIG)
pent->fields.remote_irr = 1;
}
This commit didn't change any logic related to remote_irr, since it didn't touch
ioapic_deliver() at all.
The one that is close seems to be
ff4b9df877b30 ("KVM: IOAPIC: only set remote_irr if interrupt was injected")
since it actually touched the logic of when to set remote_irr:
@@ -89,8 +89,8 @@ static void ioapic_service(struct kvm_ioapic *ioapic, unsigned
int idx)
pent = &ioapic->redirtbl[idx];
if (!pent->fields.mask) {
- ioapic_deliver(ioapic, idx);
- if (pent->fields.trig_mode == IOAPIC_LEVEL_TRIG)
+ int injected = ioapic_deliver(ioapic, idx);
+ if (injected && pent->fields.trig_mode == IOAPIC_LEVEL_TRIG)
pent->fields.remote_irr = 1;
}
But looking closer, at that time the ioapic_deliver() could only return 0 or 1
(or >1 I didn't check), i.e., it couldn't return -1 at all.
In fact, even when commit 4925663a079c was merged the then ioapic_deliver()
couldn't return -1.
So AFAICT both of the above two commits are not the right one.
I haven't found which one to blame, but it seems at some point the function
kvm_irq_delivery_to_apic() was introduced and at some other point it was changed
to be able to return -1. But I didn't dig further.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 3/4] KVM: x86: ioapic: Preserve irr_delivered for duplicate edge interrupts
2026-08-11 7:41 ` [PATCH v4 3/4] KVM: x86: ioapic: Preserve irr_delivered for duplicate edge interrupts Hao Zhang
@ 2026-08-11 11:27 ` Huang, Kai
0 siblings, 0 replies; 7+ messages in thread
From: Huang, Kai @ 2026-08-11 11:27 UTC (permalink / raw)
To: seanjc@google.com, hao_zhang_kdev@163.com
Cc: kvm@vger.kernel.org, pbonzini@redhat.com
On Tue, 2026-08-11 at 15:41 +0800, Hao Zhang wrote:
> From: Hao Zhang <zhanghao1@kylinos.cn>
>
> For edge-triggered interrupts, ioapic_set_irq() clears irr_delivered
> before checking whether the new edge is a duplicate. A duplicate edge
> is then coalesced without being serviced, leaving the interrupt visible
> in KVM_GET_IRQCHIP state even though the original interrupt was already
> delivered.
>
> Move the state update after the duplicate-edge check so duplicate edges
> do not make a delivered interrupt appear pending during VM migration.
ioapic_set_irq() returns 0 for coalesced edge triggered IRQ, meaning it is
considered not delivered. I don't see a problem if it is marked as pending in
ioapic->irr during KVM_GET_IRQCHIP for migration? It seems the result is the
coalesced IRQ will get re-injected, but what's wrong of this?
But I am not sure on this, so will leave to maintainers.
>
> Add a selftest to verify that a duplicate edge interrupt does not make
> an already-delivered interrupt visible in the saved IRR state.
IMO it's better to split kselftest changes to a separate patch out of KVM
changes.
>
> Fixes: 5bda6eed2e36 ("KVM: ioapic: Record edge-triggered interrupts delivery status")
When this commit was merged, AFAICT the then ioapic_set_irq() didn't support
coalesced edge triggered IRQ at all, so technically this one shouldn't be the
one to blame.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v4 4/4] KVM: x86: ioapic: Clear irr_delivered on level-triggered RTE
2026-08-11 7:43 ` [PATCH v4 4/4] KVM: x86: ioapic: Clear irr_delivered on level-triggered RTE Hao Zhang
@ 2026-08-11 11:43 ` Huang, Kai
0 siblings, 0 replies; 7+ messages in thread
From: Huang, Kai @ 2026-08-11 11:43 UTC (permalink / raw)
To: seanjc@google.com, hao_zhang_kdev@163.com
Cc: kvm@vger.kernel.org, pbonzini@redhat.com
On Tue, 2026-08-11 at 15:43 +0800, Hao Zhang wrote:
> From: Hao Zhang <zhanghao1@kylinos.cn>
>
> irr_delivered hides delivered edge interrupts from KVM_GET_IRQCHIP.
> The interrupt remains in IRR until the guest deasserts the line.
>
> The guest can reprogram an IOAPIC RTE from edge to level via MMIO.
> If an edge interrupt was delivered before the trigger mode change, a
> stale irr_delivered bit can incorrectly mask a subsequent pending level
> interrupt from KVM_GET_IRQCHIP.
>
> That can cause userspace to migrate the VM without a pending level
> interrupt that is still present in KVM's in-kernel IOAPIC.
>
[...]
> --- a/arch/x86/kvm/ioapic.c
> +++ b/arch/x86/kvm/ioapic.c
> @@ -377,8 +377,17 @@ static void ioapic_write_indirect(struct kvm_ioapic *ioapic, u32 val)
> * as edge-triggered. This behavior is used to simulate an
> * explicit EOI on IOAPICs that don't have the EOI register.
> */
> - if (e->fields.trig_mode == IOAPIC_EDGE_TRIG)
> + if (e->fields.trig_mode == IOAPIC_EDGE_TRIG) {
> e->fields.remote_irr = 0;
> + } else {
> + /*
> + * irr_delivered tracks edge-triggered interrupts that
> + * have already been delivered. Clear stale edge-triggered
> + * delivery state when the entry is configured for
> + * level-triggered interrupts.
> + */
> + ioapic->irr_delivered &= ~(1 << index);
> + }
To be honest I am not sure whether we should consider this case, but IIUC this
code change doesn't seem to have any harm, so will leave to others.
>
> mask_after = e->fields.mask;
> if (mask_before != mask_after)
> diff --git a/tools/testing/selftests/kvm/x86/ioapic_state_test.c b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
> index da0276a1542f..f024f4f01571 100644
> --- a/tools/testing/selftests/kvm/x86/ioapic_state_test.c
> +++ b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
Again, I think you should split selftest code change as a separate patch.
And it's better to have a cover letter for such series with multiple patches.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-11 11:43 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 7:34 [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Hao Zhang
2026-08-11 7:39 ` [PATCH v4 2/4] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
2026-08-11 7:41 ` [PATCH v4 3/4] KVM: x86: ioapic: Preserve irr_delivered for duplicate edge interrupts Hao Zhang
2026-08-11 11:27 ` Huang, Kai
2026-08-11 7:43 ` [PATCH v4 4/4] KVM: x86: ioapic: Clear irr_delivered on level-triggered RTE Hao Zhang
2026-08-11 11:43 ` Huang, Kai
2026-08-11 11:11 ` [PATCH v4 1/4] KVM: x86: ioapic: Update remote_irr only after successful delivery Huang, Kai
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox