[PATCH 1/2] KVM: selftest: arm64: Support 5-level paging in stage 1 translation table

From: Itaru Kitayama

Date: Tue Aug 25 2026 - 17:20:27 EST


Add p4d_index() for when 5-level paging required, i.e., V52 guest mode
IDs. With the index helper function, _virt_pg_map() handles 5-level
paging case, and aarch64_vcpu_setup() now is aware of the new V52 guest
mode IDs.

Signed-off-by: Itaru Kitayama <itaru.kitayama@xxxxxxxxxxx>
Reviewed-by: Takayuki Okamoto <tokamoto@xxxxxxxxxxx>
---
.../selftests/kvm/include/arm64/processor.h | 2 +
tools/testing/selftests/kvm/lib/arm64/processor.c | 87 ++++++++++++++++++++--
2 files changed, 84 insertions(+), 5 deletions(-)

diff --git a/tools/testing/selftests/kvm/include/arm64/processor.h b/tools/testing/selftests/kvm/include/arm64/processor.h
index b8a902ba8573..a6fd7a9b379e 100644
--- a/tools/testing/selftests/kvm/include/arm64/processor.h
+++ b/tools/testing/selftests/kvm/include/arm64/processor.h
@@ -113,6 +113,7 @@

#define PTE_VALID BIT(0)
#define PGD_TYPE_TABLE BIT(1)
+#define P4D_TYPE_TABLE BIT(1)
#define PUD_TYPE_TABLE BIT(1)
#define PMD_TYPE_TABLE BIT(1)
#define PTE_TYPE_PAGE BIT(1)
@@ -167,6 +168,7 @@ enum {
(v) == VECTOR_SYNC_LOWER_64 || \
(v) == VECTOR_SYNC_LOWER_32)

+u32 aarch64_get_supported_va_size(void);
void aarch64_get_supported_page_sizes(u32 ipa, u32 *ipa4k,
u32 *ipa16k, u32 *ipa64k);

diff --git a/tools/testing/selftests/kvm/lib/arm64/processor.c b/tools/testing/selftests/kvm/lib/arm64/processor.c
index 01325bf4d36f..9108e14a9b5b 100644
--- a/tools/testing/selftests/kvm/lib/arm64/processor.c
+++ b/tools/testing/selftests/kvm/lib/arm64/processor.c
@@ -29,13 +29,25 @@ static u64 pgd_index(struct kvm_vm *vm, gva_t gva)
return (gva >> shift) & mask;
}

+static u64 p4d_index(struct kvm_vm *vm, gva_t gva)
+{
+ unsigned int shift = 3 * (vm->page_shift - 3) + vm->page_shift;
+ u64 mask = (1UL << (vm->page_shift - 3)) - 1;
+
+ TEST_ASSERT(vm->mmu.pgtable_levels == 5,
+ "Mode %d does not have 5 page table levels", vm->mode);
+
+ return (gva >> shift) & mask;
+}
+
static u64 pud_index(struct kvm_vm *vm, gva_t gva)
{
unsigned int shift = 2 * (vm->page_shift - 3) + vm->page_shift;
u64 mask = (1UL << (vm->page_shift - 3)) - 1;

- TEST_ASSERT(vm->mmu.pgtable_levels == 4,
- "Mode %d does not have 4 page table levels", vm->mode);
+ TEST_ASSERT(vm->mmu.pgtable_levels >= 4,
+ "Mode %d does not have at least 4 page table levels",
+ vm->mode);

return (gva >> shift) & mask;
}
@@ -147,6 +159,12 @@ static void _virt_pg_map(struct kvm_vm *vm, gva_t gva, gpa_t gpa,
PGD_TYPE_TABLE | PTE_VALID);

