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
28static 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[insn.loongson3_lswc2_format.rt], 0, value);
99			set_fpr64(&current->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(&current->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(&current->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(&current->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(&current->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(&current->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(&current->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
317fault:
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
330sigbus:
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
337static int __init loongson_cu2_setup(void)
338{
339	return cu2_notifier(loongson_cu2_call, 0);
340}
341early_initcall(loongson_cu2_setup);
342