* [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