[PATCH 3/4] sparc32: implement futex atomics, with casa detected at run time
From: Ramin Moussavi
Date: Thu Oct 15 2026 - 05:03:16 EST
sparc32 has no futex_atomic_cmpxchg_inatomic() or
arch_futex_atomic_op_inuser() of its own. The generic versions only
work on UP, so FUTEX depends on !(SPARC32 && SMP) and an SMP kernel
has no futexes at all; NPTL cannot run on it.
Provide both. CPUs with casa use it on the user word. That is optional
on LEON and absent from every other sparc32, so instead of a config
option, probe for it once at boot: execute casa under an exception
table entry, and let do_illegal_instruction() fix up a kernel mode
trap that has one. One kernel then runs on v8 and on LEON. casa sits
in a file of its own that is assembled for LEON; the rest of the kernel
stays at -Av8.
Without casa, the word is updated under the hashed spinlock the kernel
already uses for its own atomics. The hash only takes address bits
within the page offset, so every mapping of a shared page ends up on
the same lock, which process-shared futexes need. Page faults are
disabled while it is held; the futex core faults the page in and
retries.
The FUTEX_OP_* operations are a compare-and-exchange loop, so the
instruction or the lock appears in one place only. The structure
follows the LEON futex support Andreas Larsson keeps in Gaisler's
patch set, which chooses casa at configuration time.
Tested on qemu 11.1.0:
- SS-20, -smp 4: futexes come up; with the next patch and a C
library that uses it, the uClibc-ng NPTL tests pass
- SS-20, UP: unchanged results
- leon3_generic: "sparc32: casa present", futex path through casa
- SS-20 with the probe forced on: "casa absent", the illegal
instruction trap is fixed up and boot continues
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramin Moussavi <ramin.moussavi@xxxxxxxxx>
---
arch/sparc/include/asm/cmpxchg_32.h | 4 ++
arch/sparc/include/asm/futex_32.h | 72 ++++++++++++++++++++++++++++-
arch/sparc/kernel/traps_32.c | 13 +++++-
arch/sparc/lib/Makefile | 3 +-
arch/sparc/lib/atomic32.c | 53 +++++++++++++++++++++
arch/sparc/lib/casa_32.S | 30 ++++++++++++
init/Kconfig | 1 -
7 files changed, 172 insertions(+), 4 deletions(-)
create mode 100644 arch/sparc/lib/casa_32.S
diff --git a/arch/sparc/include/asm/cmpxchg_32.h b/arch/sparc/include/asm/cmpxchg_32.h
index 8c1a3ca34eeb..c08c14b62ab5 100644
--- a/arch/sparc/include/asm/cmpxchg_32.h
+++ b/arch/sparc/include/asm/cmpxchg_32.h
@@ -64,6 +64,10 @@ __cmpxchg(volatile void *ptr, unsigned long old, unsigned long new_, int size)
u64 __cmpxchg_u64(volatile u64 *ptr, u64 old, u64 new);
#define arch_cmpxchg64(ptr, old, new) __cmpxchg_u64(ptr, old, new)
+int sparc32_user_cmpxchg_u32(u32 __user *uaddr, u32 old, u32 new, u32 *prev);
+int __sparc32_casa(u32 *addr, u32 old, u32 new, u32 *prev);
+extern bool sparc32_has_cas;
+
#include <asm-generic/cmpxchg-local.h>
/*
diff --git a/arch/sparc/include/asm/futex_32.h b/arch/sparc/include/asm/futex_32.h
index 6a332a9f099c..fe2386058a1f 100644
--- a/arch/sparc/include/asm/futex_32.h
+++ b/arch/sparc/include/asm/futex_32.h
@@ -1,6 +1,76 @@
#ifndef _ASM_FUTEX_H
#define _ASM_FUTEX_H
-#include <asm-generic/futex.h>
+#include <linux/futex.h>
+#include <linux/preempt.h>
+#include <linux/uaccess.h>
+#include <asm/cmpxchg.h>
+#include <asm/errno.h>
+
+/*
+ * Based on the LEON futex support by Andreas Larsson, with casa chosen at
+ * run time instead of at configuration time. Without casa the word is
+ * updated under the lock the kernel's own atomics use, which also works
+ * on SMP.
+ */
+static inline int
+futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
+ u32 oldval, u32 newval)
+{
+ int ret;
+
+ if (unlikely(!access_ok(uaddr, sizeof(u32))))
+ return -EFAULT;
+
+ if (!sparc32_has_cas)
+ return sparc32_user_cmpxchg_u32(uaddr, oldval, newval, uval);
+
+ preempt_disable();
+ ret = __sparc32_casa((u32 __force *)uaddr, oldval, newval, uval);
+ preempt_enable();
+ return ret;
+}
+
+static inline int
+arch_futex_atomic_op_inuser(int op, u32 oparg, int *oval, u32 __user *uaddr)
+{
+ u32 old, new, cur;
+ int ret;
+
+ if (unlikely(get_user(old, uaddr)))
+ return -EFAULT;
+
+ for (;;) {
+ switch (op) {
+ case FUTEX_OP_SET:
+ new = oparg;
+ break;
+ case FUTEX_OP_ADD:
+ new = old + oparg;
+ break;
+ case FUTEX_OP_OR:
+ new = old | oparg;
+ break;
+ case FUTEX_OP_ANDN:
+ new = old & ~oparg;
+ break;
+ case FUTEX_OP_XOR:
+ new = old ^ oparg;
+ break;
+ default:
+ return -ENOSYS;
+ }
+
+ ret = futex_atomic_cmpxchg_inatomic(&cur, uaddr, old, new);
+ if (ret)
+ return ret;
+ if (cur == old)
+ break;
+ old = cur;
+ }
+
+ *oval = old;
+ return 0;
+}
#endif
diff --git a/arch/sparc/kernel/traps_32.c b/arch/sparc/kernel/traps_32.c
index bb149f6cc34b..9d77ff2fd6fe 100644
--- a/arch/sparc/kernel/traps_32.c
+++ b/arch/sparc/kernel/traps_32.c
@@ -19,6 +19,7 @@
#include <linux/smp.h>
#include <linux/kdebug.h>
#include <linux/export.h>
+#include <linux/extable.h>
#include <linux/pgtable.h>
#include <asm/delay.h>
@@ -108,8 +109,18 @@ void do_hw_interrupt(struct pt_regs *regs, unsigned long type)
void do_illegal_instruction(struct pt_regs *regs, unsigned long pc, unsigned long npc,
unsigned long psr)
{
- if(psr & PSR_PS)
+ if (psr & PSR_PS) {
+ const struct exception_table_entry *entry;
+
+ /* The casa probe at boot lands here on CPUs without it. */
+ entry = search_exception_tables(regs->pc);
+ if (entry) {
+ regs->pc = entry->fixup;
+ regs->npc = regs->pc + 4;
+ return;
+ }
die_if_kernel("Kernel illegal instruction", regs);
+ }
#ifdef TRAP_DEBUG
printk("Ill instr. at pc=%08lx instruction is %08lx\n",
regs->pc, *(unsigned long *)regs->pc);
diff --git a/arch/sparc/lib/Makefile b/arch/sparc/lib/Makefile
index dd10cdd6f062..284236024e6a 100644
--- a/arch/sparc/lib/Makefile
+++ b/arch/sparc/lib/Makefile
@@ -51,5 +51,6 @@ lib-$(CONFIG_SPARC64) += copy_in_user.o memmove.o
lib-$(CONFIG_SPARC64) += mcount.o ipcsum.o hweight.o ffs.o
obj-$(CONFIG_SPARC64) += iomap.o
-obj-$(CONFIG_SPARC32) += atomic32.o
+obj-$(CONFIG_SPARC32) += atomic32.o casa_32.o
+AFLAGS_casa_32.o += -Wa,-Aleon
obj-$(CONFIG_SPARC64) += PeeCeeI.o
diff --git a/arch/sparc/lib/atomic32.c b/arch/sparc/lib/atomic32.c
index 8ae880ebf07a..8f4b6079bf1c 100644
--- a/arch/sparc/lib/atomic32.c
+++ b/arch/sparc/lib/atomic32.c
@@ -11,6 +11,10 @@
#include <linux/atomic.h>
#include <linux/spinlock.h>
#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/printk.h>
+#include <linux/uaccess.h>
+#include <asm/cpu_type.h>
#ifdef CONFIG_SMP
#define ATOMIC_HASH_SIZE 4
@@ -195,3 +199,52 @@ unsigned long __xchg_u32(volatile u32 *ptr, u32 new)
return (unsigned long)prev;
}
EXPORT_SYMBOL(__xchg_u32);
+
+/*
+ * Compare-and-exchange on a user word for CPUs without casa, under the
+ * lock that covers the same word in the kernel's own atomics. The hash
+ * only uses bits within the page offset, so every mapping of a shared
+ * page takes the same lock.
+ *
+ * Page faults cannot be served here. Returns -EFAULT if the word is not
+ * mapped writable; the caller faults it in and calls again.
+ */
+int sparc32_user_cmpxchg_u32(u32 __user *uaddr, u32 old, u32 new, u32 *prev)
+{
+ unsigned long flags;
+ int ret = 0;
+ u32 val;
+
+ spin_lock_irqsave(ATOMIC_HASH(uaddr), flags);
+ pagefault_disable();
+ if (get_user(val, uaddr))
+ ret = -EFAULT;
+ else if (val == old && put_user(new, uaddr))
+ ret = -EFAULT;
+ pagefault_enable();
+ spin_unlock_irqrestore(ATOMIC_HASH(uaddr), flags);
+
+ *prev = val;
+ return ret;
+}
+
+/*
+ * casa is optional on LEON and absent from every other sparc32. Probe for
+ * it rather than trusting the configuration, so that one kernel runs on
+ * both. User words must then always go through casa: userspace built for
+ * LEON uses it directly, and a lock would not exclude that.
+ */
+bool sparc32_has_cas __ro_after_init;
+
+static int __init sparc32_probe_cas(void)
+{
+ u32 word = 0, prev;
+
+ if (sparc_cpu_model != sparc_leon)
+ return 0;
+
+ sparc32_has_cas = !__sparc32_casa(&word, 0, 1, &prev) && word == 1;
+ pr_info("sparc32: casa %s\n", sparc32_has_cas ? "present" : "absent");
+ return 0;
+}
+early_initcall(sparc32_probe_cas);
diff --git a/arch/sparc/lib/casa_32.S b/arch/sparc/lib/casa_32.S
new file mode 100644
index 000000000000..eab7a5969e3f
--- /dev/null
+++ b/arch/sparc/lib/casa_32.S
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * casa for the LEON CPUs that implement it. This file alone is assembled
+ * for LEON, the rest of the kernel stays at v8. Callers check
+ * sparc32_has_cas first, except the boot probe, which relies on the
+ * illegal instruction trap being fixed up through the exception table.
+ */
+
+#include <linux/linkage.h>
+#include <asm/errno.h>
+
+/*
+ * int __sparc32_casa(u32 *addr, u32 old, u32 new, u32 *prev)
+ *
+ * Returns 0 and the previous value in *prev, or -EFAULT if the access
+ * faulted or the CPU has no casa.
+ */
+ENTRY(__sparc32_casa)
+1: casa [%o0] 0xb, %o1, %o2
+ st %o2, [%o3]
+ retl
+ clr %o0
+2: retl
+ mov -EFAULT, %o0
+ENDPROC(__sparc32_casa)
+
+ .section __ex_table, "a"
+ .align 4
+ .word 1b, 2b
+ .previous
diff --git a/init/Kconfig b/init/Kconfig
index 54f78d87344a..8fd656bbc4e0 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1682,7 +1682,6 @@ config BASE_SMALL
config FUTEX
bool "Enable futex support" if EXPERT
- depends on !(SPARC32 && SMP)
default y
imply RT_MUTEXES
help
--
2.53.0