[PATCH v4 16/21] KVM: selftests: Add support for nested NPTs

Yosry Ahmed yosry.ahmed at linux.dev
Wed Jan 7 15:12:59 PST 2026


On Tue, Dec 30, 2025 at 03:01:45PM -0800, Sean Christopherson wrote:
> From: Yosry Ahmed <yosry.ahmed at linux.dev>
> 
> Implement nCR3 and NPT initialization functions, similar to the EPT
> equivalents, and create common TDP helpers for enablement checking and
> initialization. Enable NPT for nested guests by default if the TDP MMU
> was initialized, similar to VMX.
> 
> Reuse the PTE masks from the main MMU in the NPT MMU, except for the C
> and S bits related to confidential VMs.
> 
> Signed-off-by: Yosry Ahmed <yosry.ahmed at linux.dev>
> Signed-off-by: Sean Christopherson <seanjc at google.com>

Funny story, I missed a teeny tiny part here..

diff --git a/tools/testing/selftests/kvm/lib/x86/svm.c b/tools/testing/selftests/kvm/lib/x86/svm.c
index 18e9e9089643..2e5c480c9afd 100644
--- a/tools/testing/selftests/kvm/lib/x86/svm.c
+++ b/tools/testing/selftests/kvm/lib/x86/svm.c
@@ -46,6 +46,9 @@ vcpu_alloc_svm(struct kvm_vm *vm, vm_vaddr_t *p_svm_gva)
        svm->msr_gpa = addr_gva2gpa(vm, (uintptr_t)svm->msr);
        memset(svm->msr_hva, 0, getpagesize());

+       if (vm->stage2_mmu.pgd_created)
+               svm->ncr3_gpa = vm->stage2_mmu.pgd;
+
        *p_svm_gva = svm_gva;
        return svm;
 }

---

The good news is that the test still passes after we start ACTUALLY
USING the nested NPT :)

