]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
KVM: selftests: Use a TDP MMU to share EPT page tables between vCPUs
authorYosry Ahmed <yosry.ahmed@linux.dev>
Tue, 30 Dec 2025 23:01:39 +0000 (15:01 -0800)
committerSean Christopherson <seanjc@google.com>
Thu, 8 Jan 2026 20:02:10 +0000 (12:02 -0800)
prepare_eptp() currently allocates new EPTs for each vCPU.  memstress has
its own hack to share the EPTs between vCPUs.  Currently, there is no
reason to have separate EPTs for each vCPU, and the complexity is
significant.  The only reason it doesn't matter now is because memstress
is the only user with multiple vCPUs.

Add vm_enable_ept() to allocate EPT page tables for an entire VM, and use
it everywhere to replace prepare_eptp().  Drop 'eptp' and 'eptp_hva' from
'struct vmx_pages' as they serve no purpose (e.g. the EPTP can be built
from the PGD), but keep 'eptp_gpa' so that the MMU structure doesn't need
to be passed in along with vmx_pages.  Dynamically allocate the TDP MMU
structure to avoid a cyclical dependency between kvm_util_arch.h and
kvm_util.h.

Remove the workaround in memstress to copy the EPT root between vCPUs
since that's now the default behavior.

Name the MMU tdp_mmu instead of e.g. nested_mmu or nested.mmu to avoid
recreating the same mess that KVM has with respect to "nested" MMUs, e.g.
does nested refer to the stage-2 page tables created by L1, or the stage-1
page tables created by L2?

Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Link: https://patch.msgid.link/20251230230150.4150236-11-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
tools/testing/selftests/kvm/include/x86/kvm_util_arch.h
tools/testing/selftests/kvm/include/x86/processor.h
tools/testing/selftests/kvm/include/x86/vmx.h
tools/testing/selftests/kvm/lib/x86/memstress.c
tools/testing/selftests/kvm/lib/x86/processor.c
tools/testing/selftests/kvm/lib/x86/vmx.c
tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c

index bad381d63b6ab551eb2c4d565766a1856d36b672..05a1fc1780f25996c60cc42f99dd04a93b6ebe5b 100644 (file)
@@ -26,6 +26,8 @@ struct kvm_mmu_arch {
        struct pte_masks pte_masks;
 };
 
