diff mbox series

[kvm-unit-tests,1/2] x86: nVMX: Basic test of NMI-window exiting

Message ID 20181121002620.240795-1-jmattson@google.com (mailing list archive)
State New, archived
Headers show
Series [kvm-unit-tests,1/2] x86: nVMX: Basic test of NMI-window exiting | expand

Commit Message

Jim Mattson Nov. 21, 2018, 12:26 a.m. UTC
Test various NMI-window exiting scenarios. In the active activity
state, test without any blocking, with blocking by MOV-SS, no blocking
with event injection, and with blocking by NMI. In the halted activity
state, test without any blocking, with and without event injection.

Signed-off-by: Jim Mattson <jmattson@google.com>
Reviewed-by: Peter Shier <pshier@google.com>
---
 lib/x86/desc.h    |   9 +++
 x86/unittests.cfg |   6 ++
 x86/vmx_tests.c   | 137 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 152 insertions(+)
diff mbox series

Patch

diff --git a/lib/x86/desc.h b/lib/x86/desc.h
index 977a233..7a7358a 100644
--- a/lib/x86/desc.h
+++ b/lib/x86/desc.h
@@ -224,4 +224,13 @@  void __set_exception_jmpbuf(jmp_buf *addr);
 #define set_exception_jmpbuf(jmpbuf) \
 	(setjmp(jmpbuf) ? : (__set_exception_jmpbuf(&(jmpbuf)), 0))
 
+static inline void *get_idt_addr(idt_entry_t *entry)
+{
+	uintptr_t addr = entry->offset0 | ((u32)entry->offset1 << 16);
+#ifdef __x86_64__
+	addr |= (u64)entry->offset2 << 32;
+#endif
+	return (void *)addr;
+}
+
 #endif
diff --git a/x86/unittests.cfg b/x86/unittests.cfg
index 3b21a85..04faa6e 100644
--- a/x86/unittests.cfg
+++ b/x86/unittests.cfg
@@ -578,6 +578,12 @@  extra_params = -cpu host,+vmx -m 2560 -append vmx_db_test
 arch = x86_64
 groups = vmx
 
+[vmx_nmi_window_test]
+file = vmx.flat
+extra_params = -cpu host,+vmx -m 2560 -append vmx_nmi_window_test
+arch = x86_64
+groups = vmx
+
 [vmx_eoi_bitmap_ioapic_scan]
 file = vmx.flat
 smp = 2
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index b105b23..ec4f051 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -5031,6 +5031,142 @@  static void vmx_pending_event_hlt_test(void)
 	vmx_pending_event_test_core(true);
 }
 
