1615 lines
34 KiB
C
1615 lines
34 KiB
C
/*
|
|
* Handle unaligned accesses by emulation.
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*
|
|
* Copyright (C) 1996, 1998, 1999, 2002 by Ralf Baechle
|
|
* Copyright (C) 1999 Silicon Graphics, Inc.
|
|
* Copyright (C) 2014 Imagination Technologies Ltd.
|
|
*
|
|
* This file contains exception handler for address error exception with the
|
|
* special capability to execute faulting instructions in software. The
|
|
* handler does not try to handle the case when the program counter points
|
|
* to an address not aligned to a word boundary.
|
|
*
|
|
* Putting data to unaligned addresses is a bad practice even on Intel where
|
|
* only the performance is affected. Much worse is that such code is non-
|
|
* portable. Due to several programs that die on MIPS due to alignment
|
|
* problems I decided to implement this handler anyway though I originally
|
|
* didn't intend to do this at all for user code.
|
|
*
|
|
* For now I enable fixing of address errors by default to make life easier.
|
|
* I however intend to disable this somewhen in the future when the alignment
|
|
* problems with user programs have been fixed. For programmers this is the
|
|
* right way to go.
|
|
*
|
|
* Fixing address errors is a per process option. The option is inherited
|
|
* across fork(2) and execve(2) calls. If you really want to use the
|
|
* option in your user programs - I discourage the use of the software
|
|
* emulation strongly - use the following code in your userland stuff:
|
|
*
|
|
* #include <sys/sysmips.h>
|
|
*
|
|
* ...
|
|
* sysmips(MIPS_FIXADE, x);
|
|
* ...
|
|
*
|
|
* The argument x is 0 for disabling software emulation, enabled otherwise.
|
|
*
|
|
* Below a little program to play around with this feature.
|
|
*
|
|
* #include <stdio.h>
|
|
* #include <sys/sysmips.h>
|
|
*
|
|
* struct foo {
|
|
* unsigned char bar[8];
|
|
* };
|
|
*
|
|
* main(int argc, char *argv[])
|
|
* {
|
|
* struct foo x = {0, 1, 2, 3, 4, 5, 6, 7};
|
|
* unsigned int *p = (unsigned int *) (x.bar + 3);
|
|
* int i;
|
|
*
|
|
* if (argc > 1)
|
|
* sysmips(MIPS_FIXADE, atoi(argv[1]));
|
|
*
|
|
* printf("*p = %08lx\n", *p);
|
|
*
|
|
* *p = 0xdeadface;
|
|
*
|
|
* for(i = 0; i <= 7; i++)
|
|
* printf("%02x ", x.bar[i]);
|
|
* printf("\n");
|
|
* }
|
|
*
|
|
* Coprocessor loads are not supported; I think this case is unimportant
|
|
* in the practice.
|
|
*
|
|
* TODO: Handle ndc (attempted store to doubleword in uncached memory)
|
|
* exception for the R6000.
|
|
* A store crossing a page boundary might be executed only partially.
|
|
* Undo the partial store in this case.
|
|
*/
|
|
#include <linux/context_tracking.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/perf_event.h>
|
|
|
|
#include <asm/asm.h>
|
|
#include <asm/branch.h>
|
|
#include <asm/byteorder.h>
|
|
#include <asm/cop2.h>
|
|
#include <asm/debug.h>
|
|
#include <asm/fpu.h>
|
|
#include <asm/fpu_emulator.h>
|
|
#include <asm/inst.h>
|
|
#include <asm/unaligned-emul.h>
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/traps.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include "access-helper.h"
|
|
|
|
enum {
|
|
UNALIGNED_ACTION_QUIET,
|
|
UNALIGNED_ACTION_SIGNAL,
|
|
UNALIGNED_ACTION_SHOW,
|
|
};
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static u32 unaligned_instructions;
|
|
static u32 unaligned_action;
|
|
#else
|
|
#define unaligned_action UNALIGNED_ACTION_QUIET
|
|
#endif
|
|
extern void show_registers(struct pt_regs *regs);
|
|
|
|
static void emulate_load_store_insn(struct pt_regs *regs,
|
|
void __user *addr, unsigned int *pc)
|
|
{
|
|
unsigned long origpc, orig31, value;
|
|
union mips_instruction insn;
|
|
unsigned int res;
|
|
bool user = user_mode(regs);
|
|
|
|
origpc = (unsigned long)pc;
|
|
orig31 = regs->regs[31];
|
|
|
|
perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, 0);
|
|
|
|
/*
|
|
* This load never faults.
|
|
*/
|
|
__get_inst32(&insn.word, pc, user);
|
|
|
|
switch (insn.i_format.opcode) {
|
|
/*
|
|
* These are instructions that a compiler doesn't generate. We
|
|
* can assume therefore that the code is MIPS-aware and
|
|
* really buggy. Emulating these instructions would break the
|
|
* semantics anyway.
|
|
*/
|
|
case ll_op:
|
|
case lld_op:
|
|
case sc_op:
|
|
case scd_op:
|
|
|
|
/*
|
|
* For these instructions the only way to create an address
|
|
* error is an attempted access to kernel/supervisor address
|
|
* space.
|
|
*/
|
|
case ldl_op:
|
|
case ldr_op:
|
|
case lwl_op:
|
|
case lwr_op:
|
|
case sdl_op:
|
|
case sdr_op:
|
|
case swl_op:
|
|
case swr_op:
|
|
case lb_op:
|
|
case lbu_op:
|
|
case sb_op:
|
|
goto sigbus;
|
|
|
|
/*
|
|
* The remaining opcodes are the ones that are really of
|
|
* interest.
|
|
*/
|
|
#ifdef CONFIG_MACH_INGENIC
|
|
case spec2_op:
|
|
if (insn.mxu_lx_format.func != mxu_lx_op)
|
|
goto sigbus; /* other MXU instructions we don't care */
|
|
|
|
switch (insn.mxu_lx_format.op) {
|
|
case mxu_lxw_op:
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.mxu_lx_format.rd] = value;
|
|
break;
|
|
case mxu_lxh_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
LoadHW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.dsp_format.rd] = value;
|
|
break;
|
|
case mxu_lxhu_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
LoadHWU(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.dsp_format.rd] = value;
|
|
break;
|
|
case mxu_lxb_op:
|
|
case mxu_lxbu_op:
|
|
goto sigbus;
|
|
default:
|
|
goto sigill;
|
|
}
|
|
break;
|
|
#endif
|
|
case spec3_op:
|
|
if (insn.dsp_format.func == lx_op) {
|
|
switch (insn.dsp_format.op) {
|
|
case lwx_op:
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.dsp_format.rd] = value;
|
|
break;
|
|
case lhx_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
LoadHW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.dsp_format.rd] = value;
|
|
break;
|
|
default:
|
|
goto sigill;
|
|
}
|
|
}
|
|
#ifdef CONFIG_EVA
|
|
else {
|
|
/*
|
|
* we can land here only from kernel accessing user
|
|
* memory, so we need to "switch" the address limit to
|
|
* user space, so that address check can work properly.
|
|
*/
|
|
switch (insn.spec3_format.func) {
|
|
case lhe_op:
|
|
if (!access_ok(addr, 2))
|
|
goto sigbus;
|
|
LoadHWE(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.spec3_format.rt] = value;
|
|
break;
|
|
case lwe_op:
|
|
if (!access_ok(addr, 4))
|
|
goto sigbus;
|
|
LoadWE(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.spec3_format.rt] = value;
|
|
break;
|
|
case lhue_op:
|
|
if (!access_ok(addr, 2))
|
|
goto sigbus;
|
|
LoadHWUE(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.spec3_format.rt] = value;
|
|
break;
|
|
case she_op:
|
|
if (!access_ok(addr, 2))
|
|
goto sigbus;
|
|
compute_return_epc(regs);
|
|
value = regs->regs[insn.spec3_format.rt];
|
|
StoreHWE(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
case swe_op:
|
|
if (!access_ok(addr, 4))
|
|
goto sigbus;
|
|
compute_return_epc(regs);
|
|
value = regs->regs[insn.spec3_format.rt];
|
|
StoreWE(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
default:
|
|
goto sigill;
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
case lh_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
if (IS_ENABLED(CONFIG_EVA) && user)
|
|
LoadHWE(addr, value, res);
|
|
else
|
|
LoadHW(addr, value, res);
|
|
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.i_format.rt] = value;
|
|
break;
|
|
|
|
case lw_op:
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
if (IS_ENABLED(CONFIG_EVA) && user)
|
|
LoadWE(addr, value, res);
|
|
else
|
|
LoadW(addr, value, res);
|
|
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.i_format.rt] = value;
|
|
break;
|
|
|
|
case lhu_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
if (IS_ENABLED(CONFIG_EVA) && user)
|
|
LoadHWUE(addr, value, res);
|
|
else
|
|
LoadHWU(addr, value, res);
|
|
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.i_format.rt] = value;
|
|
break;
|
|
|
|
case lwu_op:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
LoadWU(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.i_format.rt] = value;
|
|
break;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
case ld_op:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
compute_return_epc(regs);
|
|
regs->regs[insn.i_format.rt] = value;
|
|
break;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
case sh_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
compute_return_epc(regs);
|
|
value = regs->regs[insn.i_format.rt];
|
|
|
|
if (IS_ENABLED(CONFIG_EVA) && user)
|
|
StoreHWE(addr, value, res);
|
|
else
|
|
StoreHW(addr, value, res);
|
|
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
|
|
case sw_op:
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
compute_return_epc(regs);
|
|
value = regs->regs[insn.i_format.rt];
|
|
|
|
if (IS_ENABLED(CONFIG_EVA) && user)
|
|
StoreWE(addr, value, res);
|
|
else
|
|
StoreW(addr, value, res);
|
|
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
|
|
case sd_op:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
compute_return_epc(regs);
|
|
value = regs->regs[insn.i_format.rt];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
#ifdef CONFIG_MIPS_FP_SUPPORT
|
|
|
|
case lwc1_op:
|
|
case ldc1_op:
|
|
case swc1_op:
|
|
case sdc1_op:
|
|
case cop1x_op: {
|
|
void __user *fault_addr = NULL;
|
|
|
|
die_if_kernel("Unaligned FP access in kernel code", regs);
|
|
BUG_ON(!used_math());
|
|
|
|
res = fpu_emulator_cop1Handler(regs, ¤t->thread.fpu, 1,
|
|
&fault_addr);
|
|
own_fpu(1); /* Restore FPU state. */
|
|
|
|
/* Signal if something went wrong. */
|
|
process_fpemu_return(res, fault_addr, 0);
|
|
|
|
if (res == 0)
|
|
break;
|
|
return;
|
|
}
|
|
#endif /* CONFIG_MIPS_FP_SUPPORT */
|
|
|
|
#ifdef CONFIG_CPU_HAS_MSA
|
|
|
|
case msa_op: {
|
|
unsigned int wd, preempted;
|
|
enum msa_2b_fmt df;
|
|
union fpureg *fpr;
|
|
|
|
if (!cpu_has_msa)
|
|
goto sigill;
|
|
|
|
/*
|
|
* If we've reached this point then userland should have taken
|
|
* the MSA disabled exception & initialised vector context at
|
|
* some point in the past.
|
|
*/
|
|
BUG_ON(!thread_msa_context_live());
|
|
|
|
df = insn.msa_mi10_format.df;
|
|
wd = insn.msa_mi10_format.wd;
|
|
fpr = ¤t->thread.fpu.fpr[wd];
|
|
|
|
switch (insn.msa_mi10_format.func) {
|
|
case msa_ld_op:
|
|
if (!access_ok(addr, sizeof(*fpr)))
|
|
goto sigbus;
|
|
|
|
do {
|
|
/*
|
|
* If we have live MSA context keep track of
|
|
* whether we get preempted in order to avoid
|
|
* the register context we load being clobbered
|
|
* by the live context as it's saved during
|
|
* preemption. If we don't have live context
|
|
* then it can't be saved to clobber the value
|
|
* we load.
|
|
*/
|
|
preempted = test_thread_flag(TIF_USEDMSA);
|
|
|
|
res = __copy_from_user_inatomic(fpr, addr,
|
|
sizeof(*fpr));
|
|
if (res)
|
|
goto fault;
|
|
|
|
/*
|
|
* Update the hardware register if it is in use
|
|
* by the task in this quantum, in order to
|
|
* avoid having to save & restore the whole
|
|
* vector context.
|
|
*/
|
|
preempt_disable();
|
|
if (test_thread_flag(TIF_USEDMSA)) {
|
|
write_msa_wr(wd, fpr, df);
|
|
preempted = 0;
|
|
}
|
|
preempt_enable();
|
|
} while (preempted);
|
|
break;
|
|
|
|
case msa_st_op:
|
|
if (!access_ok(addr, sizeof(*fpr)))
|
|
goto sigbus;
|
|
|
|
/*
|
|
* Update from the hardware register if it is in use by
|
|
* the task in this quantum, in order to avoid having to
|
|
* save & restore the whole vector context.
|
|
*/
|
|
preempt_disable();
|
|
if (test_thread_flag(TIF_USEDMSA))
|
|
read_msa_wr(wd, fpr, df);
|
|
preempt_enable();
|
|
|
|
res = __copy_to_user_inatomic(addr, fpr, sizeof(*fpr));
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
|
|
default:
|
|
goto sigbus;
|
|
}
|
|
|
|
compute_return_epc(regs);
|
|
break;
|
|
}
|
|
#endif /* CONFIG_CPU_HAS_MSA */
|
|
|
|
#ifndef CONFIG_CPU_MIPSR6
|
|
/*
|
|
* COP2 is available to implementor for application specific use.
|
|
* It's up to applications to register a notifier chain and do
|
|
* whatever they have to do, including possible sending of signals.
|
|
*
|
|
* This instruction has been reallocated in Release 6
|
|
*/
|
|
case lwc2_op:
|
|
cu2_notifier_call_chain(CU2_LWC2_OP, regs);
|
|
break;
|
|
|
|
case ldc2_op:
|
|
cu2_notifier_call_chain(CU2_LDC2_OP, regs);
|
|
break;
|
|
|
|
case swc2_op:
|
|
cu2_notifier_call_chain(CU2_SWC2_OP, regs);
|
|
break;
|
|
|
|
case sdc2_op:
|
|
cu2_notifier_call_chain(CU2_SDC2_OP, regs);
|
|
break;
|
|
#endif
|
|
default:
|
|
/*
|
|
* Pheeee... We encountered an yet unknown instruction or
|
|
* cache coherence problem. Die sucker, die ...
|
|
*/
|
|
goto sigill;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
unaligned_instructions++;
|
|
#endif
|
|
|
|
return;
|
|
|
|
fault:
|
|
/* roll back jump/branch */
|
|
regs->cp0_epc = origpc;
|
|
regs->regs[31] = orig31;
|
|
/* Did we have an exception handler installed? */
|
|
if (fixup_exception(regs))
|
|
return;
|
|
|
|
die_if_kernel("Unhandled kernel unaligned access", regs);
|
|
force_sig(SIGSEGV);
|
|
|
|
return;
|
|
|
|
sigbus:
|
|
die_if_kernel("Unhandled kernel unaligned access", regs);
|
|
force_sig(SIGBUS);
|
|
|
|
return;
|
|
|
|
sigill:
|
|
die_if_kernel
|
|
("Unhandled kernel unaligned access or invalid instruction", regs);
|
|
force_sig(SIGILL);
|
|
}
|
|
|
|
/* Recode table from 16-bit register notation to 32-bit GPR. */
|
|
const int reg16to32[] = { 16, 17, 2, 3, 4, 5, 6, 7 };
|
|
|
|
/* Recode table from 16-bit STORE register notation to 32-bit GPR. */
|
|
static const int reg16to32st[] = { 0, 17, 2, 3, 4, 5, 6, 7 };
|
|
|
|
static void emulate_load_store_microMIPS(struct pt_regs *regs,
|
|
void __user *addr)
|
|
{
|
|
unsigned long value;
|
|
unsigned int res;
|
|
int i;
|
|
unsigned int reg = 0, rvar;
|
|
unsigned long orig31;
|
|
u16 __user *pc16;
|
|
u16 halfword;
|
|
unsigned int word;
|
|
unsigned long origpc, contpc;
|
|
union mips_instruction insn;
|
|
struct mm_decoded_insn mminsn;
|
|
bool user = user_mode(regs);
|
|
|
|
origpc = regs->cp0_epc;
|
|
orig31 = regs->regs[31];
|
|
|
|
mminsn.micro_mips_mode = 1;
|
|
|
|
/*
|
|
* This load never faults.
|
|
*/
|
|
pc16 = (unsigned short __user *)msk_isa16_mode(regs->cp0_epc);
|
|
__get_user(halfword, pc16);
|
|
pc16++;
|
|
contpc = regs->cp0_epc + 2;
|
|
word = ((unsigned int)halfword << 16);
|
|
mminsn.pc_inc = 2;
|
|
|
|
if (!mm_insn_16bit(halfword)) {
|
|
__get_user(halfword, pc16);
|
|
pc16++;
|
|
contpc = regs->cp0_epc + 4;
|
|
mminsn.pc_inc = 4;
|
|
word |= halfword;
|
|
}
|
|
mminsn.insn = word;
|
|
|
|
if (get_user(halfword, pc16))
|
|
goto fault;
|
|
mminsn.next_pc_inc = 2;
|
|
word = ((unsigned int)halfword << 16);
|
|
|
|
if (!mm_insn_16bit(halfword)) {
|
|
pc16++;
|
|
if (get_user(halfword, pc16))
|
|
goto fault;
|
|
mminsn.next_pc_inc = 4;
|
|
word |= halfword;
|
|
}
|
|
mminsn.next_insn = word;
|
|
|
|
insn = (union mips_instruction)(mminsn.insn);
|
|
if (mm_isBranchInstr(regs, mminsn, &contpc))
|
|
insn = (union mips_instruction)(mminsn.next_insn);
|
|
|
|
/* Parse instruction to find what to do */
|
|
|
|
switch (insn.mm_i_format.opcode) {
|
|
|
|
case mm_pool32a_op:
|
|
switch (insn.mm_x_format.func) {
|
|
case mm_lwxs_op:
|
|
reg = insn.mm_x_format.rd;
|
|
goto loadW;
|
|
}
|
|
|
|
goto sigbus;
|
|
|
|
case mm_pool32b_op:
|
|
switch (insn.mm_m_format.func) {
|
|
case mm_lwp_func:
|
|
reg = insn.mm_m_format.rd;
|
|
if (reg == 31)
|
|
goto sigbus;
|
|
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg] = value;
|
|
addr += 4;
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg + 1] = value;
|
|
goto success;
|
|
|
|
case mm_swp_func:
|
|
reg = insn.mm_m_format.rd;
|
|
if (reg == 31)
|
|
goto sigbus;
|
|
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
value = regs->regs[reg];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
value = regs->regs[reg + 1];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
goto success;
|
|
|
|
case mm_ldp_func:
|
|
#ifdef CONFIG_64BIT
|
|
reg = insn.mm_m_format.rd;
|
|
if (reg == 31)
|
|
goto sigbus;
|
|
|
|
if (user && !access_ok(addr, 16))
|
|
goto sigbus;
|
|
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg] = value;
|
|
addr += 8;
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg + 1] = value;
|
|
goto success;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
goto sigill;
|
|
|
|
case mm_sdp_func:
|
|
#ifdef CONFIG_64BIT
|
|
reg = insn.mm_m_format.rd;
|
|
if (reg == 31)
|
|
goto sigbus;
|
|
|
|
if (user && !access_ok(addr, 16))
|
|
goto sigbus;
|
|
|
|
value = regs->regs[reg];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 8;
|
|
value = regs->regs[reg + 1];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
goto success;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
goto sigill;
|
|
|
|
case mm_lwm32_func:
|
|
reg = insn.mm_m_format.rd;
|
|
rvar = reg & 0xf;
|
|
if ((rvar > 9) || !reg)
|
|
goto sigill;
|
|
if (reg & 0x10) {
|
|
if (user && !access_ok(addr, 4 * (rvar + 1)))
|
|
goto sigbus;
|
|
} else {
|
|
if (user && !access_ok(addr, 4 * rvar))
|
|
goto sigbus;
|
|
}
|
|
if (rvar == 9)
|
|
rvar = 8;
|
|
for (i = 16; rvar; rvar--, i++) {
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
regs->regs[i] = value;
|
|
}
|
|
if ((reg & 0xf) == 9) {
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
regs->regs[30] = value;
|
|
}
|
|
if (reg & 0x10) {
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[31] = value;
|
|
}
|
|
goto success;
|
|
|
|
case mm_swm32_func:
|
|
reg = insn.mm_m_format.rd;
|
|
rvar = reg & 0xf;
|
|
if ((rvar > 9) || !reg)
|
|
goto sigill;
|
|
if (reg & 0x10) {
|
|
if (user && !access_ok(addr, 4 * (rvar + 1)))
|
|
goto sigbus;
|
|
} else {
|
|
if (user && !access_ok(addr, 4 * rvar))
|
|
goto sigbus;
|
|
}
|
|
if (rvar == 9)
|
|
rvar = 8;
|
|
for (i = 16; rvar; rvar--, i++) {
|
|
value = regs->regs[i];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
}
|
|
if ((reg & 0xf) == 9) {
|
|
value = regs->regs[30];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
}
|
|
if (reg & 0x10) {
|
|
value = regs->regs[31];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
}
|
|
goto success;
|
|
|
|
case mm_ldm_func:
|
|
#ifdef CONFIG_64BIT
|
|
reg = insn.mm_m_format.rd;
|
|
rvar = reg & 0xf;
|
|
if ((rvar > 9) || !reg)
|
|
goto sigill;
|
|
if (reg & 0x10) {
|
|
if (user && !access_ok(addr, 8 * (rvar + 1)))
|
|
goto sigbus;
|
|
} else {
|
|
if (user && !access_ok(addr, 8 * rvar))
|
|
goto sigbus;
|
|
}
|
|
if (rvar == 9)
|
|
rvar = 8;
|
|
|
|
for (i = 16; rvar; rvar--, i++) {
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
regs->regs[i] = value;
|
|
}
|
|
if ((reg & 0xf) == 9) {
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 8;
|
|
regs->regs[30] = value;
|
|
}
|
|
if (reg & 0x10) {
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[31] = value;
|
|
}
|
|
goto success;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
goto sigill;
|
|
|
|
case mm_sdm_func:
|
|
#ifdef CONFIG_64BIT
|
|
reg = insn.mm_m_format.rd;
|
|
rvar = reg & 0xf;
|
|
if ((rvar > 9) || !reg)
|
|
goto sigill;
|
|
if (reg & 0x10) {
|
|
if (user && !access_ok(addr, 8 * (rvar + 1)))
|
|
goto sigbus;
|
|
} else {
|
|
if (user && !access_ok(addr, 8 * rvar))
|
|
goto sigbus;
|
|
}
|
|
if (rvar == 9)
|
|
rvar = 8;
|
|
|
|
for (i = 16; rvar; rvar--, i++) {
|
|
value = regs->regs[i];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 8;
|
|
}
|
|
if ((reg & 0xf) == 9) {
|
|
value = regs->regs[30];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 8;
|
|
}
|
|
if (reg & 0x10) {
|
|
value = regs->regs[31];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
}
|
|
goto success;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
goto sigill;
|
|
|
|
/* LWC2, SWC2, LDC2, SDC2 are not serviced */
|
|
}
|
|
|
|
goto sigbus;
|
|
|
|
case mm_pool32c_op:
|
|
switch (insn.mm_m_format.func) {
|
|
case mm_lwu_func:
|
|
reg = insn.mm_m_format.rd;
|
|
goto loadWU;
|
|
}
|
|
|
|
/* LL,SC,LLD,SCD are not serviced */
|
|
goto sigbus;
|
|
|
|
#ifdef CONFIG_MIPS_FP_SUPPORT
|
|
case mm_pool32f_op:
|
|
switch (insn.mm_x_format.func) {
|
|
case mm_lwxc1_func:
|
|
case mm_swxc1_func:
|
|
case mm_ldxc1_func:
|
|
case mm_sdxc1_func:
|
|
goto fpu_emul;
|
|
}
|
|
|
|
goto sigbus;
|
|
|
|
case mm_ldc132_op:
|
|
case mm_sdc132_op:
|
|
case mm_lwc132_op:
|
|
case mm_swc132_op: {
|
|
void __user *fault_addr = NULL;
|
|
|
|
fpu_emul:
|
|
/* roll back jump/branch */
|
|
regs->cp0_epc = origpc;
|
|
regs->regs[31] = orig31;
|
|
|
|
die_if_kernel("Unaligned FP access in kernel code", regs);
|
|
BUG_ON(!used_math());
|
|
BUG_ON(!is_fpu_owner());
|
|
|
|
res = fpu_emulator_cop1Handler(regs, ¤t->thread.fpu, 1,
|
|
&fault_addr);
|
|
own_fpu(1); /* restore FPU state */
|
|
|
|
/* If something went wrong, signal */
|
|
process_fpemu_return(res, fault_addr, 0);
|
|
|
|
if (res == 0)
|
|
goto success;
|
|
return;
|
|
}
|
|
#endif /* CONFIG_MIPS_FP_SUPPORT */
|
|
|
|
case mm_lh32_op:
|
|
reg = insn.mm_i_format.rt;
|
|
goto loadHW;
|
|
|
|
case mm_lhu32_op:
|
|
reg = insn.mm_i_format.rt;
|
|
goto loadHWU;
|
|
|
|
case mm_lw32_op:
|
|
reg = insn.mm_i_format.rt;
|
|
goto loadW;
|
|
|
|
case mm_sh32_op:
|
|
reg = insn.mm_i_format.rt;
|
|
goto storeHW;
|
|
|
|
case mm_sw32_op:
|
|
reg = insn.mm_i_format.rt;
|
|
goto storeW;
|
|
|
|
case mm_ld32_op:
|
|
reg = insn.mm_i_format.rt;
|
|
goto loadDW;
|
|
|
|
case mm_sd32_op:
|
|
reg = insn.mm_i_format.rt;
|
|
goto storeDW;
|
|
|
|
case mm_pool16c_op:
|
|
switch (insn.mm16_m_format.func) {
|
|
case mm_lwm16_op:
|
|
reg = insn.mm16_m_format.rlist;
|
|
rvar = reg + 1;
|
|
if (user && !access_ok(addr, 4 * rvar))
|
|
goto sigbus;
|
|
|
|
for (i = 16; rvar; rvar--, i++) {
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
regs->regs[i] = value;
|
|
}
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[31] = value;
|
|
|
|
goto success;
|
|
|
|
case mm_swm16_op:
|
|
reg = insn.mm16_m_format.rlist;
|
|
rvar = reg + 1;
|
|
if (user && !access_ok(addr, 4 * rvar))
|
|
goto sigbus;
|
|
|
|
for (i = 16; rvar; rvar--, i++) {
|
|
value = regs->regs[i];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
addr += 4;
|
|
}
|
|
value = regs->regs[31];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
|
|
goto success;
|
|
|
|
}
|
|
|
|
goto sigbus;
|
|
|
|
case mm_lhu16_op:
|
|
reg = reg16to32[insn.mm16_rb_format.rt];
|
|
goto loadHWU;
|
|
|
|
case mm_lw16_op:
|
|
reg = reg16to32[insn.mm16_rb_format.rt];
|
|
goto loadW;
|
|
|
|
case mm_sh16_op:
|
|
reg = reg16to32st[insn.mm16_rb_format.rt];
|
|
goto storeHW;
|
|
|
|
case mm_sw16_op:
|
|
reg = reg16to32st[insn.mm16_rb_format.rt];
|
|
goto storeW;
|
|
|
|
case mm_lwsp16_op:
|
|
reg = insn.mm16_r5_format.rt;
|
|
goto loadW;
|
|
|
|
case mm_swsp16_op:
|
|
reg = insn.mm16_r5_format.rt;
|
|
goto storeW;
|
|
|
|
case mm_lwgp16_op:
|
|
reg = reg16to32[insn.mm16_r3_format.rt];
|
|
goto loadW;
|
|
|
|
default:
|
|
goto sigill;
|
|
}
|
|
|
|
loadHW:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
LoadHW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg] = value;
|
|
goto success;
|
|
|
|
loadHWU:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
LoadHWU(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg] = value;
|
|
goto success;
|
|
|
|
loadW:
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg] = value;
|
|
goto success;
|
|
|
|
loadWU:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
LoadWU(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg] = value;
|
|
goto success;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
loadDW:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
regs->regs[reg] = value;
|
|
goto success;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
storeHW:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
value = regs->regs[reg];
|
|
StoreHW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
goto success;
|
|
|
|
storeW:
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
value = regs->regs[reg];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
goto success;
|
|
|
|
storeDW:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
value = regs->regs[reg];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
goto success;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
success:
|
|
regs->cp0_epc = contpc; /* advance or branch */
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
unaligned_instructions++;
|
|
#endif
|
|
return;
|
|
|
|
fault:
|
|
/* roll back jump/branch */
|
|
regs->cp0_epc = origpc;
|
|
regs->regs[31] = orig31;
|
|
/* Did we have an exception handler installed? */
|
|
if (fixup_exception(regs))
|
|
return;
|
|
|
|
die_if_kernel("Unhandled kernel unaligned access", regs);
|
|
force_sig(SIGSEGV);
|
|
|
|
return;
|
|
|
|
sigbus:
|
|
die_if_kernel("Unhandled kernel unaligned access", regs);
|
|
force_sig(SIGBUS);
|
|
|
|
return;
|
|
|
|
sigill:
|
|
die_if_kernel
|
|
("Unhandled kernel unaligned access or invalid instruction", regs);
|
|
force_sig(SIGILL);
|
|
}
|
|
|
|
static void emulate_load_store_MIPS16e(struct pt_regs *regs, void __user * addr)
|
|
{
|
|
unsigned long value;
|
|
unsigned int res;
|
|
int reg;
|
|
unsigned long orig31;
|
|
u16 __user *pc16;
|
|
unsigned long origpc;
|
|
union mips16e_instruction mips16inst, oldinst;
|
|
unsigned int opcode;
|
|
int extended = 0;
|
|
bool user = user_mode(regs);
|
|
|
|
origpc = regs->cp0_epc;
|
|
orig31 = regs->regs[31];
|
|
pc16 = (unsigned short __user *)msk_isa16_mode(origpc);
|
|
/*
|
|
* This load never faults.
|
|
*/
|
|
__get_user(mips16inst.full, pc16);
|
|
oldinst = mips16inst;
|
|
|
|
/* skip EXTEND instruction */
|
|
if (mips16inst.ri.opcode == MIPS16e_extend_op) {
|
|
extended = 1;
|
|
pc16++;
|
|
__get_user(mips16inst.full, pc16);
|
|
} else if (delay_slot(regs)) {
|
|
/* skip jump instructions */
|
|
/* JAL/JALX are 32 bits but have OPCODE in first short int */
|
|
if (mips16inst.ri.opcode == MIPS16e_jal_op)
|
|
pc16++;
|
|
pc16++;
|
|
if (get_user(mips16inst.full, pc16))
|
|
goto sigbus;
|
|
}
|
|
|
|
opcode = mips16inst.ri.opcode;
|
|
switch (opcode) {
|
|
case MIPS16e_i64_op: /* I64 or RI64 instruction */
|
|
switch (mips16inst.i64.func) { /* I64/RI64 func field check */
|
|
case MIPS16e_ldpc_func:
|
|
case MIPS16e_ldsp_func:
|
|
reg = reg16to32[mips16inst.ri64.ry];
|
|
goto loadDW;
|
|
|
|
case MIPS16e_sdsp_func:
|
|
reg = reg16to32[mips16inst.ri64.ry];
|
|
goto writeDW;
|
|
|
|
case MIPS16e_sdrasp_func:
|
|
reg = 29; /* GPRSP */
|
|
goto writeDW;
|
|
}
|
|
|
|
goto sigbus;
|
|
|
|
case MIPS16e_swsp_op:
|
|
reg = reg16to32[mips16inst.ri.rx];
|
|
if (extended && cpu_has_mips16e2)
|
|
switch (mips16inst.ri.imm >> 5) {
|
|
case 0: /* SWSP */
|
|
case 1: /* SWGP */
|
|
break;
|
|
case 2: /* SHGP */
|
|
opcode = MIPS16e_sh_op;
|
|
break;
|
|
default:
|
|
goto sigbus;
|
|
}
|
|
break;
|
|
|
|
case MIPS16e_lwpc_op:
|
|
reg = reg16to32[mips16inst.ri.rx];
|
|
break;
|
|
|
|
case MIPS16e_lwsp_op:
|
|
reg = reg16to32[mips16inst.ri.rx];
|
|
if (extended && cpu_has_mips16e2)
|
|
switch (mips16inst.ri.imm >> 5) {
|
|
case 0: /* LWSP */
|
|
case 1: /* LWGP */
|
|
break;
|
|
case 2: /* LHGP */
|
|
opcode = MIPS16e_lh_op;
|
|
break;
|
|
case 4: /* LHUGP */
|
|
opcode = MIPS16e_lhu_op;
|
|
break;
|
|
default:
|
|
goto sigbus;
|
|
}
|
|
break;
|
|
|
|
case MIPS16e_i8_op:
|
|
if (mips16inst.i8.func != MIPS16e_swrasp_func)
|
|
goto sigbus;
|
|
reg = 29; /* GPRSP */
|
|
break;
|
|
|
|
default:
|
|
reg = reg16to32[mips16inst.rri.ry];
|
|
break;
|
|
}
|
|
|
|
switch (opcode) {
|
|
|
|
case MIPS16e_lb_op:
|
|
case MIPS16e_lbu_op:
|
|
case MIPS16e_sb_op:
|
|
goto sigbus;
|
|
|
|
case MIPS16e_lh_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
LoadHW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
regs->regs[reg] = value;
|
|
break;
|
|
|
|
case MIPS16e_lhu_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
LoadHWU(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
regs->regs[reg] = value;
|
|
break;
|
|
|
|
case MIPS16e_lw_op:
|
|
case MIPS16e_lwpc_op:
|
|
case MIPS16e_lwsp_op:
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
LoadW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
regs->regs[reg] = value;
|
|
break;
|
|
|
|
case MIPS16e_lwu_op:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
LoadWU(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
regs->regs[reg] = value;
|
|
break;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
case MIPS16e_ld_op:
|
|
loadDW:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
LoadDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
regs->regs[reg] = value;
|
|
break;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
case MIPS16e_sh_op:
|
|
if (user && !access_ok(addr, 2))
|
|
goto sigbus;
|
|
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
value = regs->regs[reg];
|
|
StoreHW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
|
|
case MIPS16e_sw_op:
|
|
case MIPS16e_swsp_op:
|
|
case MIPS16e_i8_op: /* actually - MIPS16e_swrasp_func */
|
|
if (user && !access_ok(addr, 4))
|
|
goto sigbus;
|
|
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
value = regs->regs[reg];
|
|
StoreW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
|
|
case MIPS16e_sd_op:
|
|
writeDW:
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* A 32-bit kernel might be running on a 64-bit processor. But
|
|
* if we're on a 32-bit processor and an i-cache incoherency
|
|
* or race makes us see a 64-bit instruction here the sdl/sdr
|
|
* would blow up, so for now we don't handle unaligned 64-bit
|
|
* instructions on 32-bit kernels.
|
|
*/
|
|
if (user && !access_ok(addr, 8))
|
|
goto sigbus;
|
|
|
|
MIPS16e_compute_return_epc(regs, &oldinst);
|
|
value = regs->regs[reg];
|
|
StoreDW(addr, value, res);
|
|
if (res)
|
|
goto fault;
|
|
break;
|
|
#endif /* CONFIG_64BIT */
|
|
|
|
/* Cannot handle 64-bit instructions in 32-bit kernel */
|
|
goto sigill;
|
|
|
|
default:
|
|
/*
|
|
* Pheeee... We encountered an yet unknown instruction or
|
|
* cache coherence problem. Die sucker, die ...
|
|
*/
|
|
goto sigill;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
unaligned_instructions++;
|
|
#endif
|
|
|
|
return;
|
|
|
|
fault:
|
|
/* roll back jump/branch */
|
|
regs->cp0_epc = origpc;
|
|
regs->regs[31] = orig31;
|
|
/* Did we have an exception handler installed? */
|
|
if (fixup_exception(regs))
|
|
return;
|
|
|
|
die_if_kernel("Unhandled kernel unaligned access", regs);
|
|
force_sig(SIGSEGV);
|
|
|
|
return;
|
|
|
|
sigbus:
|
|
die_if_kernel("Unhandled kernel unaligned access", regs);
|
|
force_sig(SIGBUS);
|
|
|
|
return;
|
|
|
|
sigill:
|
|
die_if_kernel
|
|
("Unhandled kernel unaligned access or invalid instruction", regs);
|
|
force_sig(SIGILL);
|
|
}
|
|
|
|
asmlinkage void do_ade(struct pt_regs *regs)
|
|
{
|
|
enum ctx_state prev_state;
|
|
unsigned int *pc;
|
|
|
|
prev_state = exception_enter();
|
|
perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS,
|
|
1, regs, regs->cp0_badvaddr);
|
|
|
|
#ifdef CONFIG_64BIT
|
|
/*
|
|
* check, if we are hitting space between CPU implemented maximum
|
|
* virtual user address and 64bit maximum virtual user address
|
|
* and do exception handling to get EFAULTs for get_user/put_user
|
|
*/
|
|
if ((regs->cp0_badvaddr >= (1UL << cpu_vmbits)) &&
|
|
(regs->cp0_badvaddr < XKSSEG)) {
|
|
if (fixup_exception(regs)) {
|
|
current->thread.cp0_baduaddr = regs->cp0_badvaddr;
|
|
return;
|
|
}
|
|
goto sigbus;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Did we catch a fault trying to load an instruction?
|
|
*/
|
|
if (regs->cp0_badvaddr == regs->cp0_epc)
|
|
goto sigbus;
|
|
|
|
if (user_mode(regs) && !test_thread_flag(TIF_FIXADE))
|
|
goto sigbus;
|
|
if (unaligned_action == UNALIGNED_ACTION_SIGNAL)
|
|
goto sigbus;
|
|
|
|
/*
|
|
* Do branch emulation only if we didn't forward the exception.
|
|
* This is all so but ugly ...
|
|
*/
|
|
|
|
/*
|
|
* Are we running in microMIPS mode?
|
|
*/
|
|
if (get_isa16_mode(regs->cp0_epc)) {
|
|
/*
|
|
* Did we catch a fault trying to load an instruction in
|
|
* 16-bit mode?
|
|
*/
|
|
if (regs->cp0_badvaddr == msk_isa16_mode(regs->cp0_epc))
|
|
goto sigbus;
|
|
if (unaligned_action == UNALIGNED_ACTION_SHOW)
|
|
show_registers(regs);
|
|
|
|
if (cpu_has_mmips) {
|
|
emulate_load_store_microMIPS(regs,
|
|
(void __user *)regs->cp0_badvaddr);
|
|
return;
|
|
}
|
|
|
|
if (cpu_has_mips16) {
|
|
emulate_load_store_MIPS16e(regs,
|
|
(void __user *)regs->cp0_badvaddr);
|
|
return;
|
|
}
|
|
|
|
goto sigbus;
|
|
}
|
|
|
|
if (unaligned_action == UNALIGNED_ACTION_SHOW)
|
|
show_registers(regs);
|
|
pc = (unsigned int *)exception_epc(regs);
|
|
|
|
emulate_load_store_insn(regs, (void __user *)regs->cp0_badvaddr, pc);
|
|
|
|
return;
|
|
|
|
sigbus:
|
|
die_if_kernel("Kernel unaligned instruction access", regs);
|
|
force_sig(SIGBUS);
|
|
|
|
/*
|
|
* XXX On return from the signal handler we should advance the epc
|
|
*/
|
|
exception_exit(prev_state);
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int __init debugfs_unaligned(void)
|
|
{
|
|
debugfs_create_u32("unaligned_instructions", S_IRUGO, mips_debugfs_dir,
|
|
&unaligned_instructions);
|
|
debugfs_create_u32("unaligned_action", S_IRUGO | S_IWUSR,
|
|
mips_debugfs_dir, &unaligned_action);
|
|
return 0;
|
|
}
|
|
arch_initcall(debugfs_unaligned);
|
|
#endif
|