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(); +} + 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), -- 2.19.0.397.gdd90340f6a-goog