mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Imre Kaloz <kaloz@kernel.org>
To: Thomas Bogendoerfer <tsbogend@alpha.franken.de>,
	linux-mips@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Subject: [PATCH 2/5] MIPS: Select the R10000 LL/SC workaround at boot
Date: Mon, 26 Oct 2026 14:41:42 +0100	[thread overview]
Message-ID: <20261026134147.15988-3-kaloz@kernel.org> (raw)
In-Reply-To: <20261026134147.15988-1-kaloz@kernel.org>

Only R10000s before revision 3.0 need the branch-likely sc retry of
CONFIG_WAR_R10000_LLSC, which the whole family mispredicts.

Patch the SC_BEQZ sites to beqz at boot and module load unless a CPU
needs it; once patched, do not start such a CPU.

Signed-off-by: Imre Kaloz <kaloz@kernel.org>
---

Notes:
    checkpatch reports an ERROR (spaces around ':') and a do-while WARNING
    for SC_BEQZ. Its body is assembler text pasted into inline asm through
    __stringify(), not C: '9999:' is a GAS local label and ';' separates
    assembler statements. The new files fall under the MIPS entry in
    MAINTAINERS (F: arch/mips/).

 arch/mips/Kconfig                 |   5 +-
 arch/mips/include/asm/asm.h       |   7 +-
 arch/mips/include/asm/r10k-llsc.h |  29 ++++++++
 arch/mips/kernel/Makefile         |   1 +
 arch/mips/kernel/module.c         |   5 ++
 arch/mips/kernel/r10k-llsc.c      | 111 ++++++++++++++++++++++++++++++
 arch/mips/kernel/setup.c          |   2 +
 arch/mips/kernel/smp.c            |   2 +
 arch/mips/kernel/vmlinux.lds.S    |   8 +++
 9 files changed, 166 insertions(+), 4 deletions(-)
 create mode 100644 arch/mips/include/asm/r10k-llsc.h
 create mode 100644 arch/mips/kernel/r10k-llsc.c

diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index d7c67cebe065..f27d189922fe 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -2540,8 +2540,9 @@ config WAR_TX49XX_ICACHE_INDEX_INV
 config WAR_ICACHE_REFILLS
 	bool
 
-# On the R10000 up to version 2.6 (not sure about 2.7) there is a bug that
-# may cause ll / sc and lld / scd sequences to execute non-atomically.
+# R10000 CPUs before revision 3.0 may execute ll/sc and lld/scd sequences
+# non-atomically. Platforms that may have such a CPU select this, and the
+# workaround is dropped at boot when no such CPU is present.
 config WAR_R10000_LLSC
 	bool
 
diff --git a/arch/mips/include/asm/asm.h b/arch/mips/include/asm/asm.h
index 0ed19ffed076..e69d5fb6c1a7 100644
--- a/arch/mips/include/asm/asm.h
+++ b/arch/mips/include/asm/asm.h
@@ -335,10 +335,13 @@ symbol		=	value
 /*
  * Using a branch-likely instruction to check the result of an sc instruction
  * works around a bug present in R10000 CPUs prior to revision 3.0 that could
- * cause ll-sc sequences to execute non-atomically.
+ * cause ll-sc sequences to execute non-atomically. The address of each such
+ * branch is recorded so that it can be turned into a beqz at boot when no
+ * affected CPU is present.
  */
 #ifdef CONFIG_WAR_R10000_LLSC
-# define SC_BEQZ	beqzl
+# define SC_BEQZ	.pushsection __r10k_llsc, "a"; PTR_WD 9999f;	\
+			.popsection; 9999: beqzl
 #elif !defined(CONFIG_CC_HAS_BROKEN_INLINE_COMPAT_BRANCH) && MIPS_ISA_REV >= 6
 # define SC_BEQZ	beqzc
 #else
diff --git a/arch/mips/include/asm/r10k-llsc.h b/arch/mips/include/asm/r10k-llsc.h
new file mode 100644
index 000000000000..0f0e9b3b03a0
--- /dev/null
+++ b/arch/mips/include/asm/r10k-llsc.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_R10K_LLSC_H
+#define _ASM_R10K_LLSC_H
+
+#include <linux/types.h>
+
+#ifdef CONFIG_WAR_R10000_LLSC
+extern bool r10k_llsc_war;
+extern const unsigned long __start___r10k_llsc[], __stop___r10k_llsc[];
+
+void r10k_llsc_note_cpu(unsigned int prid);
+void r10k_llsc_init(void);
+void r10k_llsc_check_secondary(void);
+unsigned int r10k_llsc_patch(const unsigned long *start,
+			     const unsigned long *end);
+#else
+#define r10k_llsc_war	false
+
+static inline void r10k_llsc_note_cpu(unsigned int prid) { }
+static inline void r10k_llsc_init(void) { }
+static inline void r10k_llsc_check_secondary(void) { }
+static inline unsigned int r10k_llsc_patch(const unsigned long *start,
+					   const unsigned long *end)
+{
+	return 0;
+}
+#endif
+
+#endif /* _ASM_R10K_LLSC_H */
diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 95a1e674fd67..cd992852c31d 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_CPU_R2300_FPU)	+= r2300_fpu.o
 obj-$(CONFIG_CPU_R4K_FPU)	+= r4k_fpu.o
 
 obj-$(CONFIG_SMP)		+= smp.o
