]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/mips/kernel/traps.c
Merge remote-tracking branch 'kvm/linux-next'
[karo-tx-linux.git] / arch / mips / kernel / traps.c
index 33984c04b60b710516f1b0bfb88aa52aaa04629f..e334c641a81b3d30bc9a90e2de98604f76993dc4 100644 (file)
@@ -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;
-       } else if (sig) {
+
+       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;
+
+       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,18 +815,21 @@ 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),
                       SIGFPE) == NOTIFY_STOP)
                goto out;
+
+       /* Clear FCSR.Cause before enabling interrupts */
+       write_32bit_cp1_register(CP1_STATUS, fcr31 & ~FPU_CSR_ALL_X);
+       local_irq_enable();
+
        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 +846,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 +909,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 +931,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 +942,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 +972,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 +1063,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 +1325,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 +1348,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,59 +1381,73 @@ 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);
 }
 
-asmlinkage void do_msa_fpe(struct pt_regs *regs)
+asmlinkage void do_msa_fpe(struct pt_regs *regs, unsigned int msacsr)
 {
        enum ctx_state prev_state;
 
        prev_state = exception_enter();
+       if (notify_die(DIE_MSAFP, "MSA FP exception", regs, 0,
+                      regs_to_trapnr(regs), SIGFPE) == NOTIFY_STOP)
+               goto out;
+
+       /* Clear MSACSR.Cause before enabling interrupts */
+       write_msa_csr(msacsr & ~MSA_CSR_CAUSEF);
+       local_irq_enable();
+
        die_if_kernel("do_msa_fpe invoked from kernel context!", regs);
        force_sig(SIGFPE, current);
+out:
        exception_exit(prev_state);
 }
 
@@ -1969,6 +2018,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 +2105,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)