* [PATCH 0/3] sparc32: SMP futexes, casa and idiv emulation
@ 2026-09-27 0:58 Imre Kaloz
2026-09-27 0:58 ` [PATCH 1/3] sparc32: support futexes on SMP Imre Kaloz
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Imre Kaloz @ 2026-09-27 0:58 UTC (permalink / raw)
To: Andreas Larsson, sparclinux
Cc: David S. Miller, linux-kernel, Magnus Lindholm
sun4m has no compare-and-swap instruction and casa is optional on
LEON, so SMP sparc32 has had no futexes, and user code on those CPUs
has no CAS to build locks from.
Patch 1 implements the futex atomic ops in atomic32.c and drops the
!(SPARC32 && SMP) dependency of FUTEX. A LEON part that implements
casa, found by a boot-time probe, runs casa on the user word; other
CPUs take the __atomic_hash spinlocks.
Patch 2 completes user casa from illegal_instruction on CPUs without
it, through patch 1's futex_atomic_cmpxchg_inatomic(), so emulated
casa and the futex ops serialize on the same lock. It is atomic
against itself and against the futex ops, not against a plain store
to the same word, so a C library using it has to do its atomic stores
with casa too.
Patch 3 completes udiv, sdiv and their -cc forms from the same
do_illegal_instruction() dispatch: SuperSPARC and SuperSPARC-II trap
those when the {Y, rs1} dividend has significant bits above bit 51,
which gcc -mcpu=v8 output, including libgcc's __udivdi3, can produce.
It shares patch 2's windowed-operand helpers and the dispatch block,
which is why it comes last.
Patch 1 comes first because patch 2 is built on it. On its own it
races nothing: without patch 2, user casa on these CPUs is SIGILL.
Magnus Lindholm has an independent RFC covering the same ground from
a different ABI: "[RFC PATCH 0/5] sparc32: kernel assisted
compare-and-swap, and futex on SMP" (patchwork sparclinux, Message-Id
20260923201830.865553-1-linmag7@gmail.com), with a companion glibc
series (patchwork glibc, Message-Id
20260924064228.867909-1-linmag7@gmail.com). That RFC adds a new
software trap, ta 0x11 (trap type 0x91), extending David Miller's
2016 CAS-trap prototype at ta 0x23 rather than completing it, so
userspace has to be rebuilt to call it. This series instead
completes the casa opcode itself, so a binary already emitting casa
(gcc -mcpu=leon3 or -mcpu=v9 output, or a C library that encodes it
as a .word) runs unmodified on a CPU without hardware casa, with no
new trap number and no libc rebuild. Happy to share the futex and
do_illegal_instruction() plumbing with whichever ABI lands.
Andreas: do LEON3FT parts want the GRLIB-TN-0010/0011 "nop; .balignl
16" alignment before the casa word here, or is the plain encoding as
posted fine for them?
Notes, not for the log:
- checkpatch on patch 1: "Lines should not end with a '('" is the
__asm__ __volatile__( idiom of uaccess_32.h; the -ENOSYS warning is
the futex API's return for an unknown op, as on every architecture.
"does MAINTAINERS need updating?" on patches 2 and 3 is covered by
the F: arch/sparc/ entry.
- On qemu's SS-20 with two SuperSPARCs and the casa emulation alone,
futex() returns ENOSYS and musl's mutex, PI mutex and robust mutex
tests fail. With patches 1 and 2 they pass, as does a PI lock
contended between user casa and FUTEX_LOCK_PI/FUTEX_UNLOCK_PI on
both CPUs.
- The casa branch of the futex ops has not run on LEON hardware. The
probe runs on LEON only; its fixup path, which a LEON without casa
takes, has run only in a test kernel that forced the probe on
qemu's SS-20.
- The divide emulation has run on no CPU that traps it. No sparc32
model in qemu raises the trap, and no SuperSPARC or SuperSPARC-II
was available for this series; it is exercised only by build and
link.
Imre Kaloz (3):
sparc32: support futexes on SMP
sparc32: emulate casa on V8 CPUs
sparc32: emulate integer divide taken as illegal_instruction
arch/sparc/include/asm/futex_32.h | 6 +-
arch/sparc/kernel/Makefile | 2 +
arch/sparc/kernel/cas_emu_32.c | 117 +++++++++++++++++++++++++++
arch/sparc/kernel/div_emu_32.c | 127 ++++++++++++++++++++++++++++++
arch/sparc/kernel/entry.h | 2 +
arch/sparc/kernel/traps_32.c | 23 +++++-
arch/sparc/kernel/unimp_32.h | 71 +++++++++++++++++
arch/sparc/lib/atomic32.c | 122 ++++++++++++++++++++++++++++
init/Kconfig | 1 -
9 files changed, 468 insertions(+), 3 deletions(-)
create mode 100644 arch/sparc/kernel/cas_emu_32.c
create mode 100644 arch/sparc/kernel/div_emu_32.c
create mode 100644 arch/sparc/kernel/unimp_32.h
base-commit: 62f4c998b297cf233997a2b4cd6fc2d2df0319c9
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/3] sparc32: support futexes on SMP
2026-09-27 0:58 [PATCH 0/3] sparc32: SMP futexes, casa and idiv emulation Imre Kaloz
@ 2026-09-27 0:58 ` Imre Kaloz
2026-09-27 0:58 ` [PATCH 2/3] sparc32: emulate casa on V8 CPUs Imre Kaloz
2026-09-27 0:58 ` [PATCH 3/3] sparc32: emulate integer divide taken as illegal_instruction Imre Kaloz
2 siblings, 0 replies; 4+ messages in thread
From: Imre Kaloz @ 2026-09-27 0:58 UTC (permalink / raw)
To: Andreas Larsson, sparclinux
Cc: David S. Miller, linux-kernel, Magnus Lindholm
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@kernel.org>
---
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
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/3] sparc32: emulate casa on V8 CPUs
2026-09-27 0:58 [PATCH 0/3] sparc32: SMP futexes, casa and idiv emulation Imre Kaloz
2026-09-27 0:58 ` [PATCH 1/3] sparc32: support futexes on SMP Imre Kaloz
@ 2026-09-27 0:58 ` Imre Kaloz
2026-09-27 0:58 ` [PATCH 3/3] sparc32: emulate integer divide taken as illegal_instruction Imre Kaloz
2 siblings, 0 replies; 4+ messages in thread
From: Imre Kaloz @ 2026-09-27 0:58 UTC (permalink / raw)
To: Andreas Larsson, sparclinux
Cc: David S. Miller, linux-kernel, Magnus Lindholm
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@kernel.org>
---
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@kernel.org>
+ *
+ * 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@kernel.org>
+ */
+#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
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 3/3] sparc32: emulate integer divide taken as illegal_instruction
2026-09-27 0:58 [PATCH 0/3] sparc32: SMP futexes, casa and idiv emulation Imre Kaloz
2026-09-27 0:58 ` [PATCH 1/3] sparc32: support futexes on SMP Imre Kaloz
2026-09-27 0:58 ` [PATCH 2/3] sparc32: emulate casa on V8 CPUs Imre Kaloz
@ 2026-09-27 0:58 ` Imre Kaloz
2 siblings, 0 replies; 4+ messages in thread
From: Imre Kaloz @ 2026-09-27 0:58 UTC (permalink / raw)
To: Andreas Larsson, sparclinux
Cc: David S. Miller, linux-kernel, Magnus Lindholm
SuperSPARC and SuperSPARC-II implement a 52-bit by 32-bit integer divide
and raise illegal_instruction for udiv and sdiv when the 64-bit dividend
{Y, rs1} has significant bits above bit 51, as the SuperSPARC II
Addendum documents, so whether a divide traps depends on its operands.
gcc emits udiv under -mcpu=v8, including in libgcc's __udivdi3, so
64-bit division in user code on these CPUs dies with SIGILL.
Complete udiv and sdiv, and their -cc forms, from
do_illegal_instruction(), sharing the windowed-operand helpers with the
casa emulation. A CPU that executes the instruction never reaches the
handler, nor does a kernel-mode illegal instruction. A fault reading a
windowed operand raises SIGSEGV, as the casa emulation does. On overflow
the quotient saturates to 0xffffffff for udiv and to 0x7fffffff or
0x80000000 for sdiv, and the -cc forms set V, as the architecture
specifies.
Signed-off-by: Imre Kaloz <kaloz@kernel.org>
---
arch/sparc/kernel/Makefile | 1 +
arch/sparc/kernel/div_emu_32.c | 127 +++++++++++++++++++++++++++++++++
arch/sparc/kernel/entry.h | 1 +
arch/sparc/kernel/traps_32.c | 3 +
4 files changed, 132 insertions(+)
create mode 100644 arch/sparc/kernel/div_emu_32.c
diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile
index fbea79a4ab58..43e15b46b8dd 100644
--- a/arch/sparc/kernel/Makefile
+++ b/arch/sparc/kernel/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_SPARC32) += systbls_32.o
obj-y += time_$(BITS).o
obj-$(CONFIG_SPARC32) += windows.o
obj-$(CONFIG_SPARC32) += cas_emu_32.o
+obj-$(CONFIG_SPARC32) += div_emu_32.o
obj-y += cpu.o
obj-$(CONFIG_SPARC32) += devices.o
obj-y += ptrace_$(BITS).o
diff --git a/arch/sparc/kernel/div_emu_32.c b/arch/sparc/kernel/div_emu_32.c
new file mode 100644
index 000000000000..208b4071a525
--- /dev/null
+++ b/arch/sparc/kernel/div_emu_32.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Imre Kaloz <kaloz@kernel.org>
+ *
+ * SuperSPARC and SuperSPARC-II implement a 52-bit by 32-bit integer divide
+ * and raise illegal_instruction for udiv and sdiv when the dividend {Y, rs1}
+ * has significant bits above bit 51. Complete those divides here.
+ *
+ * Format 3 (op = 2), op3 0x0e udiv, 0x0f sdiv, plus 0x10 for the forms that
+ * set %icc. {Y, rs1} / rs2 saturates on overflow and leaves Y alone.
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+#include <linux/uaccess.h>
+#include <asm/ptrace.h>
+#include <asm/psr.h>
+
+#include "entry.h"
+#include "unimp_32.h"
+
+int try_emulate_div(struct pt_regs *regs, unsigned int insn)
+{
+ unsigned int op3, rd, rs1, rs2;
+ unsigned long r1, r2, result = 0;
+ int fault = 0, is_signed, cc, ovf = 0;
+
+ if ((insn >> 30) != 2)
+ return 0;
+ op3 = (insn >> 19) & 0x3f;
+ switch (op3) {
+ case 0x0e: case 0x0f: case 0x1e: case 0x1f:
+ break;
+ default:
+ return 0;
+ }
+ is_signed = op3 & 0x01;
+ cc = op3 & 0x10;
+
+ rd = (insn >> 25) & 0x1f;
+ rs1 = (insn >> 14) & 0x1f;
+ rs2 = (insn & (1 << 13)) ? 0 : (insn & 0x1f);
+
+ unimp_flush_windows(rs1, rs2, rd);
+
+ r1 = unimp_get_reg(rs1, regs, &fault);
+ if (insn & (1 << 13)) { /* i == 1: sign-extended simm13 */
+ long simm = insn & 0x1fff;
+
+ if (simm & 0x1000)
+ simm |= ~0x1fffL;
+ r2 = (unsigned long)simm;
+ } else {
+ r2 = unimp_get_reg(rs2, regs, &fault);
+ }
+ if (fault) {
+ /* A windowed operand could not be read from the user stack. */
+ send_sig_fault(SIGSEGV, SEGV_MAPERR,
+ (void __user *)regs->u_regs[UREG_FP], current);
+ return 1;
+ }
+
+ if ((unsigned int)r2 == 0) {
+ send_sig_fault(SIGFPE, FPE_INTDIV,
+ (void __user *)regs->pc, current);
+ return 1;
+ }
+ if (is_signed) {
+ long long num = ((long long)(int)regs->y << 32) |
+ (unsigned int)r1;
+
+ /*
+ * INT64_MIN / -1 is undefined in C; the quotient is +2^63,
+ * a positive overflow.
+ */
+ if (num == LLONG_MIN && (int)r2 == -1) {
+ result = 0x7fffffff;
+ ovf = 1;
+ } else {
+ long long q = num / (int)r2;
+
+ if (q > 2147483647LL) {
+ result = 0x7fffffff;
+ ovf = 1;
+ } else if (q < -2147483647LL - 1) {
+ result = 0x80000000;
+ ovf = 1;
+ } else {
+ result = (unsigned long)(unsigned int)q;
+ }
+ }
+ } else {
+ unsigned long long num =
+ ((unsigned long long)(unsigned int)regs->y << 32) |
+ (unsigned int)r1;
+ unsigned long long q = num / (unsigned int)r2;
+
+ if (q > 0xffffffffULL) {
+ result = 0xffffffff;
+ ovf = 1;
+ } else {
+ result = (unsigned long)(unsigned int)q;
+ }
+ }
+
+ if (unimp_put_reg(rd, result, regs)) {
+ send_sig_fault(SIGSEGV, SEGV_MAPERR,
+ (void __user *)regs->u_regs[UREG_FP], current);
+ return 1;
+ }
+
+ if (cc) {
+ unsigned long psr = regs->psr & ~PSR_ICC;
+
+ if (result & 0x80000000)
+ psr |= PSR_N;
+ if (result == 0)
+ psr |= PSR_Z;
+ if (ovf)
+ psr |= PSR_V;
+ regs->psr = psr;
+ }
+
+ regs->pc = regs->npc;
+ regs->npc += 4;
+ return 1;
+}
diff --git a/arch/sparc/kernel/entry.h b/arch/sparc/kernel/entry.h
index 2eb2b0be90df..4911cfd07d60 100644
--- a/arch/sparc/kernel/entry.h
+++ b/arch/sparc/kernel/entry.h
@@ -15,6 +15,7 @@ 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);
+int try_emulate_div(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 69851b19766a..f9050dee0f1c 100644
--- a/arch/sparc/kernel/traps_32.c
+++ b/arch/sparc/kernel/traps_32.c
@@ -131,6 +131,9 @@ void do_illegal_instruction(struct pt_regs *regs, unsigned long pc, unsigned lon
if (!get_user(insn, (unsigned int __user *)pc)) {
if (try_emulate_casa(regs, insn))
return;
+ /* SuperSPARC traps divides wider than 52 bits. */
+ if (try_emulate_div(regs, insn))
+ return;
}
send_sig_fault(SIGILL, ILL_ILLOPC, (void __user *)pc, current);
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-27 0:59 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-27 0:58 [PATCH 0/3] sparc32: SMP futexes, casa and idiv emulation Imre Kaloz
2026-09-27 0:58 ` [PATCH 1/3] sparc32: support futexes on SMP Imre Kaloz
2026-09-27 0:58 ` [PATCH 2/3] sparc32: emulate casa on V8 CPUs Imre Kaloz
2026-09-27 0:58 ` [PATCH 3/3] sparc32: emulate integer divide taken as illegal_instruction Imre Kaloz
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®