diff mbox series

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

Message ID 20260927005821.8369-4-kaloz@kernel.org
State New
Headers show
Series sparc32: SMP futexes, casa and idiv emulation | expand

Commit Message

Imre Kaloz Sept. 27, 2026, 12:58 a.m. UTC
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

Comments

Magnus Lindholm Sept. 27, 2026, 9:25 p.m. UTC | #1
Hi Imre,

Great to see some activity on sparc32, thanks for taking the time to do this.

On Sun, Sep 27, 2026 at 2:59 AM Imre Kaloz <kaloz@kernel.org> wrote:
>
> 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>

I stumbled upon the same issue trying to boot Linux on my SPARCstation, and
posted a fix for it on the 24th, same diagnosis, same saturating quotient and
condition-code handling, same window-flush-then-fetch structure:

  https://lore.kernel.org/sparclinux/20260924205646.870950-1-linmag7@gmail.com/

Yours factors the windowed-operand helpers into div_emu_32.c so the casa
emulation can share them; mine keeps it in traps_32.c. Either does the job, so
I'll leave it to the maintainers which they'd rather take.

Regards

Magnus
Magnus Lindholm Oct. 4, 2026, 9:05 a.m. UTC | #2
Hi Imre,

On Sun, Sep 27, 2026 at 2:59 AM Imre Kaloz <kaloz@kernel.org> wrote:
>
> 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
>

I guess I already responded to this before, but I've now had some
more time to do proper testing of this full series, I've tested
this on a real dual SuperSPARC-II SPARCstation 20, which genuinely
traps on this condition. Checked UDIV/SDIV across the overflow-
saturation boundary in both directions, including INT64_MIN / -1
(the one case easy to get backwards), a zero divisor combined with
an erratum-triggering dividend (correctly raises SIGFPE), and
UDIVCC's condition codes for both a zero and a nonzero quotient.
10/10 correct, matching the V8 semantics my own already-upstream
divide-emulation patch targets.

Tested-by: Magnus Lindholm <linmag7@gmail.com>
diff mbox series

Patch

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);