0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
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
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;
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) {
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 {
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(¤t->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0, value);
0099 set_fpr64(¤t->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;
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) {
0110 if (!access_ok(addr, 16))
0111 goto sigbus;
0112
0113
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 {
0127 if (!access_ok(addr, 16))
0128 goto sigbus;
0129
0130 lose_fpu(1);
0131 value_next = get_fpr64(¤t->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(¤t->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;
0147
0148 case CU2_LDC2_OP:
0149 switch (insn.loongson3_lsdc2_format.opcode1) {
0150
0151
0152
0153
0154
0155
0156
0157
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(¤t->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(¤t->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;
0226
0227 case CU2_SDC2_OP:
0228 switch (insn.loongson3_lsdc2_format.opcode1) {
0229
0230
0231
0232
0233
0234
0235
0236
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(¤t->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(¤t->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;
0313 }
0314
0315 return NOTIFY_OK;
0316
0317 fault:
0318
0319 regs->regs[31] = ra;
0320 regs->cp0_epc = (unsigned long)pc;
0321
0322 if (fixup_exception(regs))
0323 return NOTIFY_STOP;
0324
0325 die_if_kernel("Unhandled kernel unaligned access", regs);
0326 force_sig(SIGSEGV);
0327
0328 return NOTIFY_STOP;
0329
0330 sigbus:
0331 die_if_kernel("Unhandled kernel unaligned access", regs);
0332 force_sig(SIGBUS);
0333
0334 return NOTIFY_STOP;
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);