[PATCH 2/3] sparc64: flush the TLB on a spurious write fault

From: Stian Halseth

Date: Sat Oct 03 2026 - 08:57:30 EST


When a write faults although the PTE is already writable, the CPU holds
a stale translation and handle_pte_fault() calls
flush_tlb_fix_spurious_fault() to drop it; the THP path does the same
with the _pmd variant. The generic definitions are flush_tlb_page() and
a no-op. flush_tlb_page() is empty on sparc64, where the TLB is flushed
from set_pte_at() instead, so a thread that traps on a stale read-only
TLB entry retries the store against the same entry forever. Under a
256-thread compile load on a SPARC M7 a thread was caught spinning for
25 minutes on one store.

Define both hooks: flush the TSB entry and demap the page on every CPU
of the mm. A local demap is not enough, since sparc64 TLB entries are
tagged with the context and survive switch_mm(), so a stale entry on a
CPU the task has migrated away from is still live when it returns.

This predates git; before commit 61c77326d1df ("x86, mm: Avoid
unnecessary TLB flush") handle_pte_fault() called flush_tlb_page()
directly.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@xxxxxxxxxxxxxxx
Link: https://github.com/sparclinux/issues/issues/108
Signed-off-by: Stian Halseth <stian@xxxxxx>
---
arch/sparc/include/asm/tlbflush_64.h | 10 ++++++++++
arch/sparc/mm/tlb.c | 25 +++++++++++++++++++++++++
2 files changed, 35 insertions(+)

diff --git a/arch/sparc/include/asm/tlbflush_64.h b/arch/sparc/include/asm/tlbflush_64.h
--- a/arch/sparc/include/asm/tlbflush_64.h
+++ b/arch/sparc/include/asm/tlbflush_64.h
@@ -48,6 +48,16 @@
void __flush_tlb_page(unsigned long context, unsigned long vaddr);
void __flush_tlb_kernel_range(unsigned long start, unsigned long end);

+struct vm_area_struct;
+#define flush_tlb_fix_spurious_fault flush_tlb_fix_spurious_fault
+void flush_tlb_fix_spurious_fault(struct vm_area_struct *vma,
+ unsigned long address, pte_t *ptep);
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+#define flush_tlb_fix_spurious_fault_pmd flush_tlb_fix_spurious_fault_pmd
+void flush_tlb_fix_spurious_fault_pmd(struct vm_area_struct *vma,
+ unsigned long address, pmd_t *pmdp);
+#endif
+
#ifndef CONFIG_SMP

static inline void global_flush_tlb_page(struct mm_struct *mm, unsigned long vaddr)
diff --git a/arch/sparc/mm/tlb.c b/arch/sparc/mm/tlb.c
--- a/arch/sparc/mm/tlb.c
+++ b/arch/sparc/mm/tlb.c
@@ -32,6 +32,31 @@
return changed;
}

+/* Only base pages get here: hugetlb uses huge_ptep_set_access_flags() and
+ * THP the PMD variant below.
+ */
+void flush_tlb_fix_spurious_fault(struct vm_area_struct *vma,
+ unsigned long address, pte_t *ptep)
+{
+ address &= PAGE_MASK;
+ flush_tsb_user_page(vma->vm_mm, address, PAGE_SHIFT);
+ global_flush_tlb_page(vma->vm_mm, address);
+}
+
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+void flush_tlb_fix_spurious_fault_pmd(struct vm_area_struct *vma,
+ unsigned long address, pmd_t *pmdp)
+{
+ struct mm_struct *mm = vma->vm_mm;
+
+ address &= HPAGE_MASK;
+ flush_tsb_user_page(mm, address, REAL_HPAGE_SHIFT);
+ flush_tsb_user_page(mm, address + REAL_HPAGE_SIZE, REAL_HPAGE_SHIFT);
+ global_flush_tlb_page(mm, address);
+ global_flush_tlb_page(mm, address + REAL_HPAGE_SIZE);
+}
+#endif
+
void flush_tlb_pending(void)
{
struct tlb_batch *tb = &get_cpu_var(tlb_batch);