summaryrefslogtreecommitdiffstats
path: root/arch/mips/kernel/traps.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/mips/kernel/traps.c')
-rw-r--r--arch/mips/kernel/traps.c269
1 files changed, 157 insertions, 112 deletions
diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c
index 33984c04b60b..7d5532adc890 100644
--- a/arch/mips/kernel/traps.c
+++ b/arch/mips/kernel/traps.c
@@ -12,6 +12,7 @@
* Copyright (C) 2000, 2001, 2012 MIPS Technologies, Inc. All rights reserved.
* Copyright (C) 2014, Imagination Technologies Ltd.
*/
+#include <linux/bitops.h>
#include <linux/bug.h>
#include <linux/compiler.h>
#include <linux/context_tracking.h>
@@ -699,29 +700,60 @@ asmlinkage void do_ov(struct pt_regs *regs)
exception_exit(prev_state);
}
-int process_fpemu_return(int sig, void __user *fault_addr)
+int process_fpemu_return(int sig, void __user *fault_addr, unsigned long fcr31)
{
- if (sig == SIGSEGV || sig == SIGBUS) {
- struct siginfo si = {0};
+ struct siginfo si = { 0 };
+
+ switch (sig) {
+ case 0:
+ return 0;
+
+ case SIGFPE:
si.si_addr = fault_addr;
si.si_signo = sig;
- if (sig == SIGSEGV) {
- down_read(&current->mm->mmap_sem);
- if (find_vma(current->mm, (unsigned long)fault_addr))
- si.si_code = SEGV_ACCERR;
- else
- si.si_code = SEGV_MAPERR;
- up_read(&current->mm->mmap_sem);
- } else {
- si.si_code = BUS_ADRERR;
- }
+ /*
+ * Inexact can happen together with Overflow or Underflow.
+ * Respect the mask to deliver the correct exception.
+ */
+ fcr31 &= (fcr31 & FPU_CSR_ALL_E) <<
+ (ffs(FPU_CSR_ALL_X) - ffs(FPU_CSR_ALL_E));
+ if (fcr31 & FPU_CSR_INV_X)
+ si.si_code = FPE_FLTINV;
+ else if (fcr31 & FPU_CSR_DIV_X)
+ si.si_code = FPE_FLTDIV;
+ else if (fcr31 & FPU_CSR_OVF_X)
+ si.si_code = FPE_FLTOVF;
+ else if (fcr31 & FPU_CSR_UDF_X)
+ si.si_code = FPE_FLTUND;
+ else if (fcr31 & FPU_CSR_INE_X)
+ si.si_code = FPE_FLTRES;
+ else
+ si.si_code = __SI_FAULT;
+ force_sig_info(sig, &si, current);
+ return 1;
+
+ case SIGBUS:
+ si.si_addr = fault_addr;
+ si.si_signo = sig;
+ si.si_code = BUS_ADRERR;
+ force_sig_info(sig, &si, current);
+ return 1;
+
+ case SIGSEGV:
+ si.si_addr = fault_addr;
+ si.si_signo = sig;
+ down_read(&current->mm->mmap_sem);
+ if (find_vma(current->mm, (unsigned long)fault_addr))
+ si.si_code = SEGV_ACCERR;
+ else
+ si.si_code = SEGV_MAPERR;
+ up_read(&current->mm->mmap_sem);
force_sig_info(sig, &si, current);
return 1;
- } else if (sig) {
+
+ default:
force_sig(sig, current);
return 1;
- } else {
- return 0;
}
}
@@ -729,7 +761,8 @@ static int simulate_fp(struct pt_regs *regs, unsigned int opcode,
unsigned long old_epc, unsigned long old_ra)
{
union mips_instruction inst = { .word = opcode };
- void __user *fault_addr = NULL;
+ void __user *fault_addr;
+ unsigned long fcr31;
int sig;
/* If it's obviously not an FP instruction, skip it */
@@ -759,13 +792,20 @@ static int simulate_fp(struct pt_regs *regs, unsigned int opcode,
/* Run the emulator */
sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 1,
&fault_addr);
+ fcr31 = current->thread.fpu.fcr31;
- /* If something went wrong, signal */
- process_fpemu_return(sig, fault_addr);
+ /*
+ * We can't allow the emulated instruction to leave any of
+ * the cause bits set in $fcr31.
+ */
+ current->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X;
/* Restore the hardware register state */
own_fpu(1);
+ /* Send a signal if required. */
+ process_fpemu_return(sig, fault_addr, fcr31);
+
return 0;
}
@@ -775,7 +815,8 @@ static int simulate_fp(struct pt_regs *regs, unsigned int opcode,
asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31)
{
enum ctx_state prev_state;
- siginfo_t info = {0};
+ void __user *fault_addr;
+ int sig;
prev_state = exception_enter();
if (notify_die(DIE_FP, "FP exception", regs, 0, regs_to_trapnr(regs),
@@ -784,9 +825,6 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31)
die_if_kernel("FP exception in kernel code", regs);
if (fcr31 & FPU_CSR_UNI_X) {
- int sig;
- void __user *fault_addr = NULL;
-
/*
* Unimplemented operation exception. If we've got the full
* software emulator on-board, let's use it...
@@ -803,36 +841,23 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31)
/* Run the emulator */
sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 1,
&fault_addr);
+ fcr31 = current->thread.fpu.fcr31;
/*
* We can't allow the emulated instruction to leave any of
- * the cause bit set in $fcr31.
+ * the cause bits set in $fcr31.
*/
current->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X;
/* Restore the hardware register state */
own_fpu(1); /* Using the FPU again. */
+ } else {
+ sig = SIGFPE;
+ fault_addr = (void __user *) regs->cp0_epc;
+ }
- /* If something went wrong, signal */
- process_fpemu_return(sig, fault_addr);
-
- goto out;
- } else if (fcr31 & FPU_CSR_INV_X)
- info.si_code = FPE_FLTINV;
- else if (fcr31 & FPU_CSR_DIV_X)
- info.si_code = FPE_FLTDIV;
- else if (fcr31 & FPU_CSR_OVF_X)
- info.si_code = FPE_FLTOVF;
- else if (fcr31 & FPU_CSR_UDF_X)
- info.si_code = FPE_FLTUND;
- else if (fcr31 & FPU_CSR_INE_X)
- info.si_code = FPE_FLTRES;
- else
- info.si_code = __SI_FAULT;
- info.si_signo = SIGFPE;
- info.si_errno = 0;
- info.si_addr = (void __user *) regs->cp0_epc;
- force_sig_info(SIGFPE, &info, current);
+ /* Send a signal if required. */
+ process_fpemu_return(sig, fault_addr, fcr31);
out:
exception_exit(prev_state);
@@ -879,9 +904,9 @@ void do_trap_or_bp(struct pt_regs *regs, unsigned int code,
break;
case BRK_MEMU:
/*
- * Address errors may be deliberately induced by the FPU
- * emulator to retake control of the CPU after executing the
- * instruction in the delay slot of an emulated branch.
+ * This breakpoint code is used by the FPU emulator to retake
+ * control of the CPU after executing the instruction from the
+ * delay slot of an emulated branch.
*
* Terminate if exception was recognized as a delay slot return
* otherwise handle as normal.
@@ -901,10 +926,9 @@ void do_trap_or_bp(struct pt_regs *regs, unsigned int code,
asmlinkage void do_bp(struct pt_regs *regs)
{
+ unsigned long epc = msk_isa16_mode(exception_epc(regs));
unsigned int opcode, bcode;
enum ctx_state prev_state;
- unsigned long epc;
- u16 instr[2];
mm_segment_t seg;
seg = get_fs();
@@ -913,26 +937,28 @@ asmlinkage void do_bp(struct pt_regs *regs)
prev_state = exception_enter();
if (get_isa16_mode(regs->cp0_epc)) {
- /* Calculate EPC. */
- epc = exception_epc(regs);
- if (cpu_has_mmips) {
- if ((__get_user(instr[0], (u16 __user *)msk_isa16_mode(epc)) ||
- (__get_user(instr[1], (u16 __user *)msk_isa16_mode(epc + 2)))))
- goto out_sigsegv;
- opcode = (instr[0] << 16) | instr[1];
- } else {
+ u16 instr[2];
+
+ if (__get_user(instr[0], (u16 __user *)epc))
+ goto out_sigsegv;
+
+ if (!cpu_has_mmips) {
/* MIPS16e mode */
- if (__get_user(instr[0],
- (u16 __user *)msk_isa16_mode(epc)))
+ bcode = (instr[0] >> 5) & 0x3f;
+ } else if (mm_insn_16bit(instr[0])) {
+ /* 16-bit microMIPS BREAK */
+ bcode = instr[0] & 0xf;
+ } else {
+ /* 32-bit microMIPS BREAK */
+ if (__get_user(instr[1], (u16 __user *)(epc + 2)))
goto out_sigsegv;
- bcode = (instr[0] >> 6) & 0x3f;
- do_trap_or_bp(regs, bcode, "Break");
- goto out;
+ opcode = (instr[0] << 16) | instr[1];
+ bcode = (opcode >> 6) & ((1 << 20) - 1);
}
} else {
- if (__get_user(opcode,
- (unsigned int __user *) exception_epc(regs)))
+ if (__get_user(opcode, (unsigned int __user *)epc))
goto out_sigsegv;
+ bcode = (opcode >> 6) & ((1 << 20) - 1);
}
/*
@@ -941,9 +967,8 @@ asmlinkage void do_bp(struct pt_regs *regs)
* Gas is bug-compatible, but not always, grrr...
* We handle both cases with a simple heuristics. --macro
*/
- bcode = ((opcode >> 6) & ((1 << 20) - 1));
if (bcode >= (1 << 10))
- bcode >>= 10;
+ bcode = ((bcode & ((1 << 10) - 1)) << 10) | (bcode >> 10);
/*
* notify the kprobe handlers, if instruction is likely to
@@ -1033,22 +1058,24 @@ asmlinkage void do_ri(struct pt_regs *regs)
* as quickly as possible.
*/
if (mipsr2_emulation && cpu_has_mips_r6 &&
- likely(user_mode(regs))) {
- if (likely(get_user(opcode, epc) >= 0)) {
- status = mipsr2_decoder(regs, opcode);
- switch (status) {
- case 0:
- case SIGEMT:
- task_thread_info(current)->r2_emul_return = 1;
- return;
- case SIGILL:
- goto no_r2_instr;
- default:
- process_fpemu_return(status,
- &current->thread.cp0_baduaddr);
- task_thread_info(current)->r2_emul_return = 1;
- return;
- }
+ likely(user_mode(regs)) &&
+ likely(get_user(opcode, epc) >= 0)) {
+ unsigned long fcr31 = 0;
+
+ status = mipsr2_decoder(regs, opcode, &fcr31);
+ switch (status) {
+ case 0:
+ case SIGEMT:
+ task_thread_info(current)->r2_emul_return = 1;
+ return;
+ case SIGILL:
+ goto no_r2_instr;
+ default:
+ process_fpemu_return(status,
+ &current->thread.cp0_baduaddr,
+ fcr31);
+ task_thread_info(current)->r2_emul_return = 1;
+ return;
}
}
@@ -1293,10 +1320,13 @@ asmlinkage void do_cpu(struct pt_regs *regs)
enum ctx_state prev_state;
unsigned int __user *epc;
unsigned long old_epc, old31;
+ void __user *fault_addr;
unsigned int opcode;
+ unsigned long fcr31;
unsigned int cpid;
int status, err;
unsigned long __maybe_unused flags;
+ int sig;
prev_state = exception_enter();
cpid = (regs->cp0_cause >> CAUSEB_CE) & 3;
@@ -1313,7 +1343,7 @@ asmlinkage void do_cpu(struct pt_regs *regs)
status = -1;
if (unlikely(compute_return_epc(regs) < 0))
- goto out;
+ break;
if (get_isa16_mode(regs->cp0_epc)) {
unsigned short mmop[2] = { 0 };
@@ -1346,49 +1376,54 @@ asmlinkage void do_cpu(struct pt_regs *regs)
force_sig(status, current);
}
- goto out;
+ break;
case 3:
/*
- * Old (MIPS I and MIPS II) processors will set this code
- * for COP1X opcode instructions that replaced the original
- * COP3 space. We don't limit COP1 space instructions in
- * the emulator according to the CPU ISA, so we want to
- * treat COP1X instructions consistently regardless of which
- * code the CPU chose. Therefore we redirect this trap to
- * the FP emulator too.
- *
- * Then some newer FPU-less processors use this code
- * erroneously too, so they are covered by this choice
- * as well.
+ * The COP3 opcode space and consequently the CP0.Status.CU3
+ * bit and the CP0.Cause.CE=3 encoding have been removed as
+ * of the MIPS III ISA. From the MIPS IV and MIPS32r2 ISAs
+ * up the space has been reused for COP1X instructions, that
+ * are enabled by the CP0.Status.CU1 bit and consequently
+ * use the CP0.Cause.CE=1 encoding for Coprocessor Unusable
+ * exceptions. Some FPU-less processors that implement one
+ * of these ISAs however use this code erroneously for COP1X
+ * instructions. Therefore we redirect this trap to the FP
+ * emulator too.
*/
- if (raw_cpu_has_fpu)
+ if (raw_cpu_has_fpu || !cpu_has_mips_4_5_64_r2_r6) {
+ force_sig(SIGILL, current);
break;
+ }
/* Fall through. */
case 1:
err = enable_restore_fp_context(0);
- if (!raw_cpu_has_fpu || err) {
- int sig;
- void __user *fault_addr = NULL;
- sig = fpu_emulator_cop1Handler(regs,
- &current->thread.fpu,
- 0, &fault_addr);
- if (!process_fpemu_return(sig, fault_addr) && !err)
- mt_ase_fp_affinity();
- }
+ if (raw_cpu_has_fpu && !err)
+ break;
- goto out;
+ sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 0,
+ &fault_addr);
+ fcr31 = current->thread.fpu.fcr31;
+
+ /*
+ * We can't allow the emulated instruction to leave
+ * any of the cause bits set in $fcr31.
+ */
+ current->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X;
+
+ /* Send a signal if required. */
+ if (!process_fpemu_return(sig, fault_addr, fcr31) && !err)
+ mt_ase_fp_affinity();
+
+ break;
case 2:
raw_notifier_call_chain(&cu2_chain, CU2_EXCEPTION, regs);
- goto out;
+ break;
}
- force_sig(SIGILL, current);
-
-out:
exception_exit(prev_state);
}
@@ -1969,6 +2004,12 @@ int cp0_compare_irq_shift;
int cp0_perfcount_irq;
EXPORT_SYMBOL_GPL(cp0_perfcount_irq);
+/*
+ * Fast debug channel IRQ or -1 if not present
+ */
+int cp0_fdc_irq;
+EXPORT_SYMBOL_GPL(cp0_fdc_irq);
+
static int noulri;
static int __init ulri_disable(char *s)
@@ -2050,17 +2091,21 @@ void per_cpu_trap_init(bool is_boot_cpu)
*
* o read IntCtl.IPTI to determine the timer interrupt
* o read IntCtl.IPPCI to determine the performance counter interrupt
+ * o read IntCtl.IPFDC to determine the fast debug channel interrupt
*/
if (cpu_has_mips_r2_r6) {
cp0_compare_irq_shift = CAUSEB_TI - CAUSEB_IP;
cp0_compare_irq = (read_c0_intctl() >> INTCTLB_IPTI) & 7;
cp0_perfcount_irq = (read_c0_intctl() >> INTCTLB_IPPCI) & 7;
- if (cp0_perfcount_irq == cp0_compare_irq)
- cp0_perfcount_irq = -1;
+ cp0_fdc_irq = (read_c0_intctl() >> INTCTLB_IPFDC) & 7;
+ if (!cp0_fdc_irq)
+ cp0_fdc_irq = -1;
+
} else {
cp0_compare_irq = CP0_LEGACY_COMPARE_IRQ;
cp0_compare_irq_shift = CP0_LEGACY_PERFCNT_IRQ;
cp0_perfcount_irq = -1;
+ cp0_fdc_irq = -1;
}
if (!cpu_data[cpu].asid_cache)