| Message ID | 20260927005821.8369-4-kaloz@kernel.org |
|---|---|
| State | New |
| Headers | show |
| Series | sparc32: SMP futexes, casa and idiv emulation | expand |
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
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 --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);
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