lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <83f9b0a5dd0bc1de9d1e61954f6dd5211df45163.camel@infradead.org>
Date: Wed, 28 Jan 2026 20:49:12 -0800
From: David Woodhouse <dwmw2@...radead.org>
To: Khushit Shah <khushit.shah@...anix.com>, seanjc@...gle.com, 
	pbonzini@...hat.com, kai.huang@...el.com
Cc: mingo@...hat.com, x86@...nel.org, bp@...en8.de, hpa@...or.com, 
 linux-kernel@...r.kernel.org, kvm@...r.kernel.org,
 dave.hansen@...ux.intel.com,  tglx@...utronix.de, jon@...anix.com,
 shaju.abraham@...anix.com
Subject: [PATCH v5 4/3] KVM: selftests: Add test cases for EOI suppression
 modes

From: David Woodhouse <dwmw@...zon.co.uk>

Rather than being frightened of doing the right thing for the in-kernel
I/O APIC because "there might be bugs", let's add selftests for it to
make sure it behaves correctly. For both in-kernel I/O APIC and
userspace, exercise the following modes:

 • Legacy "quirk" behaviour (this test shows the same results on both
   old kernels and on kernels with this patch series in default mode).
 • KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST mode, both with the guest
   enabling APIC_SPIV_DIRECTED_EOI and without.
 • KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST mode.

Testing quirk mode (no flags)...
  IOAPIC v0x11, LVR directed_eoi=0, SPIV directed_eoi=0, remote_irr=0
Testing explicit enable...
  IOAPIC v0x20, LVR directed_eoi=1, SPIV directed_eoi=1, remote_irr=1
Testing explicit enable (guest doesn't use)...
  IOAPIC v0x20, LVR directed_eoi=1, SPIV directed_eoi=0, remote_irr=0
Testing explicit disable...
  IOAPIC v0x11, LVR directed_eoi=0, SPIV directed_eoi=0, remote_irr=0
All tests passed

=== Testing split IRQCHIP mode ===
Testing quirk mode (no flags)...
  Split IRQCHIP: LVR directed_eoi=1, SPIV directed_eoi=0, got_eoi_exit=1
Testing explicit enable...
  Split IRQCHIP: LVR directed_eoi=1, SPIV directed_eoi=1, got_eoi_exit=0
Testing explicit enable (guest doesn't use)...
  Split IRQCHIP: LVR directed_eoi=1, SPIV directed_eoi=0, got_eoi_exit=1
Testing explicit disable...
  Split IRQCHIP: LVR directed_eoi=0, SPIV directed_eoi=0, got_eoi_exit=1
All tests passed

There didn't seem to be a way for selftests to use split irqchip mode
until now, so this adds vm_irqchip_mode modelled on the existing
vm_guest_mode enum.

Signed-off-by: David Woodhouse <dwmw@...zon.co.uk>
---

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 148d427ff24b..01c59bf8b79f 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -122,6 +122,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_nested_tsc_scaling_test
 TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test
 TEST_GEN_PROGS_x86 += x86/xapic_ipi_test
 TEST_GEN_PROGS_x86 += x86/xapic_state_test
+TEST_GEN_PROGS_x86 += x86/suppress_eoi_test
 TEST_GEN_PROGS_x86 += x86/xcr0_cpuid_test
 TEST_GEN_PROGS_x86 += x86/xss_msr_test
 TEST_GEN_PROGS_x86 += x86/debug_regs
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index d3f3e455c031..c4eb0e95bae9 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -209,6 +209,14 @@ kvm_static_assert(sizeof(struct vm_shape) == sizeof(uint64_t));
 	shape;					\
 })
 
+enum vm_irqchip_mode {
+	VM_IRQCHIP_AUTO,
+	VM_IRQCHIP_KERNEL,
+	VM_IRQCHIP_SPLIT,
+};
+
+extern enum vm_irqchip_mode vm_irqchip_mode;
+
 #if defined(__aarch64__)
 
 extern enum vm_guest_mode vm_mode_default;
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 1a93d6361671..4858c10f7530 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -1687,21 +1687,34 @@ void *addr_gpa2alias(struct kvm_vm *vm, vm_paddr_t gpa)
 	return (void *) ((uintptr_t) region->host_alias + offset);
 }
 
+enum vm_irqchip_mode vm_irqchip_mode = VM_IRQCHIP_AUTO;
+
 /* Create an interrupt controller chip for the specified VM. */
 void vm_create_irqchip(struct kvm_vm *vm)
 {
 	int r;
 
 	/*
-	 * Allocate a fully in-kernel IRQ chip by default, but fall back to a
-	 * split model (x86 only) if that fails (KVM x86 allows compiling out
-	 * support for KVM_CREATE_IRQCHIP).
+	 * Create IRQ chip based on vm_irqchip_mode:
+	 * - VM_IRQCHIP_AUTO: Try in-kernel, fall back to split if not supported
+	 * - VM_IRQCHIP_KERNEL: Force in-kernel IRQ chip
+	 * - VM_IRQCHIP_SPLIT: Force split IRQ chip (x86 only)
 	 */
-	r = __vm_ioctl(vm, KVM_CREATE_IRQCHIP, NULL);
-	if (r && errno == ENOTTY && kvm_has_cap(KVM_CAP_SPLIT_IRQCHIP))
+	if (vm_irqchip_mode == VM_IRQCHIP_SPLIT) {
+		TEST_ASSERT(kvm_has_cap(KVM_CAP_SPLIT_IRQCHIP),
+			    "Split IRQ chip not supported");
 		vm_enable_cap(vm, KVM_CAP_SPLIT_IRQCHIP, 24);
-	else
+	} else if (vm_irqchip_mode == VM_IRQCHIP_KERNEL) {
+		r = __vm_ioctl(vm, KVM_CREATE_IRQCHIP, NULL);
 		TEST_ASSERT_VM_VCPU_IOCTL(!r, KVM_CREATE_IRQCHIP, r, vm);
+	} else {
+		/* VM_IRQCHIP_AUTO */
+		r = __vm_ioctl(vm, KVM_CREATE_IRQCHIP, NULL);
+		if (r && errno == ENOTTY && kvm_has_cap(KVM_CAP_SPLIT_IRQCHIP))
+			vm_enable_cap(vm, KVM_CAP_SPLIT_IRQCHIP, 24);
+		else
+			TEST_ASSERT_VM_VCPU_IOCTL(!r, KVM_CREATE_IRQCHIP, r, vm);
+	}
 
 	vm->has_irqchip = true;
 }
