* [RFC PATCH 0/5] sparc32: kernel assisted compare-and-swap, and futex on SMP
@ 2026-09-23 20:17 Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 1/5] sparc32: detect the compare-and-swap instruction at boot Magnus Lindholm
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Magnus Lindholm @ 2026-09-23 20:17 UTC (permalink / raw)
To: davem, andreas; +Cc: sam, sparclinux, linux-kernel, linmag7
This series is part of my effort to get modern Linux kernels and a
current glibc-based userland running on my SPARCstations. With the SILO
and kernel fixes I've been working on, these machines can now boot
recent kernels again. Booting the kernel is only part of the work,
though: running a current glibc-based userland also requires working
futex support and userspace atomics that cooperate with it.
This series addresses the kernel side by providing a kernel-assisted
32-bit compare-and-swap operation and implementing the architecture
futex atomic operations needed to enable futex support on sparc32 SMP.
On machines without hardware compare-and-swap, the proposed userspace
trap and the architecture futex helpers use the same kernel-owned
locks, allowing their operations on a shared word to participate in
the same serialization mechanism.
A companion glibc series addresses the userspace side, with the aim
of restoring baseline SPARC V8 builds. It uses native compare-and-swap
where available and the proposed kernel service otherwise, including
for libc's internal atomic stores and exchanges so that they
participate in the same protocol.
CONFIG_FUTEX depends on !(SPARC32 && SMP), so an SMP sparc32 kernel has no
futex at all, and glibc has relied on futex for internal locking for about
a decade: such a kernel cannot support a current glibc userland correctly.
Arnd Bergmann observed the same in the 2023 "sunset sun4m and sun4d"
thread [1], and on what a v8 versus a leon build actually uses [2].
The generic futex helpers must update a word in user memory atomically,
and the baseline v8 instruction set lacks the general compare-and-swap
operation that needs. A kernel-private lock does not fix it, the kernel
does get_user() then put_user(), and userspace holds no such lock, so the
mechanism has to be one both sides share. parisc does this with its LWS
compare-and-swap, the model followed here.
The ground is not new. David Miller prototyped a sparc CAS trap in 2016
and Andreas Larsson proposed kernel-assisted atomics again in 2019 [3],
including compiler builtins trapping into the kernel [4]. This is a new
ABI along those lines rather than a completion of that one; patch 2 has
the detail. Software trap 0x11 gives trap type 0x91, immediately following
the Linux syscall trap at software trap 0x10, trap type 0x90. sparc64
compat mode is deliberately unsupported, since ta 0x11 is the old 64-bit
system call trap there.
Patch 1 detects the v9 casa extension at boot. Patch 2 adds the trap -
userspace executes ta 0x11, since sparc adds 0x80 to form the type. Patch
3 puts the futex helpers on it and drops the Kconfig dependency, 4
advertises it in AT_HWCAP, 5 documents the ABI.
Patch 5 documents the limits, which are worth reading first. On a cpu
without casa, storage that participates in this protocol must have every
atomic write built from the trap, because an ordinary store does not take
the kernel's lock; that is Andreas Larsson's 2016 point, and why parisc
ships a kernel-assisted atomic store beside its LWS CAS. Replacing the
__atomic_* symbols is not sufficient, since gcc can expand some of them
inline on v8 - and covering libc's own uses is a different scope from
covering application code built with atomic builtins. One aligned 32-bit
word is also not general byte and halfword atomics. So this is a
kernel-assisted 32-bit compare-and-swap enabling futex for cooperating
userspace, not arbitrary sparcv8 userland support.
One kernel path is worth raising. futex_robust_unlock() clears the futex
word with unsafe_atomic_store_release_user(), before the hash bucket is
locked and not through either architecture helper. SPARC32 uses the
generic definition of that macro, which ends in a plain user store, so on
a no-CASA SMP system it does not acquire the lock the software CAS uses.
It can land between that CAS's load and its conditional store, letting the
CAS overwrite the unlock and still report success. Dropping the Kconfig
dependency is what exposes this on SPARC32 SMP, and it has not been
observed in practice.
The requirement is only that the two agree on how they serialise access to
the word. The macro is overridable, so SPARC32 could supply its own
definition and keep this within the architecture; whether that is
preferable to a futex-specific interface is the question I would like an
answer to.
Choosing between lock and instruction follows the hardware, not a build
option, because userspace's choice does too:
hardware trap-aware userland userland uses kernel uses
no casa v8 trap 0x11 lock array
casa leon3 casa casa
casa v8 trap 0x11 casa
Row three is why the trap handler uses casa where it exists, and why the
futex read-modify-write is a casa retry loop rather than a locked read and
write: a binary using casa inline holds no lock of ours, so between our
two accesses its update can be lost while both appear to succeed.
Applies and builds standalone on v7.3-rc1, cross-built for the
SPARCstation 20, no new warnings. Boot-tested only; the trap itself has
not been issued by a test, and the concurrency, fault-path and
cross-process cases have no runtime results yet. The casa path has never
executed here, this hardware has no casa.
Boot testing on my SPARCstation 20 used these prerequisite series, since
that machine does not boot current mainline without them:
sparc32: relocatable kernel / phys_base + Viking fixes
https://lore.kernel.org/sparclinux/20260816075141.3489194-1-linmag7@gmail.com/T/#t
sparc32: replace sp_banks with memblock (v3)
https://lore.kernel.org/sparclinux/20260901214611.60560-1-linmag7@gmail.com/
sparc32: SuperSPARC SMP synchronization fixes (v2)
https://lore.kernel.org/sparclinux/20260917075842.784996-1-linmag7@gmail.com/
[1] https://lore.kernel.org/all/01ea8c41-88cd-4123-95c7-391640845fc3@app.fastmail.com/
[2] https://lore.kernel.org/all/55c0b9a5-6ba3-4582-97f0-225d0119da5d@app.fastmail.com/
[3] https://inbox.sourceware.org/libc-alpha/10289df2-0b5e-dbd4-fbde-dd3bf96a914d@gaisler.com/
[4] https://inbox.sourceware.org/libc-alpha/bd5bc3bb-fbf2-398d-0485-21bba3c86798@gaisler.com/
Magnus Lindholm (5):
sparc32: detect the compare-and-swap instruction at boot
sparc32: add a kernel assisted compare-and-swap
sparc32: implement futex atomic ops with the compare-and-swap locks
sparc32: advertise the compare-and-swap trap in AT_HWCAP
sparc32: document the compare-and-swap trap ABI
Documentation/arch/sparc/cas-trap.rst | 163 ++++++++++++++++++++++++++
Documentation/arch/sparc/index.rst | 1 +
arch/sparc/include/asm/cas_32.h | 33 ++++++
arch/sparc/include/asm/cpu_type.h | 7 ++
arch/sparc/include/asm/elf_32.h | 7 +-
arch/sparc/include/asm/elf_64.h | 3 +
arch/sparc/include/asm/futex_32.h | 137 +++++++++++++++++++++-
arch/sparc/include/uapi/asm/traps.h | 3 +-
arch/sparc/kernel/Makefile | 1 +
arch/sparc/kernel/cas_32.c | 96 +++++++++++++++
arch/sparc/kernel/cpu.c | 24 ++++
arch/sparc/kernel/entry.S | 17 +++
arch/sparc/kernel/traps_32.c | 16 ++-
arch/sparc/kernel/ttable_32.S | 9 +-
arch/sparc/lib/Makefile | 5 +
arch/sparc/lib/casa_32.S | 46 ++++++++
init/Kconfig | 1 -
17 files changed, 559 insertions(+), 10 deletions(-)
create mode 100644 Documentation/arch/sparc/cas-trap.rst
create mode 100644 arch/sparc/include/asm/cas_32.h
create mode 100644 arch/sparc/kernel/cas_32.c
create mode 100644 arch/sparc/lib/casa_32.S
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH 1/5] sparc32: detect the compare-and-swap instruction at boot
2026-09-23 20:17 [RFC PATCH 0/5] sparc32: kernel assisted compare-and-swap, and futex on SMP Magnus Lindholm
@ 2026-09-23 20:17 ` Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 2/5] sparc32: add a kernel assisted compare-and-swap Magnus Lindholm
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Magnus Lindholm @ 2026-09-23 20:17 UTC (permalink / raw)
To: davem, andreas; +Cc: sam, sparclinux, linux-kernel, linmag7
Some 32-bit sparc implementations provide the v9 compare-and-swap as an
extension, LEON3 does, plain v8 does not. Whether it is present decides
how a user word can be made atomic, and the kernel has to make the same
choice userspace made: a binary built for such a cpu uses casa inline, one
built for v8 cannot and asks the kernel instead. Both follow from the
hardware, so deciding on the hardware keeps them in step.
Probe by executing one compare-and-swap that must succeed and checking
that it did, rather than inferring support from the absence of a trap. A
cpu without the instruction raises illegal_instruction, so
do_illegal_instruction() now looks for an exception table fixup before
dying, the way the data fault path already does; the same entry also
covers a faulting address later.
casa cannot appear in inline asm: sparc32 is built -Wa,-Av8, the assembler
rejects the instruction outright, and sparc gas has no .arch pseudo-op to
override that locally. It therefore lives in its own object assembled
-Wa,-Aleon, which adds the one instruction without letting the rest of v9
in. This affects the assembler only, not what the compiler emits.
The helpers are laid out for the LEON atomic errata rather than detecting
them. GRLIB-TN-0011 wants the atomic 16-byte aligned, or an instruction
TLB miss can release the bus lock before the store completes;
GRLIB-TN-0010 forbids reaching one from a load or a control transfer,
which a call with a load in its delay slot does. A nop at the entry plus
nop padding to the boundary covers both, for less than runtime detection
would cost. The padding is spelled .balignl because a plain .align lets
the assembler branch over the gap, and that branch lands on the atomic -
exactly the sequence TN-0010 asks us to avoid.
The probe runs once, on the boot cpu, so an SMP machine whose cpus
disagree about the instruction is not supported. None is known.
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/cas_32.h | 10 +++++++++
arch/sparc/include/asm/cpu_type.h | 7 +++++++
arch/sparc/kernel/cpu.c | 24 ++++++++++++++++++++++
arch/sparc/kernel/traps_32.c | 16 ++++++++++++++-
arch/sparc/lib/Makefile | 5 +++++
arch/sparc/lib/casa_32.S | 34 +++++++++++++++++++++++++++++++
6 files changed, 95 insertions(+), 1 deletion(-)
create mode 100644 arch/sparc/include/asm/cas_32.h
create mode 100644 arch/sparc/lib/casa_32.S
diff --git a/arch/sparc/include/asm/cas_32.h b/arch/sparc/include/asm/cas_32.h
new file mode 100644
index 000000000000..3658ff71fa70
--- /dev/null
+++ b/arch/sparc/include/asm/cas_32.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SPARC_CAS_32_H
+#define _SPARC_CAS_32_H
+
+#include <linux/types.h>
+
+/* Returns 0 and stores the value found in *prev, or -EFAULT. */
+int __sparc32_casa(u32 *addr, u32 oldval, u32 newval, u32 *prev);
+
+#endif /* _SPARC_CAS_32_H */
diff --git a/arch/sparc/include/asm/cpu_type.h b/arch/sparc/include/asm/cpu_type.h
index 2b59799859d1..c54e0abfdb2b 100644
--- a/arch/sparc/include/asm/cpu_type.h
+++ b/arch/sparc/include/asm/cpu_type.h
@@ -2,6 +2,8 @@
#ifndef __ASM_CPU_TYPE_H
#define __ASM_CPU_TYPE_H
+#include <linux/types.h>
+
/*
* Sparc (general) CPU types
*/
@@ -18,6 +20,11 @@ enum sparc_cpu {
#ifdef CONFIG_SPARC32
extern enum sparc_cpu sparc_cpu_model;
+/* True where the cpu implements the v9 casa extension (LEON3 does, plain v8
+ * does not). Probed once on the boot cpu.
+ */
+extern bool sparc32_has_casa;
+
#define SUN4M_NCPUS 4 /* Architectural limit of sun4m. */
#else
diff --git a/arch/sparc/kernel/cpu.c b/arch/sparc/kernel/cpu.c
index 79cd6ccfeac0..e09c688a5fd3 100644
--- a/arch/sparc/kernel/cpu.c
+++ b/arch/sparc/kernel/cpu.c
@@ -21,6 +21,9 @@
#include <asm/psr.h>
#include <asm/mbus.h>
#include <asm/cpudata.h>
+#ifdef CONFIG_SPARC32
+#include <asm/cas_32.h>
+#endif
#include "kernel.h"
#include "entry.h"
@@ -437,11 +440,32 @@ const struct seq_operations cpuinfo_op = {
};
#ifdef CONFIG_SPARC32
+bool sparc32_has_casa __ro_after_init;
+
+/* One compare-and-swap that must succeed: a cpu without casa traps and the
+ * exception table reports -EFAULT.
+ */
+static void __init casa_probe(void)
+{
+ static const u32 oldval = 0x600df00d;
+ static const u32 newval = 0x0badcafe;
+ u32 word = oldval;
+ u32 prev = 0;
+
+ if (!__sparc32_casa(&word, oldval, newval, &prev))
+ sparc32_has_casa = prev == oldval && word == newval;
+
+ pr_info("sparc32: compare-and-swap instruction %s\n",
+ sparc32_has_casa ? "present" : "not implemented");
+}
+
static int __init cpu_type_probe(void)
{
int psr_impl, psr_vers, fpu_vers;
int psr;
+ casa_probe();
+
psr_impl = ((get_psr() >> PSR_IMPL_SHIFT) & PSR_IMPL_SHIFTED_MASK);
psr_vers = ((get_psr() >> PSR_VERS_SHIFT) & PSR_VERS_SHIFTED_MASK);
diff --git a/arch/sparc/kernel/traps_32.c b/arch/sparc/kernel/traps_32.c
index bb149f6cc34b..85ae2fc143ec 100644
--- a/arch/sparc/kernel/traps_32.c
+++ b/arch/sparc/kernel/traps_32.c
@@ -20,6 +20,7 @@
#include <linux/kdebug.h>
#include <linux/export.h>
#include <linux/pgtable.h>
+#include <linux/extable.h>
#include <asm/delay.h>
#include <asm/ptrace.h>
@@ -108,8 +109,21 @@ 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;
+
+ /*
+ * An instruction this cpu does not implement can be probed
+ * for deliberately, so honour a fixup before dying.
+ */
+ entry = search_exception_tables(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..007d8e510ee8 100644
--- a/arch/sparc/lib/Makefile
+++ b/arch/sparc/lib/Makefile
@@ -12,6 +12,11 @@ lib-$(CONFIG_SPARC32) += blockops.o
lib-y += memscan_$(BITS).o memcmp.o strncmp_$(BITS).o
lib-$(CONFIG_SPARC32) += divdi3.o udivdi3.o
lib-$(CONFIG_SPARC32) += copy_user.o locks.o
+lib-$(CONFIG_SPARC32) += casa_32.o
+
+# casa is not in v8, which the rest of sparc32 is assembled as. -Aleon rather
+# than -Av9: it adds the one instruction without letting anything else v9 in.
+AFLAGS_casa_32.o += -Wa,-Aleon
lib-$(CONFIG_SPARC64) += atomic_64.o
lib-$(CONFIG_SPARC32) += lshrdi3.o ashldi3.o
lib-$(CONFIG_SPARC32) += muldi3.o bitext.o
diff --git a/arch/sparc/lib/casa_32.S b/arch/sparc/lib/casa_32.S
new file mode 100644
index 000000000000..67df9ea439ce
--- /dev/null
+++ b/arch/sparc/lib/casa_32.S
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Compare-and-swap for the 32-bit sparc implementations that have it.
+ */
+
+#include <linux/linkage.h>
+#include <asm/errno.h>
+
+ .text
+ .align 4
+
+/* int __sparc32_casa(u32 *addr, u32 oldval, u32 newval, u32 *prev)
+ * Returns 0 and *prev, or -EFAULT. ASI 0x0b is supervisor data.
+ */
+ENTRY(__sparc32_casa)
+ /* nop + .balignl: LEON errata GRLIB-TN-0010 and TN-0011. */
+ nop
+ .balignl 16, 0x01000000
+1: casa [%o0] 0x0b, %o1, %o2
+ st %o2, [%o3]
+ retl
+ clr %o0
+ENDPROC(__sparc32_casa)
+
+ .section .fixup,#alloc,#execinstr
+ .align 4
+2: retl
+ mov -EFAULT, %o0
+ .previous
+
+ .section __ex_table,#alloc
+ .align 4
+ .word 1b, 2b
+ .previous
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH 2/5] sparc32: add a kernel assisted compare-and-swap
2026-09-23 20:17 [RFC PATCH 0/5] sparc32: kernel assisted compare-and-swap, and futex on SMP Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 1/5] sparc32: detect the compare-and-swap instruction at boot Magnus Lindholm
@ 2026-09-23 20:17 ` Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 3/5] sparc32: implement futex atomic ops with the compare-and-swap locks Magnus Lindholm
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Magnus Lindholm @ 2026-09-23 20:17 UTC (permalink / raw)
To: davem, andreas; +Cc: sam, sparclinux, linux-kernel, linmag7
Pre-v9 sparc has no compare-and-swap instruction, so userspace can only
emulate one with a lock, and neither place to put that lock works. A lock
private to a process cannot serialise against another process operating on
the same page. A lock kept inside the object, what glibc did before
2019, ldstub on the top byte of the word, is correct across processes but
costs value bits, so it cannot implement the full-width operations a
current compiler emits calls to. A lock owned by the kernel has neither
problem. parisc solves the same problem the same way with its LWS
compare-and-swap.
Add trap type 0x91 for this. Dispatch follows the existing do_hw_divzero
pattern of reaching C rather than doing the work in trap assembly. The
ABI is
%o0 word aligned user address
%o1 expected value
%o2 new value
returning the value found at the address in %o0, the previously observed
value, as __sync_val_compare_and_swap does, with the carry bit set and an
errno in %o0 on failure.
The number is SP_TRAP_CAS in asm/traps.h, where the other software trap
numbers already live, since this one is userspace ABI from now on. The
instruction userspace executes is ta 0x11, not ta 0x91: sparc forms the
trap type by adding 0x80 to the software trap number, which is also why
the Linux system call is SP_TRAP_LINUX 0x90 and glibc issues ta 0x10.
David Miller prototyped the same idea in 2016 on software trap 0x23,
discovered through the get-kernel-features call and implemented first on
sparc64 [2]. This is a different ABI rather than that one finished:
0x11 follows the Linux syscall trap immediately, discovery is AT_HWCAP,
and sparc64 compat mode is not supported.
Where the cpu implements the instruction the lock is not used at all and
the swap is performed directly, with the user data ASI so that the kernel
reaches the word the same way a binary built for that cpu does. Choosing
on the hardware rather than on a build option is what keeps the two in
step: such a binary uses casa inline and never takes this trap, so a
kernel serialising on a lock would exclude nothing against it.
Otherwise the lock array is used. It is hashed on address bits that lie
inside the page offset, so the same physical word reached through
different virtual addresses selects the same lock. asm/futex_32.h uses
the same array, so a compare-and-swap from userspace and one performed by
the futex code exclude each other.
Page faults are disabled while the lock is held, since faulting there
would take the mmap lock with interrupts off; on failure the lock is
dropped, the page is faulted in with fault_in_safe_writeable(), not
fault_in_writeable(), which may modify the target memory while probing it
and the operation is retried.
One consequence constrains the caller rather than the kernel: where the
lock is used an ordinary store does not take it, so userspace must build
every atomic write from this service, or those writes are not atomic
against it. Andreas Larsson made the same point about
kernel-emulated casa in 2016 [1]. Documentation/arch/sparc/cas-trap.rst
states that, and the width and compiler-integration limits, as part of
the ABI.
Link: https://inbox.sourceware.org/libc-alpha/5810C1A3.9030504@gaisler.com/ [1]
Link: https://inbox.sourceware.org/libc-alpha/20161107.113825.631166023186879199.davem@davemloft.net/ [2]
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/cas_32.h | 23 +++++++
arch/sparc/include/uapi/asm/traps.h | 3 +-
arch/sparc/kernel/Makefile | 1 +
arch/sparc/kernel/cas_32.c | 96 +++++++++++++++++++++++++++++
arch/sparc/kernel/entry.S | 17 +++++
arch/sparc/kernel/ttable_32.S | 9 +--
arch/sparc/lib/casa_32.S | 12 ++++
7 files changed, 156 insertions(+), 5 deletions(-)
create mode 100644 arch/sparc/kernel/cas_32.c
diff --git a/arch/sparc/include/asm/cas_32.h b/arch/sparc/include/asm/cas_32.h
index 3658ff71fa70..400be2b9f628 100644
--- a/arch/sparc/include/asm/cas_32.h
+++ b/arch/sparc/include/asm/cas_32.h
@@ -2,9 +2,32 @@
#ifndef _SPARC_CAS_32_H
#define _SPARC_CAS_32_H
+#include <linux/spinlock.h>
#include <linux/types.h>
+struct pt_regs;
+
+asmlinkage void handle_sparc32_cas(struct pt_regs *regs, unsigned long pc,
+ unsigned long npc, unsigned long psr);
+
/* Returns 0 and stores the value found in *prev, or -EFAULT. */
int __sparc32_casa(u32 *addr, u32 oldval, u32 newval, u32 *prev);
+/* The same on a user address, using the user data ASI. */
+int __sparc32_casa_user(u32 __user *addr, u32 oldval, u32 newval, u32 *prev);
+
+#define SPARC32_ATOMIC_LOCKS 256
+
+extern raw_spinlock_t sparc32_atomic_locks[SPARC32_ATOMIC_LOCKS];
+
+/* Hash on page-offset bits only, so one physical word maps to one lock
+ * through any virtual address. asm/futex_32.h must hash identically.
+ */
+static inline raw_spinlock_t *sparc32_atomic_lock(const void __user *uaddr)
+{
+ unsigned long ua = (unsigned long)uaddr;
+
+ return &sparc32_atomic_locks[(ua >> 2) & (SPARC32_ATOMIC_LOCKS - 1)];
+}
+
#endif /* _SPARC_CAS_32_H */
diff --git a/arch/sparc/include/uapi/asm/traps.h b/arch/sparc/include/uapi/asm/traps.h
index 43fe5b8fe8be..3e4d1c0c5a94 100644
--- a/arch/sparc/include/uapi/asm/traps.h
+++ b/arch/sparc/include/uapi/asm/traps.h
@@ -81,6 +81,7 @@
#define SP_TRAP_SOLARIS 0x88 /* Solaris System Call */
#define SP_TRAP_NETBSD 0x89 /* NetBSD System Call */
#define SP_TRAP_LINUX 0x90 /* Linux System Call */
+#define SP_TRAP_CAS 0x91 /* Compare and swap, issued as "ta 0x11" */
/* Names used for compatibility with SunOS */
#define ST_SYSCALL 0x00
@@ -104,7 +105,7 @@
(level > SP_TRAP_BADFL && level < SP_TRAP_CPEXP) || \
(level > SP_TRAP_DMM && level < SP_TRAP_IMM) || \
(level > SP_TRAP_IMM && level < SP_TRAP_SUNOS) || \
- (level > SP_TRAP_LINUX && level < SP_TRAP_KBPT1))
+ (level > SP_TRAP_CAS && level < SP_TRAP_KBPT1))
/* Is this a Hardware trap? */
#define HW_TRAP_P(level) ((level > 0) && (level < SP_TRAP_SUNOS))
diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile
index 497b5714fa8f..7ae9ab4082ca 100644
--- a/arch/sparc/kernel/Makefile
+++ b/arch/sparc/kernel/Makefile
@@ -18,6 +18,7 @@ CFLAGS_REMOVE_pcr.o := -pg
endif
obj-y := head_$(BITS).o
+obj-$(CONFIG_SPARC32) += cas_32.o
obj-$(CONFIG_SPARC64) += urtt_fill.o
obj-$(CONFIG_SPARC32) += entry.o wof.o wuf.o
obj-$(CONFIG_SPARC32) += etrap_32.o
diff --git a/arch/sparc/kernel/cas_32.c b/arch/sparc/kernel/cas_32.c
new file mode 100644
index 000000000000..de9822dc80b9
--- /dev/null
+++ b/arch/sparc/kernel/cas_32.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kernel assisted compare-and-swap for pre-v9 sparc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/pagemap.h>
+
+#include <asm/ptrace.h>
+#include <asm/cas_32.h>
+#include <asm/cpu_type.h>
+
+raw_spinlock_t sparc32_atomic_locks[SPARC32_ATOMIC_LOCKS] = {
+ [0 ... (SPARC32_ATOMIC_LOCKS - 1)] =
+ __RAW_SPIN_LOCK_UNLOCKED(sparc32_atomic_locks)
+};
+
+/* Report errors in the carry bit as the syscall path does, then step over
+ * the trapping instruction.
+ */
+static void cas_return(struct pt_regs *regs, unsigned long value, bool error)
+{
+ regs->u_regs[UREG_I0] = value;
+ if (error)
+ regs->psr |= PSR_C;
+ else
+ regs->psr &= ~PSR_C;
+
+ regs->pc = regs->npc;
+ regs->npc = regs->npc + 4;
+}
+
+/* Trap type SP_TRAP_CAS; the ABI is in
+ * Documentation/arch/sparc/cas-trap.rst.
+ */
+asmlinkage void handle_sparc32_cas(struct pt_regs *regs, unsigned long pc,
+ unsigned long npc, unsigned long psr)
+{
+ u32 __user *uaddr = (u32 __user *)regs->u_regs[UREG_I0];
+ u32 oldval = regs->u_regs[UREG_I1];
+ u32 newval = regs->u_regs[UREG_I2];
+ raw_spinlock_t *lock;
+ unsigned long flags;
+ u32 val;
+ int err;
+
+ if (unlikely((unsigned long)uaddr & 3)) {
+ cas_return(regs, EINVAL, true);
+ return;
+ }
+
+ if (unlikely(!access_ok(uaddr, sizeof(u32)))) {
+ cas_return(regs, EFAULT, true);
+ return;
+ }
+
+ if (sparc32_has_casa) {
+ /* No lock needed: casa is atomic against userspace's own. */
+ if (likely(!__sparc32_casa_user(uaddr, oldval, newval, &val)))
+ cas_return(regs, val, false);
+ else
+ cas_return(regs, EFAULT, true);
+ return;
+ }
+
+ lock = sparc32_atomic_lock(uaddr);
+
+retry:
+ /* A fault must not take the mmap lock under a raw spinlock, so make
+ * it -EFAULT here and fault the page in after dropping the lock.
+ */
+ raw_spin_lock_irqsave(lock, flags);
+ pagefault_disable();
+
+ err = __get_user(val, uaddr);
+ if (!err && val == oldval)
+ err = __put_user(newval, uaddr);
+
+ pagefault_enable();
+ raw_spin_unlock_irqrestore(lock, flags);
+
+ if (likely(!err)) {
+ cas_return(regs, val, false);
+ return;
+ }
+
+ /* fault_in_writeable() may modify the target memory. */
+ if (fault_in_safe_writeable((char __user *)uaddr, sizeof(u32))) {
+ cas_return(regs, EFAULT, true);
+ return;
+ }
+
+ goto retry;
+}
diff --git a/arch/sparc/kernel/entry.S b/arch/sparc/kernel/entry.S
index ea51ef52c952..cad48f0a0835 100644
--- a/arch/sparc/kernel/entry.S
+++ b/arch/sparc/kernel/entry.S
@@ -644,6 +644,23 @@ do_cp_exception:
RESTORE_ALL
+ /* This routine handles the compare-and-swap trap, type 0x91. */
+ .align 4
+ .globl do_sparc32_cas
+do_sparc32_cas:
+ SAVE_ALL
+
+ wr %l0, PSR_ET, %psr ! re-enable traps
+ WRITE_PAUSE
+
+ add %sp, STACKFRAME_SZ, %o0
+ mov %l1, %o1
+ mov %l2, %o2
+ call handle_sparc32_cas
+ mov %l0, %o3
+
+ RESTORE_ALL
+
/* This routine handles Hardware Divide By Zero Exceptions. */
.align 4
.globl do_hw_divzero
diff --git a/arch/sparc/kernel/ttable_32.S b/arch/sparc/kernel/ttable_32.S
index e79fd786fbbb..0408f0dbae68 100644
--- a/arch/sparc/kernel/ttable_32.S
+++ b/arch/sparc/kernel/ttable_32.S
@@ -89,7 +89,8 @@ t_bad89:BAD_TRAP(0x89) /* Net-B.S. System Call */
t_bad8a:BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c) BAD_TRAP(0x8d) BAD_TRAP(0x8e)
t_bad8f:BAD_TRAP(0x8f)
t_linux:LINUX_SYSCALL_TRAP /* Linux System Call */
-t_bad91:BAD_TRAP(0x91) BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94) BAD_TRAP(0x95)
+t_cas: TRAP_ENTRY(0x91, do_sparc32_cas) /* Compare and swap */
+t_bad92:BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94) BAD_TRAP(0x95)
t_bad96:BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99) BAD_TRAP(0x9a)
t_bad9b:BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e) BAD_TRAP(0x9f)
t_getcc:GETCC_TRAP /* Get Condition Codes */
@@ -186,7 +187,7 @@ trapbase_cpu1:
BAD_TRAP(0x87) BAD_TRAP(0x88) BAD_TRAP(0x89)
BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c)
BAD_TRAP(0x8d) BAD_TRAP(0x8e) BAD_TRAP(0x8f)
- LINUX_SYSCALL_TRAP BAD_TRAP(0x91)
+ LINUX_SYSCALL_TRAP TRAP_ENTRY(0x91, do_sparc32_cas)
BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94)
BAD_TRAP(0x95) BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99)
BAD_TRAP(0x9a) BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e)
@@ -286,7 +287,7 @@ trapbase_cpu2:
BAD_TRAP(0x86) BAD_TRAP(0x87) BAD_TRAP(0x88)
BAD_TRAP(0x89) BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c)
BAD_TRAP(0x8d) BAD_TRAP(0x8e) BAD_TRAP(0x8f)
- LINUX_SYSCALL_TRAP BAD_TRAP(0x91)
+ LINUX_SYSCALL_TRAP TRAP_ENTRY(0x91, do_sparc32_cas)
BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94)
BAD_TRAP(0x95) BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99)
BAD_TRAP(0x9a) BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e)
@@ -385,7 +386,7 @@ trapbase_cpu3:
BAD_TRAP(0x89) BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c)
BAD_TRAP(0x8d) BAD_TRAP(0x8e) BAD_TRAP(0x8f)
LINUX_SYSCALL_TRAP
- BAD_TRAP(0x91) BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94)
+ TRAP_ENTRY(0x91, do_sparc32_cas) BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94)
BAD_TRAP(0x95) BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99)
BAD_TRAP(0x9a) BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e)
BAD_TRAP(0x9f)
diff --git a/arch/sparc/lib/casa_32.S b/arch/sparc/lib/casa_32.S
index 67df9ea439ce..c277f1f1f795 100644
--- a/arch/sparc/lib/casa_32.S
+++ b/arch/sparc/lib/casa_32.S
@@ -22,6 +22,17 @@ ENTRY(__sparc32_casa)
clr %o0
ENDPROC(__sparc32_casa)
+/* The same on a user address; ASI 0x0a is user data, as userspace uses. */
+ENTRY(__sparc32_casa_user)
+ /* nop + .balignl: LEON errata GRLIB-TN-0010 and TN-0011. */
+ nop
+ .balignl 16, 0x01000000
+4: casa [%o0] 0x0a, %o1, %o2
+ st %o2, [%o3]
+ retl
+ clr %o0
+ENDPROC(__sparc32_casa_user)
+
.section .fixup,#alloc,#execinstr
.align 4
2: retl
@@ -31,4 +42,5 @@ ENDPROC(__sparc32_casa)
.section __ex_table,#alloc
.align 4
.word 1b, 2b
+ .word 4b, 2b
.previous
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH 3/5] sparc32: implement futex atomic ops with the compare-and-swap locks
2026-09-23 20:17 [RFC PATCH 0/5] sparc32: kernel assisted compare-and-swap, and futex on SMP Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 1/5] sparc32: detect the compare-and-swap instruction at boot Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 2/5] sparc32: add a kernel assisted compare-and-swap Magnus Lindholm
@ 2026-09-23 20:17 ` Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 4/5] sparc32: advertise the compare-and-swap trap in AT_HWCAP Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 5/5] sparc32: document the compare-and-swap trap ABI Magnus Lindholm
4 siblings, 0 replies; 6+ messages in thread
From: Magnus Lindholm @ 2026-09-23 20:17 UTC (permalink / raw)
To: davem, andreas; +Cc: sam, sparclinux, linux-kernel, linmag7
sparc32 had no futex implementation of its own and fell back to the
asm-generic one, which on SMP cannot work: it needs to update a user word
atomically, and pre-v9 sparc has no instruction for that. CONFIG_FUTEX
was therefore made to depend on !(SPARC32 && SMP).
Now that the kernel owns a compare-and-swap on behalf of userspace, the
futex code can use the same mechanism. Where the cpu lacks the
instruction both sides serialise on the lock array used by the trap type
0x91 handler; both callers go through sparc32_atomic_lock(), so the two
hashes cannot drift apart, parisc keeps the same invariant with a comment
asking that its futex hash match its LWS code.
Where the cpu has the instruction that lock cannot serve for the
read-modify-write. A binary built for such a cpu does its own casa and
knows nothing about the array, so a lock, get_user, put_user, unlock
sequence can lose that binary's update between the two accesses while both
operations appear to have succeeded and FUTEX_WAKE_OP defines its update
of uaddr2 as atomic. arch_futex_atomic_op_inuser() therefore retries a
casa until the word is still the one the new value was computed from, the
way sparc64 does it. Only the pre-v9 path takes the lock, where it is
correct because userspace takes the same lock through the trap.
Both entry points are called with page faults already disabled, so the
user accesses fail rather than sleeping under the lock.
With a real cmpxchg available the Kconfig dependency can go, which is what
lets FUTEX_PI and robust futexes be built for this configuration rather
than refused. Their runtime behaviour here has not been tested.
One gap is known and not closed here.
futex_robust_unlock() clears the futex word with
unsafe_atomic_store_release_user(), before the hash bucket is locked and
without going through either helper added here. SPARC32 currently uses
the generic implementation of unsafe_atomic_store_release_user(), which
ultimately performs a plain user-memory store. On a no-CASA SMP system
that store does not acquire the lock used by the SPARC32 software-CAS
implementation. It can therefore occur between the trap handler's load
and conditional store, allowing the CAS to overwrite the unlock:
cpu 0, trap compare-and-swap cpu 1, robust unlock
take sparc32_atomic_lock(uaddr)
read *uaddr -> T
store 0 -> *uaddr, no lock
compare against T succeeds
store T | FUTEX_WAITERS -> *uaddr
release the lock, report success
Neither order of those two operations permits that result, so the missing
serialisation is what produced it. The flag itself is not gated
CONFIG_FUTEX_ROBUST_UNLOCK covers only the rseq fixup path, so dropping
the Kconfig dependency is what exposes this on SPARC32 SMP. It has not
been observed in practice; it is reported because it follows from the
implementation.
The requirement is only that these two operations agree on how they
serialise access to the word. The macro is overridable, so SPARC32 could
supply its own definition and keep this inside the architecture; whether
that is preferable to a futex-specific interface is a question for the
futex maintainers, and is asked rather than guessed at.
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/futex_32.h | 137 +++++++++++++++++++++++++++++-
init/Kconfig | 1 -
2 files changed, 135 insertions(+), 3 deletions(-)
diff --git a/arch/sparc/include/asm/futex_32.h b/arch/sparc/include/asm/futex_32.h
index 6a332a9f099c..35ff08a6b2bd 100644
--- a/arch/sparc/include/asm/futex_32.h
+++ b/arch/sparc/include/asm/futex_32.h
@@ -1,6 +1,139 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_FUTEX_H
#define _ASM_FUTEX_H
-#include <asm-generic/futex.h>
+#include <linux/futex.h>
+#include <linux/uaccess.h>
+#include <asm/errno.h>
+#include <asm/cas_32.h>
+#include <asm/cpu_type.h>
-#endif
+/* These share the compare-and-swap trap's lock array and must hash
+ * identically to it; see asm/cas_32.h.
+ */
+
+static inline int sparc32_futex_op(int op, int oparg, u32 oldval, u32 *newval)
+{
+ switch (op) {
+ case FUTEX_OP_SET:
+ *newval = oparg;
+ break;
+ case FUTEX_OP_ADD:
+ *newval = oldval + oparg;
+ break;
+ case FUTEX_OP_OR:
+ *newval = oldval | oparg;
+ break;
+ case FUTEX_OP_ANDN:
+ *newval = oldval & ~oparg;
+ break;
+ case FUTEX_OP_XOR:
+ *newval = oldval ^ oparg;
+ break;
+ default:
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+static inline int
+arch_futex_atomic_op_inuser(int op, int oparg, int *oval, u32 __user *uaddr)
+{
+ raw_spinlock_t *lock;
+ unsigned long flags;
+ u32 oldval, newval, prev;
+ int ret = 0;
+
+ if (sparc32_has_casa) {
+ if (!access_ok(uaddr, sizeof(u32)))
+ return -EFAULT;
+
+ if (unlikely(get_user(oldval, uaddr) != 0))
+ return -EFAULT;
+
+ /* Retry until the word still holds what newval was computed
+ * from, making the read-modify-write one atomic step.
+ */
+ for (;;) {
+ ret = sparc32_futex_op(op, oparg, oldval, &newval);
+ if (ret)
+ return ret;
+
+ if (unlikely(__sparc32_casa_user(uaddr, oldval,
+ newval, &prev) != 0))
+ return -EFAULT;
+
+ if (likely(prev == oldval))
+ break;
+
+ oldval = prev;
+ }
+
+ *oval = oldval;
+ return 0;
+ }
+
+ lock = sparc32_atomic_lock(uaddr);
+ raw_spin_lock_irqsave(lock, flags);
+
+ if (unlikely(get_user(oldval, uaddr) != 0)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = sparc32_futex_op(op, oparg, oldval, &newval);
+ if (ret)
+ goto out;
+
+ if (unlikely(put_user(newval, uaddr) != 0))
+ ret = -EFAULT;
+
+out:
+ raw_spin_unlock_irqrestore(lock, flags);
+
+ if (!ret)
+ *oval = oldval;
+
+ return ret;
+}
+
+static inline int
+futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
+ u32 oldval, u32 newval)
+{
+ raw_spinlock_t *lock;
+ unsigned long flags;
+ u32 val;
+
+ if (!access_ok(uaddr, sizeof(u32)))
+ return -EFAULT;
+
+ /* casa is atomic against userspace's own; a lock of ours is not. */
+ if (sparc32_has_casa) {
+ if (__sparc32_casa_user(uaddr, oldval, newval, &val))
+ return -EFAULT;
+ *uval = val;
+ return 0;
+ }
+
+ lock = sparc32_atomic_lock(uaddr);
+ raw_spin_lock_irqsave(lock, flags);
+
+ if (unlikely(get_user(val, uaddr) != 0)) {
+ raw_spin_unlock_irqrestore(lock, flags);
+ return -EFAULT;
+ }
+
+ if (val == oldval && unlikely(put_user(newval, uaddr) != 0)) {
+ raw_spin_unlock_irqrestore(lock, flags);
+ return -EFAULT;
+ }
+
+ raw_spin_unlock_irqrestore(lock, flags);
+
+ *uval = val;
+ return 0;
+}
+
+#endif /* _ASM_FUTEX_H */
diff --git a/init/Kconfig b/init/Kconfig
index 8583d9f06c52..9eb8086af7db 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1875,7 +1875,6 @@ config BASE_SMALL
config FUTEX
bool "Enable futex support" if EXPERT
- depends on !(SPARC32 && SMP)
default y
imply RT_MUTEXES
help
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH 4/5] sparc32: advertise the compare-and-swap trap in AT_HWCAP
2026-09-23 20:17 [RFC PATCH 0/5] sparc32: kernel assisted compare-and-swap, and futex on SMP Magnus Lindholm
` (2 preceding siblings ...)
2026-09-23 20:17 ` [RFC PATCH 3/5] sparc32: implement futex atomic ops with the compare-and-swap locks Magnus Lindholm
@ 2026-09-23 20:17 ` Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 5/5] sparc32: document the compare-and-swap trap ABI Magnus Lindholm
4 siblings, 0 replies; 6+ messages in thread
From: Magnus Lindholm @ 2026-09-23 20:17 UTC (permalink / raw)
To: davem, andreas; +Cc: sam, sparclinux, linux-kernel, linmag7
A process that wants the kernel to perform a compare-and-swap has no way
to find out whether this kernel will do it, and issuing the trap to find
out is not an option: on a kernel without it the trap is a bad trap and
the process dies.
Guessing is worse than dying. On sparc64 ta 0x11 is not an illegal
instruction but the old 64-bit system call trap, so a process that issued
it there would make a wild syscall rather than take a signal.
Advertise it in AT_HWCAP so the question can be asked first. Bit
0x10000000 is unused on sparc32 and on sparc64, so it reads as clear in
exactly the cases where the trap is absent: an older kernel, or sparc64
compat mode, where a 32-bit process reads its hwcap word from elf_64.h.
Reserve the same bit there with a comment so it is not handed to something
else later and mistaken for this.
A caller that finds the bit clear must fall back to whatever it can do by
itself or report the operation unsupported. A private lock is not a
general answer: it cannot make a word atomic against another process,
which is the reason for the trap in the first place.
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/sparc/include/asm/elf_32.h | 7 ++++++-
arch/sparc/include/asm/elf_64.h | 3 +++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/arch/sparc/include/asm/elf_32.h b/arch/sparc/include/asm/elf_32.h
index 37a6016c9ccd..c60d803cf556 100644
--- a/arch/sparc/include/asm/elf_32.h
+++ b/arch/sparc/include/asm/elf_32.h
@@ -64,6 +64,10 @@
#define HWCAP_SPARC_MULDIV 8
#define HWCAP_SPARC_V9 16
#define HWCAP_SPARC_ULTRA3 32
+/* Kernel compare-and-swap trap available; see
+ * Documentation/arch/sparc/cas-trap.rst. Do not issue the trap if clear.
+ */
+#define HWCAP_SPARC_CASTRAP 0x10000000
#define CORE_DUMP_USE_REGSET
@@ -121,7 +125,8 @@ typedef struct {
/* Most sun4m's have them all. */
#define ELF_HWCAP (HWCAP_SPARC_FLUSH | HWCAP_SPARC_STBAR | \
- HWCAP_SPARC_SWAP | HWCAP_SPARC_MULDIV)
+ HWCAP_SPARC_SWAP | HWCAP_SPARC_MULDIV | \
+ HWCAP_SPARC_CASTRAP)
/* This yields a string that ld.so will use to load implementation
specific libraries for optimization. This is more specific in
diff --git a/arch/sparc/include/asm/elf_64.h b/arch/sparc/include/asm/elf_64.h
index 694ed081cf8d..a2117a81b6d8 100644
--- a/arch/sparc/include/asm/elf_64.h
+++ b/arch/sparc/include/asm/elf_64.h
@@ -98,6 +98,9 @@
*/
#define HWCAP_SPARC_CRYPTO 0x04000000 /* CRYPTO insns available */
#define HWCAP_SPARC_ADI 0x08000000 /* ADI available */
+/* 0x10000000 is HWCAP_SPARC_CASTRAP on sparc32 and must stay clear here;
+ * a 32-bit process reads this word too and there is no such trap on sparc64.
+ */
#define CORE_DUMP_USE_REGSET
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH 5/5] sparc32: document the compare-and-swap trap ABI
2026-09-23 20:17 [RFC PATCH 0/5] sparc32: kernel assisted compare-and-swap, and futex on SMP Magnus Lindholm
` (3 preceding siblings ...)
2026-09-23 20:17 ` [RFC PATCH 4/5] sparc32: advertise the compare-and-swap trap in AT_HWCAP Magnus Lindholm
@ 2026-09-23 20:17 ` Magnus Lindholm
4 siblings, 0 replies; 6+ messages in thread
From: Magnus Lindholm @ 2026-09-23 20:17 UTC (permalink / raw)
To: davem, andreas; +Cc: sam, sparclinux, linux-kernel, linmag7
The trap is userspace ABI rather than an internal kernel facility, so
write down what it promises: the instruction and trap type, the register
convention, the failure cases, what the AT_HWCAP bit means, and the limits
a caller has to work within.
Several of those limits are easy to miss. Replacing the __atomic_* helper
symbols does not by itself route every atomic write through the trap,
because the compiler can expand some of them inline and covering libc's
own uses is a different scope from covering application code built with
atomic builtins. The service is one aligned 32-bit word, which is not the
same as general byte and halfword atomics. The guarantee is indivisibility
of that one word and no ordering beyond it. And futex_robust_unlock()
currently writes the futex word without taking the lock the trap uses, so
that one kernel path is outside the guarantee.
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
Documentation/arch/sparc/cas-trap.rst | 163 ++++++++++++++++++++++++++
Documentation/arch/sparc/index.rst | 1 +
2 files changed, 164 insertions(+)
create mode 100644 Documentation/arch/sparc/cas-trap.rst
diff --git a/Documentation/arch/sparc/cas-trap.rst b/Documentation/arch/sparc/cas-trap.rst
new file mode 100644
index 000000000000..f09d81e8f1ed
--- /dev/null
+++ b/Documentation/arch/sparc/cas-trap.rst
@@ -0,0 +1,163 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=================================
+The sparc32 compare-and-swap trap
+=================================
+
+Pre-v9 sparc has no compare-and-swap instruction. A lock held inside one
+process cannot make a word atomic against another process mapping the same
+page, and a lock kept inside the word itself costs value bits, so the kernel
+performs the operation instead. parisc provides the same service for the same
+reason.
+
+Interface
+=========
+
+``ta 0x11`` - software trap 0x11, trap type 0x91, ``SP_TRAP_CAS`` in
+``asm/traps.h`` - asks the kernel to compare and swap one 32-bit word:
+
+======= =========================================
+``%o0`` word aligned user address
+``%o1`` expected value
+``%o2`` new value
+======= =========================================
+
+On return the carry bit is clear and ``%o0`` holds the value that was found at
+the address. The caller compares it against what it expected to learn whether
+the swap happened; there is no separate success flag.
+
+Registers
+---------
+
+``%o0`` is read and written. ``%o1`` and ``%o2`` are read and are not
+modified. Every other windowed and global register is preserved across the
+trap. The integer condition codes are written - that is how success and
+failure are reported - and must be treated as clobbered.
+
+An inline-assembly wrapper therefore wants ``%o0`` as an in/out operand,
+``%o1`` and ``%o2`` as inputs, and both ``"cc"`` and ``"memory"`` in the
+clobber list, the latter so the compiler does not move accesses across the
+operation.
+
+On failure the carry bit is set and ``%o0`` holds an error number:
+
+========== =========================================
+``EINVAL`` the address is not word aligned
+``EFAULT`` the memory access could not be completed
+========== =========================================
+
+A successful return does not establish that the mapping is writable. On the
+software path the kernel writes only when the comparison succeeds, so a
+mismatching value on a read-only mapping can be returned without the kernel
+ever discovering that the mapping could not be written. The two
+implementations are not required to agree about an invalid operand: pass an
+aligned word in memory that is both readable and writable.
+
+The operation is atomic against another process issuing the same trap, against
+the futex operations in ``asm/futex_32.h``, and - on a cpu that implements
+``casa`` - against a binary using that instruction directly.
+
+The service guarantees an indivisible compare-and-swap on the addressed word
+under the participation rules below. It does not provide any additional
+acquire, release or full-barrier guarantee for other memory locations.
+Userspace must supply whatever ordering the operation it is implementing
+requires.
+
+One kernel path does not participate. ``futex_robust_unlock()`` clears the
+futex word through ``unsafe_atomic_store_release_user()``, for which sparc32
+uses the generic definition - ultimately a plain user store. On a no-casa SMP
+system that store does not acquire the lock this trap uses, so it can land
+between the trap's load and its conditional store, and the compare-and-swap
+then overwrites the unlock while still reporting success.
+
+On no-casa SMP systems the non-PI robust-unlock operations
+``FUTEX_UNLOCK_WAKE_LIST32`` and ``FUTEX_UNLOCK_BITSET_LIST32``, including
+their private variants, therefore do not serialise with the software-CAS lock
+domain. Their store can race with a trap-based compare-and-swap, and equally
+with the lock-backed operations in ``asm/futex_32.h``, which take the lock but
+still load and store separately.
+
+The integration point for this store remains an open question for review.
+
+What userspace has to do as well
+================================
+
+On a cpu without ``casa`` the kernel does the swap under a lock of its own, and
+**an ordinary store does not take that lock**::
+
+ trap another thread's plain store
+
+ lock
+ read word -> A
+ store B -> word
+ write C -> word
+ unlock
+
+``B`` is lost, and no ordering of an atomic compare-and-swap and an atomic
+store makes that a legal outcome.
+
+So userspace using this service to build atomic operations must build *every*
+atomic write from it, not just compare-and-exchange: an atomic store and an
+atomic exchange each become a compare-and-swap retry loop. An implementation
+that routes read-modify-writes through the trap but leaves atomic stores as
+plain stores is not atomic, and the failure is silent.
+
+This does not apply to a cpu that has ``casa``, where the kernel uses the
+instruction and userspace can too.
+
+Replacing the ``__atomic_*`` helpers is not sufficient
+-----------------------------------------------------
+
+Providing replacement ``__atomic_store_4`` and ``__atomic_exchange_4`` symbols
+does not by itself route every atomic write through the trap. gcc can expand
+an atomic store to a plain store, a 32-bit atomic exchange to ``swap``, and an
+atomic test-and-set to ``ldstub`` on v8. ``swap`` and ``ldstub`` are
+themselves atomic, but they do not take the kernel's lock and so do not exclude
+against its separate read and write. A call the compiler never emits cannot be
+intercepted.
+
+Two different integration scopes follow from that, and they are not
+interchangeable. Changing libc's internal atomic macros covers libc's own
+uses. It does not change application code compiled with atomic builtins; that
+needs compiler support - the possibility Andreas Larsson raised in 2019 - or a
+build that avoids the inline expansions.
+
+One aligned word is not general byte and halfword atomics
+--------------------------------------------------------
+
+The service operates on one naturally aligned 32-bit word. A byte or halfword
+atomic synthesised by read-modify-writing its containing word is not safe
+merely because every atomic on the target uses the trap::
+
+ word: [ atomic byte A | ordinary byte B | ... ]
+
+ cpu 0 cpu 1
+
+ read the whole word under the lock
+ plain store to B
+ write the whole word back,
+ including the old B
+
+The ordinary writer of the neighbouring object ``B`` has no reason to
+participate, and its store is lost. Byte and halfword atomics built this way
+need either width-aware accesses sharing this lock domain, or storage rules
+that give the atomic object its containing word to itself.
+
+Availability
+============
+
+``AT_HWCAP`` carries ``HWCAP_SPARC_CASTRAP`` (0x10000000) when the kernel
+implements the trap. Userspace must consult it rather than probing, because
+there is no safe way to probe:
+
+- on a kernel without this support the trap is fatal;
+- on sparc64 a 32-bit process reads its hwcap word from ``asm/elf_64.h``, where
+ the bit is reserved and always clear, and ``ta 0x11`` there is not an illegal
+ instruction but the old 64-bit system call trap.
+
+The bit describes the kernel service, not the hardware. It is set whether or
+not the cpu has ``casa``, because a binary built for plain v8 cannot use that
+instruction even on a cpu that has it. A binary that does have ``casa``
+available - one built for LEON3 - should use it inline and ignore the bit
+entirely; the kernel uses the instruction too where it exists, so the two agree
+on the same word.
diff --git a/Documentation/arch/sparc/index.rst b/Documentation/arch/sparc/index.rst
index ae884224eec2..3a7d6df91a84 100644
--- a/Documentation/arch/sparc/index.rst
+++ b/Documentation/arch/sparc/index.rst
@@ -7,6 +7,7 @@ Sparc Architecture
console
adi
+ cas-trap
oradax/oracle-dax
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-23 20:19 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23 20:17 [RFC PATCH 0/5] sparc32: kernel assisted compare-and-swap, and futex on SMP Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 1/5] sparc32: detect the compare-and-swap instruction at boot Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 2/5] sparc32: add a kernel assisted compare-and-swap Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 3/5] sparc32: implement futex atomic ops with the compare-and-swap locks Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 4/5] sparc32: advertise the compare-and-swap trap in AT_HWCAP Magnus Lindholm
2026-09-23 20:17 ` [RFC PATCH 5/5] sparc32: document the compare-and-swap trap ABI Magnus Lindholm
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®