From: Hao Zhang Add a regression test for debug register state restoration when KVM emulates a nested SVM VM-Exit from L2 to L1. Give L1 and L2 distinct DR6 and DR7 values, run L2 until a VMMCALL VM-Exit, and then verify that KVM saved L2's debug register state to vmcb12 while restoring L1's architectural DR6 and DR7 values. Without the fix, L1 observes stale L2 DR6 state and loses its DR7 state after the nested VM-Exit. Signed-off-by: Hao Zhang --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/x86/svm_nested_debug_regs_test.c | 104 +++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/svm_nested_debug_regs_test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 1bde5ab306cf..65ad842eecd4 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -122,6 +122,7 @@ TEST_GEN_PROGS_x86 += x86/svm_nested_clear_efer_svme TEST_GEN_PROGS_x86 += x86/svm_nested_shutdown_test TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa +TEST_GEN_PROGS_x86 += x86/svm_nested_debug_regs_test TEST_GEN_PROGS_x86 += x86/svm_nested_pat_test TEST_GEN_PROGS_x86 += x86/svm_lbr_nested_state TEST_GEN_PROGS_x86 += x86/svm_pmu_host_guest_test diff --git a/tools/testing/selftests/kvm/x86/svm_nested_debug_regs_test.c b/tools/testing/selftests/kvm/x86/svm_nested_debug_regs_test.c new file mode 100644 index 000000000000..6ef29cf0a819 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/svm_nested_debug_regs_test.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Nested SVM debug register state test. + */ +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" +#include "svm_util.h" + +#define DR6_ACTIVE_LOW 0xffff0ff0 +#define DR6_B0 BIT(0) +#define DR6_BS BIT(14) +#define DR7_FIXED_1 0x400 +#define DR7_GE BIT(9) + +#define L1_DR6 (DR6_ACTIVE_LOW | DR6_BS) +#define L2_DR6 (DR6_ACTIVE_LOW | DR6_B0) +#define L1_DR7 (DR7_FIXED_1 | DR7_GE) +#define L2_DR7 (DR7_FIXED_1) + +static inline u64 get_dr6(void) +{ + u64 val; + + asm volatile("mov %%dr6, %0" : "=r"(val) : : "memory"); + return val; +} + +static inline u64 get_dr7(void) +{ + u64 val; + + asm volatile("mov %%dr7, %0" : "=r"(val) : : "memory"); + return val; +} + +static inline void set_dr6(u64 val) +{ + asm volatile("mov %0, %%dr6" : : "r"(val) : "memory"); +} + +static inline void set_dr7(u64 val) +{ + asm volatile("mov %0, %%dr7" : : "r"(val) : "memory"); +} + +static void l2_guest_code(void) +{ + GUEST_ASSERT_EQ(get_dr6(), L2_DR6); + GUEST_ASSERT_EQ(get_dr7(), L2_DR7); + vmmcall(); +} + +static void l1_guest_code(struct svm_test_data *svm) +{ + struct vmcb *vmcb = svm->vmcb; + + set_dr6(L1_DR6); + set_dr7(L1_DR7); + + generic_svm_setup(svm, l2_guest_code); + vmcb->save.dr6 = L2_DR6; + vmcb->save.dr7 = L2_DR7; + + run_guest(vmcb, svm->vmcb_gpa); + GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL); + + GUEST_ASSERT_EQ(vmcb->save.dr6, L2_DR6); + GUEST_ASSERT_EQ(vmcb->save.dr7, L2_DR7); + GUEST_ASSERT_EQ(get_dr6(), L1_DR6); + GUEST_ASSERT_EQ(get_dr7(), L1_DR7); + + GUEST_DONE(); +} + +int main(int argc, char *argv[]) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + gva_t svm_gva; + + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM)); + + vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); + vcpu_alloc_svm(vm, &svm_gva); + vcpu_args_set(vcpu, 1, svm_gva); + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + case UCALL_DONE: + break; + default: + TEST_FAIL("Unknown ucall %lu", uc.cmd); + } + + kvm_vm_free(vm); + return 0; +} -- 2.15.0