Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * This file is subject to the terms and conditions of the GNU General Public
0003  * License.  See the file "COPYING" in the main directory of this archive
0004  * for more details.
0005  *
0006  * Copyright (C) 2014 Lemote Corporation.
0007  *   written by Huacai Chen <chenhc@lemote.com>
0008  *
0009  * based on arch/mips/cavium-octeon/cpu.c
0010  * Copyright (C) 2009 Wind River Systems,
0011  *   written by Ralf Baechle <ralf@linux-mips.org>
0012  */
0013 #include <linux/init.h>
0014 #include <linux/sched.h>
0015 #include <linux/notifier.h>
0016 #include <linux/ptrace.h>
0017 #include <linux/uaccess.h>
0018 #include <linux/sched/signal.h>
0019 
0020 #include <asm/fpu.h>
0021 #include <asm/cop2.h>
0022 #include <asm/inst.h>
0023 #include <asm/branch.h>
0024 #include <asm/current.h>
0025 #include <asm/mipsregs.h>
0026 #include <asm/unaligned-emul.h>
0027 
0028 static int loongson_cu2_call(struct notifier_block *nfb, unsigned long action,
0029     void *data)
0030 {
0031     unsigned int res, fpu_owned;
0032     unsigned long ra, value, value_next;
0033     union mips_instruction insn;
0034     int fr = !test_thread_flag(TIF_32BIT_FPREGS);
0035     struct pt_regs *regs = (struct pt_regs *)data;
0036     void __user *addr = (void __user *)regs->cp0_badvaddr;
0037     unsigned int __user *pc = (unsigned int __user *)exception_epc(regs);
0038 
0039     ra = regs->regs[31];
0040     __get_user(insn.word, pc);
0041 
0042     switch (action) {
0043     case CU2_EXCEPTION:
0044         preempt_disable();
0045         fpu_owned = __is_fpu_owner();
0046         if (!fr)
0047             set_c0_status(ST0_CU1 | ST0_CU2);
0048         else
0049             set_c0_status(ST0_CU1 | ST0_CU2 | ST0_FR);
0050         enable_fpu_hazard();
0051         KSTK_STATUS(current) |= (ST0_CU1 | ST0_CU2);
0052         if (fr)
0053             KSTK_STATUS(current) |= ST0_FR;
0054         else
0055             KSTK_STATUS(current) &= ~ST0_FR;
0056         /* If FPU is owned, we needn't init or restore fp */
0057         if (!fpu_owned) {
0058             set_thread_flag(TIF_USEDFPU);
0059             init_fp_ctx(current);
0060             _restore_fp(current);
0061         }
0062         preempt_enable();
0063 
0064         return NOTIFY_STOP; /* Don't call default notifier */
0065 
0066     case CU2_LWC2_OP:
0067         if (insn.loongson3_lswc2_format.ls == 0)
0068             goto sigbus;
0069 
0070         if (insn.loongson3_lswc2_format.fr == 0) {  /* gslq */
0071             if (!access_ok(addr, 16))
0072                 goto sigbus;
0073 
0074             LoadDW(addr, value, res);
0075             if (res)
0076                 goto fault;
0077 
0078             LoadDW(addr + 8, value_next, res);
0079             if (res)
0080                 goto fault;
0081 
0082             regs->regs[insn.loongson3_lswc2_format.rt] = value;
0083             regs->regs[insn.loongson3_lswc2_format.rq] = value_next;
0084             compute_return_epc(regs);
0085         } else {                    /* gslqc1 */
0086             if (!access_ok(addr, 16))
0087                 goto sigbus;
0088 
0089             lose_fpu(1);
0090             LoadDW(addr, value, res);
0091             if (res)
0092                 goto fault;
0093 
0094             LoadDW(addr + 8, value_next, res);
0095             if (res)
0096                 goto fault;
0097 
0098             set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0, value);
0099             set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0, value_next);
0100             compute_return_epc(regs);
0101             own_fpu(1);
0102         }
0103         return NOTIFY_STOP; /* Don't call default notifier */
0104 
0105     case CU2_SWC2_OP:
0106         if (insn.loongson3_lswc2_format.ls == 0)
0107             goto sigbus;
0108 
0109         if (insn.loongson3_lswc2_format.fr == 0) {  /* gssq */
0110             if (!access_ok(addr, 16))
0111                 goto sigbus;
0112 
0113             /* write upper 8 bytes first */
0114             value_next = regs->regs[insn.loongson3_lswc2_format.rq];
0115 
0116             StoreDW(addr + 8, value_next, res);
0117             if (res)
0118                 goto fault;
0119             value = regs->regs[insn.loongson3_lswc2_format.rt];
0120 
0121             StoreDW(addr, value, res);
0122             if (res)
0123                 goto fault;
0124 
0125             compute_return_epc(regs);
0126         } else {                    /* gssqc1 */
0127             if (!access_ok(addr, 16))
0128                 goto sigbus;
0129 
0130             lose_fpu(1);
0131             value_next = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0);
0132 
0133             StoreDW(addr + 8, value_next, res);
0134             if (res)
0135                 goto fault;
0136 
0137             value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0);
0138 
0139             StoreDW(addr, value, res);
0140             if (res)
0141                 goto fault;
0142 
0143             compute_return_epc(regs);
0144             own_fpu(1);
0145         }
0146         return NOTIFY_STOP; /* Don't call default notifier */
0147 
0148     case CU2_LDC2_OP:
0149         switch (insn.loongson3_lsdc2_format.opcode1) {
0150         /*
0151          * Loongson-3 overridden ldc2 instructions.
0152          * opcode1              instruction
0153          *   0x1          gslhx: load 2 bytes to GPR
0154          *   0x2          gslwx: load 4 bytes to GPR
0155          *   0x3          gsldx: load 8 bytes to GPR
0156          *   0x6      gslwxc1: load 4 bytes to FPR
0157          *   0x7      gsldxc1: load 8 bytes to FPR
0158          */
0159         case 0x1:
0160             if (!access_ok(addr, 2))
0161                 goto sigbus;
0162 
0163             LoadHW(addr, value, res);
0164             if (res)
0165                 goto fault;
0166 
0167             compute_return_epc(regs);
0168             regs->regs[insn.loongson3_lsdc2_format.rt] = value;
0169             break;
0170         case 0x2:
0171             if (!access_ok(addr, 4))
0172                 goto sigbus;
0173 
0174             LoadW(addr, value, res);
0175             if (res)
0176                 goto fault;
0177 
0178             compute_return_epc(regs);
0179             regs->regs[insn.loongson3_lsdc2_format.rt] = value;
0180             break;
0181         case 0x3:
0182             if (!access_ok(addr, 8))
0183                 goto sigbus;
0184 
0185             LoadDW(addr, value, res);
0186             if (res)
0187                 goto fault;
0188 
0189             compute_return_epc(regs);
0190             regs->regs[insn.loongson3_lsdc2_format.rt] = value;
0191             break;
0192         case 0x6:
0193             die_if_kernel("Unaligned FP access in kernel code", regs);
0194             BUG_ON(!used_math());
0195             if (!access_ok(addr, 4))
0196                 goto sigbus;
0197 
0198             lose_fpu(1);
0199             LoadW(addr, value, res);
0200             if (res)
0201                 goto fault;
0202 
0203             set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value);
0204             compute_return_epc(regs);
0205             own_fpu(1);
0206 
0207             break;
0208         case 0x7:
0209             die_if_kernel("Unaligned FP access in kernel code", regs);
0210             BUG_ON(!used_math());
0211             if (!access_ok(addr, 8))
0212                 goto sigbus;
0213 
0214             lose_fpu(1);
0215             LoadDW(addr, value, res);
0216             if (res)
0217                 goto fault;
0218 
0219             set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value);
0220             compute_return_epc(regs);
0221             own_fpu(1);
0222             break;
0223 
0224         }
0225         return NOTIFY_STOP; /* Don't call default notifier */
0226 
0227     case CU2_SDC2_OP:
0228         switch (insn.loongson3_lsdc2_format.opcode1) {
0229         /*
0230          * Loongson-3 overridden sdc2 instructions.
0231          * opcode1              instruction
0232          *   0x1          gsshx: store 2 bytes from GPR
0233          *   0x2          gsswx: store 4 bytes from GPR
0234          *   0x3          gssdx: store 8 bytes from GPR
0235          *   0x6          gsswxc1: store 4 bytes from FPR
0236          *   0x7          gssdxc1: store 8 bytes from FPR
0237          */
0238         case 0x1:
0239             if (!access_ok(addr, 2))
0240                 goto sigbus;
0241 
0242             compute_return_epc(regs);
0243             value = regs->regs[insn.loongson3_lsdc2_format.rt];
0244 
0245             StoreHW(addr, value, res);
0246             if (res)
0247                 goto fault;
0248 
0249             break;
0250         case 0x2:
0251             if (!access_ok(addr, 4))
0252                 goto sigbus;
0253 
0254             compute_return_epc(regs);
0255             value = regs->regs[insn.loongson3_lsdc2_format.rt];
0256 
0257             StoreW(addr, value, res);
0258             if (res)
0259                 goto fault;
0260 
0261             break;
0262         case 0x3:
0263             if (!access_ok(addr, 8))
0264                 goto sigbus;
0265 
0266             compute_return_epc(regs);
0267             value = regs->regs[insn.loongson3_lsdc2_format.rt];
0268 
0269             StoreDW(addr, value, res);
0270             if (res)
0271                 goto fault;
0272 
0273             break;
0274 
0275         case 0x6:
0276             die_if_kernel("Unaligned FP access in kernel code", regs);
0277             BUG_ON(!used_math());
0278 
0279             if (!access_ok(addr, 4))
0280                 goto sigbus;
0281 
0282             lose_fpu(1);
0283             value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0);
0284 
0285             StoreW(addr, value, res);
0286             if (res)
0287                 goto fault;
0288 
0289             compute_return_epc(regs);
0290             own_fpu(1);
0291 
0292             break;
0293         case 0x7:
0294             die_if_kernel("Unaligned FP access in kernel code", regs);
0295             BUG_ON(!used_math());
0296 
0297             if (!access_ok(addr, 8))
0298                 goto sigbus;
0299 
0300             lose_fpu(1);
0301             value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0);
0302 
0303             StoreDW(addr, value, res);
0304             if (res)
0305                 goto fault;
0306 
0307             compute_return_epc(regs);
0308             own_fpu(1);
0309 
0310             break;
0311         }
0312         return NOTIFY_STOP; /* Don't call default notifier */
0313     }
0314 
0315     return NOTIFY_OK;       /* Let default notifier send signals */
0316 
0317 fault:
0318     /* roll back jump/branch */
0319     regs->regs[31] = ra;
0320     regs->cp0_epc = (unsigned long)pc;
0321     /* Did we have an exception handler installed? */
0322     if (fixup_exception(regs))
0323         return NOTIFY_STOP; /* Don't call default notifier */
0324 
0325     die_if_kernel("Unhandled kernel unaligned access", regs);
0326     force_sig(SIGSEGV);
0327 
0328     return NOTIFY_STOP; /* Don't call default notifier */
0329 
0330 sigbus:
0331     die_if_kernel("Unhandled kernel unaligned access", regs);
0332     force_sig(SIGBUS);
0333 
0334     return NOTIFY_STOP; /* Don't call default notifier */
0335 }
0336 
0337 static int __init loongson_cu2_setup(void)
0338 {
0339     return cu2_notifier(loongson_cu2_call, 0);
0340 }
0341 early_initcall(loongson_cu2_setup);