* [PATCH 01/12] KVM: selftests: Minor improvements to asserts in test_vmx_nested_state()
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-09 21:44 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 02/12] KVM: selftests: Extend vmx_set_nested_state_test to cover SVM Yosry Ahmed
` (11 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
.../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
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH 02/12] KVM: selftests: Extend vmx_set_nested_state_test to cover SVM
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
2025-10-01 14:58 ` [PATCH 01/12] KVM: selftests: Minor improvements to asserts in test_vmx_nested_state() Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-09 22:40 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 03/12] KVM: selftests: Extend vmx_close_while_nested_test " Yosry Ahmed
` (10 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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 <errno.h>
#include <linux/kvm.h>
@@ -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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 02/12] KVM: selftests: Extend vmx_set_nested_state_test to cover SVM
2025-10-01 14:58 ` [PATCH 02/12] KVM: selftests: Extend vmx_set_nested_state_test to cover SVM Yosry Ahmed
@ 2025-10-09 22:40 ` Jim Mattson
2025-10-09 23:13 ` Yosry Ahmed
0 siblings, 1 reply; 33+ messages in thread
From: Jim Mattson @ 2025-10-09 22:40 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel,
Yosry Ahmed
On Wed, Oct 1, 2025 at 8:03 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
>
> From: Yosry Ahmed <yosryahmed@google.com>
>
> 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 <yosry.ahmed@linux.dev>
> ---
> ...
> + /* If EFER.SVME is clear, GIF must be set and guest mode is disallowed */
The GIF constraint is not architected. See
https://lore.kernel.org/kvm/20251009223153.3344555-2-jmattson@google.com/.
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH 02/12] KVM: selftests: Extend vmx_set_nested_state_test to cover SVM
2025-10-09 22:40 ` Jim Mattson
@ 2025-10-09 23:13 ` Yosry Ahmed
0 siblings, 0 replies; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-09 23:13 UTC (permalink / raw)
To: Jim Mattson; +Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel
On Thu, Oct 09, 2025 at 03:40:44PM -0700, Jim Mattson wrote:
> On Wed, Oct 1, 2025 at 8:03 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
> >
> > From: Yosry Ahmed <yosryahmed@google.com>
> >
> > 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 <yosry.ahmed@linux.dev>
> > ---
> > ...
> > + /* If EFER.SVME is clear, GIF must be set and guest mode is disallowed */
>
> The GIF constraint is not architected. See
> https://lore.kernel.org/kvm/20251009223153.3344555-2-jmattson@google.com/.
Yes, I didn't connect our discussion about clearing GIF when EFER.SVME
is cleared to the nested state code. I will remove the GIF assertion in
the next version.
Thanks!
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 03/12] KVM: selftests: Extend vmx_close_while_nested_test to cover SVM
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
2025-10-01 14:58 ` [PATCH 01/12] KVM: selftests: Minor improvements to asserts in test_vmx_nested_state() Yosry Ahmed
2025-10-01 14:58 ` [PATCH 02/12] KVM: selftests: Extend vmx_set_nested_state_test to cover SVM Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-09 22:44 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 04/12] KVM: selftests: Extend vmx_nested_tsc_scaling_test " Yosry Ahmed
` (9 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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 <string.h>
#include <sys/ioctl.h>
@@ -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
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH 04/12] KVM: selftests: Extend vmx_nested_tsc_scaling_test to cover SVM
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (2 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 03/12] KVM: selftests: Extend vmx_close_while_nested_test " Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-09 22:51 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 05/12] KVM: selftests: Remove invalid CR3 test from vmx_tsc_adjust_test Yosry Ahmed
` (8 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 04/12] KVM: selftests: Extend vmx_nested_tsc_scaling_test to cover SVM
2025-10-01 14:58 ` [PATCH 04/12] KVM: selftests: Extend vmx_nested_tsc_scaling_test " Yosry Ahmed
@ 2025-10-09 22:51 ` Jim Mattson
2025-10-09 23:19 ` Yosry Ahmed
0 siblings, 1 reply; 33+ messages in thread
From: Jim Mattson @ 2025-10-09 22:51 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel,
Yosry Ahmed
On Wed, Oct 1, 2025 at 8:03 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
>
> From: Yosry Ahmed <yosryahmed@google.com>
>
> 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 <yosry.ahmed@linux.dev>
> ---
> ...
> + /* enable TSC scaling for L2 */
> + wrmsr(MSR_AMD64_TSC_RATIO, (L2_SCALE_FACTOR << 32) | 1);
Why set bit 0 here?
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH 04/12] KVM: selftests: Extend vmx_nested_tsc_scaling_test to cover SVM
2025-10-09 22:51 ` Jim Mattson
@ 2025-10-09 23:19 ` Yosry Ahmed
0 siblings, 0 replies; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-09 23:19 UTC (permalink / raw)
To: Jim Mattson; +Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel
On Thu, Oct 09, 2025 at 03:51:13PM -0700, Jim Mattson wrote:
> On Wed, Oct 1, 2025 at 8:03 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
> >
> > From: Yosry Ahmed <yosryahmed@google.com>
> >
> > 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 <yosry.ahmed@linux.dev>
> > ---
> > ...
> > + /* enable TSC scaling for L2 */
> > + wrmsr(MSR_AMD64_TSC_RATIO, (L2_SCALE_FACTOR << 32) | 1);
>
> Why set bit 0 here?
Brainfart. Will fix in the next version, thanks.
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 05/12] KVM: selftests: Remove invalid CR3 test from vmx_tsc_adjust_test
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (3 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 04/12] KVM: selftests: Extend vmx_nested_tsc_scaling_test " Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-09 22:55 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 06/12] KVM: selftests: Extend vmx_tsc_adjust_test to cover SVM Yosry Ahmed
` (7 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 05/12] KVM: selftests: Remove invalid CR3 test from vmx_tsc_adjust_test
2025-10-01 14:58 ` [PATCH 05/12] KVM: selftests: Remove invalid CR3 test from vmx_tsc_adjust_test Yosry Ahmed
@ 2025-10-09 22:55 ` Jim Mattson
2025-10-09 23:24 ` Yosry Ahmed
0 siblings, 1 reply; 33+ messages in thread
From: Jim Mattson @ 2025-10-09 22:55 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel,
Yosry Ahmed
On Wed, Oct 1, 2025 at 8:02 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
>
> From: Yosry Ahmed <yosryahmed@google.com>
>
> 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 <yosry.ahmed@linux.dev>
Is there value in moving the invalid CR3 test elsewhere, rather than
just eliminating it?
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH 05/12] KVM: selftests: Remove invalid CR3 test from vmx_tsc_adjust_test
2025-10-09 22:55 ` Jim Mattson
@ 2025-10-09 23:24 ` Yosry Ahmed
0 siblings, 0 replies; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-09 23:24 UTC (permalink / raw)
To: Jim Mattson; +Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel
On Thu, Oct 09, 2025 at 03:55:32PM -0700, Jim Mattson wrote:
> On Wed, Oct 1, 2025 at 8:02 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
> >
> > From: Yosry Ahmed <yosryahmed@google.com>
> >
> > 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 <yosry.ahmed@linux.dev>
>
> Is there value in moving the invalid CR3 test elsewhere, rather than
> just eliminating it?
It's a very basic test, but yeah we can keep it. I was doing the lazy
thing by just dropping it to see if anyone will comment.
I will find a new home for it (or keep it here out of more laziness).
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 06/12] KVM: selftests: Extend vmx_tsc_adjust_test to cover SVM
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (4 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 05/12] KVM: selftests: Remove invalid CR3 test from vmx_tsc_adjust_test Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-09 23:27 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 07/12] KVM: selftests: Pass the root HVA directly to nested mapping functions Yosry Ahmed
` (6 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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 <string.h>
#include <sys/ioctl.h>
@@ -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
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH 07/12] KVM: selftests: Pass the root HVA directly to nested mapping functions
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (5 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 06/12] KVM: selftests: Extend vmx_tsc_adjust_test to cover SVM Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-09 23:30 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries Yosry Ahmed
` (5 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (6 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 07/12] KVM: selftests: Pass the root HVA directly to nested mapping functions Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-13 18:34 ` Jim Mattson
2025-10-13 21:41 ` Sean Christopherson
2025-10-01 14:58 ` [PATCH 09/12] KVM: selftests: Move all PTE accesses into nested_create_pte() Yosry Ahmed
` (4 subsequent siblings)
12 siblings, 2 replies; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
2025-10-01 14:58 ` [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries Yosry Ahmed
@ 2025-10-13 18:34 ` Jim Mattson
2025-10-13 21:41 ` Sean Christopherson
1 sibling, 0 replies; 33+ messages in thread
From: Jim Mattson @ 2025-10-13 18:34 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel,
Yosry Ahmed
On Wed, Oct 1, 2025 at 8:05 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
>
> From: Yosry Ahmed <yosryahmed@google.com>
>
> 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
Nit: accurate
> hugepage. The distincion will be useful in coming changes that will pass
Nit: distinction
> 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 <yosry.ahmed@linux.dev>
Reviewed-by: Jim Mattson <jmattson@google.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
2025-10-01 14:58 ` [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries Yosry Ahmed
2025-10-13 18:34 ` Jim Mattson
@ 2025-10-13 21:41 ` Sean Christopherson
2025-10-13 22:25 ` Yosry Ahmed
1 sibling, 1 reply; 33+ messages in thread
From: Sean Christopherson @ 2025-10-13 21:41 UTC (permalink / raw)
To: Yosry Ahmed; +Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed
On Wed, Oct 01, 2025, Yosry Ahmed wrote:
> From: Yosry Ahmed <yosryahmed@google.com>
>
> 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.
Yes, it's more accurate, but also less precise. I'm guessing the assert message
and comment talked about hugepages because that's the type of mappings that
caused problems at the time.
Ah, actually, I bet the code was copy+pasted from virt_create_upper_pte(), in
which case the assumptions about wanting to create a hupage are both accurate
and precise.
> The distincion will be useful in coming changes that will pass
> the value around and 'leaf' is clearer than hugepage or page_size.
What value?
> Leave the EPT bit named page_size to keep it conforming to the manual.
>
> Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
> ---
> 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",
This change is flat out wrong. The existing PRESENT PTE _might_ be a 4KiB leaf
entry, but it might also be an existing non-leaf page table.
Instead of hacking on the nested code, can we instead tweak __virt_pg_map() to
work with nested TDP? At a glance, it's already quite close, e.g. "just" needs
to be taught about EPT RWX bits and allow the call to pass in the root pointer.
> current_level, nested_paddr);
> }
> }
> --
> 2.51.0.618.g983fd99d29-goog
>
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
2025-10-13 21:41 ` Sean Christopherson
@ 2025-10-13 22:25 ` Yosry Ahmed
2025-10-13 22:58 ` Sean Christopherson
0 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-13 22:25 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel
On Mon, Oct 13, 2025 at 02:41:56PM -0700, Sean Christopherson wrote:
> On Wed, Oct 01, 2025, Yosry Ahmed wrote:
> > From: Yosry Ahmed <yosryahmed@google.com>
> >
> > 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.
>
> Yes, it's more accurate, but also less precise. I'm guessing the assert message
> and comment talked about hugepages because that's the type of mappings that
> caused problems at the time.
Given that it refers to PG_LEVEL_4K entries too, I wouldn't call it less
precise. All callers actually create 4K mappings so it is never actually
a hugepage in the current context :D
>
> Ah, actually, I bet the code was copy+pasted from virt_create_upper_pte(), in
> which case the assumptions about wanting to create a hupage are both accurate
> and precise.
>
> > The distincion will be useful in coming changes that will pass
> > the value around and 'leaf' is clearer than hugepage or page_size.
>
> What value?
'leaf'. The following changes will pass 'leaf' in as a boolean instead
of checking 'current_level == target_level' here. So passing in
'hugepage' would be inaccurate, and 'page_size' is not as clear (but
still works).
>
> > Leave the EPT bit named page_size to keep it conforming to the manual.
> >
> > Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
> > ---
> > 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",
>
> This change is flat out wrong. The existing PRESENT PTE _might_ be a 4KiB leaf
> entry, but it might also be an existing non-leaf page table.
Hmm if pte->page_size is true then it has to be a leaf page table,
right?
If it's an existing non-leaf page table we shouldn't fail, the assertion
here is when we try to override a leaf page table IIUC.
>
> Instead of hacking on the nested code, can we instead tweak __virt_pg_map() to
> work with nested TDP? At a glance, it's already quite close, e.g. "just" needs
> to be taught about EPT RWX bits and allow the call to pass in the root pointer.
That would be ideal, I'll take a look. In case I don't have time for
that unification, can this be a follow-up change?
>
> > current_level, nested_paddr);
> > }
> > }
> > --
> > 2.51.0.618.g983fd99d29-goog
> >
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
2025-10-13 22:25 ` Yosry Ahmed
@ 2025-10-13 22:58 ` Sean Christopherson
2025-10-13 23:13 ` Yosry Ahmed
0 siblings, 1 reply; 33+ messages in thread
From: Sean Christopherson @ 2025-10-13 22:58 UTC (permalink / raw)
To: Yosry Ahmed; +Cc: Paolo Bonzini, kvm, linux-kernel
On Mon, Oct 13, 2025, Yosry Ahmed wrote:
> On Mon, Oct 13, 2025 at 02:41:56PM -0700, Sean Christopherson wrote:
> > On Wed, Oct 01, 2025, Yosry Ahmed wrote:
> > > From: Yosry Ahmed <yosryahmed@google.com>
> > >
> > > 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.
> >
> > Yes, it's more accurate, but also less precise. I'm guessing the assert message
> > and comment talked about hugepages because that's the type of mappings that
> > caused problems at the time.
>
> Given that it refers to PG_LEVEL_4K entries too, I wouldn't call it less
> precise. All callers actually create 4K mappings so it is never actually
> a hugepage in the current context :D
nested_identity_map_1g()?
> > Ah, actually, I bet the code was copy+pasted from virt_create_upper_pte(), in
> > which case the assumptions about wanting to create a hupage are both accurate
> > and precise.
> >
> > > The distincion will be useful in coming changes that will pass
> > > the value around and 'leaf' is clearer than hugepage or page_size.
> >
> > What value?
>
> 'leaf'. The following changes will pass 'leaf' in as a boolean instead
> of checking 'current_level == target_level' here. So passing in
> 'hugepage' would be inaccurate, and 'page_size' is not as clear (but
> still works).
>
> >
> > > Leave the EPT bit named page_size to keep it conforming to the manual.
> > >
> > > Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
> > > ---
> > > 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",
> >
> > This change is flat out wrong. The existing PRESENT PTE _might_ be a 4KiB leaf
> > entry, but it might also be an existing non-leaf page table.
>
> Hmm if pte->page_size is true then it has to be a leaf page table,
> right?
No, because bit 7 is ignored by hardware for 4KiB entries. I.e. it can be 0 or
1 depending on the whims of software. Ugh, this code uses bit 7 to flag leaf
entries. That's lovely.
> If it's an existing non-leaf page table we shouldn't fail,
Ah, right, current_level can never be less than target_level because the first
assert will fail on iteration-1.
> the assertion here is when we try to override a leaf page table IIUC.
>
> > Instead of hacking on the nested code, can we instead tweak __virt_pg_map() to
> > work with nested TDP? At a glance, it's already quite close, e.g. "just" needs
> > to be taught about EPT RWX bits and allow the call to pass in the root pointer.
>
> That would be ideal, I'll take a look. In case I don't have time for
> that unification, can this be a follow-up change?
Part of me wants to be nice and say "yes", but most of me wants to say "no".
Struct overlays for PTEs suck. At best, they generate poor code and obfuscate
simple logic (e.g. vm->page_size vs pte->page_size is a confusion that simply
should not be possible). At worst, they lead to hard-to-debug issues like the
one that led to commit f18b4aebe107 ("kvm: selftests: do not use bitfields larger
than 32-bits for PTEs").
eptPageTableEntry obviously isn't your fault, but nptPageTableEntry is. :-D
And I suspect the hardest part of unificiation will be adding the globals to
deal with variable bit positions that are currently being handled by the struct
overlays.
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
2025-10-13 22:58 ` Sean Christopherson
@ 2025-10-13 23:13 ` Yosry Ahmed
2025-10-15 18:20 ` Sean Christopherson
0 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-13 23:13 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel
On Mon, Oct 13, 2025 at 03:58:30PM -0700, Sean Christopherson wrote:
> On Mon, Oct 13, 2025, Yosry Ahmed wrote:
> > On Mon, Oct 13, 2025 at 02:41:56PM -0700, Sean Christopherson wrote:
> > > On Wed, Oct 01, 2025, Yosry Ahmed wrote:
> > > > From: Yosry Ahmed <yosryahmed@google.com>
> > > >
> > > > 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.
> > >
> > > Yes, it's more accurate, but also less precise. I'm guessing the assert message
> > > and comment talked about hugepages because that's the type of mappings that
> > > caused problems at the time.
> >
> > Given that it refers to PG_LEVEL_4K entries too, I wouldn't call it less
> > precise. All callers actually create 4K mappings so it is never actually
> > a hugepage in the current context :D
>
> nested_identity_map_1g()?
Yeah I missed this one.
>
> > > Ah, actually, I bet the code was copy+pasted from virt_create_upper_pte(), in
> > > which case the assumptions about wanting to create a hupage are both accurate
> > > and precise.
> > >
> > > > The distincion will be useful in coming changes that will pass
> > > > the value around and 'leaf' is clearer than hugepage or page_size.
> > >
> > > What value?
> >
> > 'leaf'. The following changes will pass 'leaf' in as a boolean instead
> > of checking 'current_level == target_level' here. So passing in
> > 'hugepage' would be inaccurate, and 'page_size' is not as clear (but
> > still works).
> >
> > >
> > > > Leave the EPT bit named page_size to keep it conforming to the manual.
> > > >
> > > > Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
> > > > ---
> > > > 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",
> > >
> > > This change is flat out wrong. The existing PRESENT PTE _might_ be a 4KiB leaf
> > > entry, but it might also be an existing non-leaf page table.
> >
> > Hmm if pte->page_size is true then it has to be a leaf page table,
> > right?
>
> No, because bit 7 is ignored by hardware for 4KiB entries. I.e. it can be 0 or
> 1 depending on the whims of software. Ugh, this code uses bit 7 to flag leaf
> entries. That's lovely.
That's not my fault :P
>
> > If it's an existing non-leaf page table we shouldn't fail,
>
> Ah, right, current_level can never be less than target_level because the first
> assert will fail on iteration-1.
>
> > the assertion here is when we try to override a leaf page table IIUC.
> >
> > > Instead of hacking on the nested code, can we instead tweak __virt_pg_map() to
> > > work with nested TDP? At a glance, it's already quite close, e.g. "just" needs
> > > to be taught about EPT RWX bits and allow the call to pass in the root pointer.
> >
> > That would be ideal, I'll take a look. In case I don't have time for
> > that unification, can this be a follow-up change?
>
> Part of me wants to be nice and say "yes", but most of me wants to say "no".
So.. which part won?
>
> Struct overlays for PTEs suck. At best, they generate poor code and obfuscate
> simple logic (e.g. vm->page_size vs pte->page_size is a confusion that simply
> should not be possible). At worst, they lead to hard-to-debug issues like the
> one that led to commit f18b4aebe107 ("kvm: selftests: do not use bitfields larger
> than 32-bits for PTEs").
>
> eptPageTableEntry obviously isn't your fault, but nptPageTableEntry is. :-D
> And I suspect the hardest part of unificiation will be adding the globals to
> deal with variable bit positions that are currently being handled by the struct
> overlays.
I have no problem getting rid of eptPageTableEntry and using bitmasks
and whatnot on a uint64_t PTE (assuming that's what you are asking for
here).
But I think tweaking __virt_pg_map() will involve more than that, or
maybe I just didn't look close enough yet.
^ permalink raw reply [flat|nested] 33+ messages in thread* Re: [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
2025-10-13 23:13 ` Yosry Ahmed
@ 2025-10-15 18:20 ` Sean Christopherson
0 siblings, 0 replies; 33+ messages in thread
From: Sean Christopherson @ 2025-10-15 18:20 UTC (permalink / raw)
To: Yosry Ahmed; +Cc: Paolo Bonzini, kvm, linux-kernel
On Mon, Oct 13, 2025, Yosry Ahmed wrote:
> On Mon, Oct 13, 2025 at 03:58:30PM -0700, Sean Christopherson wrote:
> > Ah, right, current_level can never be less than target_level because the first
> > assert will fail on iteration-1.
> >
> > > the assertion here is when we try to override a leaf page table IIUC.
> > >
> > > > Instead of hacking on the nested code, can we instead tweak __virt_pg_map() to
> > > > work with nested TDP? At a glance, it's already quite close, e.g. "just" needs
> > > > to be taught about EPT RWX bits and allow the call to pass in the root pointer.
> > >
> > > That would be ideal, I'll take a look. In case I don't have time for
> > > that unification, can this be a follow-up change?
> >
> > Part of me wants to be nice and say "yes", but most of me wants to say "no".
>
> So.. which part won?
>
> >
> > Struct overlays for PTEs suck. At best, they generate poor code and obfuscate
> > simple logic (e.g. vm->page_size vs pte->page_size is a confusion that simply
> > should not be possible). At worst, they lead to hard-to-debug issues like the
> > one that led to commit f18b4aebe107 ("kvm: selftests: do not use bitfields larger
> > than 32-bits for PTEs").
> >
> > eptPageTableEntry obviously isn't your fault, but nptPageTableEntry is. :-D
> > And I suspect the hardest part of unificiation will be adding the globals to
> > deal with variable bit positions that are currently being handled by the struct
> > overlays.
>
> I have no problem getting rid of eptPageTableEntry and using bitmasks
> and whatnot on a uint64_t PTE (assuming that's what you are asking for
> here).
>
> But I think tweaking __virt_pg_map() will involve more than that, or
> maybe I just didn't look close enough yet.
For posterity, Yosry and I chatted off-list. The plan is to try and convert
__virt_pg_map() to work with variable "mmu", using an approach similar to KVM,
where the bit positions of variable flags, e.g. NX vs. X, are stored in an mmu
structure. If for whatever reason converting __virt_pg_map() is meaningfully
harder than getting nEPT and nNPT to play nice, then we'll revisit all of this.
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 09/12] KVM: selftests: Move all PTE accesses into nested_create_pte()
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (7 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 08/12] KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-13 18:41 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 10/12] KVM: selftests: Move EPT-specific init outside nested_create_pte() Yosry Ahmed
` (3 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 09/12] KVM: selftests: Move all PTE accesses into nested_create_pte()
2025-10-01 14:58 ` [PATCH 09/12] KVM: selftests: Move all PTE accesses into nested_create_pte() Yosry Ahmed
@ 2025-10-13 18:41 ` Jim Mattson
0 siblings, 0 replies; 33+ messages in thread
From: Jim Mattson @ 2025-10-13 18:41 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel,
Yosry Ahmed
On Wed, Oct 1, 2025 at 8:05 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
>
> From: Yosry Ahmed <yosryahmed@google.com>
>
> 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 <yosry.ahmed@linux.dev>
Reviewed-by: Jim Mattson <jmattson@google.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 10/12] KVM: selftests: Move EPT-specific init outside nested_create_pte()
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (8 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 09/12] KVM: selftests: Move all PTE accesses into nested_create_pte() Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-13 18:52 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 11/12] KVM: selftests: Refactor generic nested mapping outside VMX code Yosry Ahmed
` (2 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
Refactor the EPT specific initialization into nested_ept_create_pte(),
in preparation for making nested_create_pte() NPT-friendly.
Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 10/12] KVM: selftests: Move EPT-specific init outside nested_create_pte()
2025-10-01 14:58 ` [PATCH 10/12] KVM: selftests: Move EPT-specific init outside nested_create_pte() Yosry Ahmed
@ 2025-10-13 18:52 ` Jim Mattson
0 siblings, 0 replies; 33+ messages in thread
From: Jim Mattson @ 2025-10-13 18:52 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel,
Yosry Ahmed
On Wed, Oct 1, 2025 at 8:06 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
>
> From: Yosry Ahmed <yosryahmed@google.com>
>
> Refactor the EPT specific initialization into nested_ept_create_pte(),
> in preparation for making nested_create_pte() NPT-friendly.
>
> Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
> ---
> tools/testing/selftests/kvm/lib/x86/vmx.c | 71 ++++++++++++++---------
> 1 file changed, 43 insertions(+), 28 deletions(-)
>
> ...
> +
> + /*
> + * 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;
Not your change, but it seems strange to set the 'accessed' bit only
at the leaf. The CPU will set the 'accessed' bits from the PGD down as
it does the walk. So, to only have an accessed bit set on the leaf
requires the existence of some software agent to clear the higher
levels.
Reviewed-by: Jim Mattson <jmattson@google.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 11/12] KVM: selftests: Refactor generic nested mapping outside VMX code
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (9 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 10/12] KVM: selftests: Move EPT-specific init outside nested_create_pte() Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-13 19:04 ` Jim Mattson
2025-10-01 14:58 ` [PATCH 12/12] KVM: selftests: Extend vmx_dirty_log_test to cover SVM Yosry Ahmed
2025-10-01 17:37 ` [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
12 siblings, 1 reply; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 11/12] KVM: selftests: Refactor generic nested mapping outside VMX code
2025-10-01 14:58 ` [PATCH 11/12] KVM: selftests: Refactor generic nested mapping outside VMX code Yosry Ahmed
@ 2025-10-13 19:04 ` Jim Mattson
0 siblings, 0 replies; 33+ messages in thread
From: Jim Mattson @ 2025-10-13 19:04 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel,
Yosry Ahmed
On Wed, Oct 1, 2025 at 8:06 AM Yosry Ahmed <yosry.ahmed@linux.dev> wrote:
>
> From: Yosry Ahmed <yosryahmed@google.com>
>
> 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 <yosry.ahmed@linux.dev>
Reviewed-by: Jim Mattson <jmattson@google.com>
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH 12/12] KVM: selftests: Extend vmx_dirty_log_test to cover SVM
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (10 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 11/12] KVM: selftests: Refactor generic nested mapping outside VMX code Yosry Ahmed
@ 2025-10-01 14:58 ` Yosry Ahmed
2025-10-01 17:37 ` [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
12 siblings, 0 replies; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 14:58 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Yosry Ahmed, Yosry Ahmed
From: Yosry Ahmed <yosryahmed@google.com>
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 <yosry.ahmed@linux.dev>
---
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
^ permalink raw reply related [flat|nested] 33+ messages in thread* Re: [PATCH 00/12] Extend test coverage for nested SVM
2025-10-01 14:58 [PATCH 00/12] Extend test coverage for nested SVM Yosry Ahmed
` (11 preceding siblings ...)
2025-10-01 14:58 ` [PATCH 12/12] KVM: selftests: Extend vmx_dirty_log_test to cover SVM Yosry Ahmed
@ 2025-10-01 17:37 ` Yosry Ahmed
12 siblings, 0 replies; 33+ messages in thread
From: Yosry Ahmed @ 2025-10-01 17:37 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel
On Wed, Oct 01, 2025 at 02:58:04PM +0000, Yosry Ahmed wrote:
> There are multiple selftests exercising nested VMX that are not specific
> to VMX (at least not anymore). Extend their coverage to nested SVM.
>
> Yosry Ahmed (12):
> KVM: selftests: Minor improvements to asserts in
> test_vmx_nested_state()
> KVM: selftests: Extend vmx_set_nested_state_test to cover SVM
> KVM: selftests: Extend vmx_close_while_nested_test to cover SVM
> KVM: selftests: Extend vmx_nested_tsc_scaling_test to cover SVM
> KVM: selftests: Remove invalid CR3 test from vmx_tsc_adjust_test
> KVM: selftests: Extend vmx_tsc_adjust_test to cover SVM
> KVM: selftests: Pass the root HVA directly to nested mapping functions
> KVM: selftests: Use 'leaf' instead of hugepage to describe EPT entries
> KVM: selftests: Move all PTE accesses into nested_create_pte()
> KVM: selftests: Move EPT-specific init outside nested_create_pte()
> KVM: selftests: Refactor generic nested mapping outside VMX code
> KVM: selftests: Extend vmx_dirty_log_test to cover SVM
Ugh, wrong From email on all the patches due to some unorthodox
cherry-picking :)
>
> tools/testing/selftests/kvm/Makefile.kvm | 11 +-
> .../selftests/kvm/include/x86/nested_map.h | 20 ++
> .../selftests/kvm/include/x86/svm_util.h | 13 ++
> tools/testing/selftests/kvm/include/x86/vmx.h | 13 +-
> .../testing/selftests/kvm/lib/x86/memstress.c | 5 +-
> .../selftests/kvm/lib/x86/nested_map.c | 150 +++++++++++++++
> tools/testing/selftests/kvm/lib/x86/svm.c | 70 +++++++
> tools/testing/selftests/kvm/lib/x86/vmx.c | 180 +++---------------
> ...ested_test.c => close_while_nested_test.c} | 42 +++-
> ...rty_log_test.c => nested_dirty_log_test.c} | 95 ++++++---
> ...adjust_test.c => nested_tsc_adjust_test.c} | 79 ++++----
> ...aling_test.c => nested_tsc_scaling_test.c} | 48 ++++-
> ...d_state_test.c => set_nested_state_test.c} | 132 +++++++++++--
> 13 files changed, 609 insertions(+), 249 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
> rename tools/testing/selftests/kvm/x86/{vmx_close_while_nested_test.c => close_while_nested_test.c} (64%)
> rename tools/testing/selftests/kvm/x86/{vmx_dirty_log_test.c => nested_dirty_log_test.c} (62%)
> rename tools/testing/selftests/kvm/x86/{vmx_tsc_adjust_test.c => nested_tsc_adjust_test.c} (61%)
> rename tools/testing/selftests/kvm/x86/{vmx_nested_tsc_scaling_test.c => nested_tsc_scaling_test.c} (83%)
> rename tools/testing/selftests/kvm/x86/{vmx_set_nested_state_test.c => set_nested_state_test.c} (67%)
>
> --
> 2.51.0.618.g983fd99d29-goog
>
^ permalink raw reply [flat|nested] 33+ messages in thread