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 | |