* [PATCH v2 0/1] KVM: arm64: Calculate hyp VA size only once @ 2026-01-13 19:44 Petteri Kangaslampi 2026-01-13 19:44 ` [PATCH v2 1/1] " Petteri Kangaslampi 2026-01-14 10:51 ` [PATCH v2 0/1] " Marc Zyngier 0 siblings, 2 replies; 4+ messages in thread From: Petteri Kangaslampi @ 2026-01-13 19:44 UTC (permalink / raw) To: kvmarm Cc: Marc Zyngier, Oliver Upton, Vincent Donnefort, Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon, linux-arm-kernel, linux-kernel, Petteri Kangaslampi All, Here's a reworked fix to keep VA size calculations consistent between the MMU and layout initialization code paths. Previously, if the kernel was configured with a VA size of less than 48 bits (like happens in our environment), the assumptions used to calculate hyp_physvirt_offset in kvm_compute_layout() and the code that determines the VA size used in the rest of the system (calculated in kvm_mmu_init()) would get out of sync. This could eventually result in mapping failures. The patch here is against 6.19-rc4 and tested in default, nVHE, and pKVM configurations thanks to help from Vincent Donnefort. Detailed knowledge of the address map remains somewhat distributed inside the codebase, but at least everything now uses the same VA size, and further refactoring is probably best done if/when Will's pKVM changes have been merged. Petteri Kangaslampi (1): KVM: arm64: Calculate hyp VA size only once arch/arm64/include/asm/kvm_mmu.h | 3 ++- arch/arm64/kvm/arm.c | 4 ++-- arch/arm64/kvm/mmu.c | 28 ++++----------------------- arch/arm64/kvm/va_layout.c | 33 +++++++++++++++++++++++++++----- 4 files changed, 36 insertions(+), 32 deletions(-) -- 2.52.0.457.g6b5491de43-goog ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/1] KVM: arm64: Calculate hyp VA size only once 2026-01-13 19:44 [PATCH v2 0/1] KVM: arm64: Calculate hyp VA size only once Petteri Kangaslampi @ 2026-01-13 19:44 ` Petteri Kangaslampi 2026-01-14 9:27 ` Vincent Donnefort 2026-01-14 10:51 ` [PATCH v2 0/1] " Marc Zyngier 1 sibling, 1 reply; 4+ messages in thread From: Petteri Kangaslampi @ 2026-01-13 19:44 UTC (permalink / raw) To: kvmarm Cc: Marc Zyngier, Oliver Upton, Vincent Donnefort, Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon, linux-arm-kernel, linux-kernel, Petteri Kangaslampi Calculate the hypervisor's VA size only once to maintain consistency between the memory layout and MMU initialization logic. Previously the two would be inconsistent when the kernel is configured for less than IDMAP_VA_BITS of VA space. Signed-off-by: Petteri Kangaslampi <pekangas@google.com> --- arch/arm64/include/asm/kvm_mmu.h | 3 ++- arch/arm64/kvm/arm.c | 4 ++-- arch/arm64/kvm/mmu.c | 28 ++++----------------------- arch/arm64/kvm/va_layout.c | 33 +++++++++++++++++++++++++++----- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h index 2dc5e6e742bb..d968aca0461a 100644 --- a/arch/arm64/include/asm/kvm_mmu.h +++ b/arch/arm64/include/asm/kvm_mmu.h @@ -103,6 +103,7 @@ alternative_cb_end void kvm_update_va_mask(struct alt_instr *alt, __le32 *origptr, __le32 *updptr, int nr_inst); void kvm_compute_layout(void); +u32 kvm_hyp_va_bits(void); void kvm_apply_hyp_relocations(void); #define __hyp_pa(x) (((phys_addr_t)(x)) + hyp_physvirt_offset) @@ -185,7 +186,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu); phys_addr_t kvm_mmu_get_httbr(void); phys_addr_t kvm_get_idmap_vector(void); -int __init kvm_mmu_init(u32 *hyp_va_bits); +int __init kvm_mmu_init(u32 hyp_va_bits); static inline void *__kvm_vector_slot2addr(void *base, enum arm64_hyp_spectre_vector slot) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 4f80da0c0d1d..4703f0e15102 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -2568,7 +2568,7 @@ static void pkvm_hyp_init_ptrauth(void) /* Inits Hyp-mode on all online CPUs */ static int __init init_hyp_mode(void) { - u32 hyp_va_bits; + u32 hyp_va_bits = kvm_hyp_va_bits(); int cpu; int err = -ENOMEM; @@ -2582,7 +2582,7 @@ static int __init init_hyp_mode(void) /* * Allocate Hyp PGD and setup Hyp identity mapping */ - err = kvm_mmu_init(&hyp_va_bits); + err = kvm_mmu_init(hyp_va_bits); if (err) goto out_err; diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 48d7c372a4cd..d5a506c99f73 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -2284,11 +2284,9 @@ static struct kvm_pgtable_mm_ops kvm_hyp_mm_ops = { .virt_to_phys = kvm_host_pa, }; -int __init kvm_mmu_init(u32 *hyp_va_bits) +int __init kvm_mmu_init(u32 hyp_va_bits) { int err; - u32 idmap_bits; - u32 kernel_bits; hyp_idmap_start = __pa_symbol(__hyp_idmap_text_start); hyp_idmap_start = ALIGN_DOWN(hyp_idmap_start, PAGE_SIZE); @@ -2302,25 +2300,7 @@ int __init kvm_mmu_init(u32 *hyp_va_bits) */ BUG_ON((hyp_idmap_start ^ (hyp_idmap_end - 1)) & PAGE_MASK); - /* - * The ID map is always configured for 48 bits of translation, which - * may be fewer than the number of VA bits used by the regular kernel - * stage 1, when VA_BITS=52. - * - * At EL2, there is only one TTBR register, and we can't switch between - * translation tables *and* update TCR_EL2.T0SZ at the same time. Bottom - * line: we need to use the extended range with *both* our translation - * tables. - * - * So use the maximum of the idmap VA bits and the regular kernel stage - * 1 VA bits to assure that the hypervisor can both ID map its code page - * and map any kernel memory. - */ - idmap_bits = IDMAP_VA_BITS; - kernel_bits = vabits_actual; - *hyp_va_bits = max(idmap_bits, kernel_bits); - - kvm_debug("Using %u-bit virtual addresses at EL2\n", *hyp_va_bits); + kvm_debug("Using %u-bit virtual addresses at EL2\n", hyp_va_bits); kvm_debug("IDMAP page: %lx\n", hyp_idmap_start); kvm_debug("HYP VA range: %lx:%lx\n", kern_hyp_va(PAGE_OFFSET), @@ -2345,7 +2325,7 @@ int __init kvm_mmu_init(u32 *hyp_va_bits) goto out; } - err = kvm_pgtable_hyp_init(hyp_pgtable, *hyp_va_bits, &kvm_hyp_mm_ops); + err = kvm_pgtable_hyp_init(hyp_pgtable, hyp_va_bits, &kvm_hyp_mm_ops); if (err) goto out_free_pgtable; @@ -2354,7 +2334,7 @@ int __init kvm_mmu_init(u32 *hyp_va_bits) goto out_destroy_pgtable; io_map_base = hyp_idmap_start; - __hyp_va_bits = *hyp_va_bits; + __hyp_va_bits = hyp_va_bits; return 0; out_destroy_pgtable: diff --git a/arch/arm64/kvm/va_layout.c b/arch/arm64/kvm/va_layout.c index 91b22a014610..2346f9435a71 100644 --- a/arch/arm64/kvm/va_layout.c +++ b/arch/arm64/kvm/va_layout.c @@ -46,9 +46,31 @@ static void init_hyp_physvirt_offset(void) hyp_physvirt_offset = (s64)__pa(kern_va) - (s64)hyp_va; } +/* + * Calculate the actual VA size used by the hypervisor + */ +__init u32 kvm_hyp_va_bits(void) +{ + /* + * The ID map is always configured for 48 bits of translation, which may + * be different from the number of VA bits used by the regular kernel + * stage 1. + * + * At EL2, there is only one TTBR register, and we can't switch between + * translation tables *and* update TCR_EL2.T0SZ at the same time. Bottom + * line: we need to use the extended range with *both* our translation + * tables. + * + * So use the maximum of the idmap VA bits and the regular kernel stage + * 1 VA bits as the hypervisor VA size to assure that the hypervisor can + * both ID map its code page and map any kernel memory. + */ + return max(IDMAP_VA_BITS, vabits_actual); +} + /* * We want to generate a hyp VA with the following format (with V == - * vabits_actual): + * hypervisor VA bits): * * 63 ... V | V-1 | V-2 .. tag_lsb | tag_lsb - 1 .. 0 * --------------------------------------------------------- @@ -61,10 +83,11 @@ __init void kvm_compute_layout(void) { phys_addr_t idmap_addr = __pa_symbol(__hyp_idmap_text_start); u64 hyp_va_msb; + u32 hyp_va_bits = kvm_hyp_va_bits(); /* Where is my RAM region? */ - hyp_va_msb = idmap_addr & BIT(vabits_actual - 1); - hyp_va_msb ^= BIT(vabits_actual - 1); + hyp_va_msb = idmap_addr & BIT(hyp_va_bits - 1); + hyp_va_msb ^= BIT(hyp_va_bits - 1); tag_lsb = fls64((u64)phys_to_virt(memblock_start_of_DRAM()) ^ (u64)(high_memory - 1)); @@ -72,9 +95,9 @@ __init void kvm_compute_layout(void) va_mask = GENMASK_ULL(tag_lsb - 1, 0); tag_val = hyp_va_msb; - if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && tag_lsb != (vabits_actual - 1)) { + if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && tag_lsb != (hyp_va_bits - 1)) { /* We have some free bits to insert a random tag. */ - tag_val |= get_random_long() & GENMASK_ULL(vabits_actual - 2, tag_lsb); + tag_val |= get_random_long() & GENMASK_ULL(hyp_va_bits - 2, tag_lsb); } tag_val >>= tag_lsb; -- 2.52.0.457.g6b5491de43-goog ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2 1/1] KVM: arm64: Calculate hyp VA size only once 2026-01-13 19:44 ` [PATCH v2 1/1] " Petteri Kangaslampi @ 2026-01-14 9:27 ` Vincent Donnefort 0 siblings, 0 replies; 4+ messages in thread From: Vincent Donnefort @ 2026-01-14 9:27 UTC (permalink / raw) To: Petteri Kangaslampi Cc: kvmarm, Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon, linux-arm-kernel, linux-kernel On Tue, Jan 13, 2026 at 07:44:09PM +0000, Petteri Kangaslampi wrote: > Calculate the hypervisor's VA size only once to maintain consistency > between the memory layout and MMU initialization logic. Previously the > two would be inconsistent when the kernel is configured for less than > IDMAP_VA_BITS of VA space. > > Signed-off-by: Petteri Kangaslampi <pekangas@google.com> Tested-by: Vincent Donnefort <vdonnefort@google.com> > --- > arch/arm64/include/asm/kvm_mmu.h | 3 ++- > arch/arm64/kvm/arm.c | 4 ++-- > arch/arm64/kvm/mmu.c | 28 ++++----------------------- > arch/arm64/kvm/va_layout.c | 33 +++++++++++++++++++++++++++----- > 4 files changed, 36 insertions(+), 32 deletions(-) > > diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h > index 2dc5e6e742bb..d968aca0461a 100644 > --- a/arch/arm64/include/asm/kvm_mmu.h > +++ b/arch/arm64/include/asm/kvm_mmu.h > @@ -103,6 +103,7 @@ alternative_cb_end > void kvm_update_va_mask(struct alt_instr *alt, > __le32 *origptr, __le32 *updptr, int nr_inst); > void kvm_compute_layout(void); > +u32 kvm_hyp_va_bits(void); > void kvm_apply_hyp_relocations(void); > > #define __hyp_pa(x) (((phys_addr_t)(x)) + hyp_physvirt_offset) > @@ -185,7 +186,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu); > > phys_addr_t kvm_mmu_get_httbr(void); > phys_addr_t kvm_get_idmap_vector(void); > -int __init kvm_mmu_init(u32 *hyp_va_bits); > +int __init kvm_mmu_init(u32 hyp_va_bits); > > static inline void *__kvm_vector_slot2addr(void *base, > enum arm64_hyp_spectre_vector slot) > diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c > index 4f80da0c0d1d..4703f0e15102 100644 > --- a/arch/arm64/kvm/arm.c > +++ b/arch/arm64/kvm/arm.c > @@ -2568,7 +2568,7 @@ static void pkvm_hyp_init_ptrauth(void) > /* Inits Hyp-mode on all online CPUs */ > static int __init init_hyp_mode(void) > { > - u32 hyp_va_bits; > + u32 hyp_va_bits = kvm_hyp_va_bits(); > int cpu; > int err = -ENOMEM; > > @@ -2582,7 +2582,7 @@ static int __init init_hyp_mode(void) > /* > * Allocate Hyp PGD and setup Hyp identity mapping > */ > - err = kvm_mmu_init(&hyp_va_bits); > + err = kvm_mmu_init(hyp_va_bits); > if (err) > goto out_err; > > diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c > index 48d7c372a4cd..d5a506c99f73 100644 > --- a/arch/arm64/kvm/mmu.c > +++ b/arch/arm64/kvm/mmu.c > @@ -2284,11 +2284,9 @@ static struct kvm_pgtable_mm_ops kvm_hyp_mm_ops = { > .virt_to_phys = kvm_host_pa, > }; > > -int __init kvm_mmu_init(u32 *hyp_va_bits) > +int __init kvm_mmu_init(u32 hyp_va_bits) > { > int err; > - u32 idmap_bits; > - u32 kernel_bits; > > hyp_idmap_start = __pa_symbol(__hyp_idmap_text_start); > hyp_idmap_start = ALIGN_DOWN(hyp_idmap_start, PAGE_SIZE); > @@ -2302,25 +2300,7 @@ int __init kvm_mmu_init(u32 *hyp_va_bits) > */ > BUG_ON((hyp_idmap_start ^ (hyp_idmap_end - 1)) & PAGE_MASK); > > - /* > - * The ID map is always configured for 48 bits of translation, which > - * may be fewer than the number of VA bits used by the regular kernel > - * stage 1, when VA_BITS=52. > - * > - * At EL2, there is only one TTBR register, and we can't switch between > - * translation tables *and* update TCR_EL2.T0SZ at the same time. Bottom > - * line: we need to use the extended range with *both* our translation > - * tables. > - * > - * So use the maximum of the idmap VA bits and the regular kernel stage > - * 1 VA bits to assure that the hypervisor can both ID map its code page > - * and map any kernel memory. > - */ > - idmap_bits = IDMAP_VA_BITS; > - kernel_bits = vabits_actual; > - *hyp_va_bits = max(idmap_bits, kernel_bits); > - > - kvm_debug("Using %u-bit virtual addresses at EL2\n", *hyp_va_bits); > + kvm_debug("Using %u-bit virtual addresses at EL2\n", hyp_va_bits); > kvm_debug("IDMAP page: %lx\n", hyp_idmap_start); > kvm_debug("HYP VA range: %lx:%lx\n", > kern_hyp_va(PAGE_OFFSET), > @@ -2345,7 +2325,7 @@ int __init kvm_mmu_init(u32 *hyp_va_bits) > goto out; > } > > - err = kvm_pgtable_hyp_init(hyp_pgtable, *hyp_va_bits, &kvm_hyp_mm_ops); > + err = kvm_pgtable_hyp_init(hyp_pgtable, hyp_va_bits, &kvm_hyp_mm_ops); > if (err) > goto out_free_pgtable; > > @@ -2354,7 +2334,7 @@ int __init kvm_mmu_init(u32 *hyp_va_bits) > goto out_destroy_pgtable; > > io_map_base = hyp_idmap_start; > - __hyp_va_bits = *hyp_va_bits; > + __hyp_va_bits = hyp_va_bits; > return 0; > > out_destroy_pgtable: > diff --git a/arch/arm64/kvm/va_layout.c b/arch/arm64/kvm/va_layout.c > index 91b22a014610..2346f9435a71 100644 > --- a/arch/arm64/kvm/va_layout.c > +++ b/arch/arm64/kvm/va_layout.c > @@ -46,9 +46,31 @@ static void init_hyp_physvirt_offset(void) > hyp_physvirt_offset = (s64)__pa(kern_va) - (s64)hyp_va; > } > > +/* > + * Calculate the actual VA size used by the hypervisor > + */ > +__init u32 kvm_hyp_va_bits(void) > +{ > + /* > + * The ID map is always configured for 48 bits of translation, which may > + * be different from the number of VA bits used by the regular kernel > + * stage 1. > + * > + * At EL2, there is only one TTBR register, and we can't switch between > + * translation tables *and* update TCR_EL2.T0SZ at the same time. Bottom > + * line: we need to use the extended range with *both* our translation > + * tables. > + * > + * So use the maximum of the idmap VA bits and the regular kernel stage > + * 1 VA bits as the hypervisor VA size to assure that the hypervisor can > + * both ID map its code page and map any kernel memory. > + */ > + return max(IDMAP_VA_BITS, vabits_actual); > +} > + > /* > * We want to generate a hyp VA with the following format (with V == > - * vabits_actual): > + * hypervisor VA bits): > * > * 63 ... V | V-1 | V-2 .. tag_lsb | tag_lsb - 1 .. 0 > * --------------------------------------------------------- > @@ -61,10 +83,11 @@ __init void kvm_compute_layout(void) > { > phys_addr_t idmap_addr = __pa_symbol(__hyp_idmap_text_start); > u64 hyp_va_msb; > + u32 hyp_va_bits = kvm_hyp_va_bits(); > > /* Where is my RAM region? */ > - hyp_va_msb = idmap_addr & BIT(vabits_actual - 1); > - hyp_va_msb ^= BIT(vabits_actual - 1); > + hyp_va_msb = idmap_addr & BIT(hyp_va_bits - 1); > + hyp_va_msb ^= BIT(hyp_va_bits - 1); > > tag_lsb = fls64((u64)phys_to_virt(memblock_start_of_DRAM()) ^ > (u64)(high_memory - 1)); > @@ -72,9 +95,9 @@ __init void kvm_compute_layout(void) > va_mask = GENMASK_ULL(tag_lsb - 1, 0); > tag_val = hyp_va_msb; > > - if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && tag_lsb != (vabits_actual - 1)) { > + if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && tag_lsb != (hyp_va_bits - 1)) { > /* We have some free bits to insert a random tag. */ > - tag_val |= get_random_long() & GENMASK_ULL(vabits_actual - 2, tag_lsb); > + tag_val |= get_random_long() & GENMASK_ULL(hyp_va_bits - 2, tag_lsb); > } > tag_val >>= tag_lsb; > > -- > 2.52.0.457.g6b5491de43-goog > ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 0/1] KVM: arm64: Calculate hyp VA size only once 2026-01-13 19:44 [PATCH v2 0/1] KVM: arm64: Calculate hyp VA size only once Petteri Kangaslampi 2026-01-13 19:44 ` [PATCH v2 1/1] " Petteri Kangaslampi @ 2026-01-14 10:51 ` Marc Zyngier 1 sibling, 0 replies; 4+ messages in thread From: Marc Zyngier @ 2026-01-14 10:51 UTC (permalink / raw) To: kvmarm, Petteri Kangaslampi Cc: Vincent Donnefort, Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon, linux-arm-kernel, linux-kernel, Oliver Upton On Tue, 13 Jan 2026 19:44:08 +0000, Petteri Kangaslampi wrote: > All, > > Here's a reworked fix to keep VA size calculations consistent between > the MMU and layout initialization code paths. > > Previously, if the kernel was configured with a VA size of less than > 48 bits (like happens in our environment), the assumptions used to > calculate hyp_physvirt_offset in kvm_compute_layout() and the code > that determines the VA size used in the rest of the system (calculated > in kvm_mmu_init()) would get out of sync. This could eventually result > in mapping failures. > > [...] Applied to next, thanks! [1/1] KVM: arm64: Calculate hyp VA size only once commit: 8e8eb10c107e67f22f87cd8c963d30ea73f04d5f Cheers, M. -- Without deviation from the norm, progress is not possible. ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-01-14 10:51 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-01-13 19:44 [PATCH v2 0/1] KVM: arm64: Calculate hyp VA size only once Petteri Kangaslampi 2026-01-13 19:44 ` [PATCH v2 1/1] " Petteri Kangaslampi 2026-01-14 9:27 ` Vincent Donnefort 2026-01-14 10:51 ` [PATCH v2 0/1] " Marc Zyngier
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox