[PATCH 3/3] sparc32: emulate integer divide taken as illegal_instruction

From: Imre Kaloz

Date: Sat Sep 26 2026 - 21:00:14 EST


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@xxxxxxxxxx>
---
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@xxxxxxxxxx>
+ *
+ * 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