+struct kvm_mmu;
+
 struct kvm_vm_arch {
        vm_vaddr_t gdt;
        vm_vaddr_t tss;
@@ -35,6 +37,8 @@ struct kvm_vm_arch {
        uint64_t s_bit;
        int sev_fd;
        bool is_pt_protected;
+
+       struct kvm_mmu *tdp_mmu;
 };
 
 static inline bool __vm_arch_has_protected_memory(struct kvm_vm_arch *arch)
index 55dac84cd4a7a5f020c991bd4535a9198de35b2d..0164ef090787c1eff0c7a1197e262f643cf2aba0 100644 (file)
@@ -1459,6 +1459,9 @@ enum pg_level {
 #define is_huge_pte(mmu, pte)          (!!(*(pte) & PTE_HUGE_MASK(mmu)))
 #define is_nx_pte(mmu, pte)            (!!(*(pte) & PTE_NX_MASK(mmu)))
 
+void tdp_mmu_init(struct kvm_vm *vm, int pgtable_levels,
+                 struct pte_masks *pte_masks);
+
 void __virt_pg_map(struct kvm_vm *vm, struct kvm_mmu *mmu, uint64_t vaddr,
                   uint64_t paddr,  int level);
 void virt_map_level(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,
index 04b8231d032a02689bca19f45d06cc20b01b9b38..1fd83c23529ac5df8d7fec0d707c273cb44db23d 100644 (file)
@@ -520,13 +520,11 @@ struct vmx_pages {
        uint64_t vmwrite_gpa;
        void *vmwrite;
 
-       void *eptp_hva;
-       uint64_t eptp_gpa;
-       void *eptp;
-
        void *apic_access_hva;
        uint64_t apic_access_gpa;
        void *apic_access;
+
+       uint64_t eptp_gpa;
 };
 
 union vmx_basic {
@@ -568,7 +566,7 @@ void tdp_identity_map_default_memslots(struct vmx_pages *vmx,
 void tdp_identity_map_1g(struct vmx_pages *vmx, 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);
+void vm_enable_ept(struct kvm_vm *vm);
 void prepare_virtualize_apic_accesses(struct vmx_pages *vmx, struct kvm_vm *vm);
 
 #endif /* SELFTEST_KVM_VMX_H */
index 1928b00bde5174e741480717bd8863c2e77676a2..00f7f11e5f0eb882be1e1cf5dca6c47f2669fe10 100644 (file)
@@ -59,12 +59,10 @@ uint64_t memstress_nested_pages(int nr_vcpus)
        return 513 + 10 * nr_vcpus;
 }
 
-void memstress_setup_ept(struct vmx_pages *vmx, struct kvm_vm *vm)
+static void memstress_setup_ept_mappings(struct vmx_pages *vmx, struct kvm_vm *vm)
 {
        uint64_t start, end;
 
-       prepare_eptp(vmx, vm);
-
        /*
         * Identity map the first 4G and the test region with 1G pages so that
         * KVM can shadow the EPT12 with the maximum huge page size supported
@@ -79,7 +77,7 @@ void memstress_setup_ept(struct vmx_pages *vmx, struct kvm_vm *vm)
 
 void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vcpus[])
 {
-       struct vmx_pages *vmx, *vmx0 = NULL;
+       struct vmx_pages *vmx;
        struct kvm_regs regs;
        vm_vaddr_t vmx_gva;
        int vcpu_id;
@@ -87,18 +85,13 @@ void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vc
        TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
        TEST_REQUIRE(kvm_cpu_has_ept());
 
+       vm_enable_ept(vm);
        for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++) {
                vmx = vcpu_alloc_vmx(vm, &vmx_gva);
 
-               if (vcpu_id == 0) {
-                       memstress_setup_ept(vmx, vm);
-                       vmx0 = vmx;
-               } else {
-                       /* Share the same EPT table across all vCPUs. */
-                       vmx->eptp = vmx0->eptp;
-                       vmx->eptp_hva = vmx0->eptp_hva;
-                       vmx->eptp_gpa = vmx0->eptp_gpa;
-               }
+               /* The EPTs are shared across vCPUs, setup the mappings once */
+               if (vcpu_id == 0)
+                       memstress_setup_ept_mappings(vmx, vm);
 
                /*
                 * Override the vCPU to run memstress_l1_guest_code() which will
index 3800f4ff6770bcac3f42f7ea7b2c6265293cdd55..8a9298a72897672e1688a5ca9dd65549a04bb269 100644 (file)
@@ -187,6 +187,15 @@ void virt_arch_pgd_alloc(struct kvm_vm *vm)
        virt_mmu_init(vm, &vm->mmu, &pte_masks);
 }
 
+void tdp_mmu_init(struct kvm_vm *vm, int pgtable_levels,
+                 struct pte_masks *pte_masks)
+{
+       TEST_ASSERT(!vm->arch.tdp_mmu, "TDP MMU already initialized");
+
+       vm->arch.tdp_mmu = calloc(1, sizeof(*vm->arch.tdp_mmu));
+       virt_mmu_init(vm, vm->arch.tdp_mmu, pte_masks);
+}
+
 static void *virt_get_pte(struct kvm_vm *vm, struct kvm_mmu *mmu,
                          uint64_t *parent_pte, uint64_t vaddr, int level)
 {
index a3e2eae981da522eb2777b5b9391cd8f59e64c7c..9d4e391fdf2c1f7d45b8975b1dd3976b24daa70a 100644 (file)
@@ -56,6 +56,21 @@ int vcpu_enable_evmcs(struct kvm_vcpu *vcpu)
        return evmcs_ver;
 }
 
+void vm_enable_ept(struct kvm_vm *vm)
+{
+       TEST_ASSERT(kvm_cpu_has_ept(), "KVM doesn't support nested EPT");
+       if (vm->arch.tdp_mmu)
+               return;
+
+       /* TODO: Drop eptPageTableEntry in favor of PTE masks. */
+       struct pte_masks pte_masks = (struct pte_masks) {
+
+       };
+
+       /* TODO: Add support for 5-level EPT. */
+       tdp_mmu_init(vm, 4, &pte_masks);
+}
+
 /* Allocate memory regions for nested VMX tests.
  *
  * Input Args:
@@ -105,6 +120,9 @@ vcpu_alloc_vmx(struct kvm_vm *vm, vm_vaddr_t *p_vmx_gva)
        vmx->vmwrite_gpa = addr_gva2gpa(vm, (uintptr_t)vmx->vmwrite);
        memset(vmx->vmwrite_hva, 0, getpagesize());
 
+       if (vm->arch.tdp_mmu)
+               vmx->eptp_gpa = vm->arch.tdp_mmu->pgd;
+
        *p_vmx_gva = vmx_gva;
        return vmx;
 }
@@ -395,7 +413,8 @@ void __tdp_pg_map(struct vmx_pages *vmx, 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;
+       void *eptp_hva = addr_gpa2hva(vm, vm->arch.tdp_mmu->pgd);
+       struct eptPageTableEntry *pt = eptp_hva, *pte;
        uint16_t index;
 
        TEST_ASSERT(vm->mode == VM_MODE_PXXVYY_4K,
@@ -525,15 +544,6 @@ bool kvm_cpu_has_ept(void)
        return ctrl & SECONDARY_EXEC_ENABLE_EPT;
 }
 
-void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm)
-{
-       TEST_ASSERT(kvm_cpu_has_ept(), "KVM doesn't support nested EPT");
-
-       vmx->eptp = (void *)vm_vaddr_alloc_page(vm);
-       vmx->eptp_hva = addr_gva2hva(vm, (uintptr_t)vmx->eptp);
-       vmx->eptp_gpa = addr_gva2gpa(vm, (uintptr_t)vmx->eptp);
-}
-
 void prepare_virtualize_apic_accesses(struct vmx_pages *vmx, struct kvm_vm *vm)
 {
        vmx->apic_access = (void *)vm_vaddr_alloc_page(vm);
index e7d0c08ba29d267696b9a160075db86f5a2c8c9b..5c8cf8ac42a277d38eb2acba0359d020e617937b 100644 (file)
@@ -93,6 +93,9 @@ static void test_vmx_dirty_log(bool enable_ept)
 
        /* Create VM */
        vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+       if (enable_ept)
+               vm_enable_ept(vm);
+
        vmx = vcpu_alloc_vmx(vm, &vmx_pages_gva);
        vcpu_args_set(vcpu, 1, vmx_pages_gva);
 
@@ -113,14 +116,10 @@ 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.
-        *
         * When EPT is disabled, the L2 guest code will still access the same L1
         * GPAs as the EPT enabled case.
         */
        if (enable_ept) {
-               prepare_eptp(vmx, vm);
                tdp_identity_map_default_memslots(vmx, vm);
                tdp_map(vmx, vm, NESTED_TEST_MEM1, GUEST_TEST_MEM, PAGE_SIZE);
                tdp_map(vmx, vm, NESTED_TEST_MEM2, GUEST_TEST_MEM, PAGE_SIZE);