Re: [PATCH 2/3] sparc32: emulate casa on V8 CPUs

From: Magnus Lindholm

Date: Sun Oct 04 2026 - 05:02:49 EST


Hi Imre,

On Sun, Sep 27, 2026 at 2:59 AM Imre Kaloz <kaloz@xxxxxxxxxx> wrote:
>
> sun4m implements no compare-and-swap instruction, only ldstub and swap,
> so the V9 casa opcode is undefined there and raises illegal_instruction.
> Complete the immediate-ASI form from do_illegal_instruction() for both
> ASIs a compiler emits -- 0x80, the V9 primary address space, and 0x0A
> from gcc -mcpu=leon3 -- so a C library can use one casa for its 32-bit
> atomics on any V8 part. The opcode is unimplemented, so the ASI carries
> no distinction the emulated CAS has to honour. LEON parts built without
> casa take the same trap and are the LEON parts this serves.
>
> The CAS is futex_atomic_cmpxchg_inatomic(), so it is atomic against the
> futex operations and against itself on SMP. Everything else, including
> the register-ASI form and casxa, raises SIGILL.
>
> Confirmed on a dual-SuperSPARC SPARCstation 20.
>
> Signed-off-by: Imre Kaloz <kaloz@xxxxxxxxxx>
> ---
> arch/sparc/kernel/Makefile | 1 +
> arch/sparc/kernel/cas_emu_32.c | 117 +++++++++++++++++++++++++++++++++
> arch/sparc/kernel/entry.h | 1 +
> arch/sparc/kernel/traps_32.c | 7 ++
> arch/sparc/kernel/unimp_32.h | 71 ++++++++++++++++++++
> 5 files changed, 197 insertions(+)
> create mode 100644 arch/sparc/kernel/cas_emu_32.c
> create mode 100644 arch/sparc/kernel/unimp_32.h
>
> diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile
> index 497b5714fa8f..fbea79a4ab58 100644
> --- a/arch/sparc/kernel/Makefile
> +++ b/arch/sparc/kernel/Makefile
> @@ -40,6 +40,7 @@ obj-y += sys_sparc_$(BITS).o
> obj-$(CONFIG_SPARC32) += systbls_32.o
> obj-y += time_$(BITS).o
> obj-$(CONFIG_SPARC32) += windows.o
> +obj-$(CONFIG_SPARC32) += cas_emu_32.o
> obj-y += cpu.o
> obj-$(CONFIG_SPARC32) += devices.o
> obj-y += ptrace_$(BITS).o
> diff --git a/arch/sparc/kernel/cas_emu_32.c b/arch/sparc/kernel/cas_emu_32.c
> new file mode 100644
> index 000000000000..257f528d9a2c
> --- /dev/null
> +++ b/arch/sparc/kernel/cas_emu_32.c
> @@ -0,0 +1,117 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Emulation of the V9 casa compare-and-swap for V8 CPUs.
> + *
> + * Copyright (C) 2026 Imre Kaloz <kaloz@xxxxxxxxxx>
> + *
> + * sun4m implements no compare-and-swap at all, only ldstub and swap, and
> + * casa is optional on LEON. Where it is missing, casa is an undefined opcode
> + * and raises illegal_instruction (tt 0x02).
> + *
> + * Both immediate ASIs a compiler emits are accepted: 0x80, the V9 primary
> + * address space, and 0x0A, which gcc -mcpu=leon3 uses. The opcode is
> + * unimplemented wherever this runs, so the ASI carries no distinction the
> + * emulated CAS has to honour.
> + *
> + * The register-ASI form is not emulated: it reads %asi, which V8 does not
> + * have. casxa is a 64-bit CAS and is meaningless on a 32-bit CPU.
> + *
> + * The CAS takes the futex ops' lock, so it is atomic against them and
> + * against itself, but not against a plain store to the same word.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/sched/signal.h>
> +#include <linux/mm.h>
> +#include <linux/uaccess.h>
> +#include <linux/ptrace.h>
> +#include <asm/futex.h>
> +#include <asm/ptrace.h>
> +
> +#include "entry.h"
> +#include "unimp_32.h"
> +
> +#define CASA_OP 3 /* insn[31:30] */
> +#define CASA_OP3 0x3c /* insn[24:19] */
> +
> +#define ASI_P 0x80
> +#define ASI_LEON 0x0a
> +
> +/* A fault returns -EFAULT, and the caller pages the word in. */
> +static int cas_try(u32 __user *uaddr, u32 cmp, u32 newval, u32 *oldval)
> +{
> + int ret;
> +
> + pagefault_disable();
> + ret = futex_atomic_cmpxchg_inatomic(oldval, uaddr, cmp, newval);
> + pagefault_enable();
> + return ret;
> +}
> +
> +/*
> + * Returns 1 if the trap was consumed, either by emulating the CAS and
> + * advancing the PC or by delivering a fault signal for a bad address, and 0
> + * if this is not a casa we emulate, in which case the caller raises SIGILL.
> + */
> +int try_emulate_casa(struct pt_regs *regs, unsigned int insn)
> +{
> + unsigned int rd, rs1, rs2, asi;
> + unsigned long addr;
> + u32 cmp, newval, old;
> + int fault = 0, ret;
> +
> + if ((insn >> 30) != CASA_OP)
> + return 0;
> + if (((insn >> 19) & 0x3f) != CASA_OP3)
> + return 0;
> + if (insn & (1 << 13)) /* i == 1: register ASI */
> + return 0;
> + asi = (insn >> 5) & 0xff;
> + if (asi != ASI_P && asi != ASI_LEON)
> + return 0;
> +
> + rd = (insn >> 25) & 0x1f;
> + rs1 = (insn >> 14) & 0x1f;
> + rs2 = insn & 0x1f;
> +
> + unimp_flush_windows(rs1, rs2, rd);
> +
> + addr = unimp_get_reg(rs1, regs, &fault);
> + cmp = unimp_get_reg(rs2, regs, &fault);
> + newval = unimp_get_reg(rd, regs, &fault);
> + if (fault)
> + goto sigsegv;
> +
> + if (addr & 3) { /* CAS is naturally aligned */
> + send_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)addr, current);
> + return 1;
> + }
> + if (!access_ok((void __user *)addr, sizeof(u32)))
> + goto sigsegv;
> +
> + while ((ret = cas_try((u32 __user *)addr, cmp, newval, &old)) == -EFAULT) {
> + /*
> + * Page the word in writably and retry, as
> + * fault_in_user_writeable() does.
> + */
> + struct mm_struct *mm = current->mm;
> + int fx;
> +
> + mmap_read_lock(mm);
> + fx = fixup_user_fault(mm, addr, FAULT_FLAG_WRITE, NULL);
> + mmap_read_unlock(mm);
> + if (fx)
> + goto sigsegv;
> + }
> +
> + if (unimp_put_reg(rd, old, regs))
> + goto sigsegv;
> +
> + regs->pc = regs->npc;
> + regs->npc += 4;
> + return 1;
> +
> +sigsegv:
> + send_sig_fault(SIGSEGV, SEGV_MAPERR, (void __user *)addr, current);
> + return 1;
> +}
> diff --git a/arch/sparc/kernel/entry.h b/arch/sparc/kernel/entry.h
> index c746c0fd5d6b..2eb2b0be90df 100644
> --- a/arch/sparc/kernel/entry.h
> +++ b/arch/sparc/kernel/entry.h
> @@ -14,6 +14,7 @@ void handler_irq(int irq, struct pt_regs *regs);
> 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);
> +int try_emulate_casa(struct pt_regs *regs, unsigned int insn);
>
> void do_priv_instruction(struct pt_regs *regs, unsigned long pc,
> unsigned long npc, unsigned long psr);
> diff --git a/arch/sparc/kernel/traps_32.c b/arch/sparc/kernel/traps_32.c
> index e1d50820c376..69851b19766a 100644
> --- a/arch/sparc/kernel/traps_32.c
> +++ b/arch/sparc/kernel/traps_32.c
> @@ -109,6 +109,8 @@ 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)
> {
> + unsigned int insn;
> +
> if (psr & PSR_PS) {
> const struct exception_table_entry *entry;
>
> @@ -126,6 +128,11 @@ void do_illegal_instruction(struct pt_regs *regs, unsigned long pc, unsigned lon
> regs->pc, *(unsigned long *)regs->pc);
> #endif
>
> + if (!get_user(insn, (unsigned int __user *)pc)) {
> + if (try_emulate_casa(regs, insn))
> + return;
> + }
> +
> send_sig_fault(SIGILL, ILL_ILLOPC, (void __user *)pc, current);
> }
>
> diff --git a/arch/sparc/kernel/unimp_32.h b/arch/sparc/kernel/unimp_32.h
> new file mode 100644
> index 000000000000..9ab1705b9d04
> --- /dev/null
> +++ b/arch/sparc/kernel/unimp_32.h
> @@ -0,0 +1,71 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2026 Imre Kaloz <kaloz@xxxxxxxxxx>
> + */
> +#ifndef _SPARC_UNIMP_32_H
> +#define _SPARC_UNIMP_32_H
> +
> +#include <linux/uaccess.h>
> +#include <asm/ptrace.h>
> +
> +/*
> + * Operand access for instructions completed out of do_illegal_instruction().
> + *
> + * Like maybe_flush_windows() in unaligned_32.c this has to be inlined: the
> + * save/restore chain must run in the trapping window, not one level deeper
> + * behind a call, or the outermost user window is left un-flushed and
> + * try_to_clear_window_buffer() kills the task on its next fork.
> + */
> +static __always_inline void unimp_flush_windows(unsigned int rs1,
> + unsigned int rs2,
> + unsigned int rd)
> +{
> + if (rs1 >= 16 || rs2 >= 16 || rd >= 16)
> + __asm__ __volatile__("save %sp, -0x40, %sp\n\t"
> + "save %sp, -0x40, %sp\n\t"
> + "save %sp, -0x40, %sp\n\t"
> + "save %sp, -0x40, %sp\n\t"
> + "save %sp, -0x40, %sp\n\t"
> + "save %sp, -0x40, %sp\n\t"
> + "save %sp, -0x40, %sp\n\t"
> + "restore; restore; restore; restore;\n\t"
> + "restore; restore; restore;\n\t");
> +}
> +
> +/*
> + * Globals and outs come from pt_regs, locals and ins from the flushed window
> + * on the user stack. %g0 reads as zero.
> + */
> +static inline unsigned long unimp_get_reg(unsigned int reg,
> + struct pt_regs *regs, int *fault)
> +{
> + struct reg_window32 __user *win;
> + unsigned long val;
> +
> + if (reg < 16)
> + return reg ? regs->u_regs[reg] : 0;
> +
> + win = (struct reg_window32 __user *)regs->u_regs[UREG_FP];
> + if (get_user(val, &win->locals[reg - 16])) {
> + *fault = 1;
> + return 0;
> + }
> + return val;
> +}
> +
> +/* %g0 discards the result. */
> +static inline int unimp_put_reg(unsigned int reg, unsigned long val,
> + struct pt_regs *regs)
> +{
> + struct reg_window32 __user *win;
> +
> + if (reg < 16) {
> + if (reg)
> + regs->u_regs[reg] = val;
> + return 0;
> + }
> + win = (struct reg_window32 __user *)regs->u_regs[UREG_FP];
> + return put_user(val, &win->locals[reg - 16]);
> +}
> +
> +#endif /* _SPARC_UNIMP_32_H */
> --
> 2.47.3
>


Tested on a real SPARCstation 20, on both a HyperSPARC and a
SuperSPARC-II module, neither has hardware casa. A hand-encoded casa
instruction completes correctly instead of SIGILL on both.

I also verified the key property this series depends on: userspace
casa-based locking and real FUTEX_LOCK_PI/UNLOCK_PI callers correctly
serialize on the same lock word under contention (40000/40000 ops,
zero lost, on both CPUs).

Tested-by: Magnus Lindholm <linmag7@xxxxxxxxx>