> ---
>  .../selftests/kvm/include/x86/processor.h     |  2 ++
>  .../selftests/kvm/include/x86/svm_util.h      |  9 ++++++++
>  .../testing/selftests/kvm/lib/x86/memstress.c |  4 ++--
>  .../testing/selftests/kvm/lib/x86/processor.c | 15 +++++++++++++
>  tools/testing/selftests/kvm/lib/x86/svm.c     | 21 +++++++++++++++++++
>  .../selftests/kvm/x86/vmx_dirty_log_test.c    |  4 ++--
>  6 files changed, 51 insertions(+), 4 deletions(-)
> 
> diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
> index d134c886f280..deb471fb9b51 100644
> --- a/tools/testing/selftests/kvm/include/x86/processor.h
> +++ b/tools/testing/selftests/kvm/include/x86/processor.h
> @@ -1477,6 +1477,8 @@ void __virt_pg_map(struct kvm_vm *vm, struct kvm_mmu *mmu, uint64_t vaddr,
>  void virt_map_level(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,
>  		    uint64_t nr_bytes, int level);
>  
> +void vm_enable_tdp(struct kvm_vm *vm);
> +bool kvm_cpu_has_tdp(void);
>  void tdp_map(struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr, uint64_t size);
>  void tdp_identity_map_default_memslots(struct kvm_vm *vm);
>  void tdp_identity_map_1g(struct kvm_vm *vm,  uint64_t addr, uint64_t size);
> diff --git a/tools/testing/selftests/kvm/include/x86/svm_util.h b/tools/testing/selftests/kvm/include/x86/svm_util.h
> index b74c6dcddcbd..5d7c42534bc4 100644
> --- a/tools/testing/selftests/kvm/include/x86/svm_util.h
> +++ b/tools/testing/selftests/kvm/include/x86/svm_util.h
> @@ -27,6 +27,9 @@ struct svm_test_data {
>  	void *msr; /* gva */
>  	void *msr_hva;
>  	uint64_t msr_gpa;
> +
> +	/* NPT */
> +	uint64_t ncr3_gpa;
>  };
>  
>  static inline void vmmcall(void)
> @@ -57,6 +60,12 @@ 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);
>  
> +static inline bool kvm_cpu_has_npt(void)
> +{
> +	return kvm_cpu_has(X86_FEATURE_NPT);
> +}
> +void vm_enable_npt(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/memstress.c b/tools/testing/selftests/kvm/lib/x86/memstress.c
> index 3319cb57a78d..407abfc34909 100644
> --- a/tools/testing/selftests/kvm/lib/x86/memstress.c
> +++ b/tools/testing/selftests/kvm/lib/x86/memstress.c
> @@ -82,9 +82,9 @@ void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vc
>  	int vcpu_id;
>  
>  	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
> -	TEST_REQUIRE(kvm_cpu_has_ept());
> +	TEST_REQUIRE(kvm_cpu_has_tdp());
>  
> -	vm_enable_ept(vm);
> +	vm_enable_tdp(vm);
>  	for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++) {
>  		vcpu_alloc_vmx(vm, &vmx_gva);
>  
> diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c
> index 29e7d172f945..a3a4c9a4cbcb 100644
> --- a/tools/testing/selftests/kvm/lib/x86/processor.c
> +++ b/tools/testing/selftests/kvm/lib/x86/processor.c
> @@ -8,7 +8,9 @@
>  #include "kvm_util.h"
>  #include "pmu.h"
>  #include "processor.h"
> +#include "svm_util.h"
>  #include "sev.h"
> +#include "vmx.h"
>  
>  #ifndef NUM_INTERRUPTS
>  #define NUM_INTERRUPTS 256
> @@ -472,6 +474,19 @@ void virt_arch_dump(FILE *stream, struct kvm_vm *vm, uint8_t indent)
>  	}
>  }
>  
> +void vm_enable_tdp(struct kvm_vm *vm)
> +{
> +	if (kvm_cpu_has(X86_FEATURE_VMX))
> +		vm_enable_ept(vm);
> +	else
> +		vm_enable_npt(vm);
> +}
> +
> +bool kvm_cpu_has_tdp(void)
> +{
> +	return kvm_cpu_has_ept() || kvm_cpu_has_npt();
> +}
> +
>  void __tdp_map(struct kvm_vm *vm, uint64_t nested_paddr, uint64_t paddr,
>  	       uint64_t size, int level)
>  {
> diff --git a/tools/testing/selftests/kvm/lib/x86/svm.c b/tools/testing/selftests/kvm/lib/x86/svm.c
> index d239c2097391..8e4795225595 100644
> --- a/tools/testing/selftests/kvm/lib/x86/svm.c
> +++ b/tools/testing/selftests/kvm/lib/x86/svm.c
> @@ -59,6 +59,22 @@ static void vmcb_set_seg(struct vmcb_seg *seg, u16 selector,
>  	seg->base = base;
>  }
>  
> +void vm_enable_npt(struct kvm_vm *vm)
> +{
> +	struct pte_masks pte_masks;
> +
> +	TEST_ASSERT(kvm_cpu_has_npt(), "KVM doesn't supported nested NPT");
> +
> +	/*
> +	 * NPTs use the same PTE format, but deliberately drop the C-bit as the
> +	 * per-VM shared vs. private information is only meant for stage-1.
> +	 */
> +	pte_masks = vm->mmu.arch.pte_masks;
> +	pte_masks.c = 0;
> +
> +	tdp_mmu_init(vm, vm->mmu.pgtable_levels, &pte_masks);
> +}
> +
>  void generic_svm_setup(struct svm_test_data *svm, void *guest_rip, void *guest_rsp)
>  {
>  	struct vmcb *vmcb = svm->vmcb;
> @@ -102,6 +118,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/x86/vmx_dirty_log_test.c b/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c
> index 370f8d3117c2..032ab8bf60a4 100644
> --- a/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c
> +++ b/tools/testing/selftests/kvm/x86/vmx_dirty_log_test.c
> @@ -93,7 +93,7 @@ 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);
> +		vm_enable_tdp(vm);
>  
>  	vcpu_alloc_vmx(vm, &vmx_pages_gva);
>  	vcpu_args_set(vcpu, 1, vmx_pages_gva);
> @@ -170,7 +170,7 @@ int main(int argc, char *argv[])
>  
>  	test_vmx_dirty_log(/*enable_ept=*/false);
>  
> -	if (kvm_cpu_has_ept())
> +	if (kvm_cpu_has_tdp())
>  		test_vmx_dirty_log(/*enable_ept=*/true);
>  
>  	return 0;
> -- 
> 2.52.0.351.gbe84eed79e-goog
> 



More information about the linux-riscv mailing list