1 /* 2 * This file is subject to the terms and conditions of the GNU General Public 3 * License. See the file "COPYING" in the main directory of this archive 4 * for more details. 5 * 6 * Copyright (C) 2014 Lemote Corporation. 7 * written by Huacai Chen <chenhc@lemote.com> 8 * 9 * based on arch/mips/cavium-octeon/cpu.c 10 * Copyright (C) 2009 Wind River Systems, 11 * written by Ralf Baechle <ralf@linux-mips.org> 12 */ 13 #include <linux/init.h> 14 #include <linux/sched.h> 15 #include <linux/notifier.h> 16 #include <linux/ptrace.h> 17 #include <linux/uaccess.h> 18 #include <linux/sched/signal.h> 19 20 #include <asm/fpu.h> 21 #include <asm/cop2.h> 22 #include <asm/inst.h> 23 #include <asm/branch.h> 24 #include <asm/current.h> 25 #include <asm/mipsregs.h> 26 #include <asm/unaligned-emul.h> 27 28 static int loongson_cu2_call(struct notifier_block *nfb, unsigned long action, 29 void *data) 30 { 31 unsigned int res, fpu_owned; 32 unsigned long ra, value, value_next; 33 union mips_instruction insn; 34 int fr = !test_thread_flag(TIF_32BIT_FPREGS); 35 struct pt_regs *regs = (struct pt_regs *)data; 36 void __user *addr = (void __user *)regs->cp0_badvaddr; 37 unsigned int __user *pc = (unsigned int __user *)exception_epc(regs); 38 39 ra = regs->regs[31]; 40 __get_user(insn.word, pc); 41 42 switch (action) { 43 case CU2_EXCEPTION: 44 preempt_disable(); 45 fpu_owned = __is_fpu_owner(); 46 if (!fr) 47 set_c0_status(ST0_CU1 | ST0_CU2); 48 else 49 set_c0_status(ST0_CU1 | ST0_CU2 | ST0_FR); 50 enable_fpu_hazard(); 51 KSTK_STATUS(current) |= (ST0_CU1 | ST0_CU2); 52 if (fr) 53 KSTK_STATUS(current) |= ST0_FR; 54 else 55 KSTK_STATUS(current) &= ~ST0_FR; 56 /* If FPU is owned, we needn't init or restore fp */ 57 if (!fpu_owned) { 58 set_thread_flag(TIF_USEDFPU); 59 init_fp_ctx(current); 60 _restore_fp(current); 61 } 62 preempt_enable(); 63 64 return NOTIFY_STOP; /* Don't call default notifier */ 65 66 case CU2_LWC2_OP: 67 if (insn.loongson3_lswc2_format.ls == 0) 68 goto sigbus; 69 70 if (insn.loongson3_lswc2_format.fr == 0) { /* gslq */ 71 if (!access_ok(addr, 16)) 72 goto sigbus; 73 74 LoadDW(addr, value, res); 75 if (res) 76 goto fault; 77 78 LoadDW(addr + 8, value_next, res); 79 if (res) 80 goto fault; 81 82 regs->regs[insn.loongson3_lswc2_format.rt] = value; 83 regs->regs[insn.loongson3_lswc2_format.rq] = value_next; 84 compute_return_epc(regs); 85 } else { /* gslqc1 */ 86 if (!access_ok(addr, 16)) 87 goto sigbus; 88 89 lose_fpu(1); 90 LoadDW(addr, value, res); 91 if (res) 92 goto fault; 93 94 LoadDW(addr + 8, value_next, res); 95 if (res) 96 goto fault; 97 98 set_fpr64(current->thread.fpu.fpr, 99 insn.loongson3_lswc2_format.rt, value); 100 set_fpr64(current->thread.fpu.fpr, 101 insn.loongson3_lswc2_format.rq, value_next); 102 compute_return_epc(regs); 103 own_fpu(1); 104 } 105 return NOTIFY_STOP; /* Don't call default notifier */ 106 107 case CU2_SWC2_OP: 108 if (insn.loongson3_lswc2_format.ls == 0) 109 goto sigbus; 110 111 if (insn.loongson3_lswc2_format.fr == 0) { /* gssq */ 112 if (!access_ok(addr, 16)) 113 goto sigbus; 114 115 /* write upper 8 bytes first */ 116 value_next = regs->regs[insn.loongson3_lswc2_format.rq]; 117 118 StoreDW(addr + 8, value_next, res); 119 if (res) 120 goto fault; 121 value = regs->regs[insn.loongson3_lswc2_format.rt]; 122 123 StoreDW(addr, value, res); 124 if (res) 125 goto fault; 126 127 compute_return_epc(regs); 128 } else { /* gssqc1 */ 129 if (!access_ok(addr, 16)) 130 goto sigbus; 131 132 lose_fpu(1); 133 value_next = get_fpr64(current->thread.fpu.fpr, 134 insn.loongson3_lswc2_format.rq); 135 136 StoreDW(addr + 8, value_next, res); 137 if (res) 138 goto fault; 139 140 value = get_fpr64(current->thread.fpu.fpr, 141 insn.loongson3_lswc2_format.rt); 142 143 StoreDW(addr, value, res); 144 if (res) 145 goto fault; 146 147 compute_return_epc(regs); 148 own_fpu(1); 149 } 150 return NOTIFY_STOP; /* Don't call default notifier */ 151 152 case CU2_LDC2_OP: 153 switch (insn.loongson3_lsdc2_format.opcode1) { 154 /* 155 * Loongson-3 overridden ldc2 instructions. 156 * opcode1 instruction 157 * 0x1 gslhx: load 2 bytes to GPR 158 * 0x2 gslwx: load 4 bytes to GPR 159 * 0x3 gsldx: load 8 bytes to GPR 160 * 0x6 gslwxc1: load 4 bytes to FPR 161 * 0x7 gsldxc1: load 8 bytes to FPR 162 */ 163 case 0x1: 164 if (!access_ok(addr, 2)) 165 goto sigbus; 166 167 LoadHW(addr, value, res); 168 if (res) 169 goto fault; 170 171 compute_return_epc(regs); 172 regs->regs[insn.loongson3_lsdc2_format.rt] = value; 173 break; 174 case 0x2: 175 if (!access_ok(addr, 4)) 176 goto sigbus; 177 178 LoadW(addr, value, res); 179 if (res) 180 goto fault; 181 182 compute_return_epc(regs); 183 regs->regs[insn.loongson3_lsdc2_format.rt] = value; 184 break; 185 case 0x3: 186 if (!access_ok(addr, 8)) 187 goto sigbus; 188 189 LoadDW(addr, value, res); 190 if (res) 191 goto fault; 192 193 compute_return_epc(regs); 194 regs->regs[insn.loongson3_lsdc2_format.rt] = value; 195 break; 196 case 0x6: 197 die_if_kernel("Unaligned FP access in kernel code", regs); 198 BUG_ON(!used_math()); 199 if (!access_ok(addr, 4)) 200 goto sigbus; 201 202 lose_fpu(1); 203 LoadW(addr, value, res); 204 if (res) 205 goto fault; 206 207 set_fpr64(current->thread.fpu.fpr, 208 insn.loongson3_lsdc2_format.rt, value); 209 compute_return_epc(regs); 210 own_fpu(1); 211 212 break; 213 case 0x7: 214 die_if_kernel("Unaligned FP access in kernel code", regs); 215 BUG_ON(!used_math()); 216 if (!access_ok(addr, 8)) 217 goto sigbus; 218 219 lose_fpu(1); 220 LoadDW(addr, value, res); 221 if (res) 222 goto fault; 223 224 set_fpr64(current->thread.fpu.fpr, 225 insn.loongson3_lsdc2_format.rt, value); 226 compute_return_epc(regs); 227 own_fpu(1); 228 break; 229 230 } 231 return NOTIFY_STOP; /* Don't call default notifier */ 232 233 case CU2_SDC2_OP: 234 switch (insn.loongson3_lsdc2_format.opcode1) { 235 /* 236 * Loongson-3 overridden sdc2 instructions. 237 * opcode1 instruction 238 * 0x1 gsshx: store 2 bytes from GPR 239 * 0x2 gsswx: store 4 bytes from GPR 240 * 0x3 gssdx: store 8 bytes from GPR 241 * 0x6 gsswxc1: store 4 bytes from FPR 242 * 0x7 gssdxc1: store 8 bytes from FPR 243 */ 244 case 0x1: 245 if (!access_ok(addr, 2)) 246 goto sigbus; 247 248 compute_return_epc(regs); 249 value = regs->regs[insn.loongson3_lsdc2_format.rt]; 250 251 StoreHW(addr, value, res); 252 if (res) 253 goto fault; 254 255 break; 256 case 0x2: 257 if (!access_ok(addr, 4)) 258 goto sigbus; 259 260 compute_return_epc(regs); 261 value = regs->regs[insn.loongson3_lsdc2_format.rt]; 262 263 StoreW(addr, value, res); 264 if (res) 265 goto fault; 266 267 break; 268 case 0x3: 269 if (!access_ok(addr, 8)) 270 goto sigbus; 271 272 compute_return_epc(regs); 273 value = regs->regs[insn.loongson3_lsdc2_format.rt]; 274 275 StoreDW(addr, value, res); 276 if (res) 277 goto fault; 278 279 break; 280 281 case 0x6: 282 die_if_kernel("Unaligned FP access in kernel code", regs); 283 BUG_ON(!used_math()); 284 285 if (!access_ok(addr, 4)) 286 goto sigbus; 287 288 lose_fpu(1); 289 value = get_fpr64(current->thread.fpu.fpr, 290 insn.loongson3_lsdc2_format.rt); 291 292 StoreW(addr, value, res); 293 if (res) 294 goto fault; 295 296 compute_return_epc(regs); 297 own_fpu(1); 298 299 break; 300 case 0x7: 301 die_if_kernel("Unaligned FP access in kernel code", regs); 302 BUG_ON(!used_math()); 303 304 if (!access_ok(addr, 8)) 305 goto sigbus; 306 307 lose_fpu(1); 308 value = get_fpr64(current->thread.fpu.fpr, 309 insn.loongson3_lsdc2_format.rt); 310 311 StoreDW(addr, value, res); 312 if (res) 313 goto fault; 314 315 compute_return_epc(regs); 316 own_fpu(1); 317 318 break; 319 } 320 return NOTIFY_STOP; /* Don't call default notifier */ 321 } 322 323 return NOTIFY_OK; /* Let default notifier send signals */ 324 325 fault: 326 /* roll back jump/branch */ 327 regs->regs[31] = ra; 328 regs->cp0_epc = (unsigned long)pc; 329 /* Did we have an exception handler installed? */ 330 if (fixup_exception(regs)) 331 return NOTIFY_STOP; /* Don't call default notifier */ 332 333 die_if_kernel("Unhandled kernel unaligned access", regs); 334 force_sig(SIGSEGV); 335 336 return NOTIFY_STOP; /* Don't call default notifier */ 337 338 sigbus: 339 die_if_kernel("Unhandled kernel unaligned access", regs); 340 force_sig(SIGBUS); 341 342 return NOTIFY_STOP; /* Don't call default notifier */ 343 } 344 345 static int __init loongson_cu2_setup(void) 346 { 347 return cu2_notifier(loongson_cu2_call, 0); 348 } 349 early_initcall(loongson_cu2_setup); 350