[PATCH] arm64: Add 5-level page table support for 4K pages with 52-bit VA

Baoquan He baoquan.he at linux.dev
Tue Jun 9 17:46:26 PDT 2026


On 06/09/26 at 01:34pm, Xiaolei Wang wrote:
> Humble ping...
> 
> Can anyone help review this patch?

Are you posting a patch for makedumpfile utility? If yes, you should
indicate that in patch subject, such as

[PATCH][makedumpfile] arm64: Add 5-level page table support for 4K pages with

I thought it's an arm64 kernel patch.

> 
> thanks
> 
> xiaolei
> 
> On 5/19/26 11:17, Xiaolei Wang wrote:
> > From: Xiaolei wang <xiaolei.wang at windriver.com>
> > 
> > makedumpfile fails with "PAGE SIZE 0x1000 and VA Bits 52 not supported"
> > on ARM64 systems configured with CONFIG_ARM64_VA_BITS_52=y and 4KB page
> > size. This combination requires 5-level page tables, which makedumpfile
> > does not currently support.
> > 
> > Add support for 5-level page tables on ARM64 by:
> > - Adding the P4D page table level definitions and helpers
> > - Updating pud_t to contain p4d_t (reflecting the correct hierarchy)
> > - Extending calculate_plat_config() to recognize 4K + 52-bit VA as
> >    pgtable_level=5, and 16K + 52-bit VA as pgtable_level=4
> > - Adding p4d_offset() to handle the additional translation level
> > - Updating vaddr_to_paddr_arm64() to walk through PGD->P4D->PUD->PMD->PTE
> > - Using vabits_actual instead of va_bits in calculate_plat_config() and
> >    PTRS_PER_PGD to correctly handle the case where the kernel is compiled
> >    with VA_BITS=52 but the hardware only supports 48-bit VA (runtime
> >    fallback to 4-level)
> > 
> > The pgtable_level assignments follow arch/arm64/Kconfig:
> > - 4K  + 52-bit VA → 5-level (the only 5-level configuration)
> > - 16K + 52-bit VA → 4-level
> > - 4K  + 48-bit VA → 4-level
> > - 16K + 48-bit VA → 4-level
> > 
> > Fixes: https://github.com/makedumpfile/makedumpfile/issues/18
> > Signed-off-by: Xiaolei Wang <xiaolei.wang at windriver.com>
> > ---
> >   arch/arm64.c | 89 +++++++++++++++++++++++++++++++++++++++++++---------
> >   1 file changed, 74 insertions(+), 15 deletions(-)
> > 
> > diff --git a/arch/arm64.c b/arch/arm64.c
> > index 1072178..f3f33e4 100644
> > --- a/arch/arm64.c
> > +++ b/arch/arm64.c
> > @@ -29,6 +29,10 @@ typedef struct {
> >   typedef struct {
> >   	pgd_t pgd;
> > +} p4d_t;
> > +
> > +typedef struct {
> > +	p4d_t p4d;
> >   } pud_t;
> >   typedef struct {
> > @@ -42,6 +46,7 @@ typedef struct {
> >   #define __pte(x)	((pte_t) { (x) } )
> >   #define __pmd(x)	((pmd_t) { (x) } )
> >   #define __pud(x)	((pud_t) { (x) } )
> > +#define __p4d(x)	((p4d_t) { (x) } )
> >   #define __pgd(x)	((pgd_t) { (x) } )
> >   static int lpa_52_bit_support_available;
> > @@ -62,7 +67,8 @@ static unsigned long kimage_voffset;
> >   #define PAGE_OFFSET_48		((0xffffffffffffffffUL) << 48)
> >   #define pgd_val(x)		((x).pgd)
> > -#define pud_val(x)		(pgd_val((x).pgd))
> > +#define p4d_val(x)		(pgd_val((x).pgd))
> > +#define pud_val(x)		(p4d_val((x).p4d))
> >   #define pmd_val(x)		(pud_val((x).pud))
> >   #define pte_val(x)		((x).pte)
> > @@ -75,6 +81,7 @@ static unsigned long kimage_voffset;
> >   typedef unsigned long pteval_t;
> >   typedef unsigned long pmdval_t;
> >   typedef unsigned long pudval_t;
> > +typedef unsigned long p4dval_t;
> >   typedef unsigned long pgdval_t;
> >   #define PAGE_SHIFT	PAGESHIFT()
> > @@ -101,6 +108,14 @@ typedef unsigned long pgdval_t;
> >   #define PUD_MASK		(~(PUD_SIZE-1))
> >   #define PTRS_PER_PUD		PTRS_PER_PTE
> > +/*
> > + * P4D_SHIFT determines the size a level 0 page table entry can map.
> > + */
> > +#define P4D_SHIFT		ARM64_HW_PGTABLE_LEVEL_SHIFT(0)
> > +#define P4D_SIZE		(_AC(1, UL) << P4D_SHIFT)
> > +#define P4D_MASK		(~(P4D_SIZE-1))
> > +#define PTRS_PER_P4D		PTRS_PER_PTE
> > +
> >   /*
> >    * PGDIR_SHIFT determines the size a top-level page table entry can map
> >    * (depending on the configuration, this level can be 0, 1 or 2).
> > @@ -108,7 +123,7 @@ typedef unsigned long pgdval_t;
> >   #define PGDIR_SHIFT		ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - (pgtable_level))
> >   #define PGDIR_SIZE		(_AC(1, UL) << PGDIR_SHIFT)
> >   #define PGDIR_MASK		(~(PGDIR_SIZE-1))
> > -#define PTRS_PER_PGD		(1 << ((va_bits) - PGDIR_SHIFT))
> > +#define PTRS_PER_PGD		(1 << ((vabits_actual) - PGDIR_SHIFT))
> >   /*
> >    * Section address mask and size definitions.
> > @@ -178,6 +193,22 @@ pgd_pte(pgd_t pgd)
> >   #define __pgd_to_phys(pgd)		__pte_to_phys(pgd_pte(pgd))
> >   #define pgd_offset(pgd, vaddr)		((pgd_t *)(pgd) + pgd_index(vaddr))
> > +/* P4D */
> > +#define p4d_index(vaddr)		(((vaddr) >> P4D_SHIFT) & (PTRS_PER_P4D - 1))
> > +
> > +static inline pte_t p4d_pte(p4d_t p4d)
> > +{
> > +	return __pte(p4d_val(p4d));
> > +}
> > +
> > +#define __p4d_to_phys(p4d)		__pte_to_phys(p4d_pte(p4d))
> > +
> > +static inline unsigned long
> > +p4d_page_paddr(p4d_t p4d)
> > +{
> > +	return __p4d_to_phys(p4d);
> > +}
> > +
> >   static inline pte_t pud_pte(pud_t pud)
> >   {
> >   	return __pte(pud_val(pud));
> > @@ -237,13 +268,22 @@ __pa(unsigned long vaddr)
> >   		return (vaddr - kimage_voffset);
> >   }
> > +static p4d_t *
> > +p4d_offset(pgd_t *pgda, pgd_t *pgdv, unsigned long vaddr)
> > +{
> > +	if (pgtable_level > 4)
> > +		return (p4d_t *)(pgd_page_paddr(*pgdv) + p4d_index(vaddr) * sizeof(p4d_t));
> > +	else
> > +		return (p4d_t *)(pgda);
> > +}
> > +
> >   static pud_t *
> > -pud_offset(pgd_t *pgda, pgd_t *pgdv, unsigned long vaddr)
> > +pud_offset(p4d_t *p4da, p4d_t *p4dv, unsigned long vaddr)
> >   {
> >   	if (pgtable_level > 3)
> > -		return (pud_t *)(pgd_page_paddr(*pgdv) + pud_index(vaddr) * sizeof(pud_t));
> > +		return (pud_t *)(p4d_page_paddr(*p4dv) + pud_index(vaddr) * sizeof(pud_t));
> >   	else
> > -		return (pud_t *)(pgda);
> > +		return (pud_t *)(p4da);
> >   }
> >   static pmd_t *
> > @@ -257,20 +297,32 @@ pmd_offset(pud_t *puda, pud_t *pudv, unsigned long vaddr)
> >   static int calculate_plat_config(void)
> >   {
> > -	/* derive pgtable_level as per arch/arm64/Kconfig */
> > -	if ((PAGESIZE() == SZ_16K && va_bits == 36) ||
> > -			(PAGESIZE() == SZ_64K && va_bits == 42)) {
> > +	/*
> > +	 * Derive pgtable_level as per arch/arm64/Kconfig.
> > +	 * Use vabits_actual (runtime VA size) rather than va_bits (compile-time)
> > +	 * because the kernel may reduce the VA space at boot if the hardware
> > +	 * does not support 52-bit VA (LVA). In that case va_bits=52 but
> > +	 * vabits_actual=48, and the page tables are 4-level, not 5-level.
> > +	 */
> > +	int va = vabits_actual;
> > +
> > +	if ((PAGESIZE() == SZ_16K && va == 36) ||
> > +			(PAGESIZE() == SZ_64K && va == 42)) {
> >   		pgtable_level = 2;
> > -	} else if ((PAGESIZE() == SZ_64K && va_bits == 48) ||
> > -			(PAGESIZE() == SZ_64K && va_bits == 52) ||
> > -			(PAGESIZE() == SZ_4K && va_bits == 39) ||
> > -			(PAGESIZE() == SZ_16K && va_bits == 47)) {
> > +	} else if ((PAGESIZE() == SZ_64K && va == 48) ||
> > +			(PAGESIZE() == SZ_64K && va == 52) ||
> > +			(PAGESIZE() == SZ_4K && va == 39) ||
> > +			(PAGESIZE() == SZ_16K && va == 47)) {
> >   		pgtable_level = 3;
> > -	} else if ((PAGESIZE() != SZ_64K && va_bits == 48)) {
> > +	} else if ((PAGESIZE() == SZ_4K && va == 48) ||
> > +			(PAGESIZE() == SZ_16K && va == 48) ||
> > +			(PAGESIZE() == SZ_16K && va == 52)) {
> >   		pgtable_level = 4;
> > +	} else if (PAGESIZE() == SZ_4K && va == 52) {
> > +		pgtable_level = 5;
> >   	} else {
> >   		ERRMSG("PAGE SIZE %#lx and VA Bits %d not supported\n",
> > -				PAGESIZE(), va_bits);
> > +				PAGESIZE(), va);
> >   		return FALSE;
> >   	}
> >   	DEBUG_MSG("pgtable_level: %d\n", pgtable_level);
> > @@ -548,6 +600,7 @@ vaddr_to_paddr_arm64(unsigned long vaddr)
> >   	unsigned long long paddr = NOT_PADDR;
> >   	unsigned long long swapper_phys;
> >   	pgd_t	*pgda, pgdv;
> > +	p4d_t	*p4da, p4dv;
> >   	pud_t	*puda, pudv;
> >   	pmd_t	*pmda, pmdv;
> >   	pte_t 	*ptea, ptev;
> > @@ -565,7 +618,13 @@ vaddr_to_paddr_arm64(unsigned long vaddr)
> >   		return NOT_PADDR;
> >   	}
> > -	puda = pud_offset(pgda, &pgdv, vaddr);
> > +	p4da = p4d_offset(pgda, &pgdv, vaddr);
> > +	if (!readmem(PADDR, (unsigned long long)p4da, &p4dv, sizeof(p4dv))) {
> > +		ERRMSG("Can't read p4d\n");
> > +		return NOT_PADDR;
> > +	}
> > +
> > +	puda = pud_offset(p4da, &p4dv, vaddr);
> >   	if (!readmem(PADDR, (unsigned long long)puda, &pudv, sizeof(pudv))) {
> >   		ERRMSG("Can't read pud\n");
> >   		return NOT_PADDR;
> 



More information about the kexec mailing list