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
loongson_cu2_call(struct notifier_block * nfb,unsigned long action,void * data)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
loongson_cu2_setup(void)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