* [PATCH 0/3] alpha: load the MMU context on a direct mm switch
@ 2026-09-04 16:23 Magnus Lindholm
2026-09-04 16:23 ` [PATCH 1/3] alpha: do not clear remote MMU contexts in migrate_flush_tlb_page() Magnus Lindholm
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Magnus Lindholm @ 2026-09-04 16:23 UTC (permalink / raw)
To: richard.henderson, mattst88, linux-kernel, linux-alpha; +Cc: linmag7
Three fixes for how alpha handles an mm that is switched in directly
rather than by the scheduler.
Patch 1 removes a stale clearing loop in migrate_flush_tlb_page() that
patch 2 depends on being gone. Patch 2 stops the TLB shootdown shortcut
from skipping a CPU that is borrowing an mm through kthread_use_mm(),
using mm->context[cpu] as a lockless publication of which CPUs may be
using the mm once patch 1 makes every write to it single-writer. Patch 3
then makes those direct mm switches actually load the MMU context, which
they currently do not: kthread_use_mm() and sched_force_init_mm() call
switch_mm_irqs_off() directly rather than through the scheduler, so the
hardware context is never installed and the task keeps running under
whatever was loaded before.
Reproducing it. A KUnit test was written for patch 3; source can be made
available on request. It uses kunit_attach_mm(), which calls
kthread_use_mm(), and then compares the loaded context against current->mm:
# alpha_use_mm_loads_context: EXPECTATION FAILED
Expected pcb->ptbr == mm_to_ptbr(current->mm), but
pcb->ptbr == 384 (0x180)
mm_to_ptbr(current->mm) == 12555 (0x310b)
0x180 is swapper_pg_dir: the kernel thread is running on the page tables
it had before the switch. The pcb.asn check in the same test passes,
which is the signature - ev5_switch_mm() does write the ASN, so it is the
load that is missing rather than the bookkeeping.
Do not detect this by dereferencing the borrowed mm's user addresses.
do_page_fault() resolves faults against current->mm and never reloads the
context, so with a stale ptbr the same access faults indefinitely.
Testing. ES40, EV68AL (21264C) Tsunami, 3 CPUs, v7.2-rc6.
before after
KUnit alpha_mmu_context 0 of 2 pass 2 of 2 pass
Patch 2 was measured rather than argued. Dropping the shortcut outright
is the obvious fix and costs far too much, so it tests mm->context[cpu]
instead:
fork/s, single-threaded shortcut as before 1360
shortcut removed 1050 -23%
patch 1 1360
Medians of seven, seven and twelve runs, spread 1352-1367, 1046-1052 and
1340-1366. The unchanged throughput shows the shortcut remains effective
for this workload, and neither the barrier nor the context marking shows
above the noise on this machine.
No regressions in the wider suite: glibc malloc-check 25/25 and four
related tests 10/10 each, the copy-on-write and writeback reproducers
from the previous series clean, and the same again under continuous
compaction with 359768 folios migrated during the run.
One adjacent problem is deliberately not addressed. enter_lazy_tlb() sets
pcb.ptbr for the borrowed mm without setting pcb.asn, so a kernel thread
switched in through PAL_swpctx loads one address space's page tables
against another's ASN. I have not found an observable failure from that
mismatch outside kernel-thread user accesses, which go through
kthread_use_mm() and are a matched pair after patch 2. Changing it would
mean touching the VPTB self-map behaviour on every lazy switch, with no
reproducer to justify the risk.
Patches 2 and 3 have no Cc: stable: kthread_use_mm() is the only
reachable caller on alpha through KUnit's kunit_attach_mm():
lib/tests/usercopy_kunit.c, lib/tests/kunit_iov_iter.c,
mm/kasan/kasan_test_c.c and drivers/android/tests/binder_alloc_kunit.c all
map user memory that way; sched_force_init_mm() and the driver callers
need configs or hardware alpha does not have. No ordinary alpha kernel
reaches either fix.
Patch 1 is different. migrate_flush_tlb_page() runs under plain
CONFIG_COMPACTION, and the clearing loop it removes can fire from ordinary
lazy-TLB retention on a single-threaded process's previous CPU, not just
kthread borrowing. The compaction stress testing above exercises that path
and has not caught it doing observable harm, so patch 1 also carries no Cc:
stable, but for that reason, not for being unreachable.
This applies on top of the "alpha: fix stale TLB translations breaking
copy-on-write and writeback" series and depends on it: patch 2 rewrites the
same flush_tlb_page() and flush_tlb_mm() shortcuts that series touches, and
does not apply without it.
https://lore.kernel.org/linux-alpha/20260810193902.3286353-1-linmag7@gmail.com/
Magnus Lindholm (3):
alpha: do not clear remote MMU contexts in migrate_flush_tlb_page()
alpha: do not skip TLB shootdown IPIs for kthread-borrowed mms
alpha: load the MMU context when switch_mm() switches the current task
arch/alpha/include/asm/mmu_context.h | 11 +++++--
arch/alpha/kernel/smp.c | 48 ++++++++++++++--------------
arch/alpha/mm/fault.c | 2 +-
arch/alpha/mm/tlbflush.c | 16 ----------
4 files changed, 34 insertions(+), 43 deletions(-)
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/3] alpha: do not clear remote MMU contexts in migrate_flush_tlb_page()
2026-09-04 16:23 [PATCH 0/3] alpha: load the MMU context on a direct mm switch Magnus Lindholm
@ 2026-09-04 16:23 ` Magnus Lindholm
2026-09-04 16:23 ` [PATCH 2/3] alpha: do not skip TLB shootdown IPIs for kthread-borrowed mms Magnus Lindholm
2026-09-04 16:23 ` [PATCH 3/3] alpha: load the MMU context when switch_mm() switches the current task Magnus Lindholm
2 siblings, 0 replies; 4+ messages in thread
From: Magnus Lindholm @ 2026-09-04 16:23 UTC (permalink / raw)
To: richard.henderson, mattst88, linux-kernel, linux-alpha; +Cc: linmag7
After the on_each_cpu() rendezvous, migrate_flush_tlb_page() walks the
other CPUs and zeroes their mm->context[cpu] when mm_users is at most one,
described as mimicking flush_tlb_mm()'s mm_users<=1 optimization.
It is not one. flush_tlb_mm() tests mm_users before deciding whether to
send the IPIs; here every CPU has already been visited and waited for, so
nothing is saved. The callback has also just set each CPU's own slot
correctly, so the loop only overwrites it and does so while that CPU may
still be running in the address space.
Remove it. Every runtime write to mm->context[] then targets the writing
CPU's own slot, which is the invariant the next patch depends on when it
reads those slots to decide whether a shootdown can be skipped.
Fixes: dd5712f3379c ("alpha: fix user-space corruption during memory compaction")
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/mm/tlbflush.c | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/arch/alpha/mm/tlbflush.c b/arch/alpha/mm/tlbflush.c
index ccbc317b9a34..239c72b8a741 100644
--- a/arch/alpha/mm/tlbflush.c
+++ b/arch/alpha/mm/tlbflush.c
@@ -90,22 +90,6 @@ void migrate_flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)
*/
preempt_disable();
on_each_cpu(ipi_flush_mm_and_page, &d, 1);
-
- /*
- * mimic flush_tlb_mm()'s mm_users<=1 optimization.
- */
- if (atomic_read(&mm->mm_users) <= 1) {
-
- int cpu, this_cpu;
- this_cpu = smp_processor_id();
-
- for (cpu = 0; cpu < NR_CPUS; cpu++) {
- if (!cpu_online(cpu) || cpu == this_cpu)
- continue;
- if (READ_ONCE(mm->context[cpu]))
- WRITE_ONCE(mm->context[cpu], 0);
- }
- }
preempt_enable();
}
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/3] alpha: do not skip TLB shootdown IPIs for kthread-borrowed mms
2026-09-04 16:23 [PATCH 0/3] alpha: load the MMU context on a direct mm switch Magnus Lindholm
2026-09-04 16:23 ` [PATCH 1/3] alpha: do not clear remote MMU contexts in migrate_flush_tlb_page() Magnus Lindholm
@ 2026-09-04 16:23 ` Magnus Lindholm
2026-09-04 16:23 ` [PATCH 3/3] alpha: load the MMU context when switch_mm() switches the current task Magnus Lindholm
2 siblings, 0 replies; 4+ messages in thread
From: Magnus Lindholm @ 2026-09-04 16:23 UTC (permalink / raw)
To: richard.henderson, mattst88, linux-kernel, linux-alpha; +Cc: linmag7
flush_tlb_mm(), flush_tlb_page() and flush_icache_user_page() skip the
shootdown IPI when mm_users <= 1, on the assumption that no other CPU can
be running the mm. A task that borrows an mm through kthread_use_mm()
takes mmgrab() rather than mmget(), so it never appears in mm_users, and
kthread_use_mm() may be handed an mm that is already the caller's
active_mm and loaded on that CPU. Such a CPU was not only left without
an IPI, its mm->context[cpu] was cleared from under it.
mm->context[cpu] is already the record of which CPUs hold an ASN for the
mm. Test it rather than clearing it: take the shortcut only when no
other CPU has one, and otherwise fall through to the IPI, which
invalidates those CPUs properly. A CPU that merely ran the mm in the
past also holds a context and now costs an IPI, which errs in the safe
direction.
Dropping that clearing loop leaves every runtime write to mm->context[]
targeting the writing CPU's own slot, so the array becomes a lockless
publication of which CPUs may be using the mm, with a single writer per
slot. Mark the two stores that are now observed from other CPUs;
flush_tlb_other() already uses WRITE_ONCE(). The uniprocessor
flush_icache_user_page() is left alone, as nothing reads another CPU's
slot there, and init_new_context() runs before the mm is shared.
The reads also need ordering against the changes that led to the flush.
A CPU that publishes a context is in turn ordered before it goes on to
access the mm, by two different barriers: kthread_use_mm() issues one
through mmdrop_lazy_tlb() for the initial direct switch, and if the task
later migrates, the context published from ev5_switch_mm() is followed by
the scheduler's post-switch barrier, on alpha the mb() in
arch_spin_unlock() from finish_lock_switch(), as described in
Documentation/scheduler/membarrier.rst. So a CPU either already holds a
context and is seen here, or it allocates a fresh one before going on to
use the mm, and a fresh ASN carries nothing over from the previous
context.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/include/asm/mmu_context.h | 2 +-
arch/alpha/kernel/smp.c | 48 ++++++++++++++--------------
arch/alpha/mm/fault.c | 2 +-
3 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/arch/alpha/include/asm/mmu_context.h b/arch/alpha/include/asm/mmu_context.h
index 825d3b9605c9..a829f557396d 100644
--- a/arch/alpha/include/asm/mmu_context.h
+++ b/arch/alpha/include/asm/mmu_context.h
@@ -147,7 +147,7 @@ ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm,
mmc = next_mm->context[cpu];
if ((mmc ^ asn) & ~HARDWARE_ASN_MASK) {
mmc = __get_new_mm_context(next_mm, cpu);
- next_mm->context[cpu] = mmc;
+ WRITE_ONCE(next_mm->context[cpu], mmc);
}
#ifdef CONFIG_SMP
else
diff --git a/arch/alpha/kernel/smp.c b/arch/alpha/kernel/smp.c
index e21bc3920bec..c4d12d8312a7 100644
--- a/arch/alpha/kernel/smp.c
+++ b/arch/alpha/kernel/smp.c
@@ -631,6 +631,24 @@ ipi_flush_tlb_mm(void *x)
flush_tlb_other(mm);
}
+/* True if a CPU other than this one holds an ASN for MM. */
+static bool
+mm_context_elsewhere(struct mm_struct *mm)
+{
+ int cpu, this_cpu = smp_processor_id();
+
+ /* Pairs with the barrier the publishing CPU issues before using MM. */
+ smp_mb();
+
+ for_each_online_cpu(cpu) {
+ if (cpu == this_cpu)
+ continue;
+ if (READ_ONCE(mm->context[cpu]))
+ return true;
+ }
+ return false;
+}
+
void
flush_tlb_mm(struct mm_struct *mm)
{
@@ -638,14 +656,8 @@ flush_tlb_mm(struct mm_struct *mm)
if (mm == current->active_mm) {
flush_tlb_current(mm);
- if (atomic_read(&mm->mm_users) <= 1) {
- int cpu, this_cpu = smp_processor_id();
- for (cpu = 0; cpu < NR_CPUS; cpu++) {
- if (!cpu_online(cpu) || cpu == this_cpu)
- continue;
- if (mm->context[cpu])
- mm->context[cpu] = 0;
- }
+ if (atomic_read(&mm->mm_users) <= 1 &&
+ !mm_context_elsewhere(mm)) {
preempt_enable();
return;
}
@@ -690,14 +702,8 @@ flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)
/* As in ipi_flush_tlb_page(): a targeted tbi() needs MM current. */
if (mm == current->mm) {
flush_tlb_current_page(mm, vma, addr);
- if (atomic_read(&mm->mm_users) <= 1) {
- int cpu, this_cpu = smp_processor_id();
- for (cpu = 0; cpu < NR_CPUS; cpu++) {
- if (!cpu_online(cpu) || cpu == this_cpu)
- continue;
- if (mm->context[cpu])
- mm->context[cpu] = 0;
- }
+ if (atomic_read(&mm->mm_users) <= 1 &&
+ !mm_context_elsewhere(mm)) {
preempt_enable();
return;
}
@@ -747,14 +753,8 @@ flush_icache_user_page(struct vm_area_struct *vma, struct page *page,
if (mm == current->active_mm) {
__load_new_mm_context(mm);
- if (atomic_read(&mm->mm_users) <= 1) {
- int cpu, this_cpu = smp_processor_id();
- for (cpu = 0; cpu < NR_CPUS; cpu++) {
- if (!cpu_online(cpu) || cpu == this_cpu)
- continue;
- if (mm->context[cpu])
- mm->context[cpu] = 0;
- }
+ if (atomic_read(&mm->mm_users) <= 1 &&
+ !mm_context_elsewhere(mm)) {
preempt_enable();
return;
}
diff --git a/arch/alpha/mm/fault.c b/arch/alpha/mm/fault.c
index a9816bbc9f34..7bbba9010dac 100644
--- a/arch/alpha/mm/fault.c
+++ b/arch/alpha/mm/fault.c
@@ -45,7 +45,7 @@ __load_new_mm_context(struct mm_struct *next_mm)
struct pcb_struct *pcb;
mmc = __get_new_mm_context(next_mm, smp_processor_id());
- next_mm->context[smp_processor_id()] = mmc;
+ WRITE_ONCE(next_mm->context[smp_processor_id()], mmc);
pcb = ¤t_thread_info()->pcb;
pcb->asn = mmc & HARDWARE_ASN_MASK;
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 3/3] alpha: load the MMU context when switch_mm() switches the current task
2026-09-04 16:23 [PATCH 0/3] alpha: load the MMU context on a direct mm switch Magnus Lindholm
2026-09-04 16:23 ` [PATCH 1/3] alpha: do not clear remote MMU contexts in migrate_flush_tlb_page() Magnus Lindholm
2026-09-04 16:23 ` [PATCH 2/3] alpha: do not skip TLB shootdown IPIs for kthread-borrowed mms Magnus Lindholm
@ 2026-09-04 16:23 ` Magnus Lindholm
2 siblings, 0 replies; 4+ messages in thread
From: Magnus Lindholm @ 2026-09-04 16:23 UTC (permalink / raw)
To: richard.henderson, mattst88, linux-kernel, linux-alpha; +Cc: linmag7
ev5_switch_mm() only prepares the incoming PCB. The context is installed
by PAL_swpctx, which alpha_switch_to() issues against that PCB on the way
out of the scheduler.
Two callers reach switch_mm_irqs_off() without going through
alpha_switch_to(): kthread_use_mm(), which borrows an mm for the current
kernel thread, and sched_force_init_mm() on the CPU-hotplug teardown path.
Neither explicitly loads the context, so the task can carry on running
under whatever was loaded before while current->mm says otherwise. Its
user accesses can therefore resolve in the wrong address space, and
because do_page_fault() resolves faults against current->mm without
reloading, a fault taken that way repeats indefinitely.
sched_force_init_mm() needs CONFIG_HOTPLUG_CPU, which alpha does not
support, so kthread_use_mm() is the only one of the two reachable in
practice; the fix below tests the caller's identity rather than
special-casing either one.
The scheduler passes the incoming task, which is not current until
alpha_switch_to() runs; both direct callers pass current. Test for that
and load the context the way activate_mm() does. Both hold interrupts
disabled across switch_mm_irqs_off(), so this completes before any
shootdown can be taken and needs no asn_lock handshake.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/include/asm/mmu_context.h | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/arch/alpha/include/asm/mmu_context.h b/arch/alpha/include/asm/mmu_context.h
index a829f557396d..caa6a9c4e3ca 100644
--- a/arch/alpha/include/asm/mmu_context.h
+++ b/arch/alpha/include/asm/mmu_context.h
@@ -130,6 +130,8 @@ __get_new_mm_context(struct mm_struct *mm, long cpu)
return next;
}
+extern void __load_new_mm_context(struct mm_struct *);
+
__EXTERN_INLINE void
ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm,
struct task_struct *next)
@@ -139,6 +141,12 @@ ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm,
unsigned long mmc;
long cpu = smp_processor_id();
+ /* A direct switch never reaches alpha_switch_to(); load it here. */
+ if (next == current) {
+ __load_new_mm_context(next_mm);
+ return;
+ }
+
#ifdef CONFIG_SMP
cpu_data[cpu].asn_lock = 1;
barrier();
@@ -160,7 +168,6 @@ ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm,
task_thread_info(next)->pcb.asn = mmc & HARDWARE_ASN_MASK;
}
-extern void __load_new_mm_context(struct mm_struct *);
asmlinkage void do_page_fault(unsigned long address, unsigned long mmcsr,
long cause, struct pt_regs *regs);
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-04 16:24 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-04 16:23 [PATCH 0/3] alpha: load the MMU context on a direct mm switch Magnus Lindholm
2026-09-04 16:23 ` [PATCH 1/3] alpha: do not clear remote MMU contexts in migrate_flush_tlb_page() Magnus Lindholm
2026-09-04 16:23 ` [PATCH 2/3] alpha: do not skip TLB shootdown IPIs for kthread-borrowed mms Magnus Lindholm
2026-09-04 16:23 ` [PATCH 3/3] alpha: load the MMU context when switch_mm() switches the current task Magnus Lindholm
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®