Skip to content

Commit f00f519

Browse files
yosrym93sean-jc
authored andcommitted
KVM: selftests: Use a TDP MMU to share EPT page tables between vCPUs
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>
1 parent 6dd7075 commit f00f519

7 files changed

Lines changed: 48 additions & 32 deletions

File tree

tools/testing/selftests/kvm/include/x86/kvm_util_arch.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ struct kvm_mmu_arch {
2626
struct pte_masks pte_masks;
2727
};
2828

29+
struct kvm_mmu;
30+
2931
struct kvm_vm_arch {
3032
vm_vaddr_t gdt;
3133
vm_vaddr_t tss;
@@ -35,6 +37,8 @@ struct kvm_vm_arch {
3537
uint64_t s_bit;
3638
int sev_fd;
3739
bool is_pt_protected;
40+
41+
struct kvm_mmu *tdp_mmu;
3842
};
3943

4044
static inline bool __vm_arch_has_protected_memory(struct kvm_vm_arch *arch)

tools/testing/selftests/kvm/include/x86/processor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,9 @@ enum pg_level {
14591459
#define is_huge_pte(mmu, pte) (!!(*(pte) & PTE_HUGE_MASK(mmu)))
14601460
#define is_nx_pte(mmu, pte) (!!(*(pte) & PTE_NX_MASK(mmu)))
14611461

1462+
void tdp_mmu_init(struct kvm_vm *vm, int pgtable_levels,
1463+
struct pte_masks *pte_masks);
1464+
14621465
void __virt_pg_map(struct kvm_vm *vm, struct kvm_mmu *mmu, uint64_t vaddr,
14631466
uint64_t paddr, int level);
14641467
void virt_map_level(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,

tools/testing/selftests/kvm/include/x86/vmx.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -520,13 +520,11 @@ struct vmx_pages {
520520
uint64_t vmwrite_gpa;
521521
void *vmwrite;
522522

523-
void *eptp_hva;
524-
uint64_t eptp_gpa;
525-
void *eptp;
526-
527523
void *apic_access_hva;
528524
uint64_t apic_access_gpa;
529525
void *apic_access;
526+
527+
uint64_t eptp_gpa;
530528
};
531529

532530
union vmx_basic {
@@ -568,7 +566,7 @@ void tdp_identity_map_default_memslots(struct vmx_pages *vmx,
568566
void tdp_identity_map_1g(struct vmx_pages *vmx, struct kvm_vm *vm,
569567
uint64_t addr, uint64_t size);
570568
bool kvm_cpu_has_ept(void);
571-
void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm);
569+
void vm_enable_ept(struct kvm_vm *vm);
572570
void prepare_virtualize_apic_accesses(struct vmx_pages *vmx, struct kvm_vm *vm);
573571

574572
#endif /* SELFTEST_KVM_VMX_H */

tools/testing/selftests/kvm/lib/x86/memstress.c

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,10 @@ uint64_t memstress_nested_pages(int nr_vcpus)
5959
return 513 + 10 * nr_vcpus;
6060
}
6161

62-
void memstress_setup_ept(struct vmx_pages *vmx, struct kvm_vm *vm)
62+
static void memstress_setup_ept_mappings(struct vmx_pages *vmx, struct kvm_vm *vm)
6363
{
6464
uint64_t start, end;
6565

66-
prepare_eptp(vmx, vm);
67-
6866
/*
6967
* Identity map the first 4G and the test region with 1G pages so that
7068
* KVM can shadow the EPT12 with the maximum huge page size supported
@@ -79,26 +77,21 @@ void memstress_setup_ept(struct vmx_pages *vmx, struct kvm_vm *vm)
7977

8078
void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vcpus[])
8179
{
82-
struct vmx_pages *vmx, *vmx0 = NULL;
80+
struct vmx_pages *vmx;
8381
struct kvm_regs regs;
8482
vm_vaddr_t vmx_gva;
8583
int vcpu_id;
8684

8785
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
8886
TEST_REQUIRE(kvm_cpu_has_ept());
8987

88+
vm_enable_ept(vm);
9089
for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++) {
9190
vmx = vcpu_alloc_vmx(vm, &vmx_gva);
9291

93-
if (vcpu_id == 0) {
94-
memstress_setup_ept(vmx, vm);
95-
vmx0 = vmx;
96-
} else {
97-
/* Share the same EPT table across all vCPUs. */
98-
vmx->eptp = vmx0->eptp;
99-
vmx->eptp_hva = vmx0->eptp_hva;
100-
vmx->eptp_gpa = vmx0->eptp_gpa;
101-
}
92+
/* The EPTs are shared across vCPUs, setup the mappings once */
93+
if (vcpu_id == 0)
94+
memstress_setup_ept_mappings(vmx, vm);
10295

10396
/*
10497
* Override the vCPU to run memstress_l1_guest_code() which will

tools/testing/selftests/kvm/lib/x86/processor.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,15 @@ void virt_arch_pgd_alloc(struct kvm_vm *vm)
187187
virt_mmu_init(vm, &vm->mmu, &pte_masks);
188188
}
189189

190+
void tdp_mmu_init(struct kvm_vm *vm, int pgtable_levels,
191+
struct pte_masks *pte_masks)
192+
{
193+
TEST_ASSERT(!vm->arch.tdp_mmu, "TDP MMU already initialized");
194+
195+
vm->arch.tdp_mmu = calloc(1, sizeof(*vm->arch.tdp_mmu));
196+
virt_mmu_init(vm, vm->arch.tdp_mmu, pte_masks);
197+
}
198+
190199
static void *virt_get_pte(struct kvm_vm *vm, struct kvm_mmu *mmu,
191200
uint64_t *parent_pte, uint64_t vaddr, int level)
192201
{

tools/testing/selftests/kvm/lib/x86/vmx.c

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ int vcpu_enable_evmcs(struct kvm_vcpu *vcpu)
5656
return evmcs_ver;
5757
}
5858

59+
void vm_enable_ept(struct kvm_vm *vm)
60+
{
61+
TEST_ASSERT(kvm_cpu_has_ept(), "KVM doesn't support nested EPT");
62+
if (vm->arch.tdp_mmu)
63+
return;
64+
65+
/* TODO: Drop eptPageTableEntry in favor of PTE masks. */
66+
struct pte_masks pte_masks = (struct pte_masks) {
67+
68+
};
69+
70+
/* TODO: Add support for 5-level EPT. */
71+
tdp_mmu_init(vm, 4, &pte_masks);
72+
}
73+
5974
/* Allocate memory regions for nested VMX tests.
6075
*
6176
* Input Args:
@@ -105,6 +120,9 @@ vcpu_alloc_vmx(struct kvm_vm *vm, vm_vaddr_t *p_vmx_gva)
105120
vmx->vmwrite_gpa = addr_gva2gpa(vm, (uintptr_t)vmx->vmwrite);
106121
memset(vmx->vmwrite_hva, 0, getpagesize());
107122

123+
if (vm->arch.tdp_mmu)
124+
vmx->eptp_gpa = vm->arch.tdp_mmu->pgd;
125+
108126
*p_vmx_gva = vmx_gva;
109127
return vmx;
110128
}
@@ -395,7 +413,8 @@ void __tdp_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm,
395413
uint64_t nested_paddr, uint64_t paddr, int target_level)
396414
{
397415
const uint64_t page_size = PG_LEVEL_SIZE(target_level);
398-
struct eptPageTableEntry *pt = vmx->eptp_hva, *pte;
416+
void *eptp_hva = addr_gpa2hva(vm, vm->arch.tdp_mmu->pgd);
417+
struct eptPageTableEntry *pt = eptp_hva, *pte;
399418
uint16_t index;
400419

401420
TEST_ASSERT(vm->mode == VM_MODE_PXXVYY_4K,
@@ -525,15 +544,6 @@ bool kvm_cpu_has_ept(void)
525544
return ctrl & SECONDARY_EXEC_ENABLE_EPT;
526545
}
527546

528-
void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm)
529-
{
530-
TEST_ASSERT(kvm_cpu_has_ept(), "KVM doesn't support nested EPT");
531-
532-
vmx->eptp = (void *)vm_vaddr_alloc_page(vm);
533-
vmx->eptp_hva = addr_gva2hva(vm, (uintptr_t)vmx->eptp);
534-
vmx->eptp_gpa = addr_gva2gpa(vm, (uintptr_t)vmx->eptp);
535-
}
536-
537547
void prepare_virtualize_apic_accesses(struct vmx_pages *vmx, struct kvm_vm *vm)
538548
{
539549
vmx->apic_access = (void *)vm_vaddr_alloc_page(vm);

tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ static void test_vmx_dirty_log(bool enable_ept)
9393

9494
/* Create VM */
9595
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
96+
if (enable_ept)
97+
vm_enable_ept(vm);
98+
9699
vmx = vcpu_alloc_vmx(vm, &vmx_pages_gva);
97100
vcpu_args_set(vcpu, 1, vmx_pages_gva);
98101

@@ -113,14 +116,10 @@ static void test_vmx_dirty_log(bool enable_ept)
113116
* ... pages in the L2 GPA range [0xc0001000, 0xc0003000) will map to
114117
* 0xc0000000.
115118
*
116-
* Note that prepare_eptp should be called only L1's GPA map is done,
117-
* meaning after the last call to virt_map.
118-
*
119119
* When EPT is disabled, the L2 guest code will still access the same L1
120120
* GPAs as the EPT enabled case.
121121
*/
122122
if (enable_ept) {
123-
prepare_eptp(vmx, vm);
124123
tdp_identity_map_default_memslots(vmx, vm);
125124
tdp_map(vmx, vm, NESTED_TEST_MEM1, GUEST_TEST_MEM, PAGE_SIZE);
126125
tdp_map(vmx, vm, NESTED_TEST_MEM2, GUEST_TEST_MEM, PAGE_SIZE);

0 commit comments

Comments
 (0)