summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorImre Kaloz <kaloz@kernel.org>2026-10-22 01:33:39 +0200
committerAndreas Larsson <andreas@gaisler.com>2026-10-23 11:40:02 +0200
commitd957b367d560c4a74637d400ca4e0e63bbdfbcd1 (patch)
tree520cc3f8c6af04ff5bd787b7065832143a5b3187
parent524ccb38fbe123dcce52605d2d018a1a9231034c (diff)
downloadlinux-next-d957b367d560c4a74637d400ca4e0e63bbdfbcd1.tar.gz
linux-next-d957b367d560c4a74637d400ca4e0e63bbdfbcd1.zip
sparc64: flush vmalloc ranges from the D-cache on map and unmap
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> Reviewed-by: Andreas Larsson <andreas@gaisler.com> Link: https://patch.msgid.link/55b4691ea87be7f4a679197bc2ec35b81b45d233.1792609488.git.kaloz@kernel.org Signed-off-by: Andreas Larsson <andreas@gaisler.com>
-rw-r--r--arch/sparc/include/asm/cacheflush_64.h6
-rw-r--r--arch/sparc/kernel/smp_64.c18
-rw-r--r--arch/sparc/mm/init_64.c43
-rw-r--r--arch/sparc/mm/ultra.S25
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