+static int vmx_window_test_ud_count;
+
+static void vmx_window_test_ud_handler(struct ex_regs *regs)
+{
+	vmx_window_test_ud_count++;
+}
+
+static void vmx_nmi_window_test_guest(void)
+{
+	handle_exception(UD_VECTOR, vmx_window_test_ud_handler);
+
+	asm volatile("vmcall\n\t"
+		     "nop\n\t");
+
+	handle_exception(UD_VECTOR, NULL);
+}
+
+static void verify_nmi_window_exit(u64 rip)
+{
+	u32 exit_reason = vmcs_read(EXI_REASON);
+
+	report("Exit reason (%d) is 'NMI window'",
+	       exit_reason == VMX_NMI_WINDOW, exit_reason);
+	report("RIP (%#lx) is %#lx", vmcs_read(GUEST_RIP) == rip,
+	       vmcs_read(GUEST_RIP), rip);
+	report("Activity state (%ld) is 'ACTIVE'",
+	       vmcs_read(GUEST_ACTV_STATE) == ACTV_ACTIVE,
+	       vmcs_read(GUEST_ACTV_STATE));
+}
+
+static void vmx_nmi_window_test(void)
+{
+	u64 nop_addr;
+	void *ud_fault_addr = get_idt_addr(&boot_idt[UD_VECTOR]);
+
+	if (!(ctrl_pin_rev.clr & PIN_VIRT_NMI)) {
+		report_skip("CPU does not support the \"Virtual NMIs\" VM-execution control.");
+		return;
+	}
+
+	if (!(ctrl_cpu_rev[0].clr & CPU_NMI_WINDOW)) {
+		report_skip("CPU does not support the \"NMI-window exiting\" VM-execution control.");
+		return;
+	}
+
+	vmx_window_test_ud_count = 0;
+
+	report_prefix_push("NMI-window");
+	test_set_guest(vmx_nmi_window_test_guest);
+	vmcs_set_bits(PIN_CONTROLS, PIN_VIRT_NMI);
+	enter_guest();
+	skip_exit_vmcall();
+	nop_addr = vmcs_read(GUEST_RIP);
+
+	/*
+	 * Ask for "NMI-window exiting," and expect an immediate VM-exit.
+	 * RIP will not advance.
+	 */
+	report_prefix_push("active, no blocking");
+	vmcs_set_bits(CPU_EXEC_CTRL0, CPU_NMI_WINDOW);
+	enter_guest();
+	verify_nmi_window_exit(nop_addr);
+	report_prefix_pop();
+
+	/*
+	 * Ask for "NMI-window exiting" in a MOV-SS shadow, and expect
+	 * a VM-exit on the next instruction after the nop. (The nop
+	 * is one byte.)
+	 */
+	report_prefix_push("active, blocking by MOV-SS");
+	vmcs_write(GUEST_INTR_STATE, GUEST_INTR_STATE_MOVSS);
+	enter_guest();
+	verify_nmi_window_exit(nop_addr + 1);
+	report_prefix_pop();
+
+	/*
+	 * Ask for "NMI-window exiting" (with event injection), and
+	 * expect a VM-exit after the event is injected. (RIP should
+	 * be at the address specified in the IDT entry for #UD.)
+	 */
+	report_prefix_push("active, no blocking, injecting #UD");
+	vmcs_write(ENT_INTR_INFO,
+		   INTR_INFO_VALID_MASK | INTR_TYPE_HARD_EXCEPTION | UD_VECTOR);
+	enter_guest();
+	verify_nmi_window_exit((u64)ud_fault_addr);
+	report_prefix_pop();
+
+	/*
+	 * Ask for "NMI-window exiting" with NMI blocking, and expect
+	 * a VM-exit after the next IRET (i.e. after the #UD handler
+	 * returns). So, RIP should be back at one byte past the nop.
+	 */
+	report_prefix_push("active, blocking by NMI");
+	vmcs_write(GUEST_INTR_STATE, GUEST_INTR_STATE_NMI);
+	enter_guest();
+	verify_nmi_window_exit(nop_addr + 1);
+	report("#UD handler executed once (actual %d times)",
+	       vmx_window_test_ud_count == 1,
+	       vmx_window_test_ud_count);
+	report_prefix_pop();
+
+	if (!(rdmsr(MSR_IA32_VMX_MISC) & (1 << 6))) {
+		report_skip("CPU does not support activity state HLT.");
+	} else {
+		/*
+		 * Ask for "NMI-window exiting" when entering activity
+		 * state HLT, and expect an immediate VM-exit. RIP is
+		 * still one byte past the nop.
+		 */
+		report_prefix_push("halted, no blocking");
+		vmcs_write(GUEST_ACTV_STATE, ACTV_HLT);
+		enter_guest();
+		verify_nmi_window_exit(nop_addr + 1);
+		report_prefix_pop();
+
+		/*
+		 * Ask for "NMI-window exiting" when entering activity
+		 * state HLT (with event injection), and expect a
+		 * VM-exit after the event is injected. (RIP should be
+		 * at the address specified in the IDT entry for #UD.)
+		 */
+		report_prefix_push("halted, no blocking, injecting #UD");
+		vmcs_write(GUEST_ACTV_STATE, ACTV_HLT);
+		vmcs_write(ENT_INTR_INFO,
+			   INTR_INFO_VALID_MASK | INTR_TYPE_HARD_EXCEPTION |
+			   UD_VECTOR);
+		enter_guest();
+		verify_nmi_window_exit((u64)ud_fault_addr);
+		report_prefix_pop();
+	}
+
+	vmcs_clear_bits(CPU_EXEC_CTRL0, CPU_NMI_WINDOW);
+	enter_guest();
+	report_prefix_pop();
+}
+
 static void vmx_db_test_guest(void)
 {
 	/*
@@ -5833,6 +5969,7 @@  struct vmx_test vmx_tests[] = {
 	TEST(vmx_cr_load_test),
 	TEST(vmx_nm_test),
 	TEST(vmx_db_test),
+	TEST(vmx_nmi_window_test),
 	TEST(vmx_pending_event_test),
 	TEST(vmx_pending_event_hlt_test),
 	/* EPT access tests. */