* [PATCH v3 0/4] sparc64: D-cache alias flushing fixes
@ 2026-10-21 23:33 Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 1/4] sparc64: skip the trap block in xcall_deliver() when there is no target Imre Kaloz
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Imre Kaloz @ 2026-10-21 23:33 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Matthew Wilcox (Oracle), Mike Rapoport (IBM),
Andrew Morton, sparclinux, linux-kernel, linux-mm
These fix D-cache alias flushing on sparc64 CPUs with a virtually
indexed L1 D-cache (Spitfire and Cheetah).
1/4 makes xcall_deliver() bail out before it touches the trap block
when there is no other CPU to call, so an early cross call, before
init_IRQ() sets up the mondo blocks, can't end up writing to physical
address 0.
2/4 flushes only the page a PTE maps instead of its whole folio, which
removes about a million redundant page flushes from a colour-changing
mremap() of a large folio (7.3 s on a Sun Fire V240).
3/4 implements flush_cache_vmap() and flush_cache_vunmap(), which have
been no-ops. Stale vmalloc lines corrupt BPF programs and module data
on UltraSPARC III.
4/4 passes the number of PTEs to move_pte(), as Andreas suggested. It
touches mm/mremap.c and include/linux/pgtable.h, so it needs an ack
from the mm folks.
1-3 don't depend on 4 and can go in on their own. 2 and 3 are for
stable (3 needs 2), 1 and 4 aren't. The series is on top of the sparc
for-next branch.
Changes in v3:
- new 1/4 and 4/4, following Andreas' review
- 2/4: reworded the message and the move_pte() comment, marked the
UP #endif
- 3/4: reworded the message, dropped the Suggested-by (I have no
email address for nsafran1217, the Link credits them), noted the
stable dependency on 2/4
- picked up Stian's and Adrian's Tested-by tags
v2: https://lore.kernel.org/r/20260928132102.1707-1-kaloz@kernel.org
Imre Kaloz (4):
sparc64: skip the trap block in xcall_deliver() when there is no
target
sparc64: flush only the aliased page, not its whole folio
sparc64: flush vmalloc ranges from the D-cache on map and unmap
mm: pass the number of PTEs to move_pte()
arch/sparc/include/asm/cacheflush_64.h | 9 ++--
arch/sparc/include/asm/pgtable_64.h | 14 ++++--
arch/sparc/kernel/smp_64.c | 51 +++++++++++++++-----
arch/sparc/mm/init_64.c | 65 ++++++++++++++++++++++++++
arch/sparc/mm/tlb.c | 2 +-
arch/sparc/mm/ultra.S | 25 ++++++++++
include/linux/pgtable.h | 2 +-
mm/mremap.c | 2 +-
8 files changed, 148 insertions(+), 22 deletions(-)
base-commit: 2bc58762a3d74e42de129d717ae3ba10ecf0ab96
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/4] sparc64: skip the trap block in xcall_deliver() when there is no target
2026-10-21 23:33 [PATCH v3 0/4] sparc64: D-cache alias flushing fixes Imre Kaloz
@ 2026-10-21 23:33 ` Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 2/4] sparc64: flush only the aliased page, not its whole folio Imre Kaloz
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Imre Kaloz @ 2026-10-21 23:33 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Matthew Wilcox (Oracle), Mike Rapoport (IBM),
Andrew Morton, sparclinux, linux-kernel, linux-mm
xcall_deliver() writes the mondo data and the target CPU list through
the trap block's physical addresses before it knows whether any CPU is
targeted. These addresses stay zero until init_IRQ() sets them up, so a
cross call made earlier, while only the boot CPU is online, would write
starting at physical address 0.
Return before touching the trap block when no other online CPU is in
the mask.
Signed-off-by: Imre Kaloz <kaloz@kernel.org>
---
arch/sparc/kernel/smp_64.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/sparc/kernel/smp_64.c b/arch/sparc/kernel/smp_64.c
index 371460e34484..4240c129c5fe 100644
--- a/arch/sparc/kernel/smp_64.c
+++ b/arch/sparc/kernel/smp_64.c
@@ -801,6 +801,15 @@ static void xcall_deliver(u64 data0, u64 data1, u64 data2, const cpumask_t *mask
local_irq_save(flags);
this_cpu = smp_processor_id();
+
+ /* The mondo block and cpu list are not set up until init_IRQ(),
+ * so do not touch them unless there is a cpu to send to.
+ */
+ if (cpumask_any_and_but(mask, cpu_online_mask, this_cpu) >= nr_cpu_ids) {
+ local_irq_restore(flags);
+ return;
+ }
+
tb = &trap_block[this_cpu];
mondo = __va(tb->cpu_mondo_block_pa);
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 2/4] sparc64: flush only the aliased page, not its whole folio
2026-10-21 23:33 [PATCH v3 0/4] sparc64: D-cache alias flushing fixes Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 1/4] sparc64: skip the trap block in xcall_deliver() when there is no target Imre Kaloz
@ 2026-10-21 23:33 ` Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 3/4] sparc64: flush vmalloc ranges from the D-cache on map and unmap Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 4/4] mm: pass the number of PTEs to move_pte() Imre Kaloz
3 siblings, 0 replies; 5+ messages in thread
From: Imre Kaloz @ 2026-10-21 23:33 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Matthew Wilcox (Oracle), Mike Rapoport (IBM),
Andrew Morton, sparclinux, linux-kernel, linux-mm, stable,
Stian Halseth, John Paul Adrian Glaubitz
Since the conversion to folios, move_pte() and tlb_batch_add() flushed
the D-cache of every page in the folio a PTE maps. Both are called once
per PTE, so a range backed by a large folio had each of its pages
flushed once per PTE of that folio. On SMP, each page flush on Spitfire
and Cheetah is a cross-call to all other online CPUs.
Flush only the page the PTE maps instead, as before the conversion.
Both callers see the PTEs of a range one at a time, so every page of
the range still gets its own flush. flush_dcache_folio_all() has no
other users and is turned back into flush_dcache_page_all().
On a Sun Fire V240, an mremap() that moves the 1024 PTEs of a large
folio to the other colour took 7.3 s, about a million redundant page
flushes.
Fixes: 1a10a44dfc1d ("sparc64: implement the new page table range API")
Cc: stable@vger.kernel.org
Tested-by: Stian Halseth <stian@itx.no> # Sun Fire V240, SPARC T4-1
Tested-by: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
Signed-off-by: Imre Kaloz <kaloz@kernel.org>
---
arch/sparc/include/asm/cacheflush_64.h | 3 +--
arch/sparc/include/asm/pgtable_64.h | 8 ++++++--
arch/sparc/kernel/smp_64.c | 24 +++++++++++++-----------
arch/sparc/mm/init_64.c | 22 ++++++++++++++++++++++
arch/sparc/mm/tlb.c | 2 +-
5 files changed, 43 insertions(+), 16 deletions(-)
diff --git a/arch/sparc/include/asm/cacheflush_64.h b/arch/sparc/include/asm/cacheflush_64.h
index 06092572c045..02c969417e7b 100644
--- a/arch/sparc/include/asm/cacheflush_64.h
+++ b/arch/sparc/include/asm/cacheflush_64.h
@@ -38,11 +38,10 @@ void __flush_dcache_page(void *addr, int flush_icache);
void flush_dcache_folio_impl(struct folio *folio);
#ifdef CONFIG_SMP
void smp_flush_dcache_folio_impl(struct folio *folio, int cpu);
-void flush_dcache_folio_all(struct mm_struct *mm, struct folio *folio);
#else
#define smp_flush_dcache_folio_impl(folio, cpu) flush_dcache_folio_impl(folio)
-#define flush_dcache_folio_all(mm, folio) flush_dcache_folio_impl(folio)
#endif
+void flush_dcache_page_all(struct mm_struct *mm, struct page *page);
void __flush_dcache_range(unsigned long start, unsigned long end);
#define ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE 1
diff --git a/arch/sparc/include/asm/pgtable_64.h b/arch/sparc/include/asm/pgtable_64.h
index 0837ebbc5dce..1430489b9ac5 100644
--- a/arch/sparc/include/asm/pgtable_64.h
+++ b/arch/sparc/include/asm/pgtable_64.h
@@ -946,6 +946,10 @@ static inline void set_ptes(struct mm_struct *mm, unsigned long addr,
set_pte_at((mm), (addr), (ptep), __pte(0UL))
#ifdef DCACHE_ALIASING_POSSIBLE
+/* This flushes only the page the PTE maps, so it relies on move_ptes()
+ * calling it for one PTE at a time, which the default pte_batch_hint()
+ * guarantees.
+ */
#define __HAVE_ARCH_MOVE_PTE
#define move_pte(pte, old_addr, new_addr) \
({ \
@@ -955,8 +959,8 @@ static inline void set_ptes(struct mm_struct *mm, unsigned long addr,
\
if (pfn_valid(this_pfn) && \
(((old_addr) ^ (new_addr)) & (1 << 13))) \
- flush_dcache_folio_all(current->mm, \
- page_folio(pfn_to_page(this_pfn))); \
+ flush_dcache_page_all(current->mm, \
+ pfn_to_page(this_pfn)); \
} \
newpte; \
})
diff --git a/arch/sparc/kernel/smp_64.c b/arch/sparc/kernel/smp_64.c
index 4240c129c5fe..6681f5263865 100644
--- a/arch/sparc/kernel/smp_64.c
+++ b/arch/sparc/kernel/smp_64.c
@@ -991,8 +991,9 @@ void smp_flush_dcache_folio_impl(struct folio *folio, int cpu)
put_cpu();
}
-void flush_dcache_folio_all(struct mm_struct *mm, struct folio *folio)
+void flush_dcache_page_all(struct mm_struct *mm, struct page *page)
{
+ struct folio *folio = page_folio(page);
void *pg_addr;
u64 data0;
@@ -1005,7 +1006,7 @@ void flush_dcache_folio_all(struct mm_struct *mm, struct folio *folio)
atomic_inc(&dcpage_flushes);
#endif
data0 = 0;
- pg_addr = folio_address(folio);
+ pg_addr = page_address(page);
if (tlb_type == spitfire) {
data0 = ((u64)&xcall_flush_dcache_page_spitfire);
if (folio_flush_mapping(folio) != NULL)
@@ -1016,18 +1017,19 @@ void flush_dcache_folio_all(struct mm_struct *mm, struct folio *folio)
#endif
}
if (data0) {
- unsigned int i, nr = folio_nr_pages(folio);
-
- for (i = 0; i < nr; i++) {
- xcall_deliver(data0, __pa(pg_addr),
- (u64) pg_addr, cpu_online_mask);
+ xcall_deliver(data0, __pa(pg_addr),
+ (u64)pg_addr, cpu_online_mask);
#ifdef CONFIG_DEBUG_DCFLUSH
- atomic_inc(&dcpage_flushes_xcall);
+ atomic_inc(&dcpage_flushes_xcall);
#endif
- pg_addr += PAGE_SIZE;
- }
}
- __local_flush_dcache_folio(folio);
+#ifdef DCACHE_ALIASING_POSSIBLE
+ __flush_dcache_page(pg_addr,
+ tlb_type == spitfire && folio_flush_mapping(folio));
+#else
+ if (tlb_type == spitfire && folio_flush_mapping(folio))
+ __flush_icache_page(__pa(pg_addr));
+#endif
preempt_enable();
}
diff --git a/arch/sparc/mm/init_64.c b/arch/sparc/mm/init_64.c
index 103db4683b16..8d01aab43cc3 100644
--- a/arch/sparc/mm/init_64.c
+++ b/arch/sparc/mm/init_64.c
@@ -214,6 +214,28 @@ inline void flush_dcache_folio_impl(struct folio *folio)
#endif
}
+#ifndef CONFIG_SMP
+void flush_dcache_page_all(struct mm_struct *mm, struct page *page)
+{
+ struct folio *folio = page_folio(page);
+
+ if (tlb_type == hypervisor)
+ return;
+
+#ifdef CONFIG_DEBUG_DCFLUSH
+ atomic_inc(&dcpage_flushes);
+#endif
+
+#ifdef DCACHE_ALIASING_POSSIBLE
+ __flush_dcache_page(page_address(page),
+ tlb_type == spitfire && folio_flush_mapping(folio));
+#else
+ if (tlb_type == spitfire && folio_flush_mapping(folio))
+ __flush_icache_page(page_to_phys(page));
+#endif
+}
+#endif /* !CONFIG_SMP */
+
#define PG_dcache_dirty PG_arch_1
#define PG_dcache_cpu_shift 32UL
#define PG_dcache_cpu_mask \
diff --git a/arch/sparc/mm/tlb.c b/arch/sparc/mm/tlb.c
index 6d9dd5eb1328..1221814ca0e1 100644
--- a/arch/sparc/mm/tlb.c
+++ b/arch/sparc/mm/tlb.c
@@ -144,7 +144,7 @@ void tlb_batch_add(struct mm_struct *mm, unsigned long vaddr,
paddr = (unsigned long) page_address(page);
if ((paddr ^ vaddr) & (1 << 13))
- flush_dcache_folio_all(mm, folio);
+ flush_dcache_page_all(mm, page);
}
no_cache_flush:
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 3/4] sparc64: flush vmalloc ranges from the D-cache on map and unmap
2026-10-21 23:33 [PATCH v3 0/4] sparc64: D-cache alias flushing fixes Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 1/4] sparc64: skip the trap block in xcall_deliver() when there is no target Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 2/4] sparc64: flush only the aliased page, not its whole folio Imre Kaloz
@ 2026-10-21 23:33 ` Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 4/4] mm: pass the number of PTEs to move_pte() Imre Kaloz
3 siblings, 0 replies; 5+ messages in thread
From: Imre Kaloz @ 2026-10-21 23:33 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Matthew Wilcox (Oracle), Mike Rapoport (IBM),
Andrew Morton, sparclinux, linux-kernel, linux-mm, stable,
Stian Halseth, John Paul Adrian Glaubitz
The L1 D-cache of Spitfire and Cheetah is indexed with VA bit 13, but
flush_cache_vmap() and flush_cache_vunmap() are no-ops. Lines loaded
through a vmalloc address survive the unmap, and once the page is
written through the other colour, e.g. zeroed through the linear
mapping, a later mapping of the old colour reads stale data. Anonymous
user pages arrive the same way, as tlb_batch_add() does not flush them.
On UltraSPARC III this corrupts BPF programs and module data.
Flush each page on every CPU on map and unmap, or the whole D-cache in
one cross call once the range exceeds its size.
flush_cache_vmap() also runs on the vmemmap, from sparse_init() before
init_IRQ() sets up the cross-call mondo blocks. The vmemmap has no
aliases to flush, so skip addresses outside vmalloc and module space.
Now that xcall_deliver() leaves the trap block alone when there is no
target CPU, this check only saves pointless flushes. Stable kernels
lack that change and need the check, since without it
flush_dcache_all()'s cross call from sparse_init() would write starting
at physical address 0.
Link: https://github.com/sparclinux/issues/issues/29#issuecomment-5041121375
Closes: https://github.com/sparclinux/issues/issues/29
Cc: stable@vger.kernel.org # requires "sparc64: flush only the aliased page, not its whole folio"
Tested-by: Stian Halseth <stian@itx.no> # Sun Fire V240, SPARC T4-1
Tested-by: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
Signed-off-by: Imre Kaloz <kaloz@kernel.org>
---
arch/sparc/include/asm/cacheflush_64.h | 6 ++--
arch/sparc/kernel/smp_64.c | 18 +++++++++++
arch/sparc/mm/init_64.c | 43 ++++++++++++++++++++++++++
arch/sparc/mm/ultra.S | 25 +++++++++++++++
4 files changed, 90 insertions(+), 2 deletions(-)
diff --git a/arch/sparc/include/asm/cacheflush_64.h b/arch/sparc/include/asm/cacheflush_64.h
index 02c969417e7b..54a2d37008e6 100644
--- a/arch/sparc/include/asm/cacheflush_64.h
+++ b/arch/sparc/include/asm/cacheflush_64.h
@@ -42,6 +42,8 @@ void smp_flush_dcache_folio_impl(struct folio *folio, int cpu);
#define smp_flush_dcache_folio_impl(folio, cpu) flush_dcache_folio_impl(folio)
#endif
void flush_dcache_page_all(struct mm_struct *mm, struct page *page);
+void __flush_dcache_all(unsigned long size, unsigned long line_size);
+void flush_dcache_all(void);
void __flush_dcache_range(unsigned long start, unsigned long end);
#define ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE 1
@@ -73,9 +75,9 @@ void flush_ptrace_access(struct vm_area_struct *, struct page *,
#define flush_dcache_mmap_lock(mapping) do { } while (0)
#define flush_dcache_mmap_unlock(mapping) do { } while (0)
-#define flush_cache_vmap(start, end) do { } while (0)
+void flush_cache_vmap(unsigned long start, unsigned long end);
#define flush_cache_vmap_early(start, end) do { } while (0)
-#define flush_cache_vunmap(start, end) do { } while (0)
+#define flush_cache_vunmap(start, end) flush_cache_vmap(start, end)
#endif /* !__ASSEMBLER__ */
diff --git a/arch/sparc/kernel/smp_64.c b/arch/sparc/kernel/smp_64.c
index 6681f5263865..ed6900b5d562 100644
--- a/arch/sparc/kernel/smp_64.c
+++ b/arch/sparc/kernel/smp_64.c
@@ -924,6 +924,7 @@ extern unsigned long xcall_kgdb_capture;
#ifdef DCACHE_ALIASING_POSSIBLE
extern unsigned long xcall_flush_dcache_page_cheetah;
+extern unsigned long xcall_flush_dcache_all;
#endif
extern unsigned long xcall_flush_dcache_page_spitfire;
@@ -1034,6 +1035,23 @@ void flush_dcache_page_all(struct mm_struct *mm, struct page *page)
preempt_enable();
}
+#ifdef DCACHE_ALIASING_POSSIBLE
+void flush_dcache_all(void)
+{
+ unsigned long size, line_size;
+
+ if (tlb_type == hypervisor)
+ return;
+
+ preempt_disable();
+ size = local_cpu_data().dcache_size;
+ line_size = local_cpu_data().dcache_line_size;
+ smp_cross_call(&xcall_flush_dcache_all, 0, size, line_size);
+ __flush_dcache_all(size, line_size);
+ preempt_enable();
+}
+#endif
+
#ifdef CONFIG_KGDB
void kgdb_roundup_cpus(void)
{
diff --git a/arch/sparc/mm/init_64.c b/arch/sparc/mm/init_64.c
index 8d01aab43cc3..d1f94b8d6312 100644
--- a/arch/sparc/mm/init_64.c
+++ b/arch/sparc/mm/init_64.c
@@ -27,6 +27,7 @@
#include <linux/percpu.h>
#include <linux/mmzone.h>
#include <linux/gfp.h>
+#include <linux/vmalloc.h>
#include <asm/head.h>
#include <asm/page.h>
@@ -234,6 +235,17 @@ void flush_dcache_page_all(struct mm_struct *mm, struct page *page)
__flush_icache_page(page_to_phys(page));
#endif
}
+
+#ifdef DCACHE_ALIASING_POSSIBLE
+void flush_dcache_all(void)
+{
+ if (tlb_type == hypervisor)
+ return;
+
+ __flush_dcache_all(local_cpu_data().dcache_size,
+ local_cpu_data().dcache_line_size);
+}
+#endif
#endif /* !CONFIG_SMP */
#define PG_dcache_dirty PG_arch_1
@@ -3072,6 +3084,37 @@ arch_initcall(report_memory);
#define do_flush_tlb_kernel_range __flush_tlb_kernel_range
#endif
+/*
+ * The L1 D-cache is indexed with VA bit 13, so lines loaded through a
+ * vmalloc address outlive the mapping. Stores through a mapping of the
+ * other colour do not update them, and a later load through their colour
+ * returns stale data. Flush at unmap so vmalloc's lines do not follow
+ * the page back to the allocator, and at map time since anonymous user
+ * pages are freed without a flush (see tlb_batch_add()). Past the
+ * D-cache size, one whole-cache flush is cheaper than a cross call per
+ * page.
+ */
+void flush_cache_vmap(unsigned long start, unsigned long end)
+{
+#ifdef DCACHE_ALIASING_POSSIBLE
+ /* The vmemmap has no aliases and is mapped before cross calls work. */
+ if (tlb_type == hypervisor || !is_vmalloc_or_module_addr((void *)start))
+ return;
+
+ if (end - start > cpu_data(raw_smp_processor_id()).dcache_size) {
+ flush_dcache_all();
+ return;
+ }
+
+ for (; start < end; start += PAGE_SIZE) {
+ struct page *page = vmalloc_to_page((void *)start);
+
+ if (page)
+ flush_dcache_page_all(NULL, page);
+ }
+#endif
+}
+
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
if (start < HI_OBP_ADDRESS && end > LOW_OBP_ADDRESS) {
diff --git a/arch/sparc/mm/ultra.S b/arch/sparc/mm/ultra.S
index 70e658d107e0..ff190670a235 100644
--- a/arch/sparc/mm/ultra.S
+++ b/arch/sparc/mm/ultra.S
@@ -242,6 +242,20 @@ __flush_dcache_page: /* %o0=kaddr, %o1=flush_icache */
retl
nop
+ /* Zeroing every D-cache tag invalidates the whole cache on
+ * Spitfire and Cheetah alike.
+ */
+ .align 32
+ .globl __flush_dcache_all
+__flush_dcache_all: /* %o0=D-cache size, %o1=D-cache line size */
+1: subcc %o0, %o1, %o0
+ stxa %g0, [%o0] ASI_DCACHE_TAG
+ membar #Sync
+ bne,pt %icc, 1b
+ nop
+ retl
+ nop
+
#endif /* DCACHE_ALIASING_POSSIBLE */
.previous
@@ -801,6 +815,17 @@ xcall_flush_dcache_page_cheetah: /* %g1 == physical page address */
nop
retry
nop
+
+ .align 32
+ .globl xcall_flush_dcache_all
+xcall_flush_dcache_all: /* %g1 == D-cache size, %g7 == D-cache line size */
+1: subcc %g1, %g7, %g1
+ stxa %g0, [%g1] ASI_DCACHE_TAG
+ membar #Sync
+ bne,pt %icc, 1b
+ nop
+ retry
+ nop
#endif /* DCACHE_ALIASING_POSSIBLE */
.globl xcall_flush_dcache_page_spitfire
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 4/4] mm: pass the number of PTEs to move_pte()
2026-10-21 23:33 [PATCH v3 0/4] sparc64: D-cache alias flushing fixes Imre Kaloz
` (2 preceding siblings ...)
2026-10-21 23:33 ` [PATCH v3 3/4] sparc64: flush vmalloc ranges from the D-cache on map and unmap Imre Kaloz
@ 2026-10-21 23:33 ` Imre Kaloz
3 siblings, 0 replies; 5+ messages in thread
From: Imre Kaloz @ 2026-10-21 23:33 UTC (permalink / raw)
To: David S. Miller, Andreas Larsson
Cc: Matthew Wilcox (Oracle), Mike Rapoport (IBM),
Andrew Morton, David Hildenbrand, Liam R. Howlett,
Lorenzo Stoakes, Vlastimil Babka, Jann Horn, Pedro Falcato,
Suren Baghdasaryan, Michal Hocko, sparclinux, linux-kernel,
linux-mm
Since commit f822a9a81a31 ("mm: optimize mremap() by PTE batching"),
move_ptes() moves a batch of PTEs mapping consecutive pages of a folio
at a time, but calls move_pte() once per batch with the first PTE
only. sparc64, the only architecture that overrides move_pte(), flushes
from the D-cache the page that PTE maps when the move changes its
colour, so the other pages of a batch would keep lines of the old
colour. This cannot happen today only because sparc64 has no
pte_batch_hint(), and mremap_folio_pte_batch() then never batches.
Pass nr_ptes to move_pte(), as get_and_clear_ptes() takes it. The
generic version ignores it, and sparc64 flushes every page of the
batch.
Signed-off-by: Imre Kaloz <kaloz@kernel.org>
---
arch/sparc/include/asm/pgtable_64.h | 16 +++++++++-------
include/linux/pgtable.h | 2 +-
mm/mremap.c | 2 +-
3 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/arch/sparc/include/asm/pgtable_64.h b/arch/sparc/include/asm/pgtable_64.h
index 1430489b9ac5..9ca787b3cbfe 100644
--- a/arch/sparc/include/asm/pgtable_64.h
+++ b/arch/sparc/include/asm/pgtable_64.h
@@ -946,21 +946,23 @@ static inline void set_ptes(struct mm_struct *mm, unsigned long addr,
set_pte_at((mm), (addr), (ptep), __pte(0UL))
#ifdef DCACHE_ALIASING_POSSIBLE
-/* This flushes only the page the PTE maps, so it relies on move_ptes()
- * calling it for one PTE at a time, which the default pte_batch_hint()
- * guarantees.
+/* The nr PTEs map consecutive pages of one folio and all move by the
+ * same offset, so either every page of the batch aliases or none does.
*/
#define __HAVE_ARCH_MOVE_PTE
-#define move_pte(pte, old_addr, new_addr) \
+#define move_pte(pte, old_addr, new_addr, nr) \
({ \
pte_t newpte = (pte); \
if (tlb_type != hypervisor && pte_present(pte)) { \
unsigned long this_pfn = pte_pfn(pte); \
+ unsigned int i; \
\
if (pfn_valid(this_pfn) && \
- (((old_addr) ^ (new_addr)) & (1 << 13))) \
- flush_dcache_page_all(current->mm, \
- pfn_to_page(this_pfn)); \
+ (((old_addr) ^ (new_addr)) & (1 << 13))) { \
+ for (i = 0; i < (nr); i++) \
+ flush_dcache_page_all(current->mm, \
+ pfn_to_page(this_pfn + i)); \
+ } \
} \
newpte; \
})
diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h
index 8c093c119e5a..b58c0e98994e 100644
--- a/include/linux/pgtable.h
+++ b/include/linux/pgtable.h
@@ -1471,7 +1471,7 @@ static inline void arch_swap_restore(swp_entry_t entry, struct folio *folio)
#endif
#ifndef __HAVE_ARCH_MOVE_PTE
-#define move_pte(pte, old_addr, new_addr) (pte)
+#define move_pte(pte, old_addr, new_addr, nr) (pte)
#endif
#ifndef pte_accessible
diff --git a/mm/mremap.c b/mm/mremap.c
index e8df5cdb0ac9..7fcae7f0082d 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -289,7 +289,7 @@ static int move_ptes(struct pagetable_move_control *pmc,
force_flush = true;
}
pte = get_and_clear_ptes(mm, old_addr, old_ptep, nr_ptes);
- pte = move_pte(pte, old_addr, new_addr);
+ pte = move_pte(pte, old_addr, new_addr, nr_ptes);
pte = move_soft_dirty_pte(pte);
if (need_clear_uffd_wp && pte_is_uffd_wp_marker(pte))
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-21 23:35 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-21 23:33 [PATCH v3 0/4] sparc64: D-cache alias flushing fixes Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 1/4] sparc64: skip the trap block in xcall_deliver() when there is no target Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 2/4] sparc64: flush only the aliased page, not its whole folio Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 3/4] sparc64: flush vmalloc ranges from the D-cache on map and unmap Imre Kaloz
2026-10-21 23:33 ` [PATCH v3 4/4] mm: pass the number of PTEs to move_pte() Imre Kaloz
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®