[kvm-unit-tests,2/2] x86: nVMX: Basic test of interrupt-window exiting
diff mbox series

Message ID 20181121002620.240795-2-jmattson@google.com
State New
Headers show
Series
  • [kvm-unit-tests,1/2] x86: nVMX: Basic test of NMI-window exiting
Related show

Commit Message

Jim Mattson Nov. 21, 2018, 12:26 a.m. UTC
Test various interrupt-window exiting scenarios. In the active
activity state, test without any blocking (with and without event
injection), with blocking by MOV-SS, with blocking by STI, and with
RFLAGS.IF clear. 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>
---
 x86/unittests.cfg |   6 ++
 x86/vmx_tests.c   | 171 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 177 insertions(+)

Patch
diff mbox series

diff --git a/x86/unittests.cfg b/x86/unittests.cfg
index 04faa6e..366322c 100644
--- a/x86/unittests.cfg
+++ b/x86/unittests.cfg
@@ -584,6 +584,12 @@  extra_params = -cpu host,+vmx -m 2560 -append vmx_nmi_window_test
 arch = x86_64
 groups = vmx
 
+[vmx_intr_window_test]
+file = vmx.flat
+extra_params = -cpu host,+vmx -m 2560 -append vmx_intr_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 ec4f051..edba028 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -5167,6 +5167,176 @@  static void vmx_nmi_window_test(void)
 	report_prefix_pop();
 }
 
