On Wed, Mar 25, 2026 at 09:18:20AM +0000, Marc Zyngier wrote: > On Wed, 25 Mar 2026 00:36:20 +0000, > Wei-Lin Chang <[email protected]> wrote: > > > > Introduce library functions for setting up guest stage-2 page tables, > > then use that to give L2 an identity mapped stage-2 and enable it. > > > > The translation and stage-2 page table built is simple, start level 0, > > 4 levels, 4KB granules, normal cachable, 48-bit IA, 40-bit OA. > > That's a no go. The most common NV-capable out there can realistically > only do 16kB at S2, and is limited to 36bit IPA. We can't really > afford to say "too bad" and leave the main development platform behind.
Interesting, I didn't know that! I didn't consider the guest stage-2 granule size < host stage-2 granule size case either. > > > > > The nested page table code is adapted from lib/x86/vmx.c. > > I guess this starting point is the main issue. > > > > > Signed-off-by: Wei-Lin Chang <[email protected]> > > --- > > .../selftests/kvm/include/arm64/nested.h | 7 ++ > > .../selftests/kvm/include/arm64/processor.h | 9 ++ > > .../testing/selftests/kvm/lib/arm64/nested.c | 97 ++++++++++++++++++- > > 3 files changed, 111 insertions(+), 2 deletions(-) > > > > diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h > > b/tools/testing/selftests/kvm/include/arm64/nested.h > > index 739ff2ee0161..0be10a775e48 100644 > > --- a/tools/testing/selftests/kvm/include/arm64/nested.h > > +++ b/tools/testing/selftests/kvm/include/arm64/nested.h > > @@ -6,6 +6,13 @@ > > #ifndef SELFTEST_KVM_NESTED_H > > #define SELFTEST_KVM_NESTED_H > > > > +uint64_t get_l1_vtcr(void); > > + > > +void nested_map(struct kvm_vm *vm, vm_paddr_t guest_pgd, > > + uint64_t nested_paddr, uint64_t paddr, uint64_t size); > > +void nested_map_memslot(struct kvm_vm *vm, vm_paddr_t guest_pgd, > > + uint32_t memslot); > > + > > void prepare_l2_stack(struct kvm_vm *vm, struct kvm_vcpu *vcpu); > > void prepare_hyp_state(struct kvm_vm *vm, struct kvm_vcpu *vcpu); > > void prepare_eret_destination(struct kvm_vm *vm, struct kvm_vcpu *vcpu, > > void *l2_pc); > > diff --git a/tools/testing/selftests/kvm/include/arm64/processor.h > > b/tools/testing/selftests/kvm/include/arm64/processor.h > > index ac97a1c436fc..5de2e932d95a 100644 > > --- a/tools/testing/selftests/kvm/include/arm64/processor.h > > +++ b/tools/testing/selftests/kvm/include/arm64/processor.h > > @@ -104,6 +104,15 @@ > > #define TCR_HA (UL(1) << 39) > > #define TCR_DS (UL(1) << 59) > > > > +/* VTCR_EL2 specific flags */ > > +#define VTCR_EL2_T0SZ_BITS(x) ((UL(64) - (x)) << VTCR_EL2_T0SZ_SHIFT) > > + > > +#define VTCR_EL2_SL0_LV0_4K (UL(2) << VTCR_EL2_SL0_SHIFT) > > +#define VTCR_EL2_SL0_LV1_4K (UL(1) << VTCR_EL2_SL0_SHIFT) > > +#define VTCR_EL2_SL0_LV2_4K (UL(0) << VTCR_EL2_SL0_SHIFT) > > + > > +#define VTCR_EL2_PS_40_BITS (UL(2) << VTCR_EL2_PS_SHIFT) > > + > > /* > > * AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* > > registers). > > */ > > diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c > > b/tools/testing/selftests/kvm/lib/arm64/nested.c > > index 111d02f44cfe..910f8cd30f96 100644 > > --- a/tools/testing/selftests/kvm/lib/arm64/nested.c > > +++ b/tools/testing/selftests/kvm/lib/arm64/nested.c > > @@ -1,8 +1,11 @@ > > // SPDX-License-Identifier: GPL-2.0 > > /* > > - * ARM64 Nested virtualization helpers > > + * ARM64 Nested virtualization helpers, nested page table code adapted from > > + * ../x86/vmx.c. > > */ > > > > +#include <linux/sizes.h> > > + > > #include "kvm_util.h" > > #include "nested.h" > > #include "processor.h" > > @@ -18,6 +21,87 @@ static void hvc_handler(struct ex_regs *regs) > > regs->pc = (u64)after_hvc; > > } > > > > +uint64_t get_l1_vtcr(void) > > +{ > > + return VTCR_EL2_PS_40_BITS | VTCR_EL2_TG0_4K | VTCR_EL2_ORGN0_WBWA | > > + VTCR_EL2_IRGN0_WBWA | VTCR_EL2_SL0_LV0_4K | > > VTCR_EL2_T0SZ_BITS(48); > > Irk. See above. > > > +} > > + > > +static void __nested_pg_map(struct kvm_vm *vm, uint64_t guest_pgd, > > + uint64_t nested_paddr, uint64_t paddr, uint64_t flags) > > +{ > > + uint8_t attr_idx = flags & (PTE_ATTRINDX_MASK >> PTE_ATTRINDX_SHIFT); > > + uint64_t pg_attr; > > + uint64_t *ptep; > > + > > + TEST_ASSERT((nested_paddr % vm->page_size) == 0, > > + "L2 IPA not on page boundary,\n" > > + " nested_paddr: 0x%lx vm->page_size: 0x%x", nested_paddr, > > vm->page_size); > > + TEST_ASSERT((paddr % vm->page_size) == 0, > > + "Guest physical address not on page boundary,\n" > > + " paddr: 0x%lx vm->page_size: 0x%x", paddr, vm->page_size); > > + TEST_ASSERT((paddr >> vm->page_shift) <= vm->max_gfn, > > + "Physical address beyond maximum supported,\n" > > + " paddr: 0x%lx vm->max_gfn: 0x%lx vm->page_size: 0x%x", > > + paddr, vm->max_gfn, vm->page_size); > > + > > + ptep = addr_gpa2hva(vm, guest_pgd) + ((nested_paddr >> 39) & 0x1ffu) * > > 8; > > + if (!*ptep) > > + *ptep = (vm_alloc_page_table(vm) & GENMASK(47, 12)) | > > PGD_TYPE_TABLE | PTE_VALID; > > + ptep = addr_gpa2hva(vm, *ptep & GENMASK(47, 12)) + ((nested_paddr >> > > 30) & 0x1ffu) * 8; > > + if (!*ptep) > > + *ptep = (vm_alloc_page_table(vm) & GENMASK(47, 12)) | > > PUD_TYPE_TABLE | PTE_VALID; > > + ptep = addr_gpa2hva(vm, *ptep & GENMASK(47, 12)) + ((nested_paddr >> > > 21) & 0x1ffu) * 8; > > + if (!*ptep) > > + *ptep = (vm_alloc_page_table(vm) & GENMASK(47, 12)) | > > PMD_TYPE_TABLE | PTE_VALID; > > + ptep = addr_gpa2hva(vm, *ptep & GENMASK(47, 12)) + ((nested_paddr >> > > 12) & 0x1ffu) * 8; > > + > > + pg_attr = PTE_AF | PTE_ATTRINDX(attr_idx) | PTE_TYPE_PAGE | PTE_VALID; > > + pg_attr |= PTE_SHARED; > > + > > + *ptep = (paddr & GENMASK(47, 12)) | pg_attr; > > Please use named constants, and write a page table generator that is > independent of page, IA and OA sizes, as advertised to the guest. Ack, and yes, I should absolutely consider what is advertised to the guest... Thanks, Wei-Lin Chang > > Thanks, > > M. > > -- > Without deviation from the norm, progress is not possible.