+obj-$(CONFIG_WAR_R10000_LLSC)	+= r10k-llsc.o
 obj-$(CONFIG_SMP_UP)		+= smp-up.o
 obj-$(CONFIG_CPU_BMIPS)		+= smp-bmips.o bmips_vec.o bmips_5xxx_init.o
 
diff --git a/arch/mips/kernel/module.c b/arch/mips/kernel/module.c
index 37dea772fd78..e47d0d530790 100644
--- a/arch/mips/kernel/module.c
+++ b/arch/mips/kernel/module.c
@@ -20,6 +20,7 @@
 #include <linux/spinlock.h>
 #include <linux/jump_label.h>
 #include <asm/jump_label.h>
+#include <asm/r10k-llsc.h>
 
 struct mips_hi16 {
 	struct mips_hi16 *next;
@@ -423,6 +424,10 @@ int module_finalize(const Elf_Ehdr *hdr,
 
 	INIT_LIST_HEAD(&me->arch.dbe_list);
 	for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
+		if (IS_ENABLED(CONFIG_WAR_R10000_LLSC) &&
+		    !strcmp("__r10k_llsc", secstrings + s->sh_name))
+			r10k_llsc_patch((void *)s->sh_addr,
+					(void *)s->sh_addr + s->sh_size);
 		if (strcmp("__dbe_table", secstrings + s->sh_name) != 0)
 			continue;
 		me->arch.dbe_start = (void *)s->sh_addr;
diff --git a/arch/mips/kernel/r10k-llsc.c b/arch/mips/kernel/r10k-llsc.c
new file mode 100644
index 000000000000..32fd79716bf0
--- /dev/null
+++ b/arch/mips/kernel/r10k-llsc.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * R10000 CPUs before revision 3.0 may execute an ll/sc sequence
+ * non-atomically, and those up to revision 2.6 may hang in one, unless the
+ * branch retrying the sequence is a branch-likely. A kernel that may run on
+ * such a CPU is built with branch-likely retry loops, and each of them is
+ * recorded in the __r10k_llsc table. The R10000 family always predicts a
+ * branch-likely taken, so every successful sc then costs a mispredict; the
+ * recorded branches are turned into plain beqz at boot unless an affected CPU
+ * is present.
+ */
+#define pr_fmt(fmt) "r10k-llsc: " fmt
+
+#include <linux/bug.h>
+#include <linux/cache.h>
+#include <linux/cacheflush.h>
+#include <linux/init.h>
+#include <linux/printk.h>
+#include <linux/processor.h>
+#include <linux/smp.h>
+
+#include <asm/cpu.h>
+#include <asm/cpu-info.h>
+#include <asm/inst.h>
+#include <asm/r10k-llsc.h>
+
+/* Until r10k_llsc_init() has run, every recorded branch is a branch-likely. */
+bool r10k_llsc_war __ro_after_init = true;
+
+static unsigned int r10k_llsc_prid;
+
+static bool r10k_llsc_affected(unsigned int prid)
+{
+	return (prid & (PRID_COMP_MASK | PRID_IMP_MASK)) ==
+	       (PRID_COMP_LEGACY | PRID_IMP_R10000) &&
+	       (prid & PRID_REV_MASK) < PRID_REV_ENCODE_44(3, 0);
+}
+
+/*
+ * A platform whose CPUs may differ reports each CPU it will start here before
+ * r10k_llsc_init(), so that the choice covers the whole machine.
+ */
+void r10k_llsc_note_cpu(unsigned int prid)
+{
+	if (!r10k_llsc_prid && r10k_llsc_affected(prid))
+		r10k_llsc_prid = prid;
+}
+
+unsigned int r10k_llsc_patch(const unsigned long *start,
+			     const unsigned long *end)
+{
+	const unsigned long *p;
+	unsigned int n = 0;
+
+	if (r10k_llsc_war)
+		return 0;
+
+	for (p = start; p < end; p++) {
+		union mips_instruction *insn = (void *)*p;
+
+		/* Only a nop in the delay slot behaves alike under both. */
+		if (WARN_ONCE(insn[0].i_format.opcode != beql_op ||
+			      insn[0].i_format.rt || insn[1].word,
+			      "unexpected sc retry branch at %pS\n", insn))
+			continue;
+
+		insn[0].i_format.opcode = beq_op;
+		flush_icache_range((unsigned long)insn,
+				   (unsigned long)(insn + 1));
+		n++;
+	}
+
+	return n;
+}
+
+void __init r10k_llsc_init(void)
+{
+	unsigned int prid, n;
+
+	r10k_llsc_note_cpu(current_cpu_data.processor_id);
+	prid = r10k_llsc_prid;
+
+	if (prid) {
+		pr_info("enabled for R10000 revision %u.%u\n",
+			(prid >> 4) & 0xf, prid & 0xf);
+		return;
+	}
+
+	r10k_llsc_war = false;
+	n = r10k_llsc_patch(__start___r10k_llsc, __stop___r10k_llsc);
+	pr_info("not needed, %u of %lu sc retry branches patched\n", n,
+		__stop___r10k_llsc - __start___r10k_llsc);
+}
+
+/*
+ * Once the workaround has been dropped, any retry branch a CPU runs may be
+ * a plain beqz, so an affected CPU found only now is kept out of the
+ * system.
+ */
+void r10k_llsc_check_secondary(void)
+{
+	unsigned int prid = current_cpu_data.processor_id;
+
+	if (r10k_llsc_war || !r10k_llsc_affected(prid))
+		return;
+
+	pr_crit("CPU%u: R10000 revision %u.%u needs the ll/sc workaround, not starting it\n",
+		smp_processor_id(), (prid >> 4) & 0xf, prid & 0xf);
+	for (;;)
+		cpu_relax();
+}
diff --git a/arch/mips/kernel/setup.c b/arch/mips/kernel/setup.c
index 1ae6d0c0e1d6..8f5f8e473a8a 100644
--- a/arch/mips/kernel/setup.c
+++ b/arch/mips/kernel/setup.c
@@ -46,6 +46,7 @@
 #include <asm/smp-ops.h>
 #include <asm/mips-cps.h>
 #include <asm/prom.h>
+#include <asm/r10k-llsc.h>
 #include <asm/fw/fw.h>
 
 #ifdef CONFIG_MIPS_ELF_APPENDED_DTB
@@ -783,6 +784,7 @@ void __init setup_arch(char **cmdline_p)
 	prefill_possible_map();
 
 	cpu_cache_init();
+	r10k_llsc_init();
 	pagetable_init();
 
 	memblock_dump_all();
diff --git a/arch/mips/kernel/smp.c b/arch/mips/kernel/smp.c
index 0f28b4a62e72..8d32c252117a 100644
--- a/arch/mips/kernel/smp.c
+++ b/arch/mips/kernel/smp.c
@@ -33,6 +33,7 @@
 #include <asm/processor.h>
 #include <asm/idle.h>
 #include <asm/r4k-timer.h>
+#include <asm/r10k-llsc.h>
 #include <asm/mips-cps.h>
 #include <asm/mmu_context.h>
 #include <asm/time.h>
@@ -360,6 +361,7 @@ asmlinkage void start_secondary(void)
 	unsigned int cpu = raw_smp_processor_id();
 
 	cpu_probe();
+	r10k_llsc_check_secondary();
 	per_cpu_trap_init(false);
 	rcutree_report_cpu_starting(cpu);
 	mips_clockevent_init();
diff --git a/arch/mips/kernel/vmlinux.lds.S b/arch/mips/kernel/vmlinux.lds.S
index 579b2cc1995a..e7c4103037bb 100644
--- a/arch/mips/kernel/vmlinux.lds.S
+++ b/arch/mips/kernel/vmlinux.lds.S
@@ -128,6 +128,14 @@ SECTIONS
 		__mips_machines_end = .;
 	}
 
+#ifdef CONFIG_WAR_R10000_LLSC
+	__r10k_llsc : AT(ADDR(__r10k_llsc) - LOAD_OFFSET) {
+		__start___r10k_llsc = .;
+		KEEP(*(__r10k_llsc))
+		__stop___r10k_llsc = .;
+	}
+#endif
+
 	/* .exit.text is discarded at runtime, not link time, to deal with
 	 * references from .rodata
 	 */
-- 
2.47.3


  parent reply	other threads:[~2026-10-26 13:43 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26 13:41 [PATCH 0/5] " Imre Kaloz
2026-10-26 13:41 ` [PATCH 1/5] MIPS: Use SC_BEQZ in the futex and sysmips ll/sc loops Imre Kaloz
2026-10-26 13:41 ` Imre Kaloz [this message]
2026-10-26 13:41 ` [PATCH 3/5] MIPS: SGI-IP27: Report all CPUs to the R10000 LL/SC workaround Imre Kaloz
2026-10-26 13:41 ` [PATCH 4/5] MIPS: tlbex: Follow the boot-time R10000 LL/SC choice Imre Kaloz
2026-10-26 13:41 ` [PATCH 5/5] bpf, mips: " Imre Kaloz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261026134147.15988-3-kaloz@kernel.org \
    --to=kaloz@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mips@vger.kernel.org \
    --cc=tsbogend@alpha.franken.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®