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
next prev 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] MIPS: Select the R10000 LL/SC workaround at boot 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.