On 09/14/2018 12:22 PM, Jim Mattson wrote:
When L1 intercepts #NM exceptions encountered in L2, the #NM exception
should always be reflected from L0 to L1.
Signed-off-by: Jim Mattson <jmattson@xxxxxxxxxx>
Reviewed-by: Peter Shier <pshier@xxxxxxxxxx>
---
lib/x86/processor.h | 1 +
x86/unittests.cfg | 6 +++++
x86/vmx_tests.c | 65 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 72 insertions(+)
diff --git a/lib/x86/processor.h b/lib/x86/processor.h
index b8e884f..15237a5 100644
--- a/lib/x86/processor.h
+++ b/lib/x86/processor.h
@@ -25,6 +25,7 @@
#define X86_CR0_PE 0x00000001
#define X86_CR0_MP 0x00000002
+#define X86_CR0_EM 0x00000004
#define X86_CR0_TS 0x00000008
#define X86_CR0_WP 0x00010000
#define X86_CR0_AM 0x00040000
diff --git a/x86/unittests.cfg b/x86/unittests.cfg
index 9d39319..4a9702d 100644
--- a/x86/unittests.cfg
+++ b/x86/unittests.cfg
@@ -554,6 +554,12 @@ extra_params = -cpu host,+vmx -m 2560 -append vmx_cr_load_test
arch = x86_64
groups = vmx
+[vmx_nm_test]
+file = vmx.flat
+extra_params = -cpu host,+vmx -m 2560 -append vmx_nm_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 0e9d900..5d4f39a 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -4585,6 +4585,70 @@ static void vmx_cr_load_test(void)
TEST_ASSERT(!write_cr4_checking(cr4 & ~X86_CR4_PCIDE));
}
+static void vmx_nm_test_guest(void)
+{
+ write_cr0(read_cr0() | X86_CR0_TS);
+ asm volatile("fnop");
+}
+
+static void check_nm_exit(const char *test)
+{
+ u32 reason = vmcs_read(EXI_REASON);
+ u32 intr_info = vmcs_read(EXI_INTR_INFO);
+ const u32 expected = INTR_INFO_VALID_MASK | INTR_TYPE_HARD_EXCEPTION |
+ NM_VECTOR;
+
+ report("%s", reason == VMX_EXC_NMI && intr_info == expected, test);
+}
+
+/*
+ * This test checks that:
+ *
+ * (a) If L2 launches with CR0.TS clear, but later sets CR0.TS, then
+ * a subsequent #NM VM-exit is reflected to L1.
+ *
+ * (b) If L2 launches with CR0.TS clear and CR0.EM set, then a
+ * subsequent #NM VM-exit is reflected to L1.
+ */
+static void vmx_nm_test(void)
+{
+ unsigned long cr0 = read_cr0();
+
+ test_set_guest(vmx_nm_test_guest);
+
+ /*
+ * L1 wants to intercept #NM exceptions encountered in L2.
+ */
+ vmcs_write(EXC_BITMAP, 1 << NM_VECTOR);
+
+ /*
+ * Launch L2 with CR0.TS clear, but don't claim host ownership of
+ * any CR0 bits. L2 will set CR0.TS and then try to execute fnop,
+ * which will raise #NM. L0 should reflect the #NM VM-exit to L1.
+ */
+ vmcs_write(CR0_MASK, 0);
+ vmcs_write(GUEST_CR0, cr0 & ~X86_CR0_TS);
+ enter_guest();
+ check_nm_exit("fnop with CR0.TS set in L2 triggers #NM VM-exit to L1");
+
+ /*
+ * Re-enter L2 at the fnop instruction, with CR0.TS clear but
+ * CR0.EM set. The fnop will still raise #NM, and L0 should
+ * reflect the #NM VM-exit to L1.
+ */
+ vmcs_write(GUEST_CR0, (cr0 & ~X86_CR0_TS) | X86_CR0_EM);
+ enter_guest();
+ check_nm_exit("fnop with CR0.EM set in L2 triggers #NM VM-exit to L1");
+
+ /*
+ * Re-enter L2 at the fnop instruction, with both CR0.TS and
+ * CR0.EM clear. There will be no #NM, and the L2 guest should
+ * exit normally.
+ */
+ vmcs_write(GUEST_CR0, cr0 & ~(X86_CR0_TS | X86_CR0_EM));
+ enter_guest();
There is another combination that we can test:
"If the EM flag is set, the setting of the TS flag has no effect
on the execution of x87 FPU/MMX/SSE/SSE2/SSE3/SSSE3/SSE4 instructions."
+}
+
static bool cpu_has_apicv(void)
{
return ((ctrl_cpu_rev[1].clr & CPU_APIC_REG_VIRT) &&
@@ -5144,6 +5208,7 @@ struct vmx_test vmx_tests[] = {
TEST(vmx_vmcs_shadow_test),
/* Regression tests */
TEST(vmx_cr_load_test),
+ TEST(vmx_nm_test),
/* EPT access tests. */
TEST(ept_access_test_not_present),
TEST(ept_access_test_read_only),
Reviewed-by: Krish Sadhukhan <krish.sadhukhan@xxxxxxxxxx>