Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery
@ 2026-08-10  6:17 Hao Zhang
  2026-08-10  6:22 ` [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Hao Zhang @ 2026-08-10  6:17 UTC (permalink / raw)
  To: Sean Christopherson; +Cc: Paolo Bonzini, Huang, Kai, kvm

From: Hao Zhang <zhanghao1@kylinos.cn>

The I/O APIC tracks delivered interrupts in state that is later used to
decide whether an interrupt is still pending or blocked waiting for an
EOI.

For level-triggered interrupts, remote_irr means that a local APIC
accepted the interrupt and that the I/O APIC must wait for the
corresponding EOI before delivering the interrupt again.  For
edge-triggered interrupts, irr_delivered is used to hide delivered
interrupts from KVM_GET_IRQCHIP so that userspace does not reinject an
interrupt that has already left the I/O APIC.

But ioapic_service() currently updates that state before or without
checking that interrupt delivery actually succeeded.
kvm_irq_delivery_to_apic() can return -1 when no destination is found.
Treating failed delivery as success can either leave a level-triggered
pin blocked forever waiting for an EOI that will never be generated, or
cause KVM_GET_IRQCHIP to drop an undelivered edge-triggered interrupt
from the saved IRR state.

Update I/O APIC delivery state 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.")
Fixes: 5bda6eed2e36 ("KVM: ioapic: Record edge-triggered interrupts delivery status")
Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
---
Changes in v2:
 - Address Kai Huang's review by deferring both remote_irr and
   irr_delivered updates until interrupt delivery succeeds.
 - Extend the selftest to cover failed edge-triggered delivery and verify
   that the interrupt remains pending in IRR.

Link to v1: https://lore.kernel.org/all/anSOdijwS6LBbfYJ@192.168.1.215/

 arch/x86/kvm/ioapic.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
index 757667fb2bfa..540e5665fbe4 100644
--- a/arch/x86/kvm/ioapic.c
+++ b/arch/x86/kvm/ioapic.c
@@ -474,9 +474,6 @@ static int ioapic_service(struct kvm_ioapic *ioapic, int irq, bool line_status)
 	irqe.shorthand = APIC_DEST_NOSHORT;
 	irqe.msi_redir_hint = false;
 
-	if (irqe.trig_mode == IOAPIC_EDGE_TRIG)
-		ioapic->irr_delivered |= 1 << irq;
-
 	if (irq == RTC_GSI && line_status) {
 		/*
 		 * pending_eoi cannot ever become negative (see
@@ -491,8 +488,12 @@ 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)
-		entry->fields.remote_irr = 1;
+	if (ret > 0) {
+		if (irqe.trig_mode == IOAPIC_EDGE_TRIG)
+			ioapic->irr_delivered |= 1 << irq;
+		else if (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] 6+ messages in thread

* [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state
  2026-08-10  6:17 [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery Hao Zhang
@ 2026-08-10  6:22 ` Hao Zhang
  2026-08-10  6:33   ` sashiko-bot
  2026-08-10  6:45 ` [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery sashiko-bot
  2026-08-10  8:34 ` Huang, Kai
  2 siblings, 1 reply; 6+ messages in thread
From: Hao Zhang @ 2026-08-10  6:22 UTC (permalink / raw)
  To: Sean Christopherson; +Cc: Paolo Bonzini, kvm

From: Hao Zhang <zhanghao1@kylinos.cn>

Add regression coverage for I/O APIC interrupt delivery when no local
APIC can accept the interrupt.

For level-triggered interrupts, verify that failed delivery does not set
remote_irr, because no local APIC accepted the interrupt and no EOI will
ever be generated.

For edge-triggered interrupts, verify that failed delivery does not cause
KVM_GET_IRQCHIP to drop the pending IRR bit.  Edge interrupts are hidden
from the saved IRR state only after they have actually been delivered.

Both cases use an I/O APIC-only GSI and inject the interrupt before
creating any vCPUs, forcing KVM_IRQ_LINE_STATUS to report failed
delivery.

Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
---
 tools/testing/selftests/kvm/Makefile.kvm           |   1 +
 .../testing/selftests/kvm/x86/ioapic_state_test.c  | 108 +++++++++++++++++++++
 2 files changed, 109 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..3613a67f4b21
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
@@ -0,0 +1,108 @@
+// 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_undeliverable_ioapic_entry(struct kvm_vm *vm,
+					   bool level_triggered)
+{
+	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 = NO_SUCH_APIC_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 void test_no_remote_irr_for_undelivered_interrupt(void)
+{
+	struct kvm_irq_level irq = {
+		.irq = TEST_IOAPIC_PIN,
+		.level = 1,
+	};
+	struct kvm_irqchip irqchip;
+	struct kvm_vm *vm;
+
+	vm = vm_create_barebones();
+	vm_create_irqchip(vm);
+
+	set_undeliverable_ioapic_entry(vm, true);
+
+	vm_ioctl(vm, KVM_IRQ_LINE_STATUS, &irq);
+	TEST_ASSERT(irq.status == -1,
+		    "Expected failed interrupt delivery, got %d", irq.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);
+}
+
+static void test_undelivered_edge_interrupt_stays_pending(void)
+{
+	struct kvm_irq_level irq = {
+		.irq = TEST_IOAPIC_PIN,
+		.level = 1,
+	};
+	struct kvm_irqchip irqchip;
+	struct kvm_vm *vm;
+
+	vm = vm_create_barebones();
+	vm_create_irqchip(vm);
+
+	set_undeliverable_ioapic_entry(vm, false);
+
+	vm_ioctl(vm, KVM_IRQ_LINE_STATUS, &irq);
+	TEST_ASSERT(irq.status == -1,
+		    "Expected failed interrupt delivery, got %d", irq.status);
+
+	get_ioapic(vm, &irqchip);
+	TEST_ASSERT(irqchip.chip.ioapic.irr & (1 << TEST_IOAPIC_PIN),
+		    "KVM dropped an undelivered edge-triggered interrupt from IRR");
+
+	kvm_vm_free(vm);
+}
+
+int main(void)
+{
+	test_no_remote_irr_for_undelivered_interrupt();
+	test_undelivered_edge_interrupt_stays_pending();
+
+	return 0;
+}
-- 
2.15.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state
  2026-08-10  6:22 ` [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
@ 2026-08-10  6:33   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-08-10  6:33 UTC (permalink / raw)
  To: Hao Zhang; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Uninitialized `struct kvm_irqchip` passed to `KVM_GET_IRQCHIP` ioctl leaks stack memory to the kernel, triggering memory sanitizer errors.
--

--- Patch [2]: [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state ---
commit bdb3cd0c37699df03cd34fd3d221e67589076c39
Author: Hao Zhang <zhanghao1@kylinos.cn>

KVM: selftests: Verify failed IOAPIC delivery preserves state

This commit adds regression coverage for I/O APIC interrupt delivery when no
local APIC can accept the interrupt. It verifies that failed delivery behaves
correctly for both level-triggered and edge-triggered interrupts.

> 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 0000000000000..3613a67f4b217
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/ioapic_state_test.c
[ ... ]
> +static void set_undeliverable_ioapic_entry(struct kvm_vm *vm,
> +					   bool level_triggered)
> +{
> +	struct kvm_irqchip irqchip;
> +
> +	get_ioapic(vm, &irqchip);

[Severity: Medium]
Will this leak uninitialized stack memory to the kernel?

Since KVM_GET_IRQCHIP is defined as _IOWR, the kernel uses memdup_user() to
copy the entire struct kvm_irqchip from userspace. Because the struct contains
a 512-byte union and is declared uninitialized on the stack, passing it to the
kernel will trigger memory sanitizer or Valgrind errors during testing.

Could this be zero-initialized?

[ ... ]
> +static void test_no_remote_irr_for_undelivered_interrupt(void)
> +{
> +	struct kvm_irq_level irq = {
> +		.irq = TEST_IOAPIC_PIN,
> +		.level = 1,
> +	};
> +	struct kvm_irqchip irqchip;
> +	struct kvm_vm *vm;
> +
> +	vm = vm_create_barebones();
> +	vm_create_irqchip(vm);
> +
> +	set_undeliverable_ioapic_entry(vm, true);
> +
> +	vm_ioctl(vm, KVM_IRQ_LINE_STATUS, &irq);
> +	TEST_ASSERT(irq.status == -1,
> +		    "Expected failed interrupt delivery, got %d", irq.status);
> +
> +	get_ioapic(vm, &irqchip);

[Severity: Medium]
Does this struct also need to be zero-initialized to prevent memory
sanitizer errors when calling the KVM_GET_IRQCHIP ioctl?

[ ... ]
> +static void test_undelivered_edge_interrupt_stays_pending(void)
> +{
> +	struct kvm_irq_level irq = {
> +		.irq = TEST_IOAPIC_PIN,
> +		.level = 1,
> +	};
> +	struct kvm_irqchip irqchip;
> +	struct kvm_vm *vm;
> +
> +	vm = vm_create_barebones();
> +	vm_create_irqchip(vm);
> +
> +	set_undeliverable_ioapic_entry(vm, false);
> +
> +	vm_ioctl(vm, KVM_IRQ_LINE_STATUS, &irq);
> +	TEST_ASSERT(irq.status == -1,
> +		    "Expected failed interrupt delivery, got %d", irq.status);
> +
> +	get_ioapic(vm, &irqchip);

[Severity: Medium]
Does this uninitialized struct also need to be zero-initialized for the
same reason?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/anls-aUppuiFolpS@192.168.1.215?part=2

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery
  2026-08-10  6:17 [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery Hao Zhang
  2026-08-10  6:22 ` [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
@ 2026-08-10  6:45 ` sashiko-bot
  2026-08-10  8:34 ` Huang, Kai
  2 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-08-10  6:45 UTC (permalink / raw)
  To: Hao Zhang; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Clearing `irr_delivered` for duplicate edge interrupts causes spurious injections upon VM migration.
--

--- Patch [1]: [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery ---
commit 709ff05e7e6d1df68c0523d442968791b55f37d1
Author: Hao Zhang <zhanghao1@kylinos.cn>

KVM: x86: ioapic: Update state only after successful delivery

This commit defers updating I/O APIC delivery state until interrupt
delivery actually succeeds. It ensures level-triggered pins aren't blocked
forever on a failed delivery and edge-triggered interrupts aren't incorrectly
dropped from the saved IRR state.

> diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
> index 757667fb2bfa0..540e5665fbe4f 100644
> --- a/arch/x86/kvm/ioapic.c
> +++ b/arch/x86/kvm/ioapic.c

[ ... ]

> @@ -491,8 +488,12 @@ 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)
> -		entry->fields.remote_irr = 1;
> +	if (ret > 0) {
> +		if (irqe.trig_mode == IOAPIC_EDGE_TRIG)
> +			ioapic->irr_delivered |= 1 << irq;
> +		else if (irqe.trig_mode == IOAPIC_LEVEL_TRIG)
> +			entry->fields.remote_irr = 1;
> +	}

[Severity: High]
This isn't a bug introduced by this patch, but while looking at irr_delivered
tracking, does ioapic_set_irq() have an issue with duplicate edge interrupts?

If a device emulator calls KVM_IRQ_LINE multiple times for an edge-triggered
interrupt without an intervening level=0, the first call delivers the
interrupt and sets irr_delivered here in ioapic_service().

However, on subsequent calls, ioapic_set_irq() does this:

arch/x86/kvm/ioapic.c:ioapic_set_irq() {
	...
	old_irr = ioapic->irr;
	ioapic->irr |= mask;
	if (edge) {
		ioapic->irr_delivered &= ~mask;
		if (old_irr == ioapic->irr) {
			ret = 0;
			goto out;
		}
	}
	...
}

It unsets irr_delivered but then detects the duplicate edge and returns early.

Could this leave irr_delivered incorrectly cleared?

If a VM migration occurs after this, KVM_GET_IRQCHIP might read the interrupt
as pending instead of delivered.

Would this cause a spurious injection on the destination?

>  
>  	return ret;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/anls-aUppuiFolpS@192.168.1.215?part=1

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery
  2026-08-10  6:17 [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery Hao Zhang
  2026-08-10  6:22 ` [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
  2026-08-10  6:45 ` [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery sashiko-bot
@ 2026-08-10  8:34 ` Huang, Kai
  2026-08-10  9:39   ` hao_zhang_kdev
  2 siblings, 1 reply; 6+ messages in thread
From: Huang, Kai @ 2026-08-10  8:34 UTC (permalink / raw)
  To: seanjc@google.com, hao_zhang_kdev@163.com
  Cc: kvm@vger.kernel.org, pbonzini@redhat.com

On Mon, 2026-08-10 at 14:17 +0800, Hao Zhang wrote:
> From: Hao Zhang <zhanghao1@kylinos.cn>
> 
> The I/O APIC tracks delivered interrupts in state that is later used to
> decide whether an interrupt is still pending or blocked waiting for an
> EOI.
> 
> For level-triggered interrupts, remote_irr means that a local APIC
> accepted the interrupt and that the I/O APIC must wait for the
> corresponding EOI before delivering the interrupt again.  For
> edge-triggered interrupts, irr_delivered is used to hide delivered
> interrupts from KVM_GET_IRQCHIP so that userspace does not reinject an
> interrupt that has already left the I/O APIC.
> 
> But ioapic_service() currently updates that state before or without
> checking that interrupt delivery actually succeeded.
> kvm_irq_delivery_to_apic() can return -1 when no destination is found.
> Treating failed delivery as success can either leave a level-triggered
> pin blocked forever waiting for an EOI that will never be generated, or
> cause KVM_GET_IRQCHIP to drop an undelivered edge-triggered interrupt
> from the saved IRR state.
> 
> Update I/O APIC delivery state 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.")
> Fixes: 5bda6eed2e36 ("KVM: ioapic: Record edge-triggered interrupts delivery status")
> Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
> ---
> Changes in v2:
>  - Address Kai Huang's review by deferring both remote_irr and
>    irr_delivered updates until interrupt delivery succeeds.
>  - Extend the selftest to cover failed edge-triggered delivery and verify
>    that the interrupt remains pending in IRR.
> 
> Link to v1: https://lore.kernel.org/all/anSOdijwS6LBbfYJ@192.168.1.215/
> 
>  arch/x86/kvm/ioapic.c | 11 ++++++-----
>  1 file changed, 6 insertions(+), 5 deletions(-)
> 
> diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
> index 757667fb2bfa..540e5665fbe4 100644
> --- a/arch/x86/kvm/ioapic.c
> +++ b/arch/x86/kvm/ioapic.c
> @@ -474,9 +474,6 @@ static int ioapic_service(struct kvm_ioapic *ioapic, int irq, bool line_status)
>  	irqe.shorthand = APIC_DEST_NOSHORT;
>  	irqe.msi_redir_hint = false;
>  
> -	if (irqe.trig_mode == IOAPIC_EDGE_TRIG)
> -		ioapic->irr_delivered |= 1 << irq;
> -
>  	if (irq == RTC_GSI && line_status) {
>  		/*
>  		 * pending_eoi cannot ever become negative (see
> @@ -491,8 +488,12 @@ 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)
> -		entry->fields.remote_irr = 1;
> +	if (ret > 0) {
> +		if (irqe.trig_mode == IOAPIC_EDGE_TRIG)
> +			ioapic->irr_delivered |= 1 << irq;
> +		else if (irqe.trig_mode == IOAPIC_LEVEL_TRIG)
> +			entry->fields.remote_irr = 1;
> +	}
> 

Ah looking at this I immediately realized I misread the code, that I thought the
irr_delivered was for level triggered as well, but actually it is for edge
triggered.  I guess we both got it wrong :-)

For edge triggered IRQ the existing code is correct I believe, since once the
function is called the IRQ is considered delivered no matter whether it is
actually accepted by LAPIC.  I think this exactly reflects the hardware
behaviour.  (In fact, in ioapic_set_irq() you can see the IRQ is removed from
irr_delivered for edge triggered right before ioapic_service() is called.)

For level triggered, I don't see ioapic->irr is cleared by KVM, but only cleared
when ioapic_set_irq() is called with irq_level == 0.  I now (AFAICT) realize it
is the right behaviour since it should be the driver which dessert the level to
stop the level-triggered IRQ.

So in short, I think your v1 is correct, and sorry about my noise. :-(

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery
  2026-08-10  8:34 ` Huang, Kai
@ 2026-08-10  9:39   ` hao_zhang_kdev
  0 siblings, 0 replies; 6+ messages in thread
From: hao_zhang_kdev @ 2026-08-10  9:39 UTC (permalink / raw)
  To: Huang, Kai
  Cc: seanjc@google.com, hao_zhang_kdev@163.com, kvm@vger.kernel.org,
	pbonzini@redhat.com

On Mon, Aug 10, 2026, Huang, Kai wrote:
> On Mon, 2026-08-10 at 14:17 +0800, Hao Zhang wrote:
> > From: Hao Zhang <zhanghao1@kylinos.cn>
> > 
> > The I/O APIC tracks delivered interrupts in state that is later used to
> > decide whether an interrupt is still pending or blocked waiting for an
> > EOI.
> > 
> > For level-triggered interrupts, remote_irr means that a local APIC
> > accepted the interrupt and that the I/O APIC must wait for the
> > corresponding EOI before delivering the interrupt again.  For
> > edge-triggered interrupts, irr_delivered is used to hide delivered
> > interrupts from KVM_GET_IRQCHIP so that userspace does not reinject an
> > interrupt that has already left the I/O APIC.
> > 
> > But ioapic_service() currently updates that state before or without
> > checking that interrupt delivery actually succeeded.
> > kvm_irq_delivery_to_apic() can return -1 when no destination is found.
> > Treating failed delivery as success can either leave a level-triggered
> > pin blocked forever waiting for an EOI that will never be generated, or
> > cause KVM_GET_IRQCHIP to drop an undelivered edge-triggered interrupt
> > from the saved IRR state.
> > 
> > Update I/O APIC delivery state 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.")
> > Fixes: 5bda6eed2e36 ("KVM: ioapic: Record edge-triggered interrupts delivery status")
> > Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
> > ---
> > Changes in v2:
> >  - Address Kai Huang's review by deferring both remote_irr and
> >    irr_delivered updates until interrupt delivery succeeds.
> >  - Extend the selftest to cover failed edge-triggered delivery and verify
> >    that the interrupt remains pending in IRR.
> > 
> > Link to v1: https://lore.kernel.org/all/anSOdijwS6LBbfYJ@192.168.1.215/
> > 
> >  arch/x86/kvm/ioapic.c | 11 ++++++-----
> >  1 file changed, 6 insertions(+), 5 deletions(-)
> > 
> > diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c
> > index 757667fb2bfa..540e5665fbe4 100644
> > --- a/arch/x86/kvm/ioapic.c
> > +++ b/arch/x86/kvm/ioapic.c
> > @@ -474,9 +474,6 @@ static int ioapic_service(struct kvm_ioapic *ioapic, int irq, bool line_status)
> >  	irqe.shorthand = APIC_DEST_NOSHORT;
> >  	irqe.msi_redir_hint = false;
> >  
> > -	if (irqe.trig_mode == IOAPIC_EDGE_TRIG)
> > -		ioapic->irr_delivered |= 1 << irq;
> > -
> >  	if (irq == RTC_GSI && line_status) {
> >  		/*
> >  		 * pending_eoi cannot ever become negative (see
> > @@ -491,8 +488,12 @@ 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)
> > -		entry->fields.remote_irr = 1;
> > +	if (ret > 0) {
> > +		if (irqe.trig_mode == IOAPIC_EDGE_TRIG)
> > +			ioapic->irr_delivered |= 1 << irq;
> > +		else if (irqe.trig_mode == IOAPIC_LEVEL_TRIG)
> > +			entry->fields.remote_irr = 1;
> > +	}
> > 
> 
> Ah looking at this I immediately realized I misread the code, that I thought the
> irr_delivered was for level triggered as well, but actually it is for edge
> triggered.  I guess we both got it wrong :-)
> 
> For edge triggered IRQ the existing code is correct I believe, since once the
> function is called the IRQ is considered delivered no matter whether it is
> actually accepted by LAPIC.  I think this exactly reflects the hardware
> behaviour.  (In fact, in ioapic_set_irq() you can see the IRQ is removed from
> irr_delivered for edge triggered right before ioapic_service() is called.)
> 
> For level triggered, I don't see ioapic->irr is cleared by KVM, but only cleared
> when ioapic_set_irq() is called with irq_level == 0.  I now (AFAICT) realize it
> is the right behaviour since it should be the driver which dessert the level to
> stop the level-triggered IRQ.
> 
> So in short, I think your v1 is correct, and sorry about my noise. :-(

Thanks Kai,

I checked the Intel I/O APIC redirection table documentation(https://edc.intel.com/content/www/it/it/design/products-and-solutions/processors-and-chipsets/comet-lake-u/intel-400-series-chipset-on-package-platform-controller-hub-register-database/1.2/redirection-table-entry-0-rte0-offset-10/).  
The Remote IRR bit is defined for level-triggered interrupts; for edge-triggered
interrupts its meaning is undefined. So yes, treating irr_delivered like
remote_irr was wrong.

For edge-triggered interrupts, once the I/O APIC starts delivering the
interrupt, the interrupt should no longer be considered pending in the I/O
APIC state that is saved through KVM_GET_IRQCHIP, regardless of whether a
local APIC eventually accepts it.

So I agree that v2 went too far by moving the irr_delivered update behind
the delivery result check.  I will drop that part and restore the patch to
only fix the level-triggered remote_irr case.

Thanks,
Hao


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-08-10  9:40 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10  6:17 [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery Hao Zhang
2026-08-10  6:22 ` [PATCH v2 2/2] KVM: selftests: Verify failed IOAPIC delivery preserves state Hao Zhang
2026-08-10  6:33   ` sashiko-bot
2026-08-10  6:45 ` [PATCH 1/2] KVM: x86: ioapic: Update state only after successful delivery sashiko-bot
2026-08-10  8:34 ` Huang, Kai
2026-08-10  9:39   ` hao_zhang_kdev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox