* [PATCH 1/9] sparc32: inspect every PROM CPU cache geometry
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 2/9] sparc32: flush the source user alias in copy_user_highpage() Magnus Lindholm
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7
Cache discovery runs from setup_arch(), before secondary CPUs are online.
Using cpu_online() to terminate the PROM walk therefore stops after the
first CPU node on SMP systems. It cannot determine the largest cache and
smallest line size across all modules when their geometry differs. Sparse
logical CPU numbering is not required to trigger this.
Walk every PROM CPU node on SMP and retain the single-node UP behavior.
Start at the first child instead of skipping directly to its sibling, and
track whether a CPU was found independently of the end sentinel. No mixed-
module hardware failure is claimed by this change.
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/mm/srmmu.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c
index b491d7b32891..49d41b33aabf 100644
--- a/arch/sparc/mm/srmmu.c
+++ b/arch/sparc/mm/srmmu.c
@@ -964,17 +964,18 @@ static void __init init_vac_layout(void)
{
phandle nd;
int cache_lines;
+ bool found_cpu = false;
char node_str[128];
#ifdef CONFIG_SMP
- int cpu = 0;
unsigned long max_size = 0;
unsigned long min_line_size = 0x10000000;
#endif
- nd = prom_getchild(prom_root_node);
- while ((nd = prom_getsibling(nd)) != 0) {
+ for (nd = prom_getchild(prom_root_node); nd != 0;
+ nd = prom_getsibling(nd)) {
prom_getstring(nd, "device_type", node_str, sizeof(node_str));
if (!strcmp(node_str, "cpu")) {
+ found_cpu = true;
vac_line_size = prom_getint(nd, "cache-line-size");
if (vac_line_size == -1) {
prom_printf("can't determine cache-line-size, halting.\n");
@@ -992,16 +993,12 @@ static void __init init_vac_layout(void)
max_size = vac_cache_size;
if (vac_line_size < min_line_size)
min_line_size = vac_line_size;
- //FIXME: cpus not contiguous!!
- cpu++;
- if (cpu >= nr_cpu_ids || !cpu_online(cpu))
- break;
#else
break;
#endif
}
}
- if (nd == 0) {
+ if (!found_cpu) {
prom_printf("No CPU nodes found, halting.\n");
prom_halt();
}
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 2/9] sparc32: flush the source user alias in copy_user_highpage()
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
2026-11-08 22:34 ` [PATCH 1/9] sparc32: inspect every PROM CPU cache geometry Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 3/9] sparc32: do not flush a NULL address in flush_dcache_folio Magnus Lindholm
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7, stable
copy_user_highpage() also handles lowmem pages. A source user alias can be
dirtied after the fork cache flush but before write protection and its TLB
shootdown complete on another CPU. This explains why flushing at fork
alone is insufficient; the historical tests below did not isolate that
particular interleaving.
The hyperSPARC external cache is virtually indexed, so index bits lie
above the page offset and one physical page can occupy 64, 128 or 256
distinct cache locations depending on cache size. Two mappings of a page
do not see each other's dirty lines.
copy_user_highpage() reads the source through a kmap_local alias, so lines
dirtied through the user mapping sit in a different set and the copy reads
stale memory. copy_user_page() then flushes page_address(page), NULL for a
highmem page, with no NULL check.
Flush the source page's user alias with flush_cache_page() before reading
it, and flush the destination through the address actually written.
Only observable on HyperSPARC: SuperSPARC's caches are physically indexed
and MBus snooped, so viking_flush_cache_all() is a window flush and a
return.
Fork/COW torture on a two-cpu SPARCstation 20: 7 of 20 runs lost writes
before, 0 of 20 after.
The source-alias omission predates Git history; the later folio conversion
also removed sparc_flush_page_to_ram()'s NULL guard. This change uses the
mapped destination address and covers both cases.
Cc: stable@vger.kernel.org
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/page_32.h | 6 ++++++
arch/sparc/mm/init_32.c | 23 +++++++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/arch/sparc/include/asm/page_32.h b/arch/sparc/include/asm/page_32.h
index 7429291fb118..c968ec33ee98 100644
--- a/arch/sparc/include/asm/page_32.h
+++ b/arch/sparc/include/asm/page_32.h
@@ -28,6 +28,12 @@
(void)(vaddr); \
} while (0)
+#define __HAVE_ARCH_COPY_USER_HIGHPAGE
+struct page;
+struct vm_area_struct;
+void copy_user_highpage(struct page *to, struct page *from,
+ unsigned long vaddr, struct vm_area_struct *vma);
+
/* passing structs on the Sparc slow us down tremendously... */
/* #define STRICT_MM_TYPECHECKS */
diff --git a/arch/sparc/mm/init_32.c b/arch/sparc/mm/init_32.c
index 2f693b70dbd8..7b53ee9f061f 100644
--- a/arch/sparc/mm/init_32.c
+++ b/arch/sparc/mm/init_32.c
@@ -103,6 +103,29 @@ void sparc_flush_page_to_ram(struct page *page)
}
EXPORT_SYMBOL(sparc_flush_page_to_ram);
+/*
+ * The kernel reads the source page through a kmap alias, which on a
+ * virtually indexed cache lands in a different set than the user mapping
+ * userspace has been dirtying. Flush that alias first, and flush the
+ * destination through the address actually written rather than
+ * page_address(), which is NULL for a highmem page.
+ */
+void copy_user_highpage(struct page *to, struct page *from,
+ unsigned long vaddr, struct vm_area_struct *vma)
+{
+ char *vfrom, *vto;
+
+ if (vma)
+ flush_cache_page(vma, vaddr, page_to_pfn(from));
+
+ vfrom = kmap_local_page(from);
+ vto = kmap_local_page(to);
+ copy_page(vto, vfrom);
+ __flush_page_to_ram((unsigned long)vto);
+ kunmap_local(vto);
+ kunmap_local(vfrom);
+}
+
void sparc_flush_folio_to_ram(struct folio *folio)
{
unsigned long vaddr = (unsigned long)folio_address(folio);
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 3/9] sparc32: do not flush a NULL address in flush_dcache_folio
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
2026-11-08 22:34 ` [PATCH 1/9] sparc32: inspect every PROM CPU cache geometry Magnus Lindholm
2026-11-08 22:34 ` [PATCH 2/9] sparc32: flush the source user alias in copy_user_highpage() Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 4/9] sparc32: flush the cache when a kmap_local mapping is installed Magnus Lindholm
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7, stable
sparc_flush_folio_to_ram() is sparc32's flush_dcache_folio() and takes the
address to flush from folio_address(). For a highmem folio that is NULL,
so it flushes virtual address 0, and 0 + PAGE_SIZE and so on for a multi-
page folio.
On HyperSPARC, hypersparc_flush_page_to_ram() probes with
ASI_M_FLUSH_PROBE, finds nothing mapped at 0 and returns, so nothing is
flushed at all.
Returning early would be wrong: flush_dcache_folio() is also the barrier
taken before reading a folio userspace may have dirtied, as in
filemap_read() when the folio is writably mapped. Fall back to the whole-
cache operation, which reaches user aliases at any colour and on SMP is
cross-called to every cpu.
Do that only when the folio is mapped by userspace; otherwise the only
other mapping is the kmap_local slot, which arch_kmap_local_pre_unmap()
writes back on teardown. Without that test block_write_end() makes every
buffered write discard 512 KB of VAC, which cost 106 s against 56 s on a
64 MB dd.
With the mapping live and a reader on another cpu, 150 of 150 reads
returned stale data before and 0 of 150 after, every first mismatch on a
32-byte boundary, the VAC line size.
The separate HyperSPARC mapped-folio fix completes the lowmem and PKMAP
colour handling. The highmem fallback can be expensive for GUP: each
mapped folio may require whole-cache maintenance on all CPUs.
Fixes: 665f64029454 ("sparc32: implement the new page table range API")
Cc: stable@vger.kernel.org
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/mm/init_32.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/sparc/mm/init_32.c b/arch/sparc/mm/init_32.c
index 7b53ee9f061f..9cf0065fc2d4 100644
--- a/arch/sparc/mm/init_32.c
+++ b/arch/sparc/mm/init_32.c
@@ -131,6 +131,16 @@ void sparc_flush_folio_to_ram(struct folio *folio)
unsigned long vaddr = (unsigned long)folio_address(folio);
unsigned int i, nr = folio_nr_pages(folio);
+ /*
+ * No kernel address to aim at. Only a user alias can be incoherent
+ * here; the kmap the kernel used is flushed when it is unmapped.
+ */
+ if (!vaddr) {
+ if (folio_mapped(folio))
+ flush_cache_all();
+ return;
+ }
+
for (i = 0; i < nr; i++)
__flush_page_to_ram(vaddr + i * PAGE_SIZE);
}
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 4/9] sparc32: flush the cache when a kmap_local mapping is installed
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
` (2 preceding siblings ...)
2026-11-08 22:34 ` [PATCH 3/9] sparc32: do not flush a NULL address in flush_dcache_folio Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 5/9] sparc32: preset modified and referenced bits in kmap PTEs Magnus Lindholm
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7, stable
sparc32 defines arch_kmap_local_pre_map() as flush_cache_all(), but
generic highmem has never called that hook. The post-conversion flush
correction moved cache maintenance into this uncalled hook, so a new kmap
can be installed while another mapping of the same page still holds dirty
lines at a different colour.
A writer dirties a MAP_SHARED page through its user mapping on one cpu
while a reader pread()s the same offset on another: on an SS-20 with two
RT626 modules all 100 iterations came back stale, 96288 words wrong. With
the flush restored, 0 of 100.
Flush from arch_kmap_local_post_map(), which generic code does call, on
both the map and the scheduler restore paths. Running after the pte is
installed is safe: the address is not handed to the caller until the hook
returns. Route it and the tlb flush through sparc32_kmap_local_post_map()
and sparc32_kmap_local_tlb_flush() so later patches have somewhere for a
cpu-specific policy.
MBus supplies only optional VA[19:12] "super-set" bits to a virtually
indexed cache, so differing colours remain a synonym problem the bus does
not resolve.
Reference: MBus Interface Specification, SPARC International, 1997, "Basic
Assumptions for Level 2", signal VA[19:12] (MAD[53:46]).
Retain global TLB invalidation. Although the slots are CPU-local, remote
translating cache backends can populate entries for them. Local TLB
maintenance requires those remote translating users to be removed first
and belongs in the later HyperSPARC optimization.
Fixes: f99e02372af2 ("sparc/mm/highmem: flush cache and TLB")
Cc: stable@vger.kernel.org
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/highmem.h | 14 ++++++++++----
arch/sparc/mm/srmmu.c | 21 +++++++++++++++++++++
2 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/arch/sparc/include/asm/highmem.h b/arch/sparc/include/asm/highmem.h
index b1b915ac604d..14b03f348823 100644
--- a/arch/sparc/include/asm/highmem.h
+++ b/arch/sparc/include/asm/highmem.h
@@ -26,6 +26,10 @@
#include <asm/vaddrs.h>
#include <asm/pgtsrmmu.h>
+void sparc32_kmap_local_cache_flush(unsigned long vaddr);
+void sparc32_kmap_local_tlb_flush(unsigned long vaddr);
+void sparc32_kmap_local_post_map(unsigned long vaddr, pte_t pteval);
+
#define kmap_prot __pgprot(SRMMU_ET_PTE | SRMMU_PRIV | SRMMU_CACHE)
extern pte_t *pkmap_page_table;
@@ -48,10 +52,12 @@ extern pte_t *pkmap_page_table;
#define flush_cache_kmaps() flush_cache_all()
/* FIXME: Use __flush_*_one(vaddr) instead of flush_*_all() -- Anton */
-#define arch_kmap_local_pre_map(vaddr, pteval) flush_cache_all()
-#define arch_kmap_local_pre_unmap(vaddr) flush_cache_all()
-#define arch_kmap_local_post_map(vaddr, pteval) flush_tlb_all()
-#define arch_kmap_local_post_unmap(vaddr) flush_tlb_all()
+#define arch_kmap_local_pre_unmap(vaddr) \
+ sparc32_kmap_local_cache_flush(vaddr)
+#define arch_kmap_local_post_map(vaddr, pteval) \
+ sparc32_kmap_local_post_map(vaddr, pteval)
+#define arch_kmap_local_post_unmap(vaddr) \
+ sparc32_kmap_local_tlb_flush(vaddr)
#endif /* __KERNEL__ */
diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c
index 49d41b33aabf..aa53ec10cd8a 100644
--- a/arch/sparc/mm/srmmu.c
+++ b/arch/sparc/mm/srmmu.c
@@ -88,6 +88,27 @@ static int is_hypersparc;
static int srmmu_cache_pagetables;
+void sparc32_kmap_local_cache_flush(unsigned long vaddr)
+{
+ flush_cache_all();
+}
+
+/*
+ * Nothing has touched the new mapping yet, so flushing other aliases of this
+ * page here is as good as doing it before the pte was set.
+ */
+void sparc32_kmap_local_post_map(unsigned long vaddr, pte_t pteval)
+{
+ sparc32_kmap_local_cache_flush(vaddr);
+ sparc32_kmap_local_tlb_flush(vaddr);
+}
+
+void sparc32_kmap_local_tlb_flush(unsigned long vaddr)
+{
+ /* Remote translating cache operations can populate these entries. */
+ flush_tlb_all();
+}
+
/* these will be initialized in srmmu_nocache_calcsize() */
static unsigned long srmmu_nocache_size;
static unsigned long srmmu_nocache_end;
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 5/9] sparc32: preset modified and referenced bits in kmap PTEs
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
` (3 preceding siblings ...)
2026-11-08 22:34 ` [PATCH 4/9] sparc32: flush the cache when a kmap_local mapping is installed Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 6/9] sparc32: colour-align private file mappings Magnus Lindholm
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7
Preset SRMMU_DIRTY and SRMMU_REF in kernel kmap PTEs, matching
PAGE_KERNEL. These mappings do not use hardware M/R transitions for user
dirty/accessed accounting. The protection applies to persistent PKMAP
entries as well as transient local mappings.
ROSS SPARC RISC User's Guide, section 4.2.1.1, describes hardware table
walks and PTE updates when a write or translating operation encounters an
unset M/R bit. Presetting the bits avoids those updates for kernel
accesses. A whole-context cache flush does not perform that translating
access.
This also provides the PTE convention needed by subsequent alias
reconciliation, but does not by itself provide that protocol.
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/highmem.h | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/arch/sparc/include/asm/highmem.h b/arch/sparc/include/asm/highmem.h
index 14b03f348823..d451232625d3 100644
--- a/arch/sparc/include/asm/highmem.h
+++ b/arch/sparc/include/asm/highmem.h
@@ -30,7 +30,12 @@ void sparc32_kmap_local_cache_flush(unsigned long vaddr);
void sparc32_kmap_local_tlb_flush(unsigned long vaddr);
void sparc32_kmap_local_post_map(unsigned long vaddr, pte_t pteval);
-#define kmap_prot __pgprot(SRMMU_ET_PTE | SRMMU_PRIV | SRMMU_CACHE)
+/*
+ * Kernel kmap mappings do not need hardware dirty/accessed tracking.
+ * Match PAGE_KERNEL and avoid M/R updates during translated accesses.
+ */
+#define kmap_prot __pgprot(SRMMU_ET_PTE | SRMMU_PRIV | SRMMU_CACHE | \
+ SRMMU_DIRTY | SRMMU_REF)
extern pte_t *pkmap_page_table;
/*
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 6/9] sparc32: colour-align private file mappings
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
` (4 preceding siblings ...)
2026-11-08 22:34 ` [PATCH 5/9] sparc32: preset modified and referenced bits in kmap PTEs Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 7/9] sparc32: implement flush_anon_page Magnus Lindholm
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7, stable
arch_get_unmapped_area() aligns MAP_SHARED so a file page is mapped at the
colour its page offset implies, but gives MAP_PRIVATE an align_mask of
zero. A private file mapping therefore lands at an arbitrary colour, and
on a virtually indexed cache it aliases any other mapping of the same
page.
Measured on an SS-20 with a 512 KB VAC: every page of a private file
mapping sat at the wrong colour, 64 of 64, at three different offsets.
Align them as sparc64 does, whenever there is a file rather than only when
the mapping is shared. MAP_FIXED is left alone: the ELF loader places
segments with MAP_PRIVATE|MAP_FIXED at addresses that need not preserve
colour, so rejecting those makes execve() fail.
Reference: MBus Interface Specification, SPARC International, 1997, signal
VA[19:12] (MAD[53:46]).
This changes alignment on HyperSPARC, where SHMLBA follows the VAC size.
Each private file mapping can consume up to SHMLBA of extra virtual space.
Cc: stable@vger.kernel.org
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/kernel/sys_sparc_32.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/arch/sparc/kernel/sys_sparc_32.c b/arch/sparc/kernel/sys_sparc_32.c
index fb31bc0c5b48..e1530cee15b2 100644
--- a/arch/sparc/kernel/sys_sparc_32.c
+++ b/arch/sparc/kernel/sys_sparc_32.c
@@ -40,6 +40,15 @@ SYSCALL_DEFINE0(getpagesize)
return PAGE_SIZE; /* Possibly older binaries want 8192 on sun4's? */
}
+/*
+ * A file page reached at two colours is two cache lines, so a kernel-chosen
+ * mapping of one sits at the colour its page offset implies - private too.
+ */
+static bool sparc_needs_colour_align(struct file *filp, unsigned long flags)
+{
+ return filp || (flags & MAP_SHARED);
+}
+
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags, vm_flags_t vm_flags)
{
struct vm_unmapped_area_info info = {};
@@ -50,7 +59,11 @@ unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, unsi
if (flags & MAP_FIXED) {
/* We do not accept a shared mapping if it would violate
- * cache aliasing constraints.
+ * cache aliasing constraints. Private file mappings are not
+ * rejected here: the ELF loader places segments with
+ * MAP_FIXED at offsets that need not preserve colour, and
+ * refusing them makes execve() fail. sparc64 draws the line
+ * in the same place.
*/
if (!file_hugepage && (flags & MAP_SHARED) &&
((addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1)))
@@ -68,7 +81,7 @@ unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, unsi
info.low_limit = addr;
info.high_limit = TASK_SIZE;
if (!file_hugepage) {
- info.align_mask = (flags & MAP_SHARED) ?
+ info.align_mask = sparc_needs_colour_align(filp, flags) ?
(PAGE_MASK & (SHMLBA - 1)) : 0;
info.align_offset = pgoff << PAGE_SHIFT;
} else {
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 7/9] sparc32: implement flush_anon_page
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
` (5 preceding siblings ...)
2026-11-08 22:34 ` [PATCH 6/9] sparc32: colour-align private file mappings Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 8/9] sparc32: retain mm_cpumask across TLB shootdowns Magnus Lindholm
2026-11-08 22:34 ` [PATCH 9/9] sparc32: enroll and order CPUs before switching address spaces Magnus Lindholm
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7, stable
Generic GUP calls flush_anon_page() after pinning a page it is about to
reach through a kernel mapping. sparc32 never implemented it, so the user
alias can still hold dirty lines the kernel mapping cannot see.
The SMP wrapper checks whether the mm mask contains another CPU and then
uses the existing cross-call mechanism; it does not target exactly that
mask. HyperSPARC's unconditional all-CPU coverage is a separate fix.
The anonymous-page hook was introduced with a generic no-op fallback, and
SPARC32 has used that fallback since then. Supply the missing architecture
operation for the GUP coherency contract.
Fixes: 03beb07664d7 ("[PATCH] Add API for flushing Anon pages")
Cc: stable@vger.kernel.org
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/cacheflush_32.h | 6 ++++++
arch/sparc/mm/init_32.c | 11 +++++++++++
2 files changed, 17 insertions(+)
diff --git a/arch/sparc/include/asm/cacheflush_32.h b/arch/sparc/include/asm/cacheflush_32.h
index 9fee0ccfccb8..a65ee310e860 100644
--- a/arch/sparc/include/asm/cacheflush_32.h
+++ b/arch/sparc/include/asm/cacheflush_32.h
@@ -35,6 +35,12 @@
#define flush_page_for_dma(addr) \
sparc32_cachetlb_ops->page_for_dma(addr)
+struct vm_area_struct;
+
+#define ARCH_HAS_FLUSH_ANON_PAGE
+void flush_anon_page(struct vm_area_struct *vma, struct page *page,
+ unsigned long vaddr);
+
void sparc_flush_page_to_ram(struct page *page);
void sparc_flush_folio_to_ram(struct folio *folio);
diff --git a/arch/sparc/mm/init_32.c b/arch/sparc/mm/init_32.c
index 9cf0065fc2d4..d0ce9d23561e 100644
--- a/arch/sparc/mm/init_32.c
+++ b/arch/sparc/mm/init_32.c
@@ -95,6 +95,17 @@ void __init arch_mm_preinit(void)
}
}
+/*
+ * Called after generic code pins an anon page it will reach through a kernel
+ * mapping; the user alias may hold dirty lines that mapping cannot see.
+ */
+void flush_anon_page(struct vm_area_struct *vma, struct page *page,
+ unsigned long vaddr)
+{
+ if (PageAnon(page))
+ flush_cache_page(vma, vaddr, page_to_pfn(page));
+}
+
void sparc_flush_page_to_ram(struct page *page)
{
unsigned long vaddr = (unsigned long)page_address(page);
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 8/9] sparc32: retain mm_cpumask across TLB shootdowns
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
` (6 preceding siblings ...)
2026-11-08 22:34 ` [PATCH 7/9] sparc32: implement flush_anon_page Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
2026-11-08 22:34 ` [PATCH 9/9] sparc32: enroll and order CPUs before switching address spaces Magnus Lindholm
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7, stable
smp_flush_tlb_mm() narrows mm_cpumask to the current CPU when mm_users is
one and current->active_mm matches. That snapshot does not exclude another
task obtaining a reference with mmget_not_zero() and using the address
space through kthread_use_mm(). Clearing its CPU bit can make subsequent
shootdowns miss translations on that CPU. Testing current->mm instead does
not serialize against that race.
Remove the narrowing. This follows the SPARC64 fix in commit bafb056ce279
("sparc64: remove mm_cpumask clearing to fix kthread_use_mm race"). Retain
the accumulated CPU mask until normal address-space teardown.
A single-threaded mm that has migrated can consequently need remote
shootdowns for the remainder of its lifetime; sun4m broadcasts them to
online peers. This trades extra cross-calls for correct coverage.
Cc: stable@vger.kernel.org
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/mm/srmmu.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c
index aa53ec10cd8a..d3634bcb048d 100644
--- a/arch/sparc/mm/srmmu.c
+++ b/arch/sparc/mm/srmmu.c
@@ -1624,12 +1624,8 @@ static void smp_flush_cache_mm(struct mm_struct *mm)
static void smp_flush_tlb_mm(struct mm_struct *mm)
{
if (mm->context != NO_CONTEXT) {
- if (any_other_mm_cpus(mm)) {
+ if (any_other_mm_cpus(mm))
xc1(local_ops->tlb_mm, (unsigned long)mm);
- if (atomic_read(&mm->mm_users) == 1 && current->active_mm == mm)
- cpumask_copy(mm_cpumask(mm),
- cpumask_of(smp_processor_id()));
- }
local_ops->tlb_mm(mm);
}
}
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 9/9] sparc32: enroll and order CPUs before switching address spaces
2026-11-08 22:34 [PATCH 0/9] sparc32: repair cache hooks, mappings and TLB tracking Magnus Lindholm
` (7 preceding siblings ...)
2026-11-08 22:34 ` [PATCH 8/9] sparc32: retain mm_cpumask across TLB shootdowns Magnus Lindholm
@ 2026-11-08 22:34 ` Magnus Lindholm
8 siblings, 0 replies; 10+ messages in thread
From: Magnus Lindholm @ 2026-11-08 22:34 UTC (permalink / raw)
To: davem, andreas; +Cc: sparclinux, linux-kernel, linmag7, stable
The task-switch macro records a CPU in the incoming active_mm mask, but
kthread_use_mm() changes address spaces without switching tasks. A
shootdown can therefore miss a CPU using that mm.
Move enrollment to switch_mm(), before installing the context. User task
switches and activate_mm() pass here, as does kthread_use_mm(). Lazy
kernel threads inherit the already active mm on that CPU; they do not
introduce a different hardware context. Keep CPU bits until mm teardown,
as in the preceding fix, and remove the duplicate switch_to() update.
SPARC32 smp_mb() currently expands to a compiler barrier. The hash lock in
set_bit() is acquired before the mask store, so it does not provide a
hardware StoreLoad fence after enrollment. Use STBAR followed by a native
LDSTUB of a private stack byte before changing the context. STBAR orders
prior stores before the atomic operation, including under PSO; LDSTUB
blocks subsequent memory operations. The memory clobber orders the
compiler. SPARC Architecture Manual V8 sections 6.2 and 6.3 describe these
rules.
The shootdown side updates PTEs using native SWAP before inspecting the mm
mask. A newly enrolling CPU must publish its bit before accessing the new
address space: either a concurrent shootdown includes it, or it observes
the updated tables. A returning CPU remains enrolled and cannot be
omitted. Do not rely on processor-specific cache maintenance or a later
refcount operation for this ordering. The local helper does not change the
general SPARC32 barrier implementation and acquires no shared lock.
Cc: stable@vger.kernel.org
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/switch_to_32.h | 1 -
arch/sparc/mm/srmmu.c | 16 ++++++++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/arch/sparc/include/asm/switch_to_32.h b/arch/sparc/include/asm/switch_to_32.h
index 42eeafcb8a41..1d9855abbc00 100644
--- a/arch/sparc/include/asm/switch_to_32.h
+++ b/arch/sparc/include/asm/switch_to_32.h
@@ -58,7 +58,6 @@ extern struct thread_info *current_set[NR_CPUS];
#define switch_to(prev, next, last) do { \
SWITCH_ENTER(prev); \
SWITCH_DO_LAZY_FPU(next); \
- cpumask_set_cpu(smp_processor_id(), mm_cpumask(next->active_mm)); \
__asm__ __volatile__( \
"sethi %%hi(here - 0x8), %%o7\n\t" \
"mov %%g6, %%g3\n\t" \
diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c
index d3634bcb048d..e5c136c8ecd7 100644
--- a/arch/sparc/mm/srmmu.c
+++ b/arch/sparc/mm/srmmu.c
@@ -467,6 +467,18 @@ static void __init sparc_context_init(int numctx)
add_to_free_ctxlist(ctx_list_pool + ctx);
}
+/* Order CPU enrollment before accesses through the new context. */
+static inline void sparc32_mm_context_barrier(void)
+{
+#ifdef CONFIG_SMP
+ unsigned char scratch = 0;
+
+ /* STBAR orders prior stores; LDSTUB blocks subsequent loads. */
+ asm volatile("stbar\n\tldstub %0, %%g0"
+ : "+m" (scratch) : : "memory");
+#endif
+}
+
void switch_mm(struct mm_struct *old_mm, struct mm_struct *mm,
struct task_struct *tsk)
{
@@ -479,6 +491,10 @@ void switch_mm(struct mm_struct *old_mm, struct mm_struct *mm,
srmmu_ctxd_set(&srmmu_context_table[mm->context], mm->pgd);
}
+ /* kthread_use_mm() can enter an mm without a task switch. */
+ cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
+ sparc32_mm_context_barrier();
+
if (sparc_cpu_model == sparc_leon)
leon_switch_mm();
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread