Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* arm64: tlbflush: Reset active_cpu on ASID rollover
@ 2026-06-09 21:34 sk
  2026-06-09 21:34 ` [PATCH 1/2] arm64: tlbflush: Don't broadcast if mm was only active on local cpu sk
  2026-06-09 21:34 ` [PATCH 2/2] arm64: tlbflush: Reset active_cpu on ASID rollover sk
  0 siblings, 2 replies; 4+ messages in thread
From: sk @ 2026-06-09 21:34 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: linux-kernel, Catalin Marinas, Will Deacon, Ryan Roberts,
	Andrew Morton, David Hildenbrand, Anshuman Khandual,
	Mike Rapoport, Dev Jain, Kevin Brodsky, Marc Zyngier,
	Oliver Upton, cl


Hi all,

This series is based on arm64: tlbflush: Don't broadcast if mm was only active on local cpu, specifically
on commit(s) starting from https://lore.kernel.org/linux-arm-kernel/20260523134710.3827956-1-linu.cherian@arm.com/. 

Changes since the previous posting: 
* Reset active_cpu to ACTIVE_CPU_NONE when a new ASID is assigned after rollover, so we don’t remain stuck in ACTIVE_CPU_MULTIPLE when the workload later settles back to one CPU. 
* Rely on the fact that flush_context() already issues a global TLB flush at ASID assignment after rollover, ensuring there are no stale TLB entries on any CPU. 
* This restores a fresh chance for processes to take the local-only flush fast path after each ASID generation rollover.

Series overview: 
* Patch 1/2: arm64: tlbflush: Reset active_cpu on ASID rollover 
* Patch 2/2: arm64: tlbflush: Don't broadcast if mm was only active on local cpu

Thanks,

Sayali Kulkarni 
sskulkarni@amperecomputing.com (Ampere)


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/2] arm64: tlbflush: Don't broadcast if mm was only active on local cpu
  2026-06-09 21:34 arm64: tlbflush: Reset active_cpu on ASID rollover sk
@ 2026-06-09 21:34 ` sk
  2026-06-09 21:34 ` [PATCH 2/2] arm64: tlbflush: Reset active_cpu on ASID rollover sk
  1 sibling, 0 replies; 4+ messages in thread
From: sk @ 2026-06-09 21:34 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: linux-kernel, Catalin Marinas, Will Deacon, Ryan Roberts,
	Andrew Morton, David Hildenbrand, Anshuman Khandual,
	Mike Rapoport, Dev Jain, Kevin Brodsky, Marc Zyngier,
	Oliver Upton, cl, Huang Ying, Linu Cherian

From: Ryan Roberts <ryan.roberts@arm.com>

There are 3 variants of tlb flush that invalidate user mappings:
flush_tlb_mm(), flush_tlb_page() and __flush_tlb_range(). All of these
would previously unconditionally broadcast their tlbis to all cpus in
the inner shareable domain.

But this is a waste of effort if we can prove that the mm for which we
are flushing the mappings has only ever been active on the local cpu. In
that case, it is safe to avoid the broadcast and simply invalidate the
current cpu.

So let's track in mm_context_t::active_cpu either the mm has never been
active on any cpu, has been active on more than 1 cpu, or has been
active on precisely 1 cpu - and in that case, which one. We update this
when switching context, being careful to ensure that it gets updated
*before* installing the mm's pgtables. On the reader side, we ensure we
read *after* the previous write(s) to the pgtable(s) that necessitated
the tlb flush have completed. This guarrantees that if a cpu that is
doing a tlb flush sees it's own id in active_cpu, then the old pgtable
entry cannot have been seen by any other cpu and we can flush only the
local cpu.

Signed-off-by: Ryan Roberts <ryan.roberts@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Tested-by: Huang Ying <ying.huang@linux.alibaba.com>
[linu.cherian@arm.com: Adapted for v7.1 flush tlb API changes]
Signed-off-by: Linu Cherian <linu.cherian@arm.com>
---
 arch/arm64/include/asm/mmu.h         |  12 +++
 arch/arm64/include/asm/mmu_context.h |   2 +
 arch/arm64/include/asm/tlbflush.h    | 127 +++++++++++++++++++++------
 arch/arm64/mm/context.c              |  30 ++++++-
 4 files changed, 141 insertions(+), 30 deletions(-)

diff --git a/arch/arm64/include/asm/mmu.h b/arch/arm64/include/asm/mmu.h
index 5e1211c540ab..0002101c1f21 100644
--- a/arch/arm64/include/asm/mmu.h
+++ b/arch/arm64/include/asm/mmu.h
@@ -16,6 +16,17 @@
 #include <linux/refcount.h>
 #include <asm/cpufeature.h>
 
+/*
+ * Sentinal values for mm_context_t::active_cpu. ACTIVE_CPU_NONE indicates the
+ * mm has never been active on any CPU. ACTIVE_CPU_MULTIPLE indicates the mm
+ * has been active on multiple CPUs. Any other value is the ID of the single
+ * CPU that the mm has been active on.
+ */
+enum active_cpu {
+	ACTIVE_CPU_NONE = UINT_MAX,
+	ACTIVE_CPU_MULTIPLE = UINT_MAX - 1,
+};
+
 typedef struct {
 	atomic64_t	id;
 #ifdef CONFIG_COMPAT
@@ -25,6 +36,7 @@ typedef struct {
 	void		*vdso;
 	unsigned long	flags;
 	u8		pkey_allocation_map;
+	unsigned int	active_cpu;
 } mm_context_t;
 
 /*
diff --git a/arch/arm64/include/asm/mmu_context.h b/arch/arm64/include/asm/mmu_context.h
index 803b68758152..101cae0c7262 100644
--- a/arch/arm64/include/asm/mmu_context.h
+++ b/arch/arm64/include/asm/mmu_context.h
@@ -172,6 +172,8 @@ init_new_context(struct task_struct *tsk, struct mm_struct *mm)
 	/* pkey 0 is the default, so always reserve it. */
 	mm->context.pkey_allocation_map = BIT(0);
 
+	WRITE_ONCE(mm->context.active_cpu, ACTIVE_CPU_NONE);
+
 	return 0;
 }
 
diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h
index c0bf5b398041..1f75bce4fa0d 100644
--- a/arch/arm64/include/asm/tlbflush.h
+++ b/arch/arm64/include/asm/tlbflush.h
@@ -164,6 +164,12 @@ static inline void sme_dvmsync_batch(struct arch_tlbflush_unmap_batch *batch)
 
 typedef void (*tlbi_op)(u64 arg);
 
+static __always_inline void vae1(u64 arg)
+{
+	__tlbi(vae1, arg);
+	__tlbi_user(vae1, arg);
+}
+
 static __always_inline void vae1is(u64 arg)
 {
 	__tlbi(vae1is, arg);
@@ -308,6 +314,74 @@ static inline void __tlbi_sync_s1ish_hyp(void)
 	__repeat_tlbi_sync(vale2is, 0);
 }
 
+typedef unsigned __bitwise tlbf_t;
+
+/* No special behaviour. */
+#define TLBF_NONE		((__force tlbf_t)0)
+
+/* Invalidate tlb entries only, leaving the page table walk cache intact. */
+#define TLBF_NOWALKCACHE	((__force tlbf_t)BIT(0))
+
+/* Skip the trailing dsb after issuing tlbi. */
+#define TLBF_NOSYNC		((__force tlbf_t)BIT(1))
+
+/* Suppress tlb notifier callbacks for this flush operation. */
+#define TLBF_NONOTIFY		((__force tlbf_t)BIT(2))
+
+/* Perform the tlbi locally without broadcasting to other CPUs. */
+#define TLBF_NOBROADCAST	((__force tlbf_t)BIT(3))
+
+/*
+ * Determines whether the user tlbi invalidation can be performed only on the
+ * local CPU or whether it needs to be broadcast. (Returns true for local).
+ * Additionally issues appropriate barrier to ensure prior pgtable updates are
+ * visible to the table walker. Must be paired with flush_tlb_user_post().
+ */
+static inline bool flush_tlb_user_pre(struct mm_struct *mm, tlbf_t flags)
+{
+	unsigned int self, active;
+	bool local;
+
+	migrate_disable();
+
+	if (flags & TLBF_NOBROADCAST) {
+		dsb(nshst);
+		return true;
+	}
+
+	self = smp_processor_id();
+
+	/*
+	 * The load of mm->context.active_cpu must not be reordered before the
+	 * store to the pgtable that necessitated this flush. This ensures that
+	 * if the value read is our cpu id, then no other cpu can have seen the
+	 * old pgtable value and therefore does not need this old value to be
+	 * flushed from its tlb. But we don't want to upgrade the dsb(ishst),
+	 * needed to make the pgtable updates visible to the walker, to a
+	 * dsb(ish) by default. So speculatively load without a barrier and if
+	 * it indicates our cpu id, then upgrade the barrier and re-load.
+	 */
+	active = READ_ONCE(mm->context.active_cpu);
+	if (active == self) {
+		dsb(ish);
+		active = READ_ONCE(mm->context.active_cpu);
+	} else {
+		dsb(ishst);
+	}
+
+	local = active == self;
+	if (!local)
+		migrate_enable();
+
+	return local;
+}
+
+static inline void flush_tlb_user_post(bool local)
+{
+	if (local)
+		migrate_enable();
+}
+
 /*
  *	TLB Invalidation
  *	================
@@ -408,12 +482,20 @@ static inline void flush_tlb_all(void)
 static inline void flush_tlb_mm(struct mm_struct *mm)
 {
 	unsigned long asid;
+	bool local;
 
-	dsb(ishst);
+	local = flush_tlb_user_pre(mm, TLBF_NONE);
 	asid = __TLBI_VADDR(0, ASID(mm));
-	__tlbi(aside1is, asid);
-	__tlbi_user(aside1is, asid);
-	__tlbi_sync_s1ish(mm);
+	if (local) {
+		__tlbi(aside1, asid);
+		__tlbi_user(aside1, asid);
+		dsb(nsh);
+	} else {
+		__tlbi(aside1is, asid);
+		__tlbi_user(aside1is, asid);
+		__tlbi_sync_s1ish(mm);
+	}
+	flush_tlb_user_post(local);
 	mmu_notifier_arch_invalidate_secondary_tlbs(mm, 0, -1UL);
 }
 
@@ -475,6 +557,12 @@ static inline void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch)
  *    operations can only span an even number of pages. We save this for last to
  *    ensure 64KB start alignment is maintained for the LPA2 case.
  */
+static __always_inline void rvae1(u64 arg)
+{
+	__tlbi(rvae1, arg);
+	__tlbi_user(rvae1, arg);
+}
+
 static __always_inline void rvae1is(u64 arg)
 {
 	__tlbi(rvae1is, arg);
@@ -573,23 +661,6 @@ static inline bool __flush_tlb_range_limit_excess(unsigned long pages,
 	return pages >= (MAX_DVM_OPS * stride) >> PAGE_SHIFT;
 }
 
-typedef unsigned __bitwise tlbf_t;
-
-/* No special behaviour. */
-#define TLBF_NONE		((__force tlbf_t)0)
-
-/* Invalidate tlb entries only, leaving the page table walk cache intact. */
-#define TLBF_NOWALKCACHE	((__force tlbf_t)BIT(0))
-
-/* Skip the trailing dsb after issuing tlbi. */
-#define TLBF_NOSYNC		((__force tlbf_t)BIT(1))
-
-/* Suppress tlb notifier callbacks for this flush operation. */
-#define TLBF_NONOTIFY		((__force tlbf_t)BIT(2))
-
-/* Perform the tlbi locally without broadcasting to other CPUs. */
-#define TLBF_NOBROADCAST	((__force tlbf_t)BIT(3))
-
 static __always_inline void __do_flush_tlb_range(struct vm_area_struct *vma,
 					unsigned long start, unsigned long end,
 					unsigned long stride, int tlb_level,
@@ -597,6 +668,7 @@ static __always_inline void __do_flush_tlb_range(struct vm_area_struct *vma,
 {
 	struct mm_struct *mm = vma->vm_mm;
 	unsigned long asid, pages;
+	bool local;
 
 	pages = (end - start) >> PAGE_SHIFT;
 
@@ -605,10 +677,9 @@ static __always_inline void __do_flush_tlb_range(struct vm_area_struct *vma,
 		return;
 	}
 
-	if (!(flags & TLBF_NOBROADCAST))
-		dsb(ishst);
-	else
-		dsb(nshst);
+	local = flush_tlb_user_pre(mm, flags);
+	if (local && !(flags & TLBF_NOBROADCAST))
+		flags |= TLBF_NOBROADCAST;
 
 	asid = ASID(mm);
 
@@ -622,8 +693,8 @@ static __always_inline void __do_flush_tlb_range(struct vm_area_struct *vma,
 					asid, tlb_level);
 		break;
 	case TLBF_NOBROADCAST:
-		/* Combination unused */
-		BUG();
+		__flush_s1_tlb_range_op(vae1, start, pages, stride,
+					asid, tlb_level);
 		break;
 	case TLBF_NOWALKCACHE | TLBF_NOBROADCAST:
 		__flush_s1_tlb_range_op(vale1, start, pages, stride,
@@ -640,6 +711,8 @@ static __always_inline void __do_flush_tlb_range(struct vm_area_struct *vma,
 		else
 			dsb(nsh);
 	}
+
+	flush_tlb_user_post(local);
 }
 
 static inline void __flush_tlb_range(struct vm_area_struct *vma,
diff --git a/arch/arm64/mm/context.c b/arch/arm64/mm/context.c
index 0f4a28b87469..f34ed78393e0 100644
--- a/arch/arm64/mm/context.c
+++ b/arch/arm64/mm/context.c
@@ -214,9 +214,10 @@ static u64 new_context(struct mm_struct *mm)
 
 void check_and_switch_context(struct mm_struct *mm)
 {
-	unsigned long flags;
-	unsigned int cpu;
+	unsigned int cpu = smp_processor_id();
 	u64 asid, old_active_asid;
+	unsigned int active;
+	unsigned long flags;
 
 	if (system_supports_cnp())
 		cpu_set_reserved_ttbr0();
@@ -251,7 +252,6 @@ void check_and_switch_context(struct mm_struct *mm)
 		atomic64_set(&mm->context.id, asid);
 	}
 
-	cpu = smp_processor_id();
 	if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))
 		local_flush_tlb_all();
 
@@ -262,6 +262,30 @@ void check_and_switch_context(struct mm_struct *mm)
 
 	arm64_apply_bp_hardening();
 
+	/*
+	 * Update mm->context.active_cpu in such a manner that we avoid cmpxchg
+	 * and dsb unless we definitely need it. If initially ACTIVE_CPU_NONE
+	 * then we are the first cpu to run so set it to our id. If initially
+	 * any id other than ours, we are the second cpu to run so set it to
+	 * ACTIVE_CPU_MULTIPLE. If we update the value then we must issue
+	 * dsb(ishst) to ensure stores to mm->context.active_cpu are ordered
+	 * against the TTBR0 write in cpu_switch_mm()/uaccess_enable(); the
+	 * store must be visible to another cpu before this cpu could have
+	 * populated any TLB entries based on the pgtables that will be
+	 * installed.
+	 */
+	active = READ_ONCE(mm->context.active_cpu);
+	if (active != cpu && active != ACTIVE_CPU_MULTIPLE) {
+		if (active == ACTIVE_CPU_NONE)
+			active = cmpxchg_relaxed(&mm->context.active_cpu,
+						 ACTIVE_CPU_NONE, cpu);
+
+		if (active != ACTIVE_CPU_NONE)
+			WRITE_ONCE(mm->context.active_cpu, ACTIVE_CPU_MULTIPLE);
+
+		dsb(ishst);
+	}
+
 	/*
 	 * Defer TTBR0_EL1 setting for user threads to uaccess_enable() when
 	 * emulating PAN.
-- 
2.47.3



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] arm64: tlbflush: Reset active_cpu on ASID rollover
  2026-06-09 21:34 arm64: tlbflush: Reset active_cpu on ASID rollover sk
  2026-06-09 21:34 ` [PATCH 1/2] arm64: tlbflush: Don't broadcast if mm was only active on local cpu sk
@ 2026-06-09 21:34 ` sk
  2026-06-10 20:57   ` kernel test robot
  1 sibling, 1 reply; 4+ messages in thread
From: sk @ 2026-06-09 21:34 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: linux-kernel, Catalin Marinas, Will Deacon, Ryan Roberts,
	Andrew Morton, David Hildenbrand, Anshuman Khandual,
	Mike Rapoport, Dev Jain, Kevin Brodsky, Marc Zyngier,
	Oliver Upton, cl, Sayali Kulkarni

From: Sayali Kulkarni <sskulkarni@amperecomputing.com>

Once active_cpu flips to ACTIVE_CPU_MULTIPLE, it never resets, even if the process settles back to one CPU. Reset it to ACTIVE_CPU_NONE when a new ASID is assigned after rollover, since flush_context() already issued a global TLB flush at that point meaning no stale TLB entries exist on any CPU.

This gives processes a fresh chance at the local-only flush fast path after each ASID generation rollover.

Signed-off-by: Sayali Kulkarni <sskulkarni@amperecomputing.com>
---
 arch/arm64/mm/context.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/mm/context.c b/arch/arm64/mm/context.c
index f34ed78393e0..0c92cc8fb4cd 100644
--- a/arch/arm64/mm/context.c
+++ b/arch/arm64/mm/context.c
@@ -250,6 +250,7 @@ void check_and_switch_context(struct mm_struct *mm)
 	if (!asid_gen_match(asid)) {
 		asid = new_context(mm);
 		atomic64_set(&mm->context.id, asid);
+		WRITE_ONCE(mm->context.active_cpu, ACTIVE_CPU_NONE);
 	}
 
 	if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))
@@ -321,6 +322,7 @@ unsigned long arm64_mm_context_get(struct mm_struct *mm)
 		 */
 		asid = new_context(mm);
 		atomic64_set(&mm->context.id, asid);
+		WRITE_ONCE(mm->context.active_cpu, ACTIVE_CPU_NONE);
 	}
 
 	nr_pinned_asids++;
-- 
2.47.3



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/2] arm64: tlbflush: Reset active_cpu on ASID rollover
  2026-06-09 21:34 ` [PATCH 2/2] arm64: tlbflush: Reset active_cpu on ASID rollover sk
@ 2026-06-10 20:57   ` kernel test robot
  0 siblings, 0 replies; 4+ messages in thread
From: kernel test robot @ 2026-06-10 20:57 UTC (permalink / raw)
  To: sk, linux-arm-kernel
  Cc: oe-kbuild-all, linux-kernel, Catalin Marinas, Will Deacon,
	Ryan Roberts, Andrew Morton, Linux Memory Management List,
	David Hildenbrand, Anshuman Khandual, Mike Rapoport, Dev Jain,
	Kevin Brodsky, Marc Zyngier, Oliver Upton, cl, Sayali Kulkarni

Hi Ryan,

kernel test robot noticed the following build warnings:

[auto build test WARNING on arm64/for-next/core]
[also build test WARNING on kvmarm/next soc/for-next linus/master v7.1-rc7 next-20260609]
[cannot apply to arm/for-next arm/fixes]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/sk-gentwo-org/arm64-tlbflush-Reset-active_cpu-on-ASID-rollover/20260610-063444
base:   https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-next/core
patch link:    https://lore.kernel.org/r/20260609213615.2788698-3-sk%40gentwo.org
patch subject: [PATCH 2/2] arm64: tlbflush: Reset active_cpu on ASID rollover
config: arm64-randconfig-r132-20260610 (https://download.01.org/0day-ci/archive/20260611/202606110405.ytZbcvhH-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 12.5.0
sparse: v0.6.5-rc1
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260611/202606110405.ytZbcvhH-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202606110405.ytZbcvhH-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
   arch/arm64/mm/context.c: note: in included file (through arch/arm64/include/asm/atomic.h, include/linux/atomic.h, include/asm-generic/bitops/atomic.h, ...):
>> arch/arm64/include/asm/cmpxchg.h:169:1: sparse: sparse: cast truncates bits from constant value (ffffffff becomes ff)
>> arch/arm64/include/asm/cmpxchg.h:169:1: sparse: sparse: cast truncates bits from constant value (ffffffff becomes ffff)

vim +169 arch/arm64/include/asm/cmpxchg.h

10b663aef1c2479 Catalin Marinas 2012-03-05  168  
305d454aaa292be Will Deacon     2015-10-08 @169  __CMPXCHG_GEN()
305d454aaa292be Will Deacon     2015-10-08  170  __CMPXCHG_GEN(_acq)
305d454aaa292be Will Deacon     2015-10-08  171  __CMPXCHG_GEN(_rel)
305d454aaa292be Will Deacon     2015-10-08  172  __CMPXCHG_GEN(_mb)
10b663aef1c2479 Catalin Marinas 2012-03-05  173  

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-06-10 20:58 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-09 21:34 arm64: tlbflush: Reset active_cpu on ASID rollover sk
2026-06-09 21:34 ` [PATCH 1/2] arm64: tlbflush: Don't broadcast if mm was only active on local cpu sk
2026-06-09 21:34 ` [PATCH 2/2] arm64: tlbflush: Reset active_cpu on ASID rollover sk
2026-06-10 20:57   ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox