The nVMX tests already has coverage for PF exception testing by running ac_test_run() in L2. Add a similar test for nSVM to improve test parity between nSVM and nVMX. The VMX tests intercept MSR accesses and CPUID by default, hence why there is special handling for MSR and CPUID VM exits. The svm tests do not intercept the EFER accesses by default and EFER updates in L2 do not affect L1, so we do not need to handle MSR exits like the VMX tests. CPUID should be emulated by KVM so the interception is not required there either. Signed-off-by: Kevin Cheng --- x86/Makefile.common | 2 ++ x86/svm.h | 1 + x86/svm_tests.c | 71 +++++++++++++++++++++++++++++++++++++++++++++ x86/unittests.cfg | 33 ++++++++++++++++++++- 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/x86/Makefile.common b/x86/Makefile.common index ef0e09a65b07f..31d1ed5777123 100644 --- a/x86/Makefile.common +++ b/x86/Makefile.common @@ -117,6 +117,8 @@ $(TEST_DIR)/access_test.$(bin): $(TEST_DIR)/access.o $(TEST_DIR)/vmx.$(bin): $(TEST_DIR)/access.o +$(TEST_DIR)/svm.$(bin): $(TEST_DIR)/access.o + $(TEST_DIR)/svm_npt.$(bin): $(TEST_DIR)/svm.o $(TEST_DIR)/kvmclock_test.$(bin): $(TEST_DIR)/kvmclock.o diff --git a/x86/svm.h b/x86/svm.h index 052324c683019..66733570f0e37 100644 --- a/x86/svm.h +++ b/x86/svm.h @@ -112,6 +112,7 @@ struct __attribute__ ((__packed__)) vmcb_control_area { #define TLB_CONTROL_DO_NOTHING 0 #define TLB_CONTROL_FLUSH_ALL_ASID 1 +#define TLB_CONTROL_FLUSH_ASID 3 #define V_TPR_MASK 0x0f diff --git a/x86/svm_tests.c b/x86/svm_tests.c index 8dbc033533c00..a40468693b396 100644 --- a/x86/svm_tests.c +++ b/x86/svm_tests.c @@ -13,6 +13,7 @@ #include "x86/usermode.h" #include "vmalloc.h" #include "fwcfg.h" +#include "access.h" #define SVM_EXIT_MAX_DR_INTERCEPT 0x3f @@ -3943,6 +3944,60 @@ static void svm_apic_passthrough_tpr_threshold_test(void) report(svm_apic_passthrough_tpr_threshold_ipi_isr_fired, "self-IPI fired"); } +static bool tlb_control_flush; + +static void svm_pf_inv_asid_test_prepare(struct svm_test *test) +{ + vmcb->control.intercept |= BIT_ULL(INTERCEPT_INVLPG); + tlb_control_flush = false; +} + +static void svm_pf_inv_tlb_ctl_test_prepare(struct svm_test *test) +{ + vmcb->control.intercept |= BIT_ULL(INTERCEPT_INVLPG); + tlb_control_flush = true; +} + +static void svm_pf_exception_test(struct svm_test *test) +{ + ac_test_run(PT_LEVEL_PML4, false); +} + +static void svm_pf_exception_forced_emulation_test(struct svm_test *test) +{ + ac_test_run(PT_LEVEL_PML4, true); +} + +static bool svm_pf_exception_test_finished(struct svm_test *test) +{ + switch (vmcb->control.exit_code) { + case SVM_EXIT_VMMCALL: + break; + case SVM_EXIT_INVLPG: + if (tlb_control_flush) { + /* Flush L2 TLB by using TLB Control field */ + vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ASID; + } else { + /* Flush L2 TLB by assigning a new ASID. */ + vmcb->control.asid++; + } + break; + default: + report_fail("Unexpected exit to L1, Exit reason: 0x%x", + vmcb->control.exit_code); + } + + /* Advance guest RIP to the instruction after exit instruction. */ + vmcb->save.rip = vmcb->control.next_rip; + + return vmcb->control.exit_code == SVM_EXIT_VMMCALL; +} + +static bool svm_pf_exception_test_check(struct svm_test *test) +{ + return vmcb->control.exit_code == SVM_EXIT_VMMCALL; +} + struct svm_test svm_tests[] = { { "null", default_supported, default_prepare, default_prepare_gif_clear, null_test, @@ -4069,6 +4124,22 @@ struct svm_test svm_tests[] = { svm_apic_passthrough_thread_test_prepare, default_prepare_gif_clear, svm_apic_passthrough_guest, svm_apic_passthrough_test_finished, svm_apic_passthrough_test_check}, + { "svm_pf_exception_test", next_rip_supported, + default_prepare, default_prepare_gif_clear, + svm_pf_exception_test, svm_pf_exception_test_finished, + svm_pf_exception_test_check}, + { "svm_pf_exception_forced_emulation_test", next_rip_supported, + default_prepare, default_prepare_gif_clear, + svm_pf_exception_forced_emulation_test, svm_pf_exception_test_finished, + svm_pf_exception_test_check}, + { "svm_pf_inv_asid_test", next_rip_supported, + svm_pf_inv_asid_test_prepare, default_prepare_gif_clear, + svm_pf_exception_test, svm_pf_exception_test_finished, + svm_pf_exception_test_check}, + { "svm_pf_inv_tlb_ctl_test", next_rip_supported, + svm_pf_inv_tlb_ctl_test_prepare, default_prepare_gif_clear, + svm_pf_exception_test, svm_pf_exception_test_finished, + svm_pf_exception_test_check}, TEST(svm_apic_passthrough_tpr_threshold_test), TEST(svm_cr4_osxsave_test), TEST(svm_guest_state_test), diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 1a89101a5b2dd..65dcf8b6cba89 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -253,7 +253,7 @@ arch = x86_64 [svm] file = svm.flat smp = 2 -test_args = "-pause_filter_test -svm_intr_intercept_mix_smi -svm_insn_intercept_test" +test_args = "-pause_filter_test -svm_intr_intercept_mix_smi -svm_insn_intercept_test -svm_pf_exception_test -svm_pf_exception_forced_emulation_test -svm_pf_inv_asid_test -svm_pf_inv_tlb_ctl_test" qemu_params = -cpu max,+svm -m 4g arch = x86_64 groups = svm @@ -265,6 +265,37 @@ qemu_params = -cpu max,+svm -m 4g arch = x86_64 groups = svm +[svm_pf_inv_asid_test] +file = svm.flat +test_args = svm_pf_inv_asid_test +qemu_params = -cpu max,+svm -m 4g +arch = x86_64 +groups = svm +timeout = 240 + +[svm_pf_inv_tlb_ctl_test] +file = svm.flat +test_args = svm_pf_inv_tlb_ctl_test +qemu_params = -cpu max,+svm -m 4g +arch = x86_64 +groups = svm +timeout = 240 + +[svm_pf_exception_test] +file = svm.flat +test_args = svm_pf_exception_test +qemu_params = -cpu max,+svm -m 4g +arch = x86_64 +groups = svm + +[svm_pf_exception_fep] +file = svm.flat +test_args = svm_pf_exception_forced_emulation_test +qemu_params = -cpu max,+svm -m 4g +arch = x86_64 +groups = svm +timeout = 480 + [svm_intr_intercept_mix_smi] file = svm.flat test_args = svm_intr_intercept_mix_smi -- 2.52.0.322.g1dd061c0dc-goog