2005-04-17 05:20:36 +07:00
|
|
|
#include <asm/branch.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
|
|
#include <asm/fpu_emulator.h>
|
2014-04-16 07:09:53 +07:00
|
|
|
#include <asm/inst.h>
|
|
|
|
#include <asm/mipsregs.h>
|
|
|
|
#include <asm/uaccess.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
#include "ieee754.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Emulate the arbritrary instruction ir at xcp->cp0_epc. Required when
|
|
|
|
* we have to emulate the instruction in a COP1 branch delay slot. Do
|
|
|
|
* not change cp0_epc due to the instruction
|
|
|
|
*
|
|
|
|
* According to the spec:
|
2011-03-31 08:57:33 +07:00
|
|
|
* 1) it shouldn't be a branch :-)
|
2005-04-17 05:20:36 +07:00
|
|
|
* 2) it can be a COP instruction :-(
|
|
|
|
* 3) if we are tring to run a protected memory space we must take
|
|
|
|
* special care on memory access instructions :-(
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "Trampoline" return routine to catch exception following
|
|
|
|
* execution of delay-slot instruction execution.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct emuframe {
|
|
|
|
mips_instruction emul;
|
|
|
|
mips_instruction badinst;
|
|
|
|
mips_instruction cookie;
|
2005-03-01 00:55:57 +07:00
|
|
|
unsigned long epc;
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
MIPS: math-emu: Correctly handle NOP emulation
Fix an issue introduced with commit 9ab4471c9f1b ("MIPS: math-emu:
Correct delay-slot exception propagation") where the emulation of a NOP
instruction signals the need to terminate the emulation loop. This in
turn, if the PC has not changed from the entry to the loop, will cause
the kernel to terminate the program with SIGILL.
Consider this program:
static double div(double d)
{
do
d /= 2.0;
while (d > .5);
return d;
}
int main(int argc, char **argv)
{
return div(argc);
}
which gets compiled to the following binary code:
00400490 <main>:
400490: 44840000 mtc1 a0,$f0
400494: 3c020040 lui v0,0x40
400498: d44207f8 ldc1 $f2,2040(v0)
40049c: 46800021 cvt.d.w $f0,$f0
4004a0: 46220002 mul.d $f0,$f0,$f2
4004a4: 4620103c c.lt.d $f2,$f0
4004a8: 4501fffd bc1t 4004a0 <main+0x10>
4004ac: 00000000 nop
4004b0: 4620000d trunc.w.d $f0,$f0
4004b4: 03e00008 jr ra
4004b8: 44020000 mfc1 v0,$f0
4004bc: 00000000 nop
Where the FPU emulator is used, depending on the number of command-line
arguments this code will either run to completion or terminate with
SIGILL.
If no arguments are specified, then BC1T will not be taken, NOP will not
be emulated and code will complete successfully.
If one argument is specified, then BC1T will be taken once and NOP will
be emulated. At this point the entry PC value will be 0x400498 and the
new PC value, set by `mips_dsemul' will be 0x4004a0, the target of BC1T.
The emulation loop will terminate, but SIGILL will not be issued,
because the PC has changed. The FPU emulator will be entered again and
on the second execution BC1T will not be taken, NOP will not be emulated
and code will complete successfully.
If two or more arguments are specified, then the first execution of BC1T
will proceed as above. Upon reentering the FPU emulator the emulation
loop will continue to BC1T, at which point the branch will be taken and
NOP emulated again. At this point however the entry PC value will be
0x4004a0, the same as the target of BC1T. This will make the emulator
conclude that execution has not advanced and therefore an unsupported
FPU instruction has been encountered, and SIGILL will be sent to the
process.
Fix the problem by extending the internal API of `mips_dsemul', making
it return -1 if no delay slot emulation frame has been made, the
instruction has been handled and execution of the emulation loop needs
to continue as if nothing happened. Remove code from `mips_dsemul' to
reproduce steps made by the emulation loop at the conclusion of each
iteration, as those will be reached normally now. Adjust call sites
accordingly. Document the API.
Signed-off-by: Maciej W. Rozycki <macro@imgtec.com>
Cc: Aurelien Jarno <aurelien@aurel32.net>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/12172/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2016-01-22 12:20:26 +07:00
|
|
|
/*
|
|
|
|
* Set up an emulation frame for instruction IR, from a delay slot of
|
|
|
|
* a branch jumping to CPC. Return 0 if successful, -1 if no emulation
|
|
|
|
* required, otherwise a signal number causing a frame setup failure.
|
|
|
|
*/
|
2005-03-01 00:55:57 +07:00
|
|
|
int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2016-01-22 12:21:47 +07:00
|
|
|
int isa16 = get_isa16_mode(regs->cp0_epc);
|
2016-01-22 12:20:46 +07:00
|
|
|
mips_instruction break_math;
|
2007-07-13 21:02:42 +07:00
|
|
|
struct emuframe __user *fr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int err;
|
|
|
|
|
MIPS: math-emu: Correctly handle NOP emulation
Fix an issue introduced with commit 9ab4471c9f1b ("MIPS: math-emu:
Correct delay-slot exception propagation") where the emulation of a NOP
instruction signals the need to terminate the emulation loop. This in
turn, if the PC has not changed from the entry to the loop, will cause
the kernel to terminate the program with SIGILL.
Consider this program:
static double div(double d)
{
do
d /= 2.0;
while (d > .5);
return d;
}
int main(int argc, char **argv)
{
return div(argc);
}
which gets compiled to the following binary code:
00400490 <main>:
400490: 44840000 mtc1 a0,$f0
400494: 3c020040 lui v0,0x40
400498: d44207f8 ldc1 $f2,2040(v0)
40049c: 46800021 cvt.d.w $f0,$f0
4004a0: 46220002 mul.d $f0,$f0,$f2
4004a4: 4620103c c.lt.d $f2,$f0
4004a8: 4501fffd bc1t 4004a0 <main+0x10>
4004ac: 00000000 nop
4004b0: 4620000d trunc.w.d $f0,$f0
4004b4: 03e00008 jr ra
4004b8: 44020000 mfc1 v0,$f0
4004bc: 00000000 nop
Where the FPU emulator is used, depending on the number of command-line
arguments this code will either run to completion or terminate with
SIGILL.
If no arguments are specified, then BC1T will not be taken, NOP will not
be emulated and code will complete successfully.
If one argument is specified, then BC1T will be taken once and NOP will
be emulated. At this point the entry PC value will be 0x400498 and the
new PC value, set by `mips_dsemul' will be 0x4004a0, the target of BC1T.
The emulation loop will terminate, but SIGILL will not be issued,
because the PC has changed. The FPU emulator will be entered again and
on the second execution BC1T will not be taken, NOP will not be emulated
and code will complete successfully.
If two or more arguments are specified, then the first execution of BC1T
will proceed as above. Upon reentering the FPU emulator the emulation
loop will continue to BC1T, at which point the branch will be taken and
NOP emulated again. At this point however the entry PC value will be
0x4004a0, the same as the target of BC1T. This will make the emulator
conclude that execution has not advanced and therefore an unsupported
FPU instruction has been encountered, and SIGILL will be sent to the
process.
Fix the problem by extending the internal API of `mips_dsemul', making
it return -1 if no delay slot emulation frame has been made, the
instruction has been handled and execution of the emulation loop needs
to continue as if nothing happened. Remove code from `mips_dsemul' to
reproduce steps made by the emulation loop at the conclusion of each
iteration, as those will be reached normally now. Adjust call sites
accordingly. Document the API.
Signed-off-by: Maciej W. Rozycki <macro@imgtec.com>
Cc: Aurelien Jarno <aurelien@aurel32.net>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/12172/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2016-01-22 12:20:26 +07:00
|
|
|
/* NOP is easy */
|
2016-01-22 12:21:00 +07:00
|
|
|
if (ir == 0)
|
MIPS: math-emu: Correctly handle NOP emulation
Fix an issue introduced with commit 9ab4471c9f1b ("MIPS: math-emu:
Correct delay-slot exception propagation") where the emulation of a NOP
instruction signals the need to terminate the emulation loop. This in
turn, if the PC has not changed from the entry to the loop, will cause
the kernel to terminate the program with SIGILL.
Consider this program:
static double div(double d)
{
do
d /= 2.0;
while (d > .5);
return d;
}
int main(int argc, char **argv)
{
return div(argc);
}
which gets compiled to the following binary code:
00400490 <main>:
400490: 44840000 mtc1 a0,$f0
400494: 3c020040 lui v0,0x40
400498: d44207f8 ldc1 $f2,2040(v0)
40049c: 46800021 cvt.d.w $f0,$f0
4004a0: 46220002 mul.d $f0,$f0,$f2
4004a4: 4620103c c.lt.d $f2,$f0
4004a8: 4501fffd bc1t 4004a0 <main+0x10>
4004ac: 00000000 nop
4004b0: 4620000d trunc.w.d $f0,$f0
4004b4: 03e00008 jr ra
4004b8: 44020000 mfc1 v0,$f0
4004bc: 00000000 nop
Where the FPU emulator is used, depending on the number of command-line
arguments this code will either run to completion or terminate with
SIGILL.
If no arguments are specified, then BC1T will not be taken, NOP will not
be emulated and code will complete successfully.
If one argument is specified, then BC1T will be taken once and NOP will
be emulated. At this point the entry PC value will be 0x400498 and the
new PC value, set by `mips_dsemul' will be 0x4004a0, the target of BC1T.
The emulation loop will terminate, but SIGILL will not be issued,
because the PC has changed. The FPU emulator will be entered again and
on the second execution BC1T will not be taken, NOP will not be emulated
and code will complete successfully.
If two or more arguments are specified, then the first execution of BC1T
will proceed as above. Upon reentering the FPU emulator the emulation
loop will continue to BC1T, at which point the branch will be taken and
NOP emulated again. At this point however the entry PC value will be
0x4004a0, the same as the target of BC1T. This will make the emulator
conclude that execution has not advanced and therefore an unsupported
FPU instruction has been encountered, and SIGILL will be sent to the
process.
Fix the problem by extending the internal API of `mips_dsemul', making
it return -1 if no delay slot emulation frame has been made, the
instruction has been handled and execution of the emulation loop needs
to continue as if nothing happened. Remove code from `mips_dsemul' to
reproduce steps made by the emulation loop at the conclusion of each
iteration, as those will be reached normally now. Adjust call sites
accordingly. Document the API.
Signed-off-by: Maciej W. Rozycki <macro@imgtec.com>
Cc: Aurelien Jarno <aurelien@aurel32.net>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/12172/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2016-01-22 12:20:26 +07:00
|
|
|
return -1;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2016-01-22 12:21:00 +07:00
|
|
|
/* microMIPS instructions */
|
2016-01-22 12:21:47 +07:00
|
|
|
if (isa16) {
|
2016-01-22 12:21:00 +07:00
|
|
|
union mips_instruction insn = { .word = ir };
|
|
|
|
|
|
|
|
/* NOP16 aka MOVE16 $0, $0 */
|
|
|
|
if ((ir >> 16) == MM_NOP16)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* ADDIUPC */
|
|
|
|
if (insn.mm_a_format.opcode == mm_addiupc_op) {
|
|
|
|
unsigned int rs;
|
|
|
|
s32 v;
|
|
|
|
|
|
|
|
rs = (((insn.mm_a_format.rs + 0x1e) & 0xf) + 2);
|
|
|
|
v = regs->cp0_epc & ~3;
|
|
|
|
v += insn.mm_a_format.simmediate << 2;
|
|
|
|
regs->regs[rs] = (long)v;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-19 19:03:37 +07:00
|
|
|
pr_debug("dsemul %lx %lx\n", regs->cp0_epc, cpc);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The strategy is to push the instruction onto the user stack
|
|
|
|
* and put a trap after it which we can catch and jump to
|
|
|
|
* the required address any alternative apart from full
|
|
|
|
* instruction emulation!!.
|
|
|
|
*
|
|
|
|
* Algorithmics used a system call instruction, and
|
|
|
|
* borrowed that vector. MIPS/Linux version is a bit
|
|
|
|
* more heavyweight in the interests of portability and
|
2016-01-22 12:21:13 +07:00
|
|
|
* multiprocessor support. For Linux we use a BREAK 514
|
|
|
|
* instruction causing a breakpoint exception.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2016-01-22 12:21:47 +07:00
|
|
|
break_math = BREAK_MATH(isa16);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Ensure that the two instructions are in the same cache line */
|
2007-07-13 21:02:42 +07:00
|
|
|
fr = (struct emuframe __user *)
|
|
|
|
((regs->regs[29] - sizeof(struct emuframe)) & ~0x7);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Verify that the stack pointer is not competely insane */
|
|
|
|
if (unlikely(!access_ok(VERIFY_WRITE, fr, sizeof(struct emuframe))))
|
|
|
|
return SIGBUS;
|
|
|
|
|
2016-01-22 12:21:47 +07:00
|
|
|
if (isa16) {
|
2016-01-22 12:20:37 +07:00
|
|
|
err = __put_user(ir >> 16,
|
|
|
|
(u16 __user *)(&fr->emul));
|
|
|
|
err |= __put_user(ir & 0xffff,
|
|
|
|
(u16 __user *)((long)(&fr->emul) + 2));
|
2016-01-22 12:20:46 +07:00
|
|
|
err |= __put_user(break_math >> 16,
|
2016-01-22 12:20:37 +07:00
|
|
|
(u16 __user *)(&fr->badinst));
|
2016-01-22 12:20:46 +07:00
|
|
|
err |= __put_user(break_math & 0xffff,
|
2016-01-22 12:20:37 +07:00
|
|
|
(u16 __user *)((long)(&fr->badinst) + 2));
|
2013-03-26 00:09:02 +07:00
|
|
|
} else {
|
|
|
|
err = __put_user(ir, &fr->emul);
|
2016-01-22 12:20:46 +07:00
|
|
|
err |= __put_user(break_math, &fr->badinst);
|
2013-03-26 00:09:02 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
err |= __put_user((mips_instruction)BD_COOKIE, &fr->cookie);
|
|
|
|
err |= __put_user(cpc, &fr->epc);
|
|
|
|
|
|
|
|
if (unlikely(err)) {
|
2009-11-06 02:34:26 +07:00
|
|
|
MIPS_FPU_EMU_INC_STATS(errors);
|
2005-04-17 05:20:36 +07:00
|
|
|
return SIGBUS;
|
|
|
|
}
|
|
|
|
|
2016-01-22 12:21:47 +07:00
|
|
|
regs->cp0_epc = (unsigned long)&fr->emul | isa16;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2015-04-04 05:26:37 +07:00
|
|
|
flush_cache_sigtramp((unsigned long)&fr->emul);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2015-04-04 05:26:56 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
int do_dsemulret(struct pt_regs *xcp)
|
|
|
|
{
|
2016-01-22 12:21:47 +07:00
|
|
|
int isa16 = get_isa16_mode(xcp->cp0_epc);
|
2007-07-13 21:02:42 +07:00
|
|
|
struct emuframe __user *fr;
|
2005-03-01 00:55:57 +07:00
|
|
|
unsigned long epc;
|
2005-04-17 05:20:36 +07:00
|
|
|
u32 insn, cookie;
|
|
|
|
int err = 0;
|
2013-03-26 00:09:02 +07:00
|
|
|
u16 instr[2];
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-07-13 21:02:42 +07:00
|
|
|
fr = (struct emuframe __user *)
|
2013-03-26 00:09:02 +07:00
|
|
|
(msk_isa16_mode(xcp->cp0_epc) - sizeof(mips_instruction));
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If we can't even access the area, something is very wrong, but we'll
|
|
|
|
* leave that to the default handling
|
|
|
|
*/
|
|
|
|
if (!access_ok(VERIFY_READ, fr, sizeof(struct emuframe)))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do some sanity checking on the stackframe:
|
|
|
|
*
|
2008-10-29 00:38:42 +07:00
|
|
|
* - Is the instruction pointed to by the EPC an BREAK_MATH?
|
2005-04-17 05:20:36 +07:00
|
|
|
* - Is the following memory word the BD_COOKIE?
|
|
|
|
*/
|
2016-01-22 12:21:47 +07:00
|
|
|
if (isa16) {
|
2016-01-22 12:20:37 +07:00
|
|
|
err = __get_user(instr[0],
|
|
|
|
(u16 __user *)(&fr->badinst));
|
|
|
|
err |= __get_user(instr[1],
|
|
|
|
(u16 __user *)((long)(&fr->badinst) + 2));
|
2013-03-26 00:09:02 +07:00
|
|
|
insn = (instr[0] << 16) | instr[1];
|
|
|
|
} else {
|
|
|
|
err = __get_user(insn, &fr->badinst);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
err |= __get_user(cookie, &fr->cookie);
|
|
|
|
|
2016-01-22 12:21:47 +07:00
|
|
|
if (unlikely(err ||
|
|
|
|
insn != BREAK_MATH(isa16) || cookie != BD_COOKIE)) {
|
2009-11-06 02:34:26 +07:00
|
|
|
MIPS_FPU_EMU_INC_STATS(errors);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* At this point, we are satisfied that it's a BD emulation trap. Yes,
|
|
|
|
* a user might have deliberately put two malformed and useless
|
|
|
|
* instructions in a row in his program, in which case he's in for a
|
|
|
|
* nasty surprise - the next instruction will be treated as a
|
|
|
|
* continuation address! Alas, this seems to be the only way that we
|
|
|
|
* can handle signals, recursion, and longjmps() in the context of
|
|
|
|
* emulating the branch delay instruction.
|
|
|
|
*/
|
|
|
|
|
2014-04-19 19:03:37 +07:00
|
|
|
pr_debug("dsemulret\n");
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (__get_user(epc, &fr->epc)) { /* Saved EPC */
|
|
|
|
/* This is not a good situation to be in */
|
|
|
|
force_sig(SIGBUS, current);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set EPC to return to post-branch instruction */
|
|
|
|
xcp->cp0_epc = epc;
|
2014-12-04 02:12:23 +07:00
|
|
|
MIPS_FPU_EMU_INC_STATS(ds_emul);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|