Re: [RFC V1 16/16] arm64/mm: Add initial support for FEAT_D128 page tables
From: Usama Arif <hidden>
Date: 2026-02-26 14:10:34
Also in:
linux-mm, lkml
On Tue, 24 Feb 2026 10:41:53 +0530 Anshuman Khandual [off-list ref] wrote:
quoted hunk ↗ jump to hunk
Add build time support for FEAT_D128 page tables with a new Kconfig option i.e CONFIG_ARM64_D128. When selected, PTE types become 128 bits wide and PTE bits are mapped to their new locations. Besides the basic page table geometry is also updated since each table page now holds half the number of entries (aka PTRS_PER_PXX) as it did previously. Since FEAT_D128 exclusively supports the permission indirection style for page table entry permission management, given kernel compiled for FEAT_D128 requires both FEAT_S1PIE and FEAT_D128. If these architecture features are not present at boot, the kernel panics just like it does when there is a granule size mismatch. TTBR0/1_EL1 and PAR_EL1 registers become 128 bit wide when D128 is enabled, thus requiring MSRR/MRRS instructions for their updates. Because PA_BITS is still capped at 52 bits, MRS/MSR instructions are currently sufficient for the register accesses that basically operate on the lower 64 bits. Although entire 128 bits for these registers get cleared during boot via MSRR. Add support for TLBIP instruction for TLB flush macros with level hint and address range operations. Although existing TLBI based TLB flush would have been sufficient given PA_BITS is still capped at 52, but then it would have lacked both level hint and range support. This enables support for all granule size, VA_BITS and PA_BITS combination. Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Will Deacon <will@kernel.org> Cc: Ryan Roberts <ryan.roberts@arm.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Anshuman Khandual <redacted> --- arch/arm64/Kconfig | 39 ++++++- arch/arm64/Makefile | 4 + arch/arm64/include/asm/assembler.h | 4 +- arch/arm64/include/asm/el2_setup.h | 9 ++ arch/arm64/include/asm/pgtable-hwdef.h | 137 +++++++++++++++++++++++++ arch/arm64/include/asm/pgtable-prot.h | 18 +++- arch/arm64/include/asm/pgtable-types.h | 9 ++ arch/arm64/include/asm/pgtable.h | 56 +++++++++- arch/arm64/include/asm/smp.h | 1 + arch/arm64/include/asm/tlbflush.h | 65 ++++++++++++ arch/arm64/kernel/head.S | 12 +++ arch/arm64/mm/proc.S | 25 ++++- 12 files changed, 372 insertions(+), 7 deletions(-)diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 38dba5f7e4d2..aaf910295c39 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig@@ -309,6 +309,10 @@ config GCC_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS def_bool CC_IS_GCC depends on $(cc-option,-fpatchable-function-entry=2) +config CC_SUPPORTS_LSE128 + def_bool CC_IS_GCC + depends on $(cc-option, -march=armv8.1-a+lse128) + config 64BIT def_bool y@@ -395,6 +399,16 @@ config FIX_EARLYCON_MEM config PGTABLE_LEVELS int + default 4 if ARM64_D128 && ARM64_4K_PAGES && ARM64_VA_BITS_39 + default 5 if ARM64_D128 && ARM64_4K_PAGES && ARM64_VA_BITS_48 + default 5 if ARM64_D128 && ARM64_4K_PAGES && ARM64_VA_BITS_52 + default 3 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_36 + default 4 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_47 + default 4 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_48 + default 4 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_52 + default 3 if ARM64_D128 && ARM64_64K_PAGES && ARM64_VA_BITS_42 + default 3 if ARM64_D128 && ARM64_64K_PAGES && ARM64_VA_BITS_48 + default 3 if ARM64_D128 && ARM64_64K_PAGES && ARM64_VA_BITS_52 default 2 if ARM64_16K_PAGES && ARM64_VA_BITS_36 default 2 if ARM64_64K_PAGES && ARM64_VA_BITS_42 default 3 if ARM64_64K_PAGES && (ARM64_VA_BITS_48 || ARM64_VA_BITS_52)@@ -1504,7 +1518,7 @@ config ARM64_PA_BITS config ARM64_LPA2 def_bool y - depends on ARM64_PA_BITS_52 && !ARM64_64K_PAGES + depends on ARM64_PA_BITS_52 && !ARM64_64K_PAGES && !ARM64_D128 choice prompt "Endianness"@@ -2195,6 +2209,29 @@ config ARM64_HAFT endmenu # "ARMv8.9 architectural features" +menu "ARMv9.3 architectural features" + +config AS_HAS_ARMV9_3 + def_bool $(cc-option,-Wa$(comma)-march=armv9.3-a) + +config ARM64_D128 + bool "Enable support for 128 bit page table (FEAT_D128)" + depends on ARCH_SUPPORTS_INT128 + depends on CC_SUPPORTS_LSE128 + depends on AS_HAS_ARMV9_3 + depends on EXPERT + depends on !VIRTUALIZATION + depends on !KASAN + depends on !UNMAP_KERNEL_AT_EL0 + default n + help + ARMv9.3 introduces FEAT_D128, which provides a 128 bit page + table format, along with related instructions. + + If unsure, say Y. +
Should this say, If unsure, say N?
+endmenu # "ARMv9.3 architectural features" +
[...]
quoted hunk ↗ jump to hunk
diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h index 9c93ffbcc1e0..a221a1a9b87e 100644 --- a/arch/arm64/include/asm/tlbflush.h +++ b/arch/arm64/include/asm/tlbflush.h@@ -49,6 +49,19 @@ #define __tlbi(op, ...) __TLBI_N(op, ##__VA_ARGS__, 1, 0) +#ifdef CONFIG_ARM64_D128 +#define __tlbip(op, arg1, arg2) do { \ + u128 value = 0; \ + value |= (u128)arg2 << 64; \ + value |= (u128)arg1; \ + \ + asm (ARM64_ASM_PREAMBLE \ + ".arch_extension d128\n\t" \ + "tlbip " #op ", %0, %H0\n" \ + : : "r" (value)); \ +} while (0) +#endif + #define __tlbi_user(op, arg) do { \ if (arm64_kernel_unmapped_at_el0()) \ __tlbi(op, (arg) | USER_ASID_FLAG); \@@ -128,6 +141,46 @@ static inline unsigned long get_trans_granule(void) __tlbi_level(op, (arg | USER_ASID_FLAG), level); \ } while (0) +#ifdef CONFIG_ARM64_D128 +/* + * + * TLBIP Encoding + * + * +------------+-----------------+-------+-------+------------------+ + * | RES0 | BADDR | ASID | TTL | RES0 | + * +------------------------------+-------+-------+------------------+ + * |127 108|107 64|63 48|47 44|43 0| + */ + +#define __tlbip_user(op, arg, addr) do { \ + if (arm64_kernel_unmapped_at_el0()) \ + __tlbip(op, (arg) | USER_ASID_FLAG, addr); \ +} while (0) +/* + * FEAT_TTL being mandatory from armv8.4 and FEAT_D128 is available + * only from armv9.4, we dont need the capability check for TTL. + */ +#define __TLBIP_ARGS(asid, level) \ + ({ \ + u64 arg = 0; \ + \ + arg |= FIELD_PREP(TLBI_ASID_MASK, (asid)); \ + if ((level) >= 0 && (level) <= 3) { \ + arg |= FIELD_PREP(TLBI_TG_MASK, get_trans_granule()); \ + arg |= FIELD_PREP(TLBI_LVL_MASK, (level)); \ + } \ + arg; \ + }) \ + +#define __tlb_asid_level(op, addr, asid, level, tlb_user) do { \ + u64 arg1 = __TLBIP_ARGS(asid, level); \ + u64 arg2 = (addr) >> 12; \
Does 12 over here represent PAGE_SHIFT? If so, would it break 16K and 64K PAGE_SIZE?
+ \
+ __tlbip(op, arg1, arg2); \
+ if (tlb_user) \
+ __tlbip_user(op, arg1, arg2); \
+} while (0)
+#else
#define __tlb_asid_level(op, addr, asid, level, tlb_user) do { \
u64 arg1; \
\