+static void vmx_intr_window_test_guest(void)
+{
+	handle_exception(UD_VECTOR, vmx_window_test_ud_handler);
+
+	/*
+	 * The two consecutive STIs are to ensure that only the first
+	 * one has a shadow. Note that NOP and STI are one byte
+	 * instructions.
+	 */
+	asm volatile("vmcall\n\t"
+		     "nop\n\t"
+		     "sti\n\t"
+		     "sti\n\t");
+
+	handle_exception(UD_VECTOR, NULL);
+}
+
+static void verify_intr_window_exit(u64 rip)
+{
+	u32 exit_reason = vmcs_read(EXI_REASON);
+
+	report("Exit reason (%d) is 'interrupt window'",
+	       exit_reason == VMX_INTR_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_intr_window_test(void)
+{
+	u64 vmcall_addr;
+	u64 nop_addr;
+	unsigned int orig_ud_gate_type;
+	void *ud_fault_addr = get_idt_addr(&boot_idt[UD_VECTOR]);
+
+	if (!(ctrl_cpu_rev[0].clr & CPU_INTR_WINDOW)) {
+		report_skip("CPU does not support the \"interrupt-window exiting\" VM-execution control.");
+		return;
+	}
+
+	/*
+	 * Change the IDT entry for #UD from interrupt gate to trap gate,
+	 * so that it won't clear RFLAGS.IF. We don't want interrupts to
+	 * be disabled after vectoring a #UD.
+	 */
+	orig_ud_gate_type = boot_idt[UD_VECTOR].type;
+	boot_idt[UD_VECTOR].type = 15;
+
+	report_prefix_push("interrupt-window");
+	test_set_guest(vmx_intr_window_test_guest);
+	enter_guest();
+	assert_exit_reason(VMX_VMCALL);
+	vmcall_addr = vmcs_read(GUEST_RIP);
+
+	/*
+	 * Ask for "interrupt-window exiting" with RFLAGS.IF set and
+	 * no blocking; expect an immediate VM-exit. Note that we have
+	 * not advanced past the vmcall instruction yet, so RIP should
+	 * point to the vmcall instruction.
+	 */
+	report_prefix_push("active, no blocking, RFLAGS.IF=1");
+	vmcs_set_bits(CPU_EXEC_CTRL0, CPU_INTR_WINDOW);
+	vmcs_write(GUEST_RFLAGS, X86_EFLAGS_FIXED | X86_EFLAGS_IF);
+	enter_guest();
+	verify_intr_window_exit(vmcall_addr);
+	report_prefix_pop();
+
+	/*
+	 * Ask for "interrupt-window exiting" (with event injection)
+	 * with RFLAGS.IF set and no blocking; expect a VM-exit after
+	 * the event is injected. That is, RIP should should be at the
+	 * address specified in the IDT entry for #UD.
+	 */
+	report_prefix_push("active, no blocking, RFLAGS.IF=1, injecting #UD");
+	vmcs_write(ENT_INTR_INFO,
+		   INTR_INFO_VALID_MASK | INTR_TYPE_HARD_EXCEPTION | UD_VECTOR);
+	vmcall_addr = vmcs_read(GUEST_RIP);
+	enter_guest();
+	verify_intr_window_exit((u64)ud_fault_addr);
+	report_prefix_pop();
+
+	/*
+	 * Let the L2 guest run through the IRET, back to the VMCALL.
+	 * We have to clear the "interrupt-window exiting"
+	 * VM-execution control, or it would just keep causing
+	 * VM-exits. Then, advance past the VMCALL and set the
+	 * "interrupt-window exiting" VM-execution control again.
+	 */
+	vmcs_clear_bits(CPU_EXEC_CTRL0, CPU_INTR_WINDOW);
+	enter_guest();
+	skip_exit_vmcall();
+	nop_addr = vmcs_read(GUEST_RIP);
+	vmcs_set_bits(CPU_EXEC_CTRL0, CPU_INTR_WINDOW);
+
+	/*
+	 * Ask for "interrupt-window exiting" in a MOV-SS shadow with
+	 * RFLAGS.IF set, and expect a VM-exit on the next
+	 * instruction. (NOP is one byte.)
+	 */
+	report_prefix_push("active, blocking by MOV-SS, RFLAGS.IF=1");
+	vmcs_write(GUEST_INTR_STATE, GUEST_INTR_STATE_MOVSS);
+	enter_guest();
+	verify_intr_window_exit(nop_addr + 1);
+	report_prefix_pop();
+
+	/*
+	 * Back up to the NOP and ask for "interrupt-window exiting"
+	 * in an STI shadow with RFLAGS.IF set, and expect a VM-exit
+	 * on the next instruction. (NOP is one byte.)
+	 */
+	report_prefix_push("active, blocking by STI, RFLAGS.IF=1");
+	vmcs_write(GUEST_RIP, nop_addr);
+	vmcs_write(GUEST_INTR_STATE, GUEST_INTR_STATE_STI);
+	enter_guest();
+	verify_intr_window_exit(nop_addr + 1);
+	report_prefix_pop();
+
+	/*
+	 * Ask for "interrupt-window exiting" with RFLAGS.IF clear,
+	 * and expect a VM-exit on the instruction following the STI
+	 * shadow. Only the first STI (which is one byte past the NOP)
+	 * should have a shadow. The second STI (which is two bytes
+	 * past the NOP) has no shadow. Therefore, the interrupt
+	 * window opens at three bytes past the NOP.
+	 */
+	report_prefix_push("active, RFLAGS.IF = 0");
+	vmcs_write(GUEST_RFLAGS, X86_EFLAGS_FIXED);
+	enter_guest();
+	verify_intr_window_exit(nop_addr + 3);
+	report_prefix_pop();
+
+	if (!(rdmsr(MSR_IA32_VMX_MISC) & (1 << 6))) {
+		report_skip("CPU does not support activity state HLT.");
+	} else {
+		/*
+		 * Ask for "interrupt-window exiting" when entering
+		 * activity state HLT, and expect an immediate
+		 * VM-exit. RIP is still three bytes past the nop.
+		 */
+		report_prefix_push("halted, no blocking");
+		vmcs_write(GUEST_ACTV_STATE, ACTV_HLT);
+		enter_guest();
+		verify_intr_window_exit(nop_addr + 3);
+		report_prefix_pop();
+
+		/*
+		 * Ask for "interrupt-window exiting" when entering
+		 * activity state HLT (with event injection), and
+		 * expect a VM-exit after the event is injected. That
+		 * is, RIP should 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_intr_window_exit((u64)ud_fault_addr);
+		report_prefix_pop();
+	}
+
+	boot_idt[UD_VECTOR].type = orig_ud_gate_type;
+	vmcs_clear_bits(CPU_EXEC_CTRL0, CPU_INTR_WINDOW);
+	enter_guest();
+	report_prefix_pop();
+}
+
 static void vmx_db_test_guest(void)
 {
 	/*
@@ -5970,6 +6140,7 @@  struct vmx_test vmx_tests[] = {
 	TEST(vmx_nm_test),
 	TEST(vmx_db_test),
 	TEST(vmx_nmi_window_test),
+	TEST(vmx_intr_window_test),
 	TEST(vmx_pending_event_test),
 	TEST(vmx_pending_event_hlt_test),
 	/* EPT access tests. */