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(¤t->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0, value); 99 set_fpr64(¤t->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0, value_next); 100 compute_return_epc(regs); 101 own_fpu(1); 102 } 103 return NOTIFY_STOP; /* Don't call default notifier */ 104 105 case CU2_SWC2_OP: 106 if (insn.loongson3_lswc2_format.ls == 0) 107 goto sigbus; 108 109 if (insn.loongson3_lswc2_format.fr == 0) { /* gssq */ 110 if (!access_ok(addr, 16)) 111 goto sigbus; 112 113 /* write upper 8 bytes first */ 114 value_next = regs->regs[insn.loongson3_lswc2_format.rq]; 115 116 StoreDW(addr + 8, value_next, res); 117 if (res) 118 goto fault; 119 value = regs->regs[insn.loongson3_lswc2_format.rt]; 120 121 StoreDW(addr, value, res); 122 if (res) 123 goto fault; 124 125 compute_return_epc(regs); 126 } else { /* gssqc1 */ 127 if (!access_ok(addr, 16)) 128 goto sigbus; 129 130 lose_fpu(1); 131 value_next = get_fpr64(¤t->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0); 132 133 StoreDW(addr + 8, value_next, res); 134 if (res) 135 goto fault; 136 137 value = get_fpr64(¤t->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0); 138 139 StoreDW(addr, value, res); 140 if (res) 141 goto fault; 142 143 compute_return_epc(regs); 144 own_fpu(1); 145 } 146 return NOTIFY_STOP; /* Don't call default notifier */ 147 148 case CU2_LDC2_OP: 149 switch (insn.loongson3_lsdc2_format.opcode1) { 150 /* 151 * Loongson-3 overridden ldc2 instructions. 152 * opcode1 instruction 153 * 0x1 gslhx: load 2 bytes to GPR 154 * 0x2 gslwx: load 4 bytes to GPR 155 * 0x3 gsldx: load 8 bytes to GPR 156 * 0x6 gslwxc1: load 4 bytes to FPR 157 * 0x7 gsldxc1: load 8 bytes to FPR 158 */ 159 case 0x1: 160 if (!access_ok(addr, 2)) 161 goto sigbus; 162 163 LoadHW(addr, value, res); 164 if (res) 165 goto fault; 166 167 compute_return_epc(regs); 168 regs->regs[insn.loongson3_lsdc2_format.rt] = value; 169 break; 170 case 0x2: 171 if (!access_ok(addr, 4)) 172 goto sigbus; 173 174 LoadW(addr, value, res); 175 if (res) 176 goto fault; 177 178 compute_return_epc(regs); 179 regs->regs[insn.loongson3_lsdc2_format.rt] = value; 180 break; 181 case 0x3: 182 if (!access_ok(addr, 8)) 183 goto sigbus; 184 185 LoadDW(addr, value, res); 186 if (res) 187 goto fault; 188 189 compute_return_epc(regs); 190 regs->regs[insn.loongson3_lsdc2_format.rt] = value; 191 break; 192 case 0x6: 193 die_if_kernel("Unaligned FP access in kernel code", regs); 194 BUG_ON(!used_math()); 195 if (!access_ok(addr, 4)) 196 goto sigbus; 197 198 lose_fpu(1); 199 LoadW(addr, value, res); 200 if (res) 201 goto fault; 202 203 set_fpr64(¤t->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value); 204 compute_return_epc(regs); 205 own_fpu(1); 206 207 break; 208 case 0x7: 209 die_if_kernel("Unaligned FP access in kernel code", regs); 210 BUG_ON(!used_math()); 211 if (!access_ok(addr, 8)) 212 goto sigbus; 213 214 lose_fpu(1); 215 LoadDW(addr, value, res); 216 if (res) 217 goto fault; 218 219 set_fpr64(¤t->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value); 220 compute_return_epc(regs); 221 own_fpu(1); 222 break; 223 224 } 225 return NOTIFY_STOP; /* Don't call default notifier */ 226 227 case CU2_SDC2_OP: 228 switch (insn.loongson3_lsdc2_format.opcode1) { 229 /* 230 * Loongson-3 overridden sdc2 instructions. 231 * opcode1 instruction 232 * 0x1 gsshx: store 2 bytes from GPR 233 * 0x2 gsswx: store 4 bytes from GPR 234 * 0x3 gssdx: store 8 bytes from GPR 235 * 0x6 gsswxc1: store 4 bytes from FPR 236 * 0x7 gssdxc1: store 8 bytes from FPR 237 */ 238 case 0x1: 239 if (!access_ok(addr, 2)) 240 goto sigbus; 241 242 compute_return_epc(regs); 243 value = regs->regs[insn.loongson3_lsdc2_format.rt]; 244 245 StoreHW(addr, value, res); 246 if (res) 247 goto fault; 248 249 break; 250 case 0x2: 251 if (!access_ok(addr, 4)) 252 goto sigbus; 253 254 compute_return_epc(regs); 255 value = regs->regs[insn.loongson3_lsdc2_format.rt]; 256 257 StoreW(addr, value, res); 258 if (res) 259 goto fault; 260 261 break; 262 case 0x3: 263 if (!access_ok(addr, 8)) 264 goto sigbus; 265 266 compute_return_epc(regs); 267 value = regs->regs[insn.loongson3_lsdc2_format.rt]; 268 269 StoreDW(addr, value, res); 270 if (res) 271 goto fault; 272 273 break; 274 275 case 0x6: 276 die_if_kernel("Unaligned FP access in kernel code", regs); 277 BUG_ON(!used_math()); 278 279 if (!access_ok(addr, 4)) 280 goto sigbus; 281 282 lose_fpu(1); 283 value = get_fpr64(¤t->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0); 284 285 StoreW(addr, value, res); 286 if (res) 287 goto fault; 288 289 compute_return_epc(regs); 290 own_fpu(1); 291 292 break; 293 case 0x7: 294 die_if_kernel("Unaligned FP access in kernel code", regs); 295 BUG_ON(!used_math()); 296 297 if (!access_ok(addr, 8)) 298 goto sigbus; 299 300 lose_fpu(1); 301 value = get_fpr64(¤t->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0); 302 303 StoreDW(addr, value, res); 304 if (res) 305 goto fault; 306 307 compute_return_epc(regs); 308 own_fpu(1); 309 310 break; 311 } 312 return NOTIFY_STOP; /* Don't call default notifier */ 313 } 314 315 return NOTIFY_OK; /* Let default notifier send signals */ 316 317 fault: 318 /* roll back jump/branch */ 319 regs->regs[31] = ra; 320 regs->cp0_epc = (unsigned long)pc; 321 /* Did we have an exception handler installed? */ 322 if (fixup_exception(regs)) 323 return NOTIFY_STOP; /* Don't call default notifier */ 324 325 die_if_kernel("Unhandled kernel unaligned access", regs); 326 force_sig(SIGSEGV); 327 328 return NOTIFY_STOP; /* Don't call default notifier */ 329 330 sigbus: 331 die_if_kernel("Unhandled kernel unaligned access", regs); 332 force_sig(SIGBUS); 333 334 return NOTIFY_STOP; /* Don't call default notifier */ 335 } 336 337 static int __init loongson_cu2_setup(void) 338 { 339 return cu2_notifier(loongson_cu2_call, 0); 340 } 341 early_initcall(loongson_cu2_setup); 342
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.