switch (vm->mmu.pgtable_levels) {
+ case 5:
+ ptep = addr_gpa2hva(vm, pte_addr(vm, *ptep)) + p4d_index(vm, gva) * 8;
+ if (!*ptep)
+ *ptep = addr_pte(vm, vm_alloc_page_table(vm),
+ P4D_TYPE_TABLE | PTE_VALID);
+ /* fall through */
case 4:
ptep = addr_gpa2hva(vm, pte_addr(vm, *ptep)) + pud_index(vm, gva) * 8;
if (!*ptep)
@@ -163,7 +181,7 @@ static void _virt_pg_map(struct kvm_vm *vm, gva_t gva, gpa_t gpa,
ptep = addr_gpa2hva(vm, pte_addr(vm, *ptep)) + pte_index(vm, gva) * 8;
break;
default:
- TEST_FAIL("Page table levels must be 2, 3, or 4");
+ TEST_FAIL("Page table levels must be 2, 3, 4, or 5");
}

pg_attr = PTE_AF | PTE_ATTRINDX(attr_idx) | PTE_TYPE_PAGE | PTE_VALID;
@@ -182,18 +200,35 @@ void virt_arch_pg_map(struct kvm_vm *vm, gva_t gva, gpa_t gpa)

u64 *virt_get_pte_hva_at_level(struct kvm_vm *vm, gva_t gva, int level)
{
+ int start_level = 4 - vm->mmu.pgtable_levels;
u64 *ptep;

+ TEST_ASSERT(level >= start_level && level <= 3,
+ "Invalid translation level %d, valid range is %d-3",
+ level, start_level);
+
if (!vm->mmu.pgd_created)
goto unmapped_gva;

ptep = addr_gpa2hva(vm, vm->mmu.pgd) + pgd_index(vm, gva) * 8;
if (!ptep)
goto unmapped_gva;
- if (level == 0)
+ /*
+ * Stage-1 translation starts at level -1 for a five-level page
+ * table, and at levels 0, 1, or 2 for four-, three-, or two-level
+ * page tables, respectively.
+ */
+ if (level == start_level)
return ptep;

switch (vm->mmu.pgtable_levels) {
+ case 5:
+ ptep = addr_gpa2hva(vm, pte_addr(vm, *ptep)) + p4d_index(vm, gva) * 8;
+ if (!ptep)
+ goto unmapped_gva;
+ if (level == 0)
+ break;
+ /* fall through */
case 4:
ptep = addr_gpa2hva(vm, pte_addr(vm, *ptep)) + pud_index(vm, gva) * 8;
if (!ptep)
@@ -214,7 +249,7 @@ u64 *virt_get_pte_hva_at_level(struct kvm_vm *vm, gva_t gva, int level)
goto unmapped_gva;
break;
default:
- TEST_FAIL("Page table levels must be 2, 3, or 4");
+ TEST_FAIL("Page table levels must be 2, 3, 4, or 5");
}

return ptep;
@@ -321,12 +356,14 @@ void aarch64_vcpu_setup(struct kvm_vcpu *vcpu, struct kvm_vcpu_init *init)
case VM_MODE_PXXVYY_4K:
TEST_FAIL("AArch64 does not support 4K sized pages "
"with ANY-bit physical address ranges");
+ case VM_MODE_P52V52_64K:
case VM_MODE_P52V48_64K:
case VM_MODE_P48V48_64K:
case VM_MODE_P40V48_64K:
case VM_MODE_P36V48_64K:
tcr_el1 |= TCR_TG0_64K;
break;
+ case VM_MODE_P52V52_16K:
case VM_MODE_P52V48_16K:
case VM_MODE_P48V48_16K:
case VM_MODE_P40V48_16K:
@@ -334,6 +371,7 @@ void aarch64_vcpu_setup(struct kvm_vcpu *vcpu, struct kvm_vcpu_init *init)
case VM_MODE_P36V47_16K:
tcr_el1 |= TCR_TG0_16K;
break;
+ case VM_MODE_P52V52_4K:
case VM_MODE_P52V48_4K:
case VM_MODE_P48V48_4K:
case VM_MODE_P40V48_4K:
@@ -348,6 +386,9 @@ void aarch64_vcpu_setup(struct kvm_vcpu *vcpu, struct kvm_vcpu_init *init)

/* Configure output size */
switch (vm->mode) {
+ case VM_MODE_P52V52_4K:
+ case VM_MODE_P52V52_16K:
+ case VM_MODE_P52V52_64K:
case VM_MODE_P52V48_4K:
case VM_MODE_P52V48_16K:
case VM_MODE_P52V48_64K:
@@ -578,6 +619,42 @@ static u32 max_ipa_for_page_size(u32 vm_ipa, u32 gran,
return min(vm_ipa, 48U);
}

+u32 aarch64_get_supported_va_size(void)
+{
+ struct kvm_vcpu_init preferred_init = {};
+ int kvm_fd, vm_fd, vcpu_fd, err;
+ u64 val;
+ u32 va_range;
+ struct kvm_one_reg reg = {
+ .id = KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1),
+ .addr = (u64)&val,
+ };
+
+ kvm_fd = open_kvm_dev_path_or_exit();
+ vm_fd = __kvm_ioctl(kvm_fd, KVM_CREATE_VM, NULL);
+ TEST_ASSERT(vm_fd >= 0, KVM_IOCTL_ERROR(KVM_CREATE_VM, vm_fd));
+
+ vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0);
+ TEST_ASSERT(vcpu_fd >= 0, KVM_IOCTL_ERROR(KVM_CREATE_VCPU, vcpu_fd));
+
+ err = ioctl(vm_fd, KVM_ARM_PREFERRED_TARGET, &preferred_init);
+ TEST_ASSERT(err == 0, KVM_IOCTL_ERROR(KVM_ARM_PREFERRED_TARGET, err));
+
+ err = ioctl(vcpu_fd, KVM_ARM_VCPU_INIT, &preferred_init);
+ TEST_ASSERT(err == 0, KVM_IOCTL_ERROR(KVM_ARM_VCPU_INIT, err));
+
+ err = ioctl(vcpu_fd, KVM_GET_ONE_REG, &reg);
+ TEST_ASSERT(err == 0, KVM_IOCTL_ERROR(KVM_GET_ONE_REG, err));
+
+ va_range = FIELD_GET(ID_AA64MMFR2_EL1_VARange, val);
+
+ close(vcpu_fd);
+ close(vm_fd);
+ close(kvm_fd);
+
+ return va_range >= ID_AA64MMFR2_EL1_VARange_52 ? 52 : 48;
+}
+
void aarch64_get_supported_page_sizes(u32 ipa, u32 *ipa4k,
u32 *ipa16k, u32 *ipa64k)
{

--
2.43.0