diff mbox series

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

Message ID 20261003125627.3419702-3-stian@itx.no
State New
Headers show
Series sparc64: stale TLB/TSB entries can livelock a thread | expand

Commit Message

Stian Halseth Oct. 3, 2026, 12:56 p.m. UTC
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@vger.kernel.org
Link: https://github.com/sparclinux/issues/issues/108
Signed-off-by: Stian Halseth <stian@itx.no>
---
 arch/sparc/include/asm/tlbflush_64.h   |  10 ++++++++++
 arch/sparc/mm/tlb.c                    |  25 +++++++++++++++++++++++++
 2 files changed, 35 insertions(+)
diff mbox series

Patch

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);