diff --git a/tools/testing/selftests/kvm/x86/suppress_eoi_test.c b/tools/testing/selftests/kvm/x86/suppress_eoi_test.c
new file mode 100644
index 000000000000..ea14690b3116
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/suppress_eoi_test.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test KVM's handling of Suppress EOI Broadcast in x2APIC mode.
+ */
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include "apic.h"
+
+#define TEST_VECTOR		0xa2
+#define TEST_IRQ		5
+#define APIC_LVR_DIRECTED_EOI	(1 << 24)
+#define APIC_SPIV_DIRECTED_EOI	(1 << 12)
+
+#define APIC_ISR	0x100
+#define APIC_LVTPC	0x340
+#define APIC_LVT0	0x350
+#define APIC_LVT1	0x360
+#define APIC_LVTERR	0x370
+
+#define IOAPIC_BASE_GPA		0xfec00000
+#define IOAPIC_REG_SELECT	0x00
+#define IOAPIC_REG_WINDOW	0x10
+#define IOAPIC_REG_VERSION	0x01
+#define IOAPIC_REG_EOI		0x40
+
+static uint32_t ioapic_version;
+
+static void guest_irq_handler(struct ex_regs *regs)
+{
+}
+
+static uint32_t ioapic_read_reg(uint32_t reg)
+{
+	volatile uint32_t *ioapic = (volatile uint32_t *)IOAPIC_BASE_GPA;
+	ioapic[0] = reg;
+	return ioapic[4];
+}
+
+static void ioapic_write_reg(uint32_t reg, uint32_t val)
+{
+	volatile uint32_t *ioapic = (volatile uint32_t *)IOAPIC_BASE_GPA;
+	ioapic[0] = reg;
+	ioapic[4] = val;
+}
+
+static void guest_code(uint64_t use_directed)
+{
+	uint64_t apic_base;
+	uint32_t spiv;
+
+	/* Enable x2APIC mode */
+	apic_base = rdmsr(MSR_IA32_APICBASE);
+	wrmsr(MSR_IA32_APICBASE, apic_base | X2APIC_ENABLE);
+
+	/* Mask all LVT entries to prevent spurious interrupts/NMIs */
+	x2apic_write_reg(APIC_LVTT, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVTPC, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVT0, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVT1, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVTERR, APIC_LVT_MASKED);
+
+	/* Enable APIC */
+	x2apic_write_reg(APIC_SPIV, APIC_SPIV_APIC_ENABLED | TEST_VECTOR);
+
+	/* Read IOAPIC version */
+	ioapic_version = ioapic_read_reg(IOAPIC_REG_VERSION);
+
+	/* Conditionally set APIC_SPIV_DIRECTED_EOI based on flag */
+	if (use_directed) {
+		spiv = x2apic_read_reg(APIC_SPIV);
+		x2apic_write_reg(APIC_SPIV, spiv | APIC_SPIV_DIRECTED_EOI);
+	}
+	spiv = x2apic_read_reg(APIC_SPIV);
+
+	GUEST_SYNC(ioapic_version | (spiv << 16));
+
+	/* Enable interrupts and wait for interrupt to be delivered */
+	asm volatile("sti; hlt");
+
+	/* Write EOI to trigger broadcast (or not) */
+	x2apic_write_reg(APIC_EOI, 0);
+
+	GUEST_SYNC(0);
+
+	/* If IOAPIC v0x20, write directed EOI to clear remote_irr */
+	if ((ioapic_version & 0xff) == 0x20) {
+		ioapic_write_reg(IOAPIC_REG_EOI, TEST_VECTOR);
+		GUEST_SYNC(1);
+	}
+
+	GUEST_DONE();
+}
+
+static void test_suppress_eoi(uint64_t x2apic_flags, bool expect_advertised, bool expect_implemented,
+			      bool use_directed)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct kvm_lapic_state lapic;
+	struct kvm_irqchip chip;
+	struct kvm_irq_level irq_level;
+	struct ucall uc;
+	uint32_t lvr, ioapic_ver, spiv_after;
+	bool remote_irr_set;
+
+	use_directed = use_directed;
+
+	vm = vm_create(1);
+
+	if (x2apic_flags)
+		vm_enable_cap(vm, KVM_CAP_X2APIC_API, x2apic_flags);
+
+	if (!vm->has_irqchip)
+		vm_create_irqchip(vm);
+
+	vcpu = vm_vcpu_add(vm, 0, guest_code);
+	vcpu_args_set(vcpu, 1, use_directed);
+
+	vm_install_exception_handler(vm, TEST_VECTOR, guest_irq_handler);
+
+	/* Map IOAPIC for guest access */
+	virt_map(vm, IOAPIC_BASE_GPA, IOAPIC_BASE_GPA, 1);
+
+	/* Configure level-triggered interrupt in IOAPIC */
+	chip.chip_id = KVM_IRQCHIP_IOAPIC;
+	vm_ioctl(vm, KVM_GET_IRQCHIP, &chip);
+
+	chip.chip.ioapic.redirtbl[TEST_IRQ].fields.vector = TEST_VECTOR;
+	chip.chip.ioapic.redirtbl[TEST_IRQ].fields.delivery_mode = 0; /* fixed */
+	chip.chip.ioapic.redirtbl[TEST_IRQ].fields.dest_mode = 0; /* physical */
+	chip.chip.ioapic.redirtbl[TEST_IRQ].fields.mask = 0; /* unmasked */
+	chip.chip.ioapic.redirtbl[TEST_IRQ].fields.trig_mode = 1; /* level */
+	chip.chip.ioapic.redirtbl[TEST_IRQ].fields.dest_id = vcpu->id;
+
+	vm_ioctl(vm, KVM_SET_IRQCHIP, &chip);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+	ioapic_ver = uc.args[1] & 0xff;
+	spiv_after = (uc.args[1] >> 16) & 0xffff;
+
+	/* Inject level-triggered interrupt */
+	irq_level.irq = TEST_IRQ;
+	irq_level.level = 1;
+	vm_ioctl(vm, KVM_IRQ_LINE, &irq_level);
+
+	/* De-assert immediately so we only get one interrupt */
+	irq_level.level = 0;
+	vm_ioctl(vm, KVM_IRQ_LINE, &irq_level);
+
+	/* Guest receives interrupt and writes EOI */
+	vcpu_run(vcpu);
+
+	/* Check what ucall we got */
+	int ucall_type = get_ucall(vcpu, &uc);
+
+	/* Handle guest completion based on what it did */
+	if (ucall_type == UCALL_SYNC) {
+		/* Guest has more to do */
+		vcpu_run(vcpu);
+		ucall_type = get_ucall(vcpu, &uc);
+
+		if (ucall_type == UCALL_SYNC) {
+			/* Guest wrote EOIR */
+			vcpu_run(vcpu);
+			TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_DONE);
+		} else {
+			TEST_ASSERT_EQ(ucall_type, UCALL_DONE);
+		}
+	} else {
+		TEST_ASSERT_EQ(ucall_type, UCALL_DONE);
+	}
+
+	/* Check remote_irr after all guest EOI activity */
+	chip.chip_id = KVM_IRQCHIP_IOAPIC;
+	vm_ioctl(vm, KVM_GET_IRQCHIP, &chip);
+	remote_irr_set = chip.chip.ioapic.redirtbl[TEST_IRQ].fields.remote_irr;
+
+	/* De-assert IRQ line */
+	irq_level.level = 0;
+	vm_ioctl(vm, KVM_IRQ_LINE, &irq_level);
+
+	/* Check LAPIC LVR */
+	vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic);
+	lvr = *(u32 *)&lapic.regs[APIC_LVR];
+
+	printf("  IOAPIC v0x%x, LVR directed_eoi=%d, SPIV directed_eoi=%d, remote_irr=%d\n",
+	       ioapic_ver, !!(lvr & APIC_LVR_DIRECTED_EOI),
+	       !!(spiv_after & APIC_SPIV_DIRECTED_EOI), remote_irr_set);
+
+	if (expect_advertised) {
+		TEST_ASSERT(lvr & APIC_LVR_DIRECTED_EOI,
+			    "Expected APIC_LVR_DIRECTED_EOI, got LVR=0x%x", lvr);
+	} else {
+		TEST_ASSERT(!(lvr & APIC_LVR_DIRECTED_EOI),
+			    "Expected no APIC_LVR_DIRECTED_EOI, got LVR=0x%x", lvr);
+	}
+
+	/* Check IOAPIC version */
+	if (expect_implemented) {
+		TEST_ASSERT(ioapic_ver == 0x20,
+			    "Expected IOAPIC v0x20 (with EOIR), got 0x%x", ioapic_ver);
+	} else {
+		TEST_ASSERT(ioapic_ver == 0x11,
+			    "Expected IOAPIC v0x11 (no EOIR), got 0x%x", ioapic_ver);
+	}
+
+	/* Check SPIV and remote_irr based on whether guest used directed EOI */
+	if (use_directed) {
+		TEST_ASSERT(spiv_after & APIC_SPIV_DIRECTED_EOI,
+			    "Expected APIC_SPIV_DIRECTED_EOI set, got SPIV=0x%x", spiv_after);
+		TEST_ASSERT(remote_irr_set,
+			    "Expected remote_irr set (EOI suppressed), got cleared");
+	} else {
+		TEST_ASSERT(!(spiv_after & APIC_SPIV_DIRECTED_EOI),
+			    "Expected APIC_SPIV_DIRECTED_EOI clear, got SPIV=0x%x", spiv_after);
+		TEST_ASSERT(!remote_irr_set,
+			    "Expected remote_irr cleared (EOI broadcast), got set");
+	}
+
+	kvm_vm_free(vm);
+}
+
+static void guest_code_split(uint64_t use_directed)
+{
+	uint64_t apic_base;
+	uint32_t spiv;
+
+	/* Enable x2APIC mode */
+	apic_base = rdmsr(MSR_IA32_APICBASE);
+	wrmsr(MSR_IA32_APICBASE, apic_base | X2APIC_ENABLE);
+
+	/* Mask all LVT entries */
+	x2apic_write_reg(APIC_LVTT, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVTPC, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVT0, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVT1, APIC_LVT_MASKED);
+	x2apic_write_reg(APIC_LVTERR, APIC_LVT_MASKED);
+
+	/* Enable APIC */
+	x2apic_write_reg(APIC_SPIV, APIC_SPIV_APIC_ENABLED | TEST_VECTOR);
+
+	/* Conditionally set APIC_SPIV_DIRECTED_EOI */
+	if (use_directed) {
+		spiv = x2apic_read_reg(APIC_SPIV);
+		x2apic_write_reg(APIC_SPIV, spiv | APIC_SPIV_DIRECTED_EOI);
+	}
+	spiv = x2apic_read_reg(APIC_SPIV);
+
+	GUEST_SYNC(spiv);
+
+	/* Enable interrupts and wait for interrupt */
+	asm volatile("sti; hlt");
+
+	/* Write EOI */
+	x2apic_write_reg(APIC_EOI, 0);
+
+	GUEST_DONE();
+}
+
+static void test_suppress_eoi_split(uint64_t x2apic_flags, bool expect_advertised, bool use_directed)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct kvm_lapic_state lapic;
+	struct ucall uc;
+	uint32_t lvr, spiv_after;
+	bool got_eoi_exit;
+	enum vm_irqchip_mode saved_mode = vm_irqchip_mode;
+
+	vm_irqchip_mode = VM_IRQCHIP_SPLIT;
+	vm = vm_create(1);
+	vm_irqchip_mode = saved_mode;
+
+	if (x2apic_flags)
+		vm_enable_cap(vm, KVM_CAP_X2APIC_API, x2apic_flags);
+
+	vcpu = vm_vcpu_add(vm, 0, guest_code_split);
+	vcpu_args_set(vcpu, 1, use_directed);
+
+	/* Set up IRQ routing so kernel knows userspace IOAPIC handles TEST_IRQ */
+	struct kvm_irq_routing *routing = calloc(1, sizeof(*routing) + sizeof(routing->entries[0]));
+	routing->nr = 1;
+	routing->entries[0].gsi = TEST_IRQ;
+	routing->entries[0].type = KVM_IRQ_ROUTING_MSI;
+	routing->entries[0].u.msi.address_lo = 0xfee00000;  /* Dest ID 0 */
+	routing->entries[0].u.msi.address_hi = 0;
+	routing->entries[0].u.msi.data = TEST_VECTOR | (1 << 15);  /* Level-triggered */
+	__vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
+	free(routing);
+
+	vm_install_exception_handler(vm, TEST_VECTOR, guest_irq_handler);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+	spiv_after = uc.args[1];
+
+	/* Inject via GSI (which routes to MSI) */
+	struct kvm_irq_level irq_level = {
+		.irq = TEST_IRQ,
+		.level = 1,
+	};
+	vm_ioctl(vm, KVM_IRQ_LINE, &irq_level);
+	irq_level.level = 0;
+	vm_ioctl(vm, KVM_IRQ_LINE, &irq_level);
+
+	/* Guest receives interrupt and writes EOI */
+	vcpu_run(vcpu);
+
+
+	/* Check if we got KVM_EXIT_IOAPIC_EOI */
+	got_eoi_exit = (vcpu->run->exit_reason == KVM_EXIT_IOAPIC_EOI &&
+			vcpu->run->eoi.vector == TEST_VECTOR);
+
+	/* If we got EOI exit, continue guest to finish */
+	if (got_eoi_exit) {
+		vcpu_run(vcpu);
+	}
+
+	/* Let guest finish */
+	int ucall_type = get_ucall(vcpu, &uc);
+	if (ucall_type == UCALL_SYNC) {
+		vcpu_run(vcpu);
+		ucall_type = get_ucall(vcpu, &uc);
+		if (ucall_type == UCALL_SYNC) {
+			vcpu_run(vcpu);
+			TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_DONE);
+		} else {
+			TEST_ASSERT_EQ(ucall_type, UCALL_DONE);
+		}
+	} else {
+		TEST_ASSERT_EQ(ucall_type, UCALL_DONE);
+	}
+
+	/* Check LAPIC LVR */
+	vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic);
+	lvr = *(u32 *)&lapic.regs[APIC_LVR];
+
+	printf("  Split IRQCHIP: LVR directed_eoi=%d, SPIV directed_eoi=%d, got_eoi_exit=%d\n",
+	       !!(lvr & APIC_LVR_DIRECTED_EOI),
+	       !!(spiv_after & APIC_SPIV_DIRECTED_EOI), got_eoi_exit);
+
+	if (expect_advertised) {
+		TEST_ASSERT(lvr & APIC_LVR_DIRECTED_EOI,
+			    "Expected APIC_LVR_DIRECTED_EOI, got LVR=0x%x", lvr);
+	} else {
+		TEST_ASSERT(!(lvr & APIC_LVR_DIRECTED_EOI),
+			    "Expected no APIC_LVR_DIRECTED_EOI, got LVR=0x%x", lvr);
+	}
+
+	/* Check EOI exit based on whether guest used directed EOI */
+	if (use_directed) {
+		TEST_ASSERT(spiv_after & APIC_SPIV_DIRECTED_EOI,
+			    "Expected APIC_SPIV_DIRECTED_EOI set, got SPIV=0x%x", spiv_after);
+		TEST_ASSERT(!got_eoi_exit,
+			    "Expected no EOI exit (suppressed), but got one");
+	} else {
+		TEST_ASSERT(!(spiv_after & APIC_SPIV_DIRECTED_EOI),
+			    "Expected APIC_SPIV_DIRECTED_EOI clear, got SPIV=0x%x", spiv_after);
+		if (expect_advertised) {
+			/* Quirk mode: advertised but should still broadcast */
+			if (!got_eoi_exit) {
+				printf("  Note: No EOI exit in quirk mode (old kernel behavior)\n");
+			}
+		} else {
+			/* Feature not advertised, no EOI exits expected */
+		}
+	}
+
+	kvm_vm_free(vm);
+}
+
+int main(void)
+{
+	int cap;
+
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_X2APIC_API));
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_IRQCHIP));
+
+	cap = kvm_check_cap(KVM_CAP_X2APIC_API);
+
+	/*
+	 * Test that KVM correctly handles the suppress EOI broadcast flags.
+	 * Note: The actual behavior depends on the kernel implementation.
+	 * This test documents the expected behavior per the commit messages.
+	 *
+	 * Quirk mode: Don't advertise or implement (legacy behavior)
+	 * Explicit enable: Advertise and implement
+	 * Explicit disable: Don't advertise or implement
+	 */
+
+	printf("Testing quirk mode (no flags)...\n");
+	test_suppress_eoi(0, false, false, false);
+
+	if (cap & KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST) {
+		printf("Testing explicit enable...\n");
+		test_suppress_eoi(KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST, true, true, true);
+
+		printf("Testing explicit enable (guest doesn't use)...\n");
+		test_suppress_eoi(KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST, true, true, false);
+	} else {
+		printf("Skipping explicit enable (not supported)...\n");
+	}
+
+	if (cap & KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST) {
+		printf("Testing explicit disable...\n");
+		test_suppress_eoi(KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST, false, false, false);
+	} else {
+		printf("Skipping explicit disable (not supported)...\n");
+	}
+
+	printf("All tests passed\n");
+
+	/* Test split irqchip mode */
+	printf("\n=== Testing split IRQCHIP mode ===\n");
+
+	printf("Testing quirk mode (no flags)...\n");
+	test_suppress_eoi_split(0, true, false);  /* Quirk: advertised in split mode */
+
+	if (cap & KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST) {
+		printf("Testing explicit enable...\n");
+		test_suppress_eoi_split(KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST, true, true);
+
+		printf("Testing explicit enable (guest doesn't use)...\n");
+		test_suppress_eoi_split(KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST, true, false);
+	} else {
+		printf("Skipping explicit enable (not supported)...\n");
+	}
+
+	if (cap & KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST) {
+		printf("Testing explicit disable...\n");
+		test_suppress_eoi_split(KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST, false, false);
+	} else {
+		printf("Skipping explicit disable (not supported)...\n");
+	}
+
+	printf("All tests passed\n");
+	return 0;
+}
+


Download attachment "smime.p7s" of type "application/pkcs7-signature" (5069 bytes)

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