[PATCH 16/17] arm64/mm: Enable TLBIP instruction based TLB flush

From: Anshuman Khandual

Date: Wed Jul 29 2026 - 08:43:36 EST


From: Linu Cherian <linu.cherian@xxxxxxx>

Add TLBIP instruction support for TLB flush operation updating all relevant
macros as necessary. This enables back pgtable level hint and address range
support during TLB flush operation for D128 translation.

Signed-off-by: Linu Cherian <linu.cherian@xxxxxxx>
Signed-off-by: Anshuman Khandual <anshuman.khandual@xxxxxxx>
---
arch/arm64/include/asm/tlbflush.h | 68 +++++++++++++++++++++++++++----
1 file changed, 59 insertions(+), 9 deletions(-)

diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h
index c103380ec87e..3b6384b9c200 100644
--- a/arch/arm64/include/asm/tlbflush.h
+++ b/arch/arm64/include/asm/tlbflush.h
@@ -41,6 +41,25 @@

#define __tlbi(op, ...) __TLBI_N(op, ##__VA_ARGS__, 1, 0)

+#ifdef CONFIG_ARM64_D128
+#define __tlbip(op, arg) do { \
+ asm (ARM64_ASM_PREAMBLE \
+ ".arch_extension d128\n\t" \
+ "tlbip " #op ", %0, %H0\n" \
+ : : "r" (arg.full)); \
+} while (0)
+
+#define __tlbip_user(op, arg) do { \
+ if (arm64_kernel_unmapped_at_el0()) { \
+ arg.low |= USER_ASID_FLAG; \
+ __tlbip(op, (arg)); \
+ } \
+} while (0)
+
+#endif
+
+#define TLBI_ASID_MASK GENMASK_ULL(63, 48)
+
#define __tlbi_user(op, arg) do { \
if (arm64_kernel_unmapped_at_el0()) \
__tlbi(op, (arg) | USER_ASID_FLAG); \
@@ -130,9 +149,15 @@ static inline void sme_dvmsync_batch(void)

#define TLBI_TTL_UNKNOWN INT_MAX

+#ifdef CONFIG_ARM64_D128
+typedef union __u128_halves tlbi_arg_t;
+#define __tlbi_wrapper(op, arg) __tlbip(op, arg)
+#define __tlbi_user_wrapper(op, arg) __tlbip_user(op, arg)
+#else
typedef u64 tlbi_arg_t;
#define __tlbi_wrapper(op, arg) __tlbi(op, arg)
#define __tlbi_user_wrapper(op, arg) __tlbi_user(op, arg)
+#endif

typedef void (*tlbi_op)(tlbi_arg_t arg);

@@ -179,17 +204,28 @@ static __always_inline void ipas2e1is(tlbi_arg_t arg)
__tlbi_wrapper(ipas2e1is, arg);
}

-static __always_inline void __tlbi_level_asid(tlbi_op op, u64 addr, u32 level,
- u16 asid)
+static __always_inline void __tlbi_update_level(u32 level, u64 *arg)
{
- u64 arg = __TLBI_VADDR(addr, asid);
-
if (alternative_has_cap_unlikely(ARM64_HAS_ARMv8_4_TTL) && level <= 3) {
u64 ttl = level | (get_trans_granule() << 2);

- FIELD_MODIFY(TLBI_TTL_MASK, &arg, ttl);
+ FIELD_MODIFY(TLBI_TTL_MASK, arg, ttl);
}
+}
+
+static __always_inline void __tlbi_level_asid(tlbi_op op, u64 addr, u32 level, u16 asid)
+{
+#ifdef CONFIG_ARM64_D128
+ union __u128_halves arg;
+
+ arg.low = FIELD_PREP(TLBI_ASID_MASK, asid);
+ __tlbi_update_level(level, &arg.low);
+ arg.high = addr >> 12;
+#else
+ u64 arg = __TLBI_VADDR(addr, asid);

+ __tlbi_update_level(level, &arg);
+#endif
op(arg);
}

@@ -475,19 +511,33 @@ static __always_inline void ripas2e1is(tlbi_arg_t arg)
__tlbi_wrapper(ripas2e1is, arg);
}

-static __always_inline void __tlbi_range(tlbi_op op, u64 addr,
- u16 asid, int scale, int num,
- u32 level, bool lpa2)
+static __always_inline u64 __tlbi_range_args_encode_comm(u16 asid, int scale, int num, u32 level)
{
u64 arg = 0;

- arg |= FIELD_PREP(TLBIR_BADDR_MASK, addr >> (lpa2 ? 16 : PAGE_SHIFT));
arg |= FIELD_PREP(TLBIR_TTL_MASK, level > 3 ? 0 : level);
arg |= FIELD_PREP(TLBIR_NUM_MASK, num);
arg |= FIELD_PREP(TLBIR_SCALE_MASK, scale);
arg |= FIELD_PREP(TLBIR_TG_MASK, get_trans_granule());
arg |= FIELD_PREP(TLBIR_ASID_MASK, asid);

+ return arg;
+}
+
+static __always_inline void __tlbi_range(tlbi_op op, u64 addr,
+ u16 asid, int scale, int num,
+ u32 level, bool lpa2)
+{
+#ifdef CONFIG_ARM64_D128
+ union __u128_halves arg;
+
+ arg.low = __tlbi_range_args_encode_comm(asid, scale, num, level);
+ arg.high = addr >> 12;
+#else
+ u64 arg = __tlbi_range_args_encode_comm(asid, scale, num, level);
+
+ arg |= FIELD_PREP(TLBIR_BADDR_MASK, addr >> (lpa2 ? 16 : PAGE_SHIFT));
+#endif
op(arg);
}

--
2.43.0