[PATCH 4/4] sparc32: add the sparc_atomic_cmpxchg_32 system call

From: Ramin Moussavi

Date: Thu Oct 15 2026 - 04:58:09 EST


User space built for v8 has no compare-and-swap. C libraries emulate it
with ldstub locks of their own, and those have two problems the kernel
cannot see. They are not async-signal-safe: a handler that reaches an
atomic while the code it interrupted holds the lock spins forever. And
the kernel does not know them: FUTEX_WAKE_OP and robust futexes write
the same words without taking those locks.

Let user space ask the kernel instead. The call goes through
futex_atomic_cmpxchg_inatomic(), so it agrees with whatever the futex
code does to the same word, casa on LEON or the hashed lock otherwise,
and a single system call cannot be interrupted by a signal halfway.
m68k solved the same problem the same way with atomic_cmpxchg_32.

The previous value goes back in %o1 and the return value is 0 or an
error, so that every 32-bit value can be told apart from an error; the
carry flag decides. A misaligned word gives -EINVAL. On a fault the
page is faulted in for writing and the operation retried, so a
copy-on-write page breaks first and a read-only one gives -EFAULT.

The 64-bit kernel leaves the compat slot empty: a 32-bit program on a
v9 CPU gets -ENOSYS and keeps its own emulation, which works there.

Tested on qemu 11.1.0 (SS-20 UP, SS-20 -smp 4, leon3_generic) with a
test program: hit and miss, a word of 0xffffffff, a misaligned word,
NULL, a read-only page, copy-on-write after fork(), and two processes
counting to 40000 on a shared page. A kernel without the call gives
-ENOSYS. A uClibc-ng that probes for the call at startup and uses it
for all its atomics passes its full test suite on UP and on -smp 4,
and a loop of 3000 runs of tst-mallocfork, which forks from a signal
handler, no longer hangs.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ramin Moussavi <ramin.moussavi@xxxxxxxxx>
---
arch/sparc/kernel/sys_sparc_32.c | 35 ++++++++++++++++++++++++++
arch/sparc/kernel/syscalls/syscall.tbl | 1 +
arch/sparc/kernel/systbls.h | 1 +
3 files changed, 37 insertions(+)

diff --git a/arch/sparc/kernel/sys_sparc_32.c b/arch/sparc/kernel/sys_sparc_32.c
index fb31bc0c5b48..d065f0a41b04 100644
--- a/arch/sparc/kernel/sys_sparc_32.c
+++ b/arch/sparc/kernel/sys_sparc_32.c
@@ -26,6 +26,7 @@
#include <linux/hugetlb.h>

#include <linux/uaccess.h>
+#include <asm/futex.h>
#include <asm/unistd.h>

#include "systbls.h"
@@ -95,6 +96,40 @@ SYSCALL_DEFINE0(sparc_pipe)
return error;
}

+/*
+ * Compare-and-exchange for userspace that cannot rely on casa. It goes
+ * through the futex primitive, so it agrees with whatever the kernel does
+ * to the same word. The previous value goes back in %o1, so that every
+ * 32-bit value can be told apart from an error.
+ */
+SYSCALL_DEFINE3(sparc_atomic_cmpxchg_32, u32 __user *, uaddr,
+ u32, old, u32, new)
+{
+ struct mm_struct *mm = current->mm;
+ u32 prev;
+ int ret;
+
+ if ((unsigned long)uaddr & 3)
+ return -EINVAL;
+
+ for (;;) {
+ pagefault_disable();
+ ret = futex_atomic_cmpxchg_inatomic(&prev, uaddr, old, new);
+ pagefault_enable();
+ if (ret != -EFAULT)
+ break;
+ mmap_read_lock(mm);
+ ret = fixup_user_fault(mm, (unsigned long)uaddr,
+ FAULT_FLAG_WRITE, NULL);
+ mmap_read_unlock(mm);
+ if (ret < 0)
+ return -EFAULT;
+ }
+
+ current_pt_regs()->u_regs[UREG_I1] = prev;
+ return 0;
+}
+
int sparc_mmap_check(unsigned long addr, unsigned long len)
{
/* See asm-sparc/uaccess.h */
diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
index 5b9fe0e8140f..939e0130b7e2 100644
--- a/arch/sparc/kernel/syscalls/syscall.tbl
+++ b/arch/sparc/kernel/syscalls/syscall.tbl
@@ -518,3 +518,4 @@
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
472 common fchroot sys_fchroot
+473 32 sparc_atomic_cmpxchg_32 sys_sparc_atomic_cmpxchg_32 sys_ni_syscall
diff --git a/arch/sparc/kernel/systbls.h b/arch/sparc/kernel/systbls.h
index bf014267d619..25e1ceefe323 100644
--- a/arch/sparc/kernel/systbls.h
+++ b/arch/sparc/kernel/systbls.h
@@ -11,6 +11,7 @@

asmlinkage long sys_getpagesize(void);
asmlinkage long sys_sparc_pipe(void);
+asmlinkage long sys_sparc_atomic_cmpxchg_32(u32 __user *uaddr, u32 old, u32 new);
asmlinkage long sys_nis_syscall(void);
asmlinkage long sys_getdomainname(char __user *name, int len);
void do_rt_sigreturn(struct pt_regs *regs);
--
2.53.0