From: Yosry Ahmed Display the address as hex if the asserts for the vmxon_pa and vmcs12_pa fail, and assert that the flags are 0 as expected. Signed-off-by: Yosry Ahmed --- .../selftests/kvm/x86/vmx_set_nested_state_test.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/vmx_set_nested_state_test.c b/tools/testing/selftests/kvm/x86/vmx_set_nested_state_test.c index 67a62a5a88951..c4c400d2824c1 100644 --- a/tools/testing/selftests/kvm/x86/vmx_set_nested_state_test.c +++ b/tools/testing/selftests/kvm/x86/vmx_set_nested_state_test.c @@ -241,8 +241,14 @@ void test_vmx_nested_state(struct kvm_vcpu *vcpu) TEST_ASSERT(state->size >= sizeof(*state) && state->size <= state_sz, "Size must be between %ld and %d. The size returned was %d.", sizeof(*state), state_sz, state->size); - TEST_ASSERT(state->hdr.vmx.vmxon_pa == -1ull, "vmxon_pa must be -1ull."); - TEST_ASSERT(state->hdr.vmx.vmcs12_pa == -1ull, "vmcs_pa must be -1ull."); + TEST_ASSERT(state->hdr.vmx.vmxon_pa == -1ull, + "vmxon_pa must be 0x%llx, but was 0x%llx", + -1ull, state->hdr.vmx.vmxon_pa); + TEST_ASSERT(state->hdr.vmx.vmcs12_pa == -1ull, + "vmcs12_pa must be 0x%llx, but was 0x%llx", + -1llu, state->hdr.vmx.vmcs12_pa); + TEST_ASSERT(state->flags == 0, + "Flags must be equal to 0, but was 0x%hx", state->flags); free(state); } -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Add test cases for the validation checks in svm_set_nested_state(), and allow the test to run with SVM as well as VMX. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/Makefile.kvm | 2 +- ...d_state_test.c => set_nested_state_test.c} | 122 ++++++++++++++++-- 2 files changed, 113 insertions(+), 11 deletions(-) rename tools/testing/selftests/kvm/x86/{vmx_set_nested_state_test.c => set_nested_state_test.c} (70%) diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 148d427ff24be..6582396518b19 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -116,7 +116,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_dirty_log_test TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state TEST_GEN_PROGS_x86 += x86/vmx_msrs_test TEST_GEN_PROGS_x86 += x86/vmx_invalid_nested_guest_state -TEST_GEN_PROGS_x86 += x86/vmx_set_nested_state_test +TEST_GEN_PROGS_x86 += x86/set_nested_state_test TEST_GEN_PROGS_x86 += x86/vmx_tsc_adjust_test TEST_GEN_PROGS_x86 += x86/vmx_nested_tsc_scaling_test TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test diff --git a/tools/testing/selftests/kvm/x86/vmx_set_nested_state_test.c b/tools/testing/selftests/kvm/x86/set_nested_state_test.c similarity index 70% rename from tools/testing/selftests/kvm/x86/vmx_set_nested_state_test.c rename to tools/testing/selftests/kvm/x86/set_nested_state_test.c index c4c400d2824c1..98a9cd4873d19 100644 --- a/tools/testing/selftests/kvm/x86/vmx_set_nested_state_test.c +++ b/tools/testing/selftests/kvm/x86/set_nested_state_test.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * vmx_set_nested_state_test + * set_nested_state_test * * Copyright (C) 2019, Google LLC. * @@ -11,6 +11,7 @@ #include "kvm_util.h" #include "processor.h" #include "vmx.h" +#include "svm_util.h" #include #include @@ -253,6 +254,104 @@ void test_vmx_nested_state(struct kvm_vcpu *vcpu) free(state); } +static void vcpu_efer_enable_svm(struct kvm_vcpu *vcpu) +{ + uint64_t old_efer = vcpu_get_msr(vcpu, MSR_EFER); + + vcpu_set_msr(vcpu, MSR_EFER, old_efer | EFER_SVME); +} + +static void vcpu_efer_disable_svm(struct kvm_vcpu *vcpu) +{ + uint64_t old_efer = vcpu_get_msr(vcpu, MSR_EFER); + + vcpu_set_msr(vcpu, MSR_EFER, old_efer & ~EFER_SVME); +} + +void set_default_svm_state(struct kvm_nested_state *state, int size) +{ + memset(state, 0, size); + state->format = 1; + state->size = size; + state->hdr.svm.vmcb_pa = 0x3000; +} + +void test_svm_nested_state(struct kvm_vcpu *vcpu) +{ + /* Add a page for VMCB. */ + const int state_sz = sizeof(struct kvm_nested_state) + getpagesize(); + struct kvm_nested_state *state = + (struct kvm_nested_state *)malloc(state_sz); + + vcpu_set_cpuid_feature(vcpu, X86_FEATURE_SVM); + + /* The format must be set to 1. 0 for VMX, 1 for SVM. */ + set_default_svm_state(state, state_sz); + state->format = 0; + test_nested_state_expect_einval(vcpu, state); + + /* Invalid flags are rejected, KVM_STATE_NESTED_EVMCS is VMX-only */ + set_default_svm_state(state, state_sz); + state->flags = KVM_STATE_NESTED_EVMCS; + test_nested_state_expect_einval(vcpu, state); + + /* If EFER.SVME is clear, GIF must be set and guest mode is disallowed */ + vcpu_efer_disable_svm(vcpu); + + set_default_svm_state(state, state_sz); + state->flags = 0; + test_nested_state_expect_einval(vcpu, state); + + state->flags = KVM_STATE_NESTED_GUEST_MODE; + test_nested_state_expect_einval(vcpu, state); + + state->flags = KVM_STATE_NESTED_GIF_SET; + test_nested_state(vcpu, state); + + /* Enable SVM in the guest EFER. */ + vcpu_efer_enable_svm(vcpu); + + /* Setting vmcb_pa to a non-aligned address is only fine when not entering guest mode */ + set_default_svm_state(state, state_sz); + state->hdr.svm.vmcb_pa = -1ull; + state->flags = 0; + test_nested_state(vcpu, state); + state->flags = KVM_STATE_NESTED_GUEST_MODE; + test_nested_state_expect_einval(vcpu, state); + + /* + * Size must be large enough to fit kvm_nested_state and VMCB + * only when entering guest mode. + */ + set_default_svm_state(state, state_sz/2); + state->flags = 0; + test_nested_state(vcpu, state); + state->flags = KVM_STATE_NESTED_GUEST_MODE; + test_nested_state_expect_einval(vcpu, state); + + /* + * Test that if we leave nesting the state reflects that when we get it + * again, except for vmcb_pa, which is always returned as 0 when not in + * guest mode. + */ + set_default_svm_state(state, state_sz); + state->hdr.svm.vmcb_pa = -1ull; + state->flags = KVM_STATE_NESTED_GIF_SET; + test_nested_state(vcpu, state); + vcpu_nested_state_get(vcpu, state); + TEST_ASSERT(state->size >= sizeof(*state) && state->size <= state_sz, + "Size must be between %ld and %d. The size returned was %d.", + sizeof(*state), state_sz, state->size); + TEST_ASSERT(state->hdr.svm.vmcb_pa == 0, + "vmcb_pa must be 0, but was %llx", + state->hdr.svm.vmcb_pa); + TEST_ASSERT(state->flags == KVM_STATE_NESTED_GIF_SET, + "Flags must be equal to 0x%hx, but was 0x%hx", + KVM_STATE_NESTED_GIF_SET, state->flags); + + free(state); +} + int main(int argc, char *argv[]) { struct kvm_vm *vm; @@ -261,20 +360,20 @@ int main(int argc, char *argv[]) have_evmcs = kvm_check_cap(KVM_CAP_HYPERV_ENLIGHTENED_VMCS); + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) || + kvm_cpu_has(X86_FEATURE_SVM)); TEST_REQUIRE(kvm_has_cap(KVM_CAP_NESTED_STATE)); - /* - * AMD currently does not implement set_nested_state, so for now we - * just early out. - */ - TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX)); - vm = vm_create_with_one_vcpu(&vcpu, NULL); /* - * First run tests with VMX disabled to check error handling. + * First run tests with VMX/SVM disabled to check error handling. + * test_{vmx/svm}_nested_state() will re-enable as needed. */ - vcpu_clear_cpuid_feature(vcpu, X86_FEATURE_VMX); + if (kvm_cpu_has(X86_FEATURE_VMX)) + vcpu_clear_cpuid_feature(vcpu, X86_FEATURE_VMX); + else + vcpu_clear_cpuid_feature(vcpu, X86_FEATURE_SVM); /* Passing a NULL kvm_nested_state causes a EFAULT. */ test_nested_state_expect_efault(vcpu, NULL); @@ -303,7 +402,10 @@ int main(int argc, char *argv[]) state.flags = KVM_STATE_NESTED_RUN_PENDING; test_nested_state_expect_einval(vcpu, &state); - test_vmx_nested_state(vcpu); + if (kvm_cpu_has(X86_FEATURE_VMX)) + test_vmx_nested_state(vcpu); + else + test_svm_nested_state(vcpu); kvm_vm_free(vm); return 0; -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Add SVM L1 code to run the nested guest, and allow the test to run with SVM as well as VMX. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/Makefile.kvm | 2 +- ...ested_test.c => close_while_nested_test.c} | 42 +++++++++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) rename tools/testing/selftests/kvm/x86/{vmx_close_while_nested_test.c => close_while_nested_test.c} (64%) diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 6582396518b19..61c6abdc9b36f 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -111,7 +111,7 @@ TEST_GEN_PROGS_x86 += x86/ucna_injection_test TEST_GEN_PROGS_x86 += x86/userspace_io_test TEST_GEN_PROGS_x86 += x86/userspace_msr_exit_test TEST_GEN_PROGS_x86 += x86/vmx_apic_access_test -TEST_GEN_PROGS_x86 += x86/vmx_close_while_nested_test +TEST_GEN_PROGS_x86 += x86/close_while_nested_test TEST_GEN_PROGS_x86 += x86/vmx_dirty_log_test TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state TEST_GEN_PROGS_x86 += x86/vmx_msrs_test diff --git a/tools/testing/selftests/kvm/x86/vmx_close_while_nested_test.c b/tools/testing/selftests/kvm/x86/close_while_nested_test.c similarity index 64% rename from tools/testing/selftests/kvm/x86/vmx_close_while_nested_test.c rename to tools/testing/selftests/kvm/x86/close_while_nested_test.c index dad988351493e..cf5f24c83c448 100644 --- a/tools/testing/selftests/kvm/x86/vmx_close_while_nested_test.c +++ b/tools/testing/selftests/kvm/x86/close_while_nested_test.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * vmx_close_while_nested + * close_while_nested_test * * Copyright (C) 2019, Red Hat, Inc. * @@ -12,6 +12,7 @@ #include "kvm_util.h" #include "processor.h" #include "vmx.h" +#include "svm_util.h" #include #include @@ -22,6 +23,8 @@ enum { PORT_L0_EXIT = 0x2000, }; +#define L2_GUEST_STACK_SIZE 64 + static void l2_guest_code(void) { /* Exit to L0 */ @@ -29,9 +32,8 @@ static void l2_guest_code(void) : : [port] "d" (PORT_L0_EXIT) : "rax"); } -static void l1_guest_code(struct vmx_pages *vmx_pages) +static void l1_vmx_code(struct vmx_pages *vmx_pages) { -#define L2_GUEST_STACK_SIZE 64 unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages)); @@ -45,19 +47,43 @@ static void l1_guest_code(struct vmx_pages *vmx_pages) GUEST_ASSERT(0); } +static void l1_svm_code(struct svm_test_data *svm) +{ + unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; + + /* Prepare the VMCB for L2 execution. */ + generic_svm_setup(svm, l2_guest_code, + &l2_guest_stack[L2_GUEST_STACK_SIZE]); + + run_guest(svm->vmcb, svm->vmcb_gpa); + GUEST_ASSERT(0); +} + +static void l1_guest_code(void *data) +{ + if (this_cpu_has(X86_FEATURE_VMX)) + l1_vmx_code(data); + else + l1_svm_code(data); +} + int main(int argc, char *argv[]) { - vm_vaddr_t vmx_pages_gva; + vm_vaddr_t guest_gva; struct kvm_vcpu *vcpu; struct kvm_vm *vm; - TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX)); + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) || + kvm_cpu_has(X86_FEATURE_SVM)); vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); - /* Allocate VMX pages and shared descriptors (vmx_pages). */ - vcpu_alloc_vmx(vm, &vmx_pages_gva); - vcpu_args_set(vcpu, 1, vmx_pages_gva); + if (kvm_cpu_has(X86_FEATURE_VMX)) + vcpu_alloc_vmx(vm, &guest_gva); + else + vcpu_alloc_svm(vm, &guest_gva); + + vcpu_args_set(vcpu, 1, guest_gva); for (;;) { volatile struct kvm_run *run = vcpu->run; -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Add SVM L1 code to run the nested guest, and allow the test to run with SVM as well as VMX. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/Makefile.kvm | 2 +- ...aling_test.c => nested_tsc_scaling_test.c} | 48 +++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) rename tools/testing/selftests/kvm/x86/{vmx_nested_tsc_scaling_test.c => nested_tsc_scaling_test.c} (83%) diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 61c6abdc9b36f..dc68147ace97f 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -118,7 +118,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_msrs_test TEST_GEN_PROGS_x86 += x86/vmx_invalid_nested_guest_state TEST_GEN_PROGS_x86 += x86/set_nested_state_test TEST_GEN_PROGS_x86 += x86/vmx_tsc_adjust_test -TEST_GEN_PROGS_x86 += x86/vmx_nested_tsc_scaling_test +TEST_GEN_PROGS_x86 += x86/nested_tsc_scaling_test TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test TEST_GEN_PROGS_x86 += x86/xapic_ipi_test TEST_GEN_PROGS_x86 += x86/xapic_state_test diff --git a/tools/testing/selftests/kvm/x86/vmx_nested_tsc_scaling_test.c b/tools/testing/selftests/kvm/x86/nested_tsc_scaling_test.c similarity index 83% rename from tools/testing/selftests/kvm/x86/vmx_nested_tsc_scaling_test.c rename to tools/testing/selftests/kvm/x86/nested_tsc_scaling_test.c index 1759fa5cb3f29..56ab91544775f 100644 --- a/tools/testing/selftests/kvm/x86/vmx_nested_tsc_scaling_test.c +++ b/tools/testing/selftests/kvm/x86/nested_tsc_scaling_test.c @@ -13,6 +13,7 @@ #include "kvm_util.h" #include "vmx.h" +#include "svm_util.h" #include "kselftest.h" /* L2 is scaled up (from L1's perspective) by this factor */ @@ -79,7 +80,30 @@ static void l2_guest_code(void) __asm__ __volatile__("vmcall"); } -static void l1_guest_code(struct vmx_pages *vmx_pages) +static void l1_svm_code(struct svm_test_data *svm) +{ + unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; + + /* check that L1's frequency looks alright before launching L2 */ + check_tsc_freq(UCHECK_L1); + + generic_svm_setup(svm, l2_guest_code, + &l2_guest_stack[L2_GUEST_STACK_SIZE]); + + /* enable TSC scaling for L2 */ + wrmsr(MSR_AMD64_TSC_RATIO, (L2_SCALE_FACTOR << 32) | 1); + + /* launch L2 */ + run_guest(svm->vmcb, svm->vmcb_gpa); + GUEST_ASSERT(svm->vmcb->control.exit_code == SVM_EXIT_VMMCALL); + + /* check that L1's frequency still looks good */ + check_tsc_freq(UCHECK_L1); + + GUEST_DONE(); +} + +static void l1_vmx_code(struct vmx_pages *vmx_pages) { unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; uint32_t control; @@ -116,11 +140,19 @@ static void l1_guest_code(struct vmx_pages *vmx_pages) GUEST_DONE(); } +static void l1_guest_code(void *data) +{ + if (this_cpu_has(X86_FEATURE_VMX)) + l1_vmx_code(data); + else + l1_svm_code(data); +} + int main(int argc, char *argv[]) { struct kvm_vcpu *vcpu; struct kvm_vm *vm; - vm_vaddr_t vmx_pages_gva; + vm_vaddr_t guest_gva = 0; uint64_t tsc_start, tsc_end; uint64_t tsc_khz; @@ -129,7 +161,8 @@ int main(int argc, char *argv[]) uint64_t l1_tsc_freq = 0; uint64_t l2_tsc_freq = 0; - TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX)); + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) || + kvm_cpu_has(X86_FEATURE_SVM)); TEST_REQUIRE(kvm_has_cap(KVM_CAP_TSC_CONTROL)); TEST_REQUIRE(sys_clocksource_is_based_on_tsc()); @@ -152,8 +185,13 @@ int main(int argc, char *argv[]) printf("real TSC frequency is around: %"PRIu64"\n", l0_tsc_freq); vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); - vcpu_alloc_vmx(vm, &vmx_pages_gva); - vcpu_args_set(vcpu, 1, vmx_pages_gva); + + if (kvm_cpu_has(X86_FEATURE_VMX)) + vcpu_alloc_vmx(vm, &guest_gva); + else + vcpu_alloc_svm(vm, &guest_gva); + + vcpu_args_set(vcpu, 1, guest_gva); tsc_khz = __vcpu_ioctl(vcpu, KVM_GET_TSC_KHZ, NULL); TEST_ASSERT(tsc_khz != -1, "vcpu ioctl KVM_GET_TSC_KHZ failed"); -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Checking that VMLAUNCH fails with an invalid CR3 is irrelevant to this test. Remove it to simplify the test a little bit before generalizing it to cover SVM. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c b/tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c index 2ceb5c78c4427..2dcc0306a0d9b 100644 --- a/tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c +++ b/tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c @@ -77,7 +77,6 @@ static void l1_guest_code(struct vmx_pages *vmx_pages) #define L2_GUEST_STACK_SIZE 64 unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; uint32_t control; - uintptr_t save_cr3; GUEST_ASSERT(rdtsc() < TSC_ADJUST_VALUE); wrmsr(MSR_IA32_TSC, rdtsc() - TSC_ADJUST_VALUE); @@ -94,15 +93,6 @@ static void l1_guest_code(struct vmx_pages *vmx_pages) vmwrite(CPU_BASED_VM_EXEC_CONTROL, control); vmwrite(TSC_OFFSET, TSC_OFFSET_VALUE); - /* Jump into L2. First, test failure to load guest CR3. */ - save_cr3 = vmreadz(GUEST_CR3); - vmwrite(GUEST_CR3, -1ull); - GUEST_ASSERT(!vmlaunch()); - GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == - (EXIT_REASON_FAILED_VMENTRY | EXIT_REASON_INVALID_STATE)); - check_ia32_tsc_adjust(-1 * TSC_ADJUST_VALUE); - vmwrite(GUEST_CR3, save_cr3); - GUEST_ASSERT(!vmlaunch()); GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL); -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Add SVM L1 code to run the nested guest, and allow the test to run with SVM as well as VMX. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/Makefile.kvm | 2 +- ...adjust_test.c => nested_tsc_adjust_test.c} | 69 ++++++++++++------- 2 files changed, 46 insertions(+), 25 deletions(-) rename tools/testing/selftests/kvm/x86/{vmx_tsc_adjust_test.c => nested_tsc_adjust_test.c} (61%) diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index dc68147ace97f..9b3c99acd51a3 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -117,7 +117,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state TEST_GEN_PROGS_x86 += x86/vmx_msrs_test TEST_GEN_PROGS_x86 += x86/vmx_invalid_nested_guest_state TEST_GEN_PROGS_x86 += x86/set_nested_state_test -TEST_GEN_PROGS_x86 += x86/vmx_tsc_adjust_test +TEST_GEN_PROGS_x86 += x86/nested_tsc_adjust_test TEST_GEN_PROGS_x86 += x86/nested_tsc_scaling_test TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test TEST_GEN_PROGS_x86 += x86/xapic_ipi_test diff --git a/tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c b/tools/testing/selftests/kvm/x86/nested_tsc_adjust_test.c similarity index 61% rename from tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c rename to tools/testing/selftests/kvm/x86/nested_tsc_adjust_test.c index 2dcc0306a0d9b..cc825a0b41dbf 100644 --- a/tools/testing/selftests/kvm/x86/vmx_tsc_adjust_test.c +++ b/tools/testing/selftests/kvm/x86/nested_tsc_adjust_test.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * vmx_tsc_adjust_test + * nested_tsc_adjust_test * * Copyright (C) 2018, Google LLC. * @@ -22,6 +22,7 @@ #include "kvm_util.h" #include "processor.h" #include "vmx.h" +#include "svm_util.h" #include #include @@ -35,6 +36,8 @@ #define TSC_ADJUST_VALUE (1ll << 32) #define TSC_OFFSET_VALUE -(1ll << 48) +#define L2_GUEST_STACK_SIZE 64 + enum { PORT_ABORT = 0x1000, PORT_REPORT, @@ -72,32 +75,47 @@ static void l2_guest_code(void) __asm__ __volatile__("vmcall"); } -static void l1_guest_code(struct vmx_pages *vmx_pages) +static void l1_guest_code(void *data) { -#define L2_GUEST_STACK_SIZE 64 unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; - uint32_t control; + /* Set TSC from L1 and make sure TSC_ADJUST is updated correctly */ GUEST_ASSERT(rdtsc() < TSC_ADJUST_VALUE); wrmsr(MSR_IA32_TSC, rdtsc() - TSC_ADJUST_VALUE); check_ia32_tsc_adjust(-1 * TSC_ADJUST_VALUE); - GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages)); - GUEST_ASSERT(load_vmcs(vmx_pages)); - - /* Prepare the VMCS for L2 execution. */ - prepare_vmcs(vmx_pages, l2_guest_code, - &l2_guest_stack[L2_GUEST_STACK_SIZE]); - control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); - control |= CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_USE_TSC_OFFSETTING; - vmwrite(CPU_BASED_VM_EXEC_CONTROL, control); - vmwrite(TSC_OFFSET, TSC_OFFSET_VALUE); - - GUEST_ASSERT(!vmlaunch()); - GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL); + /* + * Run L2 with TSC_OFFSET. L2 will write to TSC, and L1 is not + * intercepting the write so it should update L1's TSC_ADJUST. + */ + if (this_cpu_has(X86_FEATURE_VMX)) { + struct vmx_pages *vmx_pages = data; + uint32_t control; + + GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages)); + GUEST_ASSERT(load_vmcs(vmx_pages)); + + prepare_vmcs(vmx_pages, l2_guest_code, + &l2_guest_stack[L2_GUEST_STACK_SIZE]); + control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); + control |= CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_USE_TSC_OFFSETTING; + vmwrite(CPU_BASED_VM_EXEC_CONTROL, control); + vmwrite(TSC_OFFSET, TSC_OFFSET_VALUE); + + GUEST_ASSERT(!vmlaunch()); + GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL); + } else { + struct svm_test_data *svm = data; + + generic_svm_setup(svm, l2_guest_code, + &l2_guest_stack[L2_GUEST_STACK_SIZE]); + + svm->vmcb->control.tsc_offset = TSC_OFFSET_VALUE; + run_guest(svm->vmcb, svm->vmcb_gpa); + GUEST_ASSERT(svm->vmcb->control.exit_code == SVM_EXIT_VMMCALL); + } check_ia32_tsc_adjust(-2 * TSC_ADJUST_VALUE); - GUEST_DONE(); } @@ -109,16 +127,19 @@ static void report(int64_t val) int main(int argc, char *argv[]) { - vm_vaddr_t vmx_pages_gva; + vm_vaddr_t nested_gva; struct kvm_vcpu *vcpu; - TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX)); + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) || + kvm_cpu_has(X86_FEATURE_SVM)); - vm = vm_create_with_one_vcpu(&vcpu, (void *) l1_guest_code); + vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); + if (kvm_cpu_has(X86_FEATURE_VMX)) + vcpu_alloc_vmx(vm, &nested_gva); + else + vcpu_alloc_svm(vm, &nested_gva); - /* Allocate VMX pages and shared descriptors (vmx_pages). */ - vcpu_alloc_vmx(vm, &vmx_pages_gva); - vcpu_args_set(vcpu, 1, vmx_pages_gva); + vcpu_args_set(vcpu, 1, nested_gva); for (;;) { struct ucall uc; -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed The nested mapping functions used to create EPT mappings currently accept a struct vmx_pages argument, only to get the EPT root from it later. In preparation for generalizing these functions to work for NPTs, pass the EPT root HVA directly instead. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/include/x86/vmx.h | 8 +++---- .../testing/selftests/kvm/lib/x86/memstress.c | 4 ++-- tools/testing/selftests/kvm/lib/x86/vmx.c | 24 +++++++++---------- .../selftests/kvm/x86/vmx_dirty_log_test.c | 6 ++--- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tools/testing/selftests/kvm/include/x86/vmx.h b/tools/testing/selftests/kvm/include/x86/vmx.h index edb3c391b9824..06ae68cf9635c 100644 --- a/tools/testing/selftests/kvm/include/x86/vmx.h +++ b/tools/testing/selftests/kvm/include/x86/vmx.h @@ -559,13 +559,13 @@ bool load_vmcs(struct vmx_pages *vmx); bool ept_1g_pages_supported(void); -void nested_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_pg_map(void *root_hva, struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr); -void nested_map(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_map(void *root_hva, struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr, uint64_t size); -void nested_map_memslot(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_map_memslot(void *root_hva, struct kvm_vm *vm, uint32_t memslot); -void nested_identity_map_1g(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_identity_map_1g(void *root_hva, struct kvm_vm *vm, uint64_t addr, uint64_t size); bool kvm_cpu_has_ept(void); void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm, diff --git a/tools/testing/selftests/kvm/lib/x86/memstress.c b/tools/testing/selftests/kvm/lib/x86/memstress.c index 7f5d62a65c68a..7981e295cac70 100644 --- a/tools/testing/selftests/kvm/lib/x86/memstress.c +++ b/tools/testing/selftests/kvm/lib/x86/memstress.c @@ -70,11 +70,11 @@ void memstress_setup_ept(struct vmx_pages *vmx, struct kvm_vm *vm) * KVM can shadow the EPT12 with the maximum huge page size supported * by the backing source. */ - nested_identity_map_1g(vmx, vm, 0, 0x100000000ULL); + nested_identity_map_1g(vmx->eptp_hva, vm, 0, 0x100000000ULL); start = align_down(memstress_args.gpa, PG_SIZE_1G); end = align_up(memstress_args.gpa + memstress_args.size, PG_SIZE_1G); - nested_identity_map_1g(vmx, vm, start, end - start); + nested_identity_map_1g(vmx->eptp_hva, vm, start, end - start); } void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vcpus[]) diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index d4d1208dd0238..04c4b97bcd1e7 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -394,11 +394,11 @@ static void nested_create_pte(struct kvm_vm *vm, } -void __nested_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm, +void __nested_pg_map(void *root_hva, struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr, int target_level) { const uint64_t page_size = PG_LEVEL_SIZE(target_level); - struct eptPageTableEntry *pt = vmx->eptp_hva, *pte; + struct eptPageTableEntry *pt = root_hva, *pte; uint16_t index; TEST_ASSERT(vm->mode == VM_MODE_PXXV48_4K, "Attempt to use " @@ -445,10 +445,10 @@ void __nested_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm, } -void nested_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_pg_map(void *root_hva, struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr) { - __nested_pg_map(vmx, vm, nested_paddr, paddr, PG_LEVEL_4K); + __nested_pg_map(root_hva, vm, nested_paddr, paddr, PG_LEVEL_4K); } /* @@ -468,7 +468,7 @@ void nested_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm, * Within the VM given by vm, creates a nested guest translation for the * page range starting at nested_paddr to the page range starting at paddr. */ -void __nested_map(struct vmx_pages *vmx, struct kvm_vm *vm, +void __nested_map(void *root_hva, struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr, uint64_t size, int level) { @@ -479,22 +479,22 @@ void __nested_map(struct vmx_pages *vmx, struct kvm_vm *vm, TEST_ASSERT(paddr + size > paddr, "Paddr overflow"); while (npages--) { - __nested_pg_map(vmx, vm, nested_paddr, paddr, level); + __nested_pg_map(root_hva, vm, nested_paddr, paddr, level); nested_paddr += page_size; paddr += page_size; } } -void nested_map(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_map(void *root_hva, struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr, uint64_t size) { - __nested_map(vmx, vm, nested_paddr, paddr, size, PG_LEVEL_4K); + __nested_map(root_hva, vm, nested_paddr, paddr, size, PG_LEVEL_4K); } /* Prepare an identity extended page table that maps all the * physical pages in VM. */ -void nested_map_memslot(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_map_memslot(void *root_hva, struct kvm_vm *vm, uint32_t memslot) { sparsebit_idx_t i, last; @@ -508,7 +508,7 @@ void nested_map_memslot(struct vmx_pages *vmx, struct kvm_vm *vm, if (i > last) break; - nested_map(vmx, vm, + nested_map(root_hva, vm, (uint64_t)i << vm->page_shift, (uint64_t)i << vm->page_shift, 1 << vm->page_shift); @@ -516,10 +516,10 @@ void nested_map_memslot(struct vmx_pages *vmx, struct kvm_vm *vm, } /* Identity map a region with 1GiB Pages. */ -void nested_identity_map_1g(struct vmx_pages *vmx, struct kvm_vm *vm, +void nested_identity_map_1g(void *root_hva, struct kvm_vm *vm, uint64_t addr, uint64_t size) { - __nested_map(vmx, vm, addr, addr, size, PG_LEVEL_1G); + __nested_map(root_hva, vm, addr, addr, size, PG_LEVEL_1G); } bool kvm_cpu_has_ept(void) diff --git a/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c b/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c index fa512d033205f..21a57805e9780 100644 --- a/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c +++ b/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c @@ -121,9 +121,9 @@ static void test_vmx_dirty_log(bool enable_ept) */ if (enable_ept) { prepare_eptp(vmx, vm, 0); - nested_map_memslot(vmx, vm, 0); - nested_map(vmx, vm, NESTED_TEST_MEM1, GUEST_TEST_MEM, 4096); - nested_map(vmx, vm, NESTED_TEST_MEM2, GUEST_TEST_MEM, 4096); + nested_map_memslot(vmx->eptp_hva, vm, 0); + nested_map(vmx->eptp_hva, vm, NESTED_TEST_MEM1, GUEST_TEST_MEM, 4096); + nested_map(vmx->eptp_hva, vm, NESTED_TEST_MEM2, GUEST_TEST_MEM, 4096); } bmap = bitmap_zalloc(TEST_MEM_PAGES); -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed The assertions use 'hugepage' to describe a terminal EPT entry, but 'leaf' is more accruate as a PG_LEVEL_4K EPT entry is a leaf but not a hugepage. The distincion will be useful in coming changes that will pass the value around and 'leaf' is clearer than hugepage or page_size. Leave the EPT bit named page_size to keep it conforming to the manual. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/lib/x86/vmx.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index 04c4b97bcd1e7..673756b27e903 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -380,15 +380,15 @@ static void nested_create_pte(struct kvm_vm *vm, pte->address = vm_alloc_page_table(vm) >> vm->page_shift; } else { /* - * Entry already present. Assert that the caller doesn't want - * a hugepage at this level, and that there isn't a hugepage at - * this level. + * Entry already present. Assert that the caller doesn't want a + * leaf entry at this level, and that there isn't a leaf entry + * at this level. */ TEST_ASSERT(current_level != target_level, - "Cannot create hugepage at level: %u, nested_paddr: 0x%lx", + "Cannot create leaf entry at level: %u, nested_paddr: 0x%lx", current_level, nested_paddr); TEST_ASSERT(!pte->page_size, - "Cannot create page table at level: %u, nested_paddr: 0x%lx", + "Leaf entry already exists at level: %u, nested_paddr: 0x%lx", current_level, nested_paddr); } } -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed In preparation for making the nested mapping functions work for NPT, move all logic that directly accesses the PTE into nested_create_pte(), as these accesses will be different for SVM. Stop using struct eptPageTableEntry in the caller, instead pass a uint64_t pointer (and add an assertion on the size to make sure it stays correct). Calculate whether or not an EPT entry is a leaf in __nested_pg_map(), and return the address from nested_create_pte() to __nested_pg_map(). Also, set the access and dirty bits in nested_create_pte() for leaf entries. This matches the current behavior and removes all direct accesses to the EPT entry from __nested_pg_map(). Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/lib/x86/vmx.c | 69 +++++++++++++---------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index 673756b27e903..b0e6267eac806 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -33,6 +33,7 @@ struct eptPageTableEntry { uint64_t ignored_62_52:11; uint64_t suppress_ve:1; }; +kvm_static_assert(sizeof(struct eptPageTableEntry) == sizeof(uint64_t)); struct eptPageTablePointer { uint64_t memory_type:3; @@ -42,6 +43,8 @@ struct eptPageTablePointer { uint64_t address:40; uint64_t reserved_63_52:12; }; +kvm_static_assert(sizeof(struct eptPageTablePointer) == sizeof(uint64_t)); + int vcpu_enable_evmcs(struct kvm_vcpu *vcpu) { uint16_t evmcs_ver; @@ -362,35 +365,46 @@ void prepare_vmcs(struct vmx_pages *vmx, void *guest_rip, void *guest_rsp) init_vmcs_guest_state(guest_rip, guest_rsp); } -static void nested_create_pte(struct kvm_vm *vm, - struct eptPageTableEntry *pte, - uint64_t nested_paddr, - uint64_t paddr, - int current_level, - int target_level) +static uint64_t nested_create_pte(struct kvm_vm *vm, + uint64_t *pte, + uint64_t nested_paddr, + uint64_t paddr, + int level, + bool leaf) { - if (!pte->readable) { - pte->writable = true; - pte->readable = true; - pte->executable = true; - pte->page_size = (current_level == target_level); - if (pte->page_size) - pte->address = paddr >> vm->page_shift; + struct eptPageTableEntry *epte = (struct eptPageTableEntry *)pte; + + if (!epte->readable) { + epte->writable = true; + epte->readable = true; + epte->executable = true; + epte->page_size = leaf; + + if (leaf) + epte->address = paddr >> vm->page_shift; else - pte->address = vm_alloc_page_table(vm) >> vm->page_shift; + epte->address = vm_alloc_page_table(vm) >> vm->page_shift; + + /* + * For now mark these as accessed and dirty because the only + * testcase we have needs that. Can be reconsidered later. + */ + epte->accessed = leaf; + epte->dirty = leaf; } else { /* * Entry already present. Assert that the caller doesn't want a * leaf entry at this level, and that there isn't a leaf entry * at this level. */ - TEST_ASSERT(current_level != target_level, + TEST_ASSERT(!leaf, "Cannot create leaf entry at level: %u, nested_paddr: 0x%lx", - current_level, nested_paddr); - TEST_ASSERT(!pte->page_size, + level, nested_paddr); + TEST_ASSERT(!epte->page_size, "Leaf entry already exists at level: %u, nested_paddr: 0x%lx", - current_level, nested_paddr); + level, nested_paddr); } + return epte->address; } @@ -398,8 +412,9 @@ void __nested_pg_map(void *root_hva, struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr, int target_level) { const uint64_t page_size = PG_LEVEL_SIZE(target_level); - struct eptPageTableEntry *pt = root_hva, *pte; - uint16_t index; + uint64_t *pt = root_hva, *pte; + uint16_t index, address; + bool leaf; TEST_ASSERT(vm->mode == VM_MODE_PXXV48_4K, "Attempt to use " "unknown or unsupported guest mode, mode: 0x%x", vm->mode); @@ -427,22 +442,16 @@ void __nested_pg_map(void *root_hva, struct kvm_vm *vm, for (int level = PG_LEVEL_512G; level >= PG_LEVEL_4K; level--) { index = (nested_paddr >> PG_LEVEL_SHIFT(level)) & 0x1ffu; pte = &pt[index]; + leaf = (level == target_level); - nested_create_pte(vm, pte, nested_paddr, paddr, level, target_level); + address = nested_create_pte(vm, pte, nested_paddr, paddr, level, leaf); - if (pte->page_size) + if (leaf) break; - pt = addr_gpa2hva(vm, pte->address * vm->page_size); + pt = addr_gpa2hva(vm, address * vm->page_size); } - /* - * For now mark these as accessed and dirty because the only - * testcase we have needs that. Can be reconsidered later. - */ - pte->accessed = true; - pte->dirty = true; - } void nested_pg_map(void *root_hva, struct kvm_vm *vm, -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Refactor the EPT specific initialization into nested_ept_create_pte(), in preparation for making nested_create_pte() NPT-friendly. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/lib/x86/vmx.c | 71 ++++++++++++++--------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index b0e6267eac806..eeacf42bf30b1 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -365,46 +365,61 @@ void prepare_vmcs(struct vmx_pages *vmx, void *guest_rip, void *guest_rsp) init_vmcs_guest_state(guest_rip, guest_rsp); } +static bool nested_ept_create_pte(struct kvm_vm *vm, + uint64_t *pte, + uint64_t paddr, + uint64_t *address, + bool *leaf) +{ + struct eptPageTableEntry *epte = (struct eptPageTableEntry *)pte; + + /* PTE already exists? */ + if (epte->readable) { + *leaf = epte->page_size; + *address = epte->address; + return false; + } + + epte->writable = true; + epte->readable = true; + epte->executable = true; + epte->page_size = *leaf; + + if (*leaf) + epte->address = paddr >> vm->page_shift; + else + epte->address = vm_alloc_page_table(vm) >> vm->page_shift; + + *address = epte->address; + + /* + * For now mark these as accessed and dirty because the only + * testcase we have needs that. Can be reconsidered later. + */ + epte->accessed = *leaf; + epte->dirty = *leaf; + return true; +} + static uint64_t nested_create_pte(struct kvm_vm *vm, uint64_t *pte, uint64_t nested_paddr, uint64_t paddr, int level, - bool leaf) + bool want_leaf) { - struct eptPageTableEntry *epte = (struct eptPageTableEntry *)pte; - - if (!epte->readable) { - epte->writable = true; - epte->readable = true; - epte->executable = true; - epte->page_size = leaf; + bool leaf = want_leaf; + uint64_t address; - if (leaf) - epte->address = paddr >> vm->page_shift; - else - epte->address = vm_alloc_page_table(vm) >> vm->page_shift; - - /* - * For now mark these as accessed and dirty because the only - * testcase we have needs that. Can be reconsidered later. - */ - epte->accessed = leaf; - epte->dirty = leaf; - } else { - /* - * Entry already present. Assert that the caller doesn't want a - * leaf entry at this level, and that there isn't a leaf entry - * at this level. - */ - TEST_ASSERT(!leaf, + if (!nested_ept_create_pte(vm, pte, paddr, &address, &leaf)) { + TEST_ASSERT(!want_leaf, "Cannot create leaf entry at level: %u, nested_paddr: 0x%lx", level, nested_paddr); - TEST_ASSERT(!epte->page_size, + TEST_ASSERT(!leaf, "Leaf entry already exists at level: %u, nested_paddr: 0x%lx", level, nested_paddr); } - return epte->address; + return address; } -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Now that the nested mapping functions in vmx.c are all generic except for nested_ept_create_pte(), move them all out into a new nested_map.c lib file and expose nested_ept_create_pte() in vmx.h. This allows reusing the code for NPT in following changes. While we're at it, merge nested_pg_map() and __nested_pg_map(), as the former is unused, and make sure all functions not exposed in the header are static. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/include/x86/nested_map.h | 20 +++ tools/testing/selftests/kvm/include/x86/vmx.h | 13 +- .../testing/selftests/kvm/lib/x86/memstress.c | 1 + .../selftests/kvm/lib/x86/nested_map.c | 150 +++++++++++++++++ tools/testing/selftests/kvm/lib/x86/vmx.c | 155 +----------------- .../selftests/kvm/x86/vmx_dirty_log_test.c | 1 + 7 files changed, 183 insertions(+), 158 deletions(-) create mode 100644 tools/testing/selftests/kvm/include/x86/nested_map.h create mode 100644 tools/testing/selftests/kvm/lib/x86/nested_map.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 9b3c99acd51a3..9547d7263e236 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -23,6 +23,7 @@ LIBKVM_x86 += lib/x86/apic.c LIBKVM_x86 += lib/x86/handlers.S LIBKVM_x86 += lib/x86/hyperv.c LIBKVM_x86 += lib/x86/memstress.c +LIBKVM_x86 += lib/x86/nested_map.c LIBKVM_x86 += lib/x86/pmu.c LIBKVM_x86 += lib/x86/processor.c LIBKVM_x86 += lib/x86/sev.c diff --git a/tools/testing/selftests/kvm/include/x86/nested_map.h b/tools/testing/selftests/kvm/include/x86/nested_map.h new file mode 100644 index 0000000000000..362162dd6db43 --- /dev/null +++ b/tools/testing/selftests/kvm/include/x86/nested_map.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tools/testing/selftests/kvm/include/x86_64/nested_map.h + * + * Copyright (C) 2025, Google LLC. + */ + +#ifndef SELFTEST_KVM_NESTED_MAP_H +#define SELFTEST_KVM_NESTED_MAP_H + +#include "kvm_util.h" + +void nested_map(void *root_hva, struct kvm_vm *vm, + uint64_t nested_paddr, uint64_t paddr, uint64_t size); +void nested_map_memslot(void *root_hva, struct kvm_vm *vm, + uint32_t memslot); +void nested_identity_map_1g(void *root_hva, struct kvm_vm *vm, + uint64_t addr, uint64_t size); + +#endif /* SELFTEST_KVM_NESTED_MAP_H */ diff --git a/tools/testing/selftests/kvm/include/x86/vmx.h b/tools/testing/selftests/kvm/include/x86/vmx.h index 06ae68cf9635c..49d763144dbfe 100644 --- a/tools/testing/selftests/kvm/include/x86/vmx.h +++ b/tools/testing/selftests/kvm/include/x86/vmx.h @@ -559,14 +559,11 @@ bool load_vmcs(struct vmx_pages *vmx); bool ept_1g_pages_supported(void); -void nested_pg_map(void *root_hva, struct kvm_vm *vm, - uint64_t nested_paddr, uint64_t paddr); -void nested_map(void *root_hva, struct kvm_vm *vm, - uint64_t nested_paddr, uint64_t paddr, uint64_t size); -void nested_map_memslot(void *root_hva, struct kvm_vm *vm, - uint32_t memslot); -void nested_identity_map_1g(void *root_hva, struct kvm_vm *vm, - uint64_t addr, uint64_t size); +bool nested_ept_create_pte(struct kvm_vm *vm, + uint64_t *pte, + uint64_t paddr, + uint64_t *address, + bool *leaf); bool kvm_cpu_has_ept(void); void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm, uint32_t eptp_memslot); diff --git a/tools/testing/selftests/kvm/lib/x86/memstress.c b/tools/testing/selftests/kvm/lib/x86/memstress.c index 7981e295cac70..d3e2fbd550acd 100644 --- a/tools/testing/selftests/kvm/lib/x86/memstress.c +++ b/tools/testing/selftests/kvm/lib/x86/memstress.c @@ -12,6 +12,7 @@ #include "test_util.h" #include "kvm_util.h" #include "memstress.h" +#include "nested_map.h" #include "processor.h" #include "vmx.h" diff --git a/tools/testing/selftests/kvm/lib/x86/nested_map.c b/tools/testing/selftests/kvm/lib/x86/nested_map.c new file mode 100644 index 0000000000000..454ab3e2f5b7e --- /dev/null +++ b/tools/testing/selftests/kvm/lib/x86/nested_map.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tools/testing/selftests/kvm/lib/x86_64/nested_map.c + * + * Copyright (C) 2025, Google LLC. + */ + +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" +#include "nested_map.h" +#include "vmx.h" + +static uint64_t nested_create_pte(struct kvm_vm *vm, + uint64_t *pte, + uint64_t nested_paddr, + uint64_t paddr, + int level, + bool want_leaf) +{ + bool leaf = want_leaf; + uint64_t address; + + if (!nested_ept_create_pte(vm, pte, paddr, &address, &leaf)) { + TEST_ASSERT(!want_leaf, + "Cannot create leaf entry at level: %u, nested_paddr: 0x%lx", + level, nested_paddr); + TEST_ASSERT(!leaf, + "Leaf entry already exists at level: %u, nested_paddr: 0x%lx", + level, nested_paddr); + } + return address; +} + +static void nested_pg_map(void *root_hva, struct kvm_vm *vm, uint64_t + nested_paddr, uint64_t paddr, int target_level) +{ + const uint64_t page_size = PG_LEVEL_SIZE(target_level); + uint64_t *pt = root_hva, *pte; + uint16_t index, address; + bool leaf; + + TEST_ASSERT(vm->mode == VM_MODE_PXXV48_4K, "Attempt to use " + "unknown or unsupported guest mode, mode: 0x%x", vm->mode); + + TEST_ASSERT((nested_paddr >> 48) == 0, + "Nested physical address 0x%lx requires 5-level paging", + nested_paddr); + TEST_ASSERT((nested_paddr % page_size) == 0, + "Nested physical address not on page boundary,\n" + " nested_paddr: 0x%lx page_size: 0x%lx", + nested_paddr, page_size); + TEST_ASSERT((nested_paddr >> vm->page_shift) <= vm->max_gfn, + "Physical address beyond beyond maximum supported,\n" + " nested_paddr: 0x%lx vm->max_gfn: 0x%lx vm->page_size: 0x%x", + paddr, vm->max_gfn, vm->page_size); + TEST_ASSERT((paddr % page_size) == 0, + "Physical address not on page boundary,\n" + " paddr: 0x%lx page_size: 0x%lx", + paddr, page_size); + TEST_ASSERT((paddr >> vm->page_shift) <= vm->max_gfn, + "Physical address beyond beyond maximum supported,\n" + " paddr: 0x%lx vm->max_gfn: 0x%lx vm->page_size: 0x%x", + paddr, vm->max_gfn, vm->page_size); + + for (int level = PG_LEVEL_512G; level >= PG_LEVEL_4K; level--) { + index = (nested_paddr >> PG_LEVEL_SHIFT(level)) & 0x1ffu; + pte = &pt[index]; + leaf = (level == target_level); + + address = nested_create_pte(vm, pte, nested_paddr, paddr, level, leaf); + + if (leaf) + break; + + pt = addr_gpa2hva(vm, address * vm->page_size); + } + +} + +/* + * Map a range of EPT guest physical addresses to the VM's physical address + * + * Input Args: + * vm - Virtual Machine + * nested_paddr - Nested guest physical address to map + * paddr - VM Physical Address + * size - The size of the range to map + * level - The level at which to map the range + * + * Output Args: None + * + * Return: None + * + * Within the VM given by vm, creates a nested guest translation for the + * page range starting at nested_paddr to the page range starting at paddr. + */ +static void __nested_map(void *root_hva, struct kvm_vm *vm, uint64_t + nested_paddr, uint64_t paddr, uint64_t size, int level) +{ + size_t page_size = PG_LEVEL_SIZE(level); + size_t npages = size / page_size; + + TEST_ASSERT(nested_paddr + size > nested_paddr, "Vaddr overflow"); + TEST_ASSERT(paddr + size > paddr, "Paddr overflow"); + + while (npages--) { + nested_pg_map(root_hva, vm, nested_paddr, paddr, level); + nested_paddr += page_size; + paddr += page_size; + } +} + +void nested_map(void *root_hva, struct kvm_vm *vm, + uint64_t nested_paddr, uint64_t paddr, uint64_t size) +{ + __nested_map(root_hva, vm, nested_paddr, paddr, size, PG_LEVEL_4K); +} + +/* + * Prepare an identity nested page table that maps all the + * physical pages in VM. + */ +void nested_map_memslot(void *root_hva, struct kvm_vm *vm, + uint32_t memslot) +{ + sparsebit_idx_t i, last; + struct userspace_mem_region *region = + memslot2region(vm, memslot); + + i = (region->region.guest_phys_addr >> vm->page_shift) - 1; + last = i + (region->region.memory_size >> vm->page_shift); + for (;;) { + i = sparsebit_next_clear(region->unused_phy_pages, i); + if (i > last) + break; + + nested_map(root_hva, vm, + (uint64_t)i << vm->page_shift, + (uint64_t)i << vm->page_shift, + 1 << vm->page_shift); + } +} + +/* Identity map a region with 1GiB Pages. */ +void nested_identity_map_1g(void *root_hva, struct kvm_vm *vm, + uint64_t addr, uint64_t size) +{ + __nested_map(root_hva, vm, addr, addr, size, PG_LEVEL_1G); +} diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index eeacf42bf30b1..24345213fcd04 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -365,11 +365,11 @@ void prepare_vmcs(struct vmx_pages *vmx, void *guest_rip, void *guest_rsp) init_vmcs_guest_state(guest_rip, guest_rsp); } -static bool nested_ept_create_pte(struct kvm_vm *vm, - uint64_t *pte, - uint64_t paddr, - uint64_t *address, - bool *leaf) +bool nested_ept_create_pte(struct kvm_vm *vm, + uint64_t *pte, + uint64_t paddr, + uint64_t *address, + bool *leaf) { struct eptPageTableEntry *epte = (struct eptPageTableEntry *)pte; @@ -401,151 +401,6 @@ static bool nested_ept_create_pte(struct kvm_vm *vm, return true; } -static uint64_t nested_create_pte(struct kvm_vm *vm, - uint64_t *pte, - uint64_t nested_paddr, - uint64_t paddr, - int level, - bool want_leaf) -{ - bool leaf = want_leaf; - uint64_t address; - - if (!nested_ept_create_pte(vm, pte, paddr, &address, &leaf)) { - TEST_ASSERT(!want_leaf, - "Cannot create leaf entry at level: %u, nested_paddr: 0x%lx", - level, nested_paddr); - TEST_ASSERT(!leaf, - "Leaf entry already exists at level: %u, nested_paddr: 0x%lx", - level, nested_paddr); - } - return address; -} - - -void __nested_pg_map(void *root_hva, struct kvm_vm *vm, - uint64_t nested_paddr, uint64_t paddr, int target_level) -{ - const uint64_t page_size = PG_LEVEL_SIZE(target_level); - uint64_t *pt = root_hva, *pte; - uint16_t index, address; - bool leaf; - - TEST_ASSERT(vm->mode == VM_MODE_PXXV48_4K, "Attempt to use " - "unknown or unsupported guest mode, mode: 0x%x", vm->mode); - - TEST_ASSERT((nested_paddr >> 48) == 0, - "Nested physical address 0x%lx requires 5-level paging", - nested_paddr); - TEST_ASSERT((nested_paddr % page_size) == 0, - "Nested physical address not on page boundary,\n" - " nested_paddr: 0x%lx page_size: 0x%lx", - nested_paddr, page_size); - TEST_ASSERT((nested_paddr >> vm->page_shift) <= vm->max_gfn, - "Physical address beyond beyond maximum supported,\n" - " nested_paddr: 0x%lx vm->max_gfn: 0x%lx vm->page_size: 0x%x", - paddr, vm->max_gfn, vm->page_size); - TEST_ASSERT((paddr % page_size) == 0, - "Physical address not on page boundary,\n" - " paddr: 0x%lx page_size: 0x%lx", - paddr, page_size); - TEST_ASSERT((paddr >> vm->page_shift) <= vm->max_gfn, - "Physical address beyond beyond maximum supported,\n" - " paddr: 0x%lx vm->max_gfn: 0x%lx vm->page_size: 0x%x", - paddr, vm->max_gfn, vm->page_size); - - for (int level = PG_LEVEL_512G; level >= PG_LEVEL_4K; level--) { - index = (nested_paddr >> PG_LEVEL_SHIFT(level)) & 0x1ffu; - pte = &pt[index]; - leaf = (level == target_level); - - address = nested_create_pte(vm, pte, nested_paddr, paddr, level, leaf); - - if (leaf) - break; - - pt = addr_gpa2hva(vm, address * vm->page_size); - } - -} - -void nested_pg_map(void *root_hva, struct kvm_vm *vm, - uint64_t nested_paddr, uint64_t paddr) -{ - __nested_pg_map(root_hva, vm, nested_paddr, paddr, PG_LEVEL_4K); -} - -/* - * Map a range of EPT guest physical addresses to the VM's physical address - * - * Input Args: - * vm - Virtual Machine - * nested_paddr - Nested guest physical address to map - * paddr - VM Physical Address - * size - The size of the range to map - * level - The level at which to map the range - * - * Output Args: None - * - * Return: None - * - * Within the VM given by vm, creates a nested guest translation for the - * page range starting at nested_paddr to the page range starting at paddr. - */ -void __nested_map(void *root_hva, struct kvm_vm *vm, - uint64_t nested_paddr, uint64_t paddr, uint64_t size, - int level) -{ - size_t page_size = PG_LEVEL_SIZE(level); - size_t npages = size / page_size; - - TEST_ASSERT(nested_paddr + size > nested_paddr, "Vaddr overflow"); - TEST_ASSERT(paddr + size > paddr, "Paddr overflow"); - - while (npages--) { - __nested_pg_map(root_hva, vm, nested_paddr, paddr, level); - nested_paddr += page_size; - paddr += page_size; - } -} - -void nested_map(void *root_hva, struct kvm_vm *vm, - uint64_t nested_paddr, uint64_t paddr, uint64_t size) -{ - __nested_map(root_hva, vm, nested_paddr, paddr, size, PG_LEVEL_4K); -} - -/* Prepare an identity extended page table that maps all the - * physical pages in VM. - */ -void nested_map_memslot(void *root_hva, struct kvm_vm *vm, - uint32_t memslot) -{ - sparsebit_idx_t i, last; - struct userspace_mem_region *region = - memslot2region(vm, memslot); - - i = (region->region.guest_phys_addr >> vm->page_shift) - 1; - last = i + (region->region.memory_size >> vm->page_shift); - for (;;) { - i = sparsebit_next_clear(region->unused_phy_pages, i); - if (i > last) - break; - - nested_map(root_hva, vm, - (uint64_t)i << vm->page_shift, - (uint64_t)i << vm->page_shift, - 1 << vm->page_shift); - } -} - -/* Identity map a region with 1GiB Pages. */ -void nested_identity_map_1g(void *root_hva, struct kvm_vm *vm, - uint64_t addr, uint64_t size) -{ - __nested_map(root_hva, vm, addr, addr, size, PG_LEVEL_1G); -} - bool kvm_cpu_has_ept(void) { uint64_t ctrl; diff --git a/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c b/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c index 21a57805e9780..db88a1e5e9d0c 100644 --- a/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c +++ b/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c @@ -11,6 +11,7 @@ #include "test_util.h" #include "kvm_util.h" +#include "nested_map.h" #include "processor.h" #include "vmx.h" -- 2.51.0.618.g983fd99d29-goog From: Yosry Ahmed Add the necessary infrastructure to support setting up nested NPTs and creating nested NPT mappings. There is some redundancy between nested_npt_create_pte() and nested_ept_create_pte(), especially that we access the same fields in both. An alternative is to have a single function in nested_map.c, and use macros to cast an obaque PTE pointer to the correct type (EPT entry vs NPT entry). Add a check in kvm_cpu_has_ept() to return false on AMD CPUs without attempting to read VMX-specific MSRs, since now it can be called on AMD CPUs. Generalize the code in vmx_dirty_log_test.c by adding SVM-specific L1 code, doing some renaming (e.g. EPT -> TDP), and having setup code for both SVM and VMX in test_dirty_log(). Having multiple points to check for SVM vs VMX is not ideal, but the alternatives either include a lot of redundancy or a lot of abstracting functions that will make the test logic harder to follow. Signed-off-by: Yosry Ahmed --- tools/testing/selftests/kvm/Makefile.kvm | 2 +- .../selftests/kvm/include/x86/svm_util.h | 13 +++ tools/testing/selftests/kvm/lib/x86/svm.c | 70 ++++++++++++++ tools/testing/selftests/kvm/lib/x86/vmx.c | 3 + ...rty_log_test.c => nested_dirty_log_test.c} | 94 ++++++++++++++----- 5 files changed, 155 insertions(+), 27 deletions(-) rename tools/testing/selftests/kvm/x86/{vmx_dirty_log_test.c => nested_dirty_log_test.c} (62%) diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 9547d7263e236..acedbf726f493 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -113,7 +113,7 @@ TEST_GEN_PROGS_x86 += x86/userspace_io_test TEST_GEN_PROGS_x86 += x86/userspace_msr_exit_test TEST_GEN_PROGS_x86 += x86/vmx_apic_access_test TEST_GEN_PROGS_x86 += x86/close_while_nested_test -TEST_GEN_PROGS_x86 += x86/vmx_dirty_log_test +TEST_GEN_PROGS_x86 += x86/nested_dirty_log_test TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state TEST_GEN_PROGS_x86 += x86/vmx_msrs_test TEST_GEN_PROGS_x86 += x86/vmx_invalid_nested_guest_state diff --git a/tools/testing/selftests/kvm/include/x86/svm_util.h b/tools/testing/selftests/kvm/include/x86/svm_util.h index b74c6dcddcbd6..84b79113b5433 100644 --- a/tools/testing/selftests/kvm/include/x86/svm_util.h +++ b/tools/testing/selftests/kvm/include/x86/svm_util.h @@ -27,6 +27,11 @@ struct svm_test_data { void *msr; /* gva */ void *msr_hva; uint64_t msr_gpa; + + /* NPT */ + void *ncr3; /* gva */ + void *ncr3_hva; + uint64_t ncr3_gpa; }; static inline void vmmcall(void) @@ -57,6 +62,14 @@ struct svm_test_data *vcpu_alloc_svm(struct kvm_vm *vm, vm_vaddr_t *p_svm_gva); void generic_svm_setup(struct svm_test_data *svm, void *guest_rip, void *guest_rsp); void run_guest(struct vmcb *vmcb, uint64_t vmcb_gpa); +bool nested_npt_create_pte(struct kvm_vm *vm, + uint64_t *pte, + uint64_t paddr, + uint64_t *address, + bool *leaf); +bool kvm_cpu_has_npt(void); +void prepare_npt(struct svm_test_data *svm, struct kvm_vm *vm); + int open_sev_dev_path_or_exit(void); #endif /* SELFTEST_KVM_SVM_UTILS_H */ diff --git a/tools/testing/selftests/kvm/lib/x86/svm.c b/tools/testing/selftests/kvm/lib/x86/svm.c index d239c20973918..9524abf7e779a 100644 --- a/tools/testing/selftests/kvm/lib/x86/svm.c +++ b/tools/testing/selftests/kvm/lib/x86/svm.c @@ -16,6 +16,23 @@ struct gpr64_regs guest_regs; u64 rflags; +struct nptPageTableEntry { + uint64_t present:1; + uint64_t writable:1; + uint64_t user:1; + uint64_t pwt:1; + uint64_t pcd:1; + uint64_t accessed:1; + uint64_t dirty:1; + uint64_t page_size:1; + uint64_t global:1; + uint64_t avail1:3; + uint64_t address:40; + uint64_t avail2:11; + uint64_t nx:1; +}; +static_assert(sizeof(struct nptPageTableEntry) == sizeof(uint64_t)); + /* Allocate memory regions for nested SVM tests. * * Input Args: @@ -59,6 +76,54 @@ static void vmcb_set_seg(struct vmcb_seg *seg, u16 selector, seg->base = base; } +bool nested_npt_create_pte(struct kvm_vm *vm, + uint64_t *pte, + uint64_t paddr, + uint64_t *address, + bool *leaf) +{ + struct nptPageTableEntry *npte = (struct nptPageTableEntry *)pte; + + if (npte->present) { + *leaf = npte->page_size; + *address = npte->address; + return false; + } + + npte->present = true; + npte->writable = true; + npte->page_size = *leaf; + + if (*leaf) + npte->address = paddr >> vm->page_shift; + else + npte->address = vm_alloc_page_table(vm) >> vm->page_shift; + + *address = npte->address; + + /* + * For now mark these as accessed and dirty because the only + * testcase we have needs that. Can be reconsidered later. + */ + npte->accessed = *leaf; + npte->dirty = *leaf; + return true; +} + +bool kvm_cpu_has_npt(void) +{ + return kvm_cpu_has(X86_FEATURE_NPT); +} + +void prepare_npt(struct svm_test_data *svm, struct kvm_vm *vm) +{ + TEST_ASSERT(kvm_cpu_has_npt(), "KVM doesn't support nested NPT"); + + svm->ncr3 = (void *)vm_vaddr_alloc_page(vm); + svm->ncr3_hva = addr_gva2hva(vm, (uintptr_t)svm->ncr3); + svm->ncr3_gpa = addr_gva2gpa(vm, (uintptr_t)svm->ncr3); +} + void generic_svm_setup(struct svm_test_data *svm, void *guest_rip, void *guest_rsp) { struct vmcb *vmcb = svm->vmcb; @@ -102,6 +167,11 @@ void generic_svm_setup(struct svm_test_data *svm, void *guest_rip, void *guest_r vmcb->save.rip = (u64)guest_rip; vmcb->save.rsp = (u64)guest_rsp; guest_regs.rdi = (u64)svm; + + if (svm->ncr3_gpa) { + ctrl->nested_ctl |= SVM_NESTED_CTL_NP_ENABLE; + ctrl->nested_cr3 = svm->ncr3_gpa; + } } /* diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index 24345213fcd04..0ced959184cd9 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -405,6 +405,9 @@ bool kvm_cpu_has_ept(void) { uint64_t ctrl; + if (!kvm_cpu_has(X86_FEATURE_VMX)) + return false; + ctrl = kvm_get_feature_msr(MSR_IA32_VMX_TRUE_PROCBASED_CTLS) >> 32; if (!(ctrl & CPU_BASED_ACTIVATE_SECONDARY_CONTROLS)) return false; diff --git a/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c b/tools/testing/selftests/kvm/x86/nested_dirty_log_test.c similarity index 62% rename from tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c rename to tools/testing/selftests/kvm/x86/nested_dirty_log_test.c index db88a1e5e9d0c..56f741ddce944 100644 --- a/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c +++ b/tools/testing/selftests/kvm/x86/nested_dirty_log_test.c @@ -13,6 +13,7 @@ #include "kvm_util.h" #include "nested_map.h" #include "processor.h" +#include "svm_util.h" #include "vmx.h" /* The memory slot index to track dirty pages */ @@ -26,6 +27,8 @@ #define NESTED_TEST_MEM1 0xc0001000 #define NESTED_TEST_MEM2 0xc0002000 +#define L2_GUEST_STACK_SIZE 64 + static void l2_guest_code(u64 *a, u64 *b) { READ_ONCE(*a); @@ -43,20 +46,19 @@ static void l2_guest_code(u64 *a, u64 *b) vmcall(); } -static void l2_guest_code_ept_enabled(void) +static void l2_guest_code_tdp_enabled(void) { l2_guest_code((u64 *)NESTED_TEST_MEM1, (u64 *)NESTED_TEST_MEM2); } -static void l2_guest_code_ept_disabled(void) +static void l2_guest_code_tdp_disabled(void) { - /* Access the same L1 GPAs as l2_guest_code_ept_enabled() */ + /* Access the same L1 GPAs as l2_guest_code_tdp_enabled() */ l2_guest_code((u64 *)GUEST_TEST_MEM, (u64 *)GUEST_TEST_MEM); } -void l1_guest_code(struct vmx_pages *vmx) +void l1_vmx_code(struct vmx_pages *vmx) { -#define L2_GUEST_STACK_SIZE 64 unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; void *l2_rip; @@ -65,9 +67,9 @@ void l1_guest_code(struct vmx_pages *vmx) GUEST_ASSERT(load_vmcs(vmx)); if (vmx->eptp_gpa) - l2_rip = l2_guest_code_ept_enabled; + l2_rip = l2_guest_code_tdp_enabled; else - l2_rip = l2_guest_code_ept_disabled; + l2_rip = l2_guest_code_tdp_disabled; prepare_vmcs(vmx, l2_rip, &l2_guest_stack[L2_GUEST_STACK_SIZE]); @@ -78,10 +80,38 @@ void l1_guest_code(struct vmx_pages *vmx) GUEST_DONE(); } -static void test_vmx_dirty_log(bool enable_ept) +static void l1_svm_code(struct svm_test_data *svm) +{ + unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; + void *l2_rip; + + if (svm->ncr3_gpa) + l2_rip = l2_guest_code_tdp_enabled; + else + l2_rip = l2_guest_code_tdp_disabled; + + generic_svm_setup(svm, l2_rip, &l2_guest_stack[L2_GUEST_STACK_SIZE]); + + GUEST_SYNC(false); + run_guest(svm->vmcb, svm->vmcb_gpa); + GUEST_SYNC(false); + GUEST_ASSERT(svm->vmcb->control.exit_code == SVM_EXIT_VMMCALL); + GUEST_DONE(); +} + +static void l1_guest_code(void *data) +{ + if (this_cpu_has(X86_FEATURE_VMX)) + l1_vmx_code(data); + else + l1_svm_code(data); +} + +static void test_dirty_log(bool enable_tdp) { - vm_vaddr_t vmx_pages_gva = 0; - struct vmx_pages *vmx; + struct svm_test_data *svm = NULL; + struct vmx_pages *vmx = NULL; + vm_vaddr_t nested_gva = 0; unsigned long *bmap; uint64_t *host_test_mem; @@ -90,12 +120,16 @@ static void test_vmx_dirty_log(bool enable_ept) struct ucall uc; bool done = false; - pr_info("Nested EPT: %s\n", enable_ept ? "enabled" : "disabled"); + pr_info("Nested TDP: %s\n", enable_tdp ? "enabled" : "disabled"); /* Create VM */ vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); - vmx = vcpu_alloc_vmx(vm, &vmx_pages_gva); - vcpu_args_set(vcpu, 1, vmx_pages_gva); + if (kvm_cpu_has(X86_FEATURE_VMX)) + vmx = vcpu_alloc_vmx(vm, &nested_gva); + else + svm = vcpu_alloc_svm(vm, &nested_gva); + + vcpu_args_set(vcpu, 1, nested_gva); /* Add an extra memory slot for testing dirty logging */ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, @@ -114,17 +148,25 @@ static void test_vmx_dirty_log(bool enable_ept) * ... pages in the L2 GPA range [0xc0001000, 0xc0003000) will map to * 0xc0000000. * - * Note that prepare_eptp should be called only L1's GPA map is done, - * meaning after the last call to virt_map. + * Note that prepare_eptp()/prepare_npt() should be called only when + * L1's GPA map is done, meaning after the last call to virt_map. * - * When EPT is disabled, the L2 guest code will still access the same L1 - * GPAs as the EPT enabled case. + * When TDP is disabled, the L2 guest code will still access the same L1 + * GPAs as the TDP enabled case. */ - if (enable_ept) { - prepare_eptp(vmx, vm, 0); - nested_map_memslot(vmx->eptp_hva, vm, 0); - nested_map(vmx->eptp_hva, vm, NESTED_TEST_MEM1, GUEST_TEST_MEM, 4096); - nested_map(vmx->eptp_hva, vm, NESTED_TEST_MEM2, GUEST_TEST_MEM, 4096); + if (enable_tdp) { + void *root_hva; + + if (kvm_cpu_has(X86_FEATURE_VMX)) { + prepare_eptp(vmx, vm, 0); + root_hva = vmx->eptp_hva; + } else { + prepare_npt(svm, vm); + root_hva = svm->ncr3_hva; + } + nested_map_memslot(root_hva, vm, 0); + nested_map(root_hva, vm, NESTED_TEST_MEM1, GUEST_TEST_MEM, 4096); + nested_map(root_hva, vm, NESTED_TEST_MEM2, GUEST_TEST_MEM, 4096); } bmap = bitmap_zalloc(TEST_MEM_PAGES); @@ -169,12 +211,12 @@ static void test_vmx_dirty_log(bool enable_ept) int main(int argc, char *argv[]) { - TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX)); + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) || kvm_cpu_has(X86_FEATURE_SVM)); - test_vmx_dirty_log(/*enable_ept=*/false); + test_dirty_log(/*enable_tdp=*/false); - if (kvm_cpu_has_ept()) - test_vmx_dirty_log(/*enable_ept=*/true); + if (kvm_cpu_has_ept() || kvm_cpu_has_npt()) + test_dirty_log(/*enable_tdp=*/true); return 0; } -- 2.51.0.618.g983fd99d29-goog