Re: [PATCH 1/3] sparc32: support futexes on SMP

From: Magnus Lindholm

Date: Sun Oct 04 2026 - 04:51:30 EST


Hi Imre,

On Sun, Sep 27, 2026 at 2:59 AM Imre Kaloz <kaloz@xxxxxxxxxx> wrote:
>
> asm-generic/futex.h provides the futex atomic ops for UP only, so FUTEX
> has depended on !(SPARC32 && SMP).
>
> Implement them in atomic32.c for both. LEON parts that implement casa
> run it on the user word, as user code updates the word with casa there
> and takes no kernel lock. Other CPUs have no compare-and-swap; the ops
> serialize on __atomic_hash, which lets a kernel emulation of user casa
> share the lock.
>
> casa is optional on LEON, so it is probed once at boot. On a part
> without it the kernel-mode illegal_instruction is fixed up from the
> exception table.
>
> Signed-off-by: Imre Kaloz <kaloz@xxxxxxxxxx>
> ---
> arch/sparc/include/asm/futex_32.h | 6 +-
> arch/sparc/kernel/traps_32.c | 13 +++-
> arch/sparc/lib/atomic32.c | 122 ++++++++++++++++++++++++++++++
> init/Kconfig | 1 -
> 4 files changed, 139 insertions(+), 3 deletions(-)
>
> diff --git a/arch/sparc/include/asm/futex_32.h b/arch/sparc/include/asm/futex_32.h
> index 6a332a9f099c..2271aad566f8 100644
> --- a/arch/sparc/include/asm/futex_32.h
> +++ b/arch/sparc/include/asm/futex_32.h
> @@ -1,6 +1,10 @@
> #ifndef _ASM_FUTEX_H
> #define _ASM_FUTEX_H
>
> -#include <asm-generic/futex.h>
> +#include <linux/types.h>
> +
> +int arch_futex_atomic_op_inuser(int op, u32 oparg, int *oval, u32 __user *uaddr);
> +int futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
> + u32 oldval, u32 newval);
>
> #endif
> diff --git a/arch/sparc/kernel/traps_32.c b/arch/sparc/kernel/traps_32.c
> index bb149f6cc34b..e1d50820c376 100644
> --- a/arch/sparc/kernel/traps_32.c
> +++ b/arch/sparc/kernel/traps_32.c
> @@ -11,6 +11,7 @@
> */
>
> #include <linux/cpu.h>
> +#include <linux/extable.h>
> #include <linux/sched/mm.h>
> #include <linux/sched/debug.h>
> #include <linux/mm_types.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 on a CPU without casa. */
> + 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/atomic32.c b/arch/sparc/lib/atomic32.c
> index 8ae880ebf07a..be712c2917d5 100644
> --- a/arch/sparc/lib/atomic32.c
> +++ b/arch/sparc/lib/atomic32.c
> @@ -9,8 +9,13 @@
> */
>
> #include <linux/atomic.h>
> +#include <linux/futex.h>
> +#include <linux/init.h>
> #include <linux/spinlock.h>
> #include <linux/module.h>
> +#include <linux/uaccess.h>
> +#include <asm/cpu_type.h>
> +#include <asm/futex.h>
>
> #ifdef CONFIG_SMP
> #define ATOMIC_HASH_SIZE 4
> @@ -195,3 +200,120 @@ unsigned long __xchg_u32(volatile u32 *ptr, u32 new)
> return (unsigned long)prev;
> }
> EXPORT_SYMBOL(__xchg_u32);
> +
> +/* Only some LEON parts implement casa. */
> +static bool sparc32_casa __ro_after_init;
> +
> +static int __casa_user(u32 *uval, u32 __user *uaddr, u32 oldval, u32 newval)
> +{
> + /*
> + * casa [%o0] 0xb, %o1, %o2, as a word any assembler accepts. ASI
> + * 0x0b is the supervisor address space; the emulated casa in
> + * cas_emu_32.c instead accepts the user ASIs 0x0a and 0x80.
> + */
> + register u32 __user *uaddr_r asm("o0") = uaddr;
> + register u32 oldval_r asm("o1") = oldval;
> + register u32 newval_r asm("o2") = newval;
> + int ret = 0;
> +
> + __asm__ __volatile__(
> + "1: .word 0xd5e20169\n"
> + "2:\n"
> + " .section .fixup,#alloc,#execinstr\n"
> + " .align 4\n"
> + "3: sethi %%hi(2b), %0\n"
> + " jmpl %0 + %%lo(2b), %%g0\n"
> + " mov %4, %0\n"
> + " .previous\n"
> + " .section __ex_table,#alloc\n"
> + " .align 4\n"
> + " .word 1b, 3b\n"
> + " .previous\n"
> + : "+r" (ret), "+r" (newval_r)
> + : "r" (uaddr_r), "r" (oldval_r), "i" (-EFAULT)
> + : "memory");
> +
> + *uval = newval_r;
> + return ret;
> +}
> +
> +/* Without casa the illegal_instruction trap takes the fixup above. */
> +static int __init sparc32_casa_probe(void)
> +{
> + u32 word = 0, val;
> +
> + if (sparc_cpu_model == sparc_leon)
> + sparc32_casa = !__casa_user(&val, (u32 __force __user *)&word,
> + 0, 1);
> + return 0;
> +}
> +early_initcall(sparc32_casa_probe);
> +
> +/*
> + * Without casa, user CAS is emulated under the same lock. ATOMIC_HASH()
> + * uses page offset bits only, so every mapping of a word takes one lock.
> + */
> +int futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
> + u32 oldval, u32 newval)
> +{
> + unsigned long flags;
> + u32 val;
> + int ret;
> +
> + if (!access_ok(uaddr, sizeof(u32)))
> + return -EFAULT;
> + if (sparc32_casa)
> + return __casa_user(uval, uaddr, oldval, newval);
> +
> + /* Every caller already runs with page faults disabled. */
> + spin_lock_irqsave(ATOMIC_HASH(uaddr), flags);
> + ret = __get_user(val, uaddr);
> + if (!ret && val == oldval)
> + ret = __put_user(newval, uaddr);
> + spin_unlock_irqrestore(ATOMIC_HASH(uaddr), flags);
> +
> + if (!ret)
> + *uval = val;
> + return ret;
> +}
> +
> +int arch_futex_atomic_op_inuser(int op, u32 oparg, int *oval, u32 __user *uaddr)
> +{
> + u32 oldval, newval, val;
> + int ret;
> +
> + if (get_user(oldval, uaddr))
> + return -EFAULT;
> +
> + for (;;) {
> + 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;
> + }
> +
> + ret = futex_atomic_cmpxchg_inatomic(&val, uaddr, oldval, newval);
> + if (ret)
> + return ret;
> + if (val == oldval)
> + break;
> + oldval = val;
> + }
> +
> + *oval = oldval;
> + return 0;
> +}
> 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.47.3
>

Tested on a real SPARCstation 20 (SMP), on both a HyperSPARC and a
SuperSPARC-II module. Raw FUTEX_WAIT/WAKE and FUTEX_LOCK_PI/UNLOCK_PI
all work on both (no more ENOSYS), and an 8-thread pthread_mutex
contention test (1.6M locked increments) finished with zero lost
updates on both CPUs.

Tested-by: Magnus Lindholm <linmag7@xxxxxxxxx>