[PATCH 2/4] arm64: Convert VTCR_EL2 to sysreg infratructure

Alexandru Elisei alexandru.elisei at arm.com
Wed Dec 3 03:43:14 PST 2025


Hi Marc,

On Sat, Nov 29, 2025 at 02:45:23PM +0000, Marc Zyngier wrote:
> Our definition of VTCR_EL2 is both partial (tons of fields are
> missing) and totally inconsistent (some constants are shifted,
> some are not). They are also expressed in terms of TCR, which is
> rather inconvenient.
> 
> Replace the ad-hoc definitions with the the generated version.
> This results in a bunch of additional changes to make the code
> with the unshifted nature of generated enumerations.
> 
> The register data was extracted from the BSD licenced AARCHMRS
> (AARCHMRS_OPENSOURCE_A_profile_FAT-2025-09_ASL0).

Looks correct to me:

Reviewed-by: Alexandru Elisei <alexandru.elisei at arm.com>

Thanks,
Alex

> 
> Signed-off-by: Marc Zyngier <maz at kernel.org>
> ---
>  arch/arm64/include/asm/kvm_arm.h | 52 +++++++----------------------
>  arch/arm64/include/asm/sysreg.h  |  1 -
>  arch/arm64/kvm/hyp/pgtable.c     |  8 ++---
>  arch/arm64/kvm/nested.c          |  8 ++---
>  arch/arm64/tools/sysreg          | 57 ++++++++++++++++++++++++++++++++
>  5 files changed, 76 insertions(+), 50 deletions(-)
> 
> diff --git a/arch/arm64/include/asm/kvm_arm.h b/arch/arm64/include/asm/kvm_arm.h
> index 1da290aeedce7..cd2dc378baee6 100644
> --- a/arch/arm64/include/asm/kvm_arm.h
> +++ b/arch/arm64/include/asm/kvm_arm.h
> @@ -123,37 +123,7 @@
>  #define TCR_EL2_MASK	(TCR_EL2_TG0_MASK | TCR_EL2_SH0_MASK | \
>  			 TCR_EL2_ORGN0_MASK | TCR_EL2_IRGN0_MASK)
>  
> -/* VTCR_EL2 Registers bits */
> -#define VTCR_EL2_DS		TCR_EL2_DS
> -#define VTCR_EL2_RES1		(1U << 31)
> -#define VTCR_EL2_HD		(1 << 22)
> -#define VTCR_EL2_HA		(1 << 21)
> -#define VTCR_EL2_PS_SHIFT	TCR_EL2_PS_SHIFT
> -#define VTCR_EL2_PS_MASK	TCR_EL2_PS_MASK
> -#define VTCR_EL2_TG0_MASK	TCR_TG0_MASK
> -#define VTCR_EL2_TG0_4K		TCR_TG0_4K
> -#define VTCR_EL2_TG0_16K	TCR_TG0_16K
> -#define VTCR_EL2_TG0_64K	TCR_TG0_64K
> -#define VTCR_EL2_SH0_MASK	TCR_SH0_MASK
> -#define VTCR_EL2_SH0_INNER	TCR_SH0_INNER
> -#define VTCR_EL2_ORGN0_MASK	TCR_ORGN0_MASK
> -#define VTCR_EL2_ORGN0_WBWA	TCR_ORGN0_WBWA
> -#define VTCR_EL2_IRGN0_MASK	TCR_IRGN0_MASK
> -#define VTCR_EL2_IRGN0_WBWA	TCR_IRGN0_WBWA
> -#define VTCR_EL2_SL0_SHIFT	6
> -#define VTCR_EL2_SL0_MASK	(3 << VTCR_EL2_SL0_SHIFT)
> -#define VTCR_EL2_T0SZ_MASK	0x3f
> -#define VTCR_EL2_VS_SHIFT	19
> -#define VTCR_EL2_VS_8BIT	(0 << VTCR_EL2_VS_SHIFT)
> -#define VTCR_EL2_VS_16BIT	(1 << VTCR_EL2_VS_SHIFT)
> -
> -#define VTCR_EL2_T0SZ(x)	TCR_T0SZ(x)
> -
>  /*
> - * We configure the Stage-2 page tables to always restrict the IPA space to be
> - * 40 bits wide (T0SZ = 24).  Systems with a PARange smaller than 40 bits are
> - * not known to exist and will break with this configuration.
> - *
>   * The VTCR_EL2 is configured per VM and is initialised in kvm_init_stage2_mmu.
>   *
>   * Note that when using 4K pages, we concatenate two first level page tables
> @@ -161,9 +131,6 @@
>   *
>   */
>  
> -#define VTCR_EL2_COMMON_BITS	(VTCR_EL2_SH0_INNER | VTCR_EL2_ORGN0_WBWA | \
> -				 VTCR_EL2_IRGN0_WBWA | VTCR_EL2_RES1)
> -
>  /*
>   * VTCR_EL2:SL0 indicates the entry level for Stage2 translation.
>   * Interestingly, it depends on the page size.
> @@ -195,30 +162,35 @@
>   */
>  #ifdef CONFIG_ARM64_64K_PAGES
>  
> -#define VTCR_EL2_TGRAN			VTCR_EL2_TG0_64K
> +#define VTCR_EL2_TGRAN			64K
>  #define VTCR_EL2_TGRAN_SL0_BASE		3UL
>  
>  #elif defined(CONFIG_ARM64_16K_PAGES)
>  
> -#define VTCR_EL2_TGRAN			VTCR_EL2_TG0_16K
> +#define VTCR_EL2_TGRAN			16K
>  #define VTCR_EL2_TGRAN_SL0_BASE		3UL
>  
>  #else	/* 4K */
>  
> -#define VTCR_EL2_TGRAN			VTCR_EL2_TG0_4K
> +#define VTCR_EL2_TGRAN			4K
>  #define VTCR_EL2_TGRAN_SL0_BASE		2UL
>  
>  #endif
>  
>  #define VTCR_EL2_LVLS_TO_SL0(levels)	\
> -	((VTCR_EL2_TGRAN_SL0_BASE - (4 - (levels))) << VTCR_EL2_SL0_SHIFT)
> +	FIELD_PREP(VTCR_EL2_SL0, (VTCR_EL2_TGRAN_SL0_BASE - (4 - (levels))))
>  #define VTCR_EL2_SL0_TO_LVLS(sl0)	\
>  	((sl0) + 4 - VTCR_EL2_TGRAN_SL0_BASE)
>  #define VTCR_EL2_LVLS(vtcr)		\
> -	VTCR_EL2_SL0_TO_LVLS(((vtcr) & VTCR_EL2_SL0_MASK) >> VTCR_EL2_SL0_SHIFT)
> +	VTCR_EL2_SL0_TO_LVLS(FIELD_GET(VTCR_EL2_SL0, (vtcr)))
> +
> +#define VTCR_EL2_FLAGS	(SYS_FIELD_PREP_ENUM(VTCR_EL2, SH0, INNER)	    | \
> +			 SYS_FIELD_PREP_ENUM(VTCR_EL2, ORGN0, WBWA)	    | \
> +			 SYS_FIELD_PREP_ENUM(VTCR_EL2, IRGN0, WBWA)	    | \
> +			 SYS_FIELD_PREP_ENUM(VTCR_EL2, TG0, VTCR_EL2_TGRAN) | \
> +			 VTCR_EL2_RES1)
>  
> -#define VTCR_EL2_FLAGS			(VTCR_EL2_COMMON_BITS | VTCR_EL2_TGRAN)
> -#define VTCR_EL2_IPA(vtcr)		(64 - ((vtcr) & VTCR_EL2_T0SZ_MASK))
> +#define VTCR_EL2_IPA(vtcr)		(64 - FIELD_GET(VTCR_EL2_T0SZ, (vtcr)))
>  
>  /*
>   * ARM VMSAv8-64 defines an algorithm for finding the translation table
> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> index c231d2a3e5159..acad7a7621b9e 100644
> --- a/arch/arm64/include/asm/sysreg.h
> +++ b/arch/arm64/include/asm/sysreg.h
> @@ -516,7 +516,6 @@
>  #define SYS_TTBR1_EL2			sys_reg(3, 4, 2, 0, 1)
>  #define SYS_TCR_EL2			sys_reg(3, 4, 2, 0, 2)
>  #define SYS_VTTBR_EL2			sys_reg(3, 4, 2, 1, 0)
> -#define SYS_VTCR_EL2			sys_reg(3, 4, 2, 1, 2)
>  
>  #define SYS_HAFGRTR_EL2			sys_reg(3, 4, 3, 1, 6)
>  #define SYS_SPSR_EL2			sys_reg(3, 4, 4, 0, 0)
> diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
> index 947ac1a951a5b..e0bd6a0172729 100644
> --- a/arch/arm64/kvm/hyp/pgtable.c
> +++ b/arch/arm64/kvm/hyp/pgtable.c
> @@ -583,8 +583,8 @@ u64 kvm_get_vtcr(u64 mmfr0, u64 mmfr1, u32 phys_shift)
>  	u64 vtcr = VTCR_EL2_FLAGS;
>  	s8 lvls;
>  
> -	vtcr |= kvm_get_parange(mmfr0) << VTCR_EL2_PS_SHIFT;
> -	vtcr |= VTCR_EL2_T0SZ(phys_shift);
> +	vtcr |= FIELD_PREP(VTCR_EL2_PS, kvm_get_parange(mmfr0));
> +	vtcr |= FIELD_PREP(VTCR_EL2_T0SZ, (UL(64) - phys_shift));
>  	/*
>  	 * Use a minimum 2 level page table to prevent splitting
>  	 * host PMD huge pages at stage2.
> @@ -624,9 +624,7 @@ u64 kvm_get_vtcr(u64 mmfr0, u64 mmfr1, u32 phys_shift)
>  		vtcr |= VTCR_EL2_DS;
>  
>  	/* Set the vmid bits */
> -	vtcr |= (get_vmid_bits(mmfr1) == 16) ?
> -		VTCR_EL2_VS_16BIT :
> -		VTCR_EL2_VS_8BIT;
> +	vtcr |= (get_vmid_bits(mmfr1) == 16) ? VTCR_EL2_VS : 0;
>  
>  	return vtcr;
>  }
> diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
> index 911fc99ed99d9..e1ef8930c97b3 100644
> --- a/arch/arm64/kvm/nested.c
> +++ b/arch/arm64/kvm/nested.c
> @@ -377,7 +377,7 @@ static void vtcr_to_walk_info(u64 vtcr, struct s2_walk_info *wi)
>  {
>  	wi->t0sz = vtcr & TCR_EL2_T0SZ_MASK;
>  
> -	switch (vtcr & VTCR_EL2_TG0_MASK) {
> +	switch (FIELD_GET(VTCR_EL2_TG0_MASK, vtcr)) {
>  	case VTCR_EL2_TG0_4K:
>  		wi->pgshift = 12;	 break;
>  	case VTCR_EL2_TG0_16K:
> @@ -513,7 +513,7 @@ static u8 get_guest_mapping_ttl(struct kvm_s2_mmu *mmu, u64 addr)
>  
>  	lockdep_assert_held_write(&kvm_s2_mmu_to_kvm(mmu)->mmu_lock);
>  
> -	switch (vtcr & VTCR_EL2_TG0_MASK) {
> +	switch (FIELD_GET(VTCR_EL2_TG0_MASK, vtcr)) {
>  	case VTCR_EL2_TG0_4K:
>  		ttl = (TLBI_TTL_TG_4K << 2);
>  		break;
> @@ -530,7 +530,7 @@ static u8 get_guest_mapping_ttl(struct kvm_s2_mmu *mmu, u64 addr)
>  
>  again:
>  	/* Iteratively compute the block sizes for a particular granule size */
> -	switch (vtcr & VTCR_EL2_TG0_MASK) {
> +	switch (FIELD_GET(VTCR_EL2_TG0_MASK, vtcr)) {
>  	case VTCR_EL2_TG0_4K:
>  		if	(sz < SZ_4K)	sz = SZ_4K;
>  		else if (sz < SZ_2M)	sz = SZ_2M;
> @@ -593,7 +593,7 @@ unsigned long compute_tlb_inval_range(struct kvm_s2_mmu *mmu, u64 val)
>  
>  	if (!max_size) {
>  		/* Compute the maximum extent of the invalidation */
> -		switch (mmu->tlb_vtcr & VTCR_EL2_TG0_MASK) {
> +		switch (FIELD_GET(VTCR_EL2_TG0_MASK, mmu->tlb_vtcr)) {
>  		case VTCR_EL2_TG0_4K:
>  			max_size = SZ_1G;
>  			break;
> diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
> index 9d388f87d9a13..6f43b2ae5993b 100644
> --- a/arch/arm64/tools/sysreg
> +++ b/arch/arm64/tools/sysreg
> @@ -4400,6 +4400,63 @@ Field	56:12	BADDR
>  Res0	11:0
>  EndSysreg
>  
> +Sysreg	VTCR_EL2	3	4	2	1	2
> +Res0	63:46	
> +Field	45	HDBSS
> +Field	44	HAFT
> +Res0	43:42	
> +Field	41	TL0
> +Field	40	GCSH
> +Res0	39
> +Field	38	D128
> +Field	37	S2POE
> +Field	36	S2PIE
> +Field	35	TL1
> +Field	34	AssuredOnly
> +Field	33	SL2
> +Field	32	DS
> +Res1	31
> +Field	30	NSA
> +Field	29	NSW
> +Field	28	HWU62
> +Field	27	HWU61
> +Field	26	HWU60
> +Field	25	HWU59
> +Res0	24:23
> +Field	22	HD
> +Field	21	HA
> +Res0	20
> +Enum	19	VS
> +	0b0	8BIT
> +	0b1	16BIT
> +EndEnum
> +Field	18:16	PS
> +Enum	15:14	TG0
> +	0b00	4K
> +	0b01	64K
> +	0b10	16K
> +EndEnum
> +Enum	13:12	SH0
> +	0b00	NONE
> +	0b01	OUTER
> +	0b11	INNER
> +EndEnum
> +Enum	11:10	ORGN0
> +	0b00	NC
> +	0b01	WBWA
> +	0b10	WT
> +	0b11	WBnWA
> +EndEnum
> +Enum	9:8	IRGN0
> +	0b00	NC
> +	0b01	WBWA
> +	0b10	WT
> +	0b11	WBnWA
> +EndEnum
> +Field	7:6	SL0
> +Field	5:0	T0SZ
> +EndSysreg
> +
>  Sysreg	GCSCR_EL2	3	4	2	5	0
>  Fields	GCSCR_ELx
>  EndSysreg
> -- 
> 2.47.3
> 



More information about the linux-arm-kernel mailing list