diff mbox series

[kvm-unit-tests,5/5] nVMX: add test for posted interrupts

Message ID 20231211185552.3856862-6-jmattson@google.com (mailing list archive)
State New, archived
Headers show
Series nVMX: Simple posted interrupts test | expand

Commit Message

Jim Mattson Dec. 11, 2023, 6:55 p.m. UTC
From: Oliver Upton <oliver.upton@linux.dev>

Test virtual posted interrupts under the following conditions:

    - vTPR[7:4] >= VECTOR[7:4]: Expect the L2 interrupt to be blocked.
      The bit corresponding to the posted interrupt should be set in L2's
      vIRR. Test with a running guest.

    - vTPR[7:4] < VECTOR[7:4]: Expect the interrupt to be delivered and the
      ISR to execute once. Test with a running and halted guest.

Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
Co-developed-by: Jim Mattson <jmattson@google.com>
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 lib/x86/asm/bitops.h |   8 +++
 x86/unittests.cfg    |   8 +++
 x86/vmx_tests.c      | 133 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 149 insertions(+)
diff mbox series

Patch

diff --git a/lib/x86/asm/bitops.h b/lib/x86/asm/bitops.h
index 13a25ec9853d..54ec9c424cd6 100644
--- a/lib/x86/asm/bitops.h
+++ b/lib/x86/asm/bitops.h
@@ -13,4 +13,12 @@ 
 
 #define HAVE_BUILTIN_FLS 1
 
+static inline void test_and_set_bit(long nr, unsigned long *addr)
+{
+	asm volatile("lock; bts %1,%0"
+		     : "+m" (*addr)
+		     : "Ir" (nr)
+		     : "memory");
+}
+
 #endif
diff --git a/x86/unittests.cfg b/x86/unittests.cfg
index f307168b0e01..9598c61ef7ac 100644
--- a/x86/unittests.cfg
+++ b/x86/unittests.cfg
@@ -366,6 +366,14 @@  arch = x86_64
 groups = vmx
 timeout = 10
 
+[vmx_posted_intr_test]
+file = vmx.flat
+smp = 2
+extra_params = -cpu max,+vmx -append "vmx_posted_interrupts_test"
+arch = x86_64
+groups = vmx
+timeout = 10
+
 [vmx_apic_passthrough_thread]
 file = vmx.flat
 smp = 2
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index a26f77e92f72..1a3da59632dc 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -65,6 +65,11 @@  static u32 *get_vapic_page(void)
 	return (u32 *)phys_to_virt(vmcs_read(APIC_VIRT_ADDR));
 }
 
+static u64 *get_pi_desc(void)
+{
+	return (u64 *)phys_to_virt(vmcs_read(POSTED_INTR_DESC_ADDR));
+}
+
 static void basic_guest_main(void)
 {
 	report_pass("Basic VMX test");
@@ -9327,6 +9332,18 @@  static void enable_vid(void)
 	vmcs_set_bits(CPU_EXEC_CTRL1, CPU_VINTD | CPU_VIRT_X2APIC);
 }
 
+#define	PI_VECTOR	255
+
+static void enable_posted_interrupts(void)
+{
+	void *pi_desc = alloc_page();
+
+	vmcs_set_bits(PIN_CONTROLS, PIN_POST_INTR);
+	vmcs_set_bits(EXI_CONTROLS, EXI_INTA);
+	vmcs_write(PINV, PI_VECTOR);
+	vmcs_write(POSTED_INTR_DESC_ADDR, (u64)pi_desc);
+}
+
 static void trigger_ioapic_scan_thread(void *data)
 {
 	/* Wait until other CPU entered L2 */
@@ -10722,12 +10739,18 @@  enum Vid_op {
 	VID_OP_SET_CR8,
 	VID_OP_SELF_IPI,
 	VID_OP_TERMINATE,
+	VID_OP_SPIN,
+	VID_OP_HLT,
 };
 
 struct vmx_basic_vid_test_guest_args {
 	enum Vid_op op;
 	u8 nr;
 	u32 isr_exec_cnt;
+	u32 *virtual_apic_page;
+	u64 *pi_desc;
+	u32 dest;
+	bool in_guest;
 } vmx_basic_vid_test_guest_args;
 
 /*
@@ -10743,6 +10766,14 @@  static void set_virr_bit(volatile u32 *virtual_apic_page, u8 nr)
 	virtual_apic_page[page_offset] |= mask;
 }
 
+static void clear_virr_bit(volatile u32 *virtual_apic_page, u8 nr)
+{
+	u32 page_offset = (0x200 | ((nr & 0xE0) >> 1)) / sizeof(u32);
+	u32 mask = 1 << (nr & 0x1f);
+
+	virtual_apic_page[page_offset] &= ~mask;
+}
+
 static bool get_virr_bit(volatile u32 *virtual_apic_page, u8 nr)
 {
 	u32 page_offset = (0x200 | ((nr & 0xE0) >> 1)) / sizeof(u32);
@@ -10783,6 +10814,24 @@  static void vmx_basic_vid_test_guest(void)
 		case VID_OP_SELF_IPI:
 			vmx_x2apic_write(APIC_SELF_IPI, nr);
 			break;
+		case VID_OP_HLT:
+			cli();
+			barrier();
+			args->in_guest = true;
+			barrier();
+			safe_halt();
+			break;
+		case VID_OP_SPIN: {
+			u32 *virtual_apic_page = args->virtual_apic_page;
+			u32 prev_cnt = args->isr_exec_cnt;
+			u8 nr = args->nr;
+
+			args->in_guest = true;
+			while (args->isr_exec_cnt == prev_cnt &&
+			       !get_virr_bit(virtual_apic_page, nr))
+				pause();
+			clear_virr_bit(virtual_apic_page, nr);
+		}
 		default:
 			break;
 		}
@@ -10803,6 +10852,7 @@  static void set_isrs_for_vmx_basic_vid_test(void)
 	 */
 	for (nr = 0x21; nr < 0x100; nr++) {
 		vmcs_write(GUEST_INT_STATUS, 0);
+		args->virtual_apic_page = get_vapic_page();
 		args->op = VID_OP_SET_ISR;
 		args->nr = nr;
 		args->isr_exec_cnt = 0;
@@ -10812,6 +10862,27 @@  static void set_isrs_for_vmx_basic_vid_test(void)
 	report(true, "Set ISR for vectors 33-255.");
 }
 
+static void post_interrupt(u8 vector, u32 dest)
+{
+	volatile struct vmx_basic_vid_test_guest_args *args =
+		&vmx_basic_vid_test_guest_args;
+
+	test_and_set_bit(vector, args->pi_desc);
+	test_and_set_bit(256, args->pi_desc);
+	apic_icr_write(PI_VECTOR, dest);
+}
+
+static void vmx_posted_interrupts_test_worker(void *data)
+{
+	volatile struct vmx_basic_vid_test_guest_args *args =
+		&vmx_basic_vid_test_guest_args;
+
+	while (!args->in_guest)
+		pause();
+
+	post_interrupt(args->nr, args->dest);
+}
+
 /*
  * Test virtual interrupt delivery (VID) at VM-entry or TPR virtualization
  *
@@ -10843,6 +10914,7 @@  static void test_basic_vid(u8 nr, u8 tpr, enum Vid_op op, u32 isr_exec_cnt_want,
 	 * delivery, sets VPPR to VTPR, when SVI is 0.
 	 */
 	args->isr_exec_cnt = 0;
+	args->virtual_apic_page = get_vapic_page();
 	args->op = op;
 	switch (op) {
 	case VID_OP_SELF_IPI:
@@ -10855,6 +10927,15 @@  static void test_basic_vid(u8 nr, u8 tpr, enum Vid_op op, u32 isr_exec_cnt_want,
 		args->nr = task_priority_class(tpr);
 		set_vtpr(0xff);
 		break;
+	case VID_OP_SPIN:
+	case VID_OP_HLT:
+		vmcs_write(GUEST_INT_STATUS, 0);
+		args->nr = nr;
+		set_vtpr(tpr);
+		args->in_guest = false;
+		barrier();
+		on_cpu_async(1, vmx_posted_interrupts_test_worker, NULL);
+		break;
 	default:
 		vmcs_write(GUEST_INT_STATUS, nr);
 		set_vtpr(tpr);
@@ -10998,6 +11079,57 @@  static void vmx_eoi_virt_test(void)
 	assert_exit_reason(VMX_VMCALL);
 }
 
+static void vmx_posted_interrupts_test(void)
+{
+	volatile struct vmx_basic_vid_test_guest_args *args =
+		&vmx_basic_vid_test_guest_args;
+	u16 vector;
+	u8 class;
+
+	if (!cpu_has_apicv()) {
+		report_skip("%s : Not all required APICv bits supported", __func__);
+		return;
+	}
+
+	if (cpu_count() < 2) {
+		report_skip("%s : CPU count < 2", __func__);
+		return;
+	}
+
+	enable_vid();
+	enable_posted_interrupts();
+	args->pi_desc = get_pi_desc();
+	args->dest = apic_id();
+
+	test_set_guest(vmx_basic_vid_test_guest);
+	set_isrs_for_vmx_basic_vid_test();
+
+	for (class = 0; class < 16; class++) {
+		for (vector = 33; vector < 256; vector++) {
+			u32 isr_exec_cnt_want =
+					(task_priority_class(vector) > class) ?
+					1 : 0;
+
+			test_basic_vid(vector, class << 4, VID_OP_SPIN,
+				       isr_exec_cnt_want, false);
+
+			/*
+			 * Only test posted interrupts to a halted vCPU if we
+			 * expect the interrupt to be serviced. Otherwise, the
+			 * vCPU could HLT indefinitely.
+			 */
+			if (isr_exec_cnt_want)
+				test_basic_vid(vector, class << 4, VID_OP_HLT,
+					       isr_exec_cnt_want, false);
+		}
+	}
+	report(true, "Posted vectors 33-25 cross TPR classes 0-0xf, running and sometimes halted\n");
+
+	/* Terminate the guest */
+	args->op = VID_OP_TERMINATE;
+	enter_guest();
+}
+
 #define TEST(name) { #name, .v2 = name }
 
 /* name/init/guest_main/exit_handler/syscall_handler/guest_regs */
@@ -11054,6 +11186,7 @@  struct vmx_test vmx_tests[] = {
 	TEST(virt_x2apic_mode_test),
 	TEST(vmx_basic_vid_test),
 	TEST(vmx_eoi_virt_test),
+	TEST(vmx_posted_interrupts_test),
 	/* APIC pass-through tests */
 	TEST(vmx_apic_passthrough_test),
 	TEST(vmx_apic_passthrough_thread_test),