xref: /linux/arch/loongarch/kernel/unaligned.c (revision cdd5b5a9761fd66d17586e4f4ba6588c70e640ea)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Handle unaligned accesses by emulation.
4  *
5  * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
6  *
7  * Derived from MIPS:
8  * Copyright (C) 1996, 1998, 1999, 2002 by Ralf Baechle
9  * Copyright (C) 1999 Silicon Graphics, Inc.
10  * Copyright (C) 2014 Imagination Technologies Ltd.
11  */
12 #include <linux/mm.h>
13 #include <linux/sched.h>
14 #include <linux/signal.h>
15 #include <linux/debugfs.h>
16 #include <linux/perf_event.h>
17 
18 #include <asm/asm.h>
19 #include <asm/branch.h>
20 #include <asm/fpu.h>
21 #include <asm/inst.h>
22 
23 #include "access-helper.h"
24 
25 #ifdef CONFIG_DEBUG_FS
26 static u32 unaligned_instructions_user;
27 static u32 unaligned_instructions_kernel;
28 #endif
29 
read_fpr(unsigned int idx)30 static inline unsigned long read_fpr(unsigned int idx)
31 {
32 #define READ_FPR(idx, __value)		\
33 	__asm__ __volatile__("movfr2gr.d %0, $f"#idx"\n\t" : "=r"(__value));
34 
35 	unsigned long __value;
36 
37 	switch (idx) {
38 	case 0:
39 		READ_FPR(0, __value);
40 		break;
41 	case 1:
42 		READ_FPR(1, __value);
43 		break;
44 	case 2:
45 		READ_FPR(2, __value);
46 		break;
47 	case 3:
48 		READ_FPR(3, __value);
49 		break;
50 	case 4:
51 		READ_FPR(4, __value);
52 		break;
53 	case 5:
54 		READ_FPR(5, __value);
55 		break;
56 	case 6:
57 		READ_FPR(6, __value);
58 		break;
59 	case 7:
60 		READ_FPR(7, __value);
61 		break;
62 	case 8:
63 		READ_FPR(8, __value);
64 		break;
65 	case 9:
66 		READ_FPR(9, __value);
67 		break;
68 	case 10:
69 		READ_FPR(10, __value);
70 		break;
71 	case 11:
72 		READ_FPR(11, __value);
73 		break;
74 	case 12:
75 		READ_FPR(12, __value);
76 		break;
77 	case 13:
78 		READ_FPR(13, __value);
79 		break;
80 	case 14:
81 		READ_FPR(14, __value);
82 		break;
83 	case 15:
84 		READ_FPR(15, __value);
85 		break;
86 	case 16:
87 		READ_FPR(16, __value);
88 		break;
89 	case 17:
90 		READ_FPR(17, __value);
91 		break;
92 	case 18:
93 		READ_FPR(18, __value);
94 		break;
95 	case 19:
96 		READ_FPR(19, __value);
97 		break;
98 	case 20:
99 		READ_FPR(20, __value);
100 		break;
101 	case 21:
102 		READ_FPR(21, __value);
103 		break;
104 	case 22:
105 		READ_FPR(22, __value);
106 		break;
107 	case 23:
108 		READ_FPR(23, __value);
109 		break;
110 	case 24:
111 		READ_FPR(24, __value);
112 		break;
113 	case 25:
114 		READ_FPR(25, __value);
115 		break;
116 	case 26:
117 		READ_FPR(26, __value);
118 		break;
119 	case 27:
120 		READ_FPR(27, __value);
121 		break;
122 	case 28:
123 		READ_FPR(28, __value);
124 		break;
125 	case 29:
126 		READ_FPR(29, __value);
127 		break;
128 	case 30:
129 		READ_FPR(30, __value);
130 		break;
131 	case 31:
132 		READ_FPR(31, __value);
133 		break;
134 	default:
135 		panic("unexpected idx '%d'", idx);
136 	}
137 #undef READ_FPR
138 	return __value;
139 }
140 
write_fpr(unsigned int idx,unsigned long value)141 static inline void write_fpr(unsigned int idx, unsigned long value)
142 {
143 #define WRITE_FPR(idx, value)		\
144 	__asm__ __volatile__("movgr2fr.d $f"#idx", %0\n\t" :: "r"(value));
145 
146 	switch (idx) {
147 	case 0:
148 		WRITE_FPR(0, value);
149 		break;
150 	case 1:
151 		WRITE_FPR(1, value);
152 		break;
153 	case 2:
154 		WRITE_FPR(2, value);
155 		break;
156 	case 3:
157 		WRITE_FPR(3, value);
158 		break;
159 	case 4:
160 		WRITE_FPR(4, value);
161 		break;
162 	case 5:
163 		WRITE_FPR(5, value);
164 		break;
165 	case 6:
166 		WRITE_FPR(6, value);
167 		break;
168 	case 7:
169 		WRITE_FPR(7, value);
170 		break;
171 	case 8:
172 		WRITE_FPR(8, value);
173 		break;
174 	case 9:
175 		WRITE_FPR(9, value);
176 		break;
177 	case 10:
178 		WRITE_FPR(10, value);
179 		break;
180 	case 11:
181 		WRITE_FPR(11, value);
182 		break;
183 	case 12:
184 		WRITE_FPR(12, value);
185 		break;
186 	case 13:
187 		WRITE_FPR(13, value);
188 		break;
189 	case 14:
190 		WRITE_FPR(14, value);
191 		break;
192 	case 15:
193 		WRITE_FPR(15, value);
194 		break;
195 	case 16:
196 		WRITE_FPR(16, value);
197 		break;
198 	case 17:
199 		WRITE_FPR(17, value);
200 		break;
201 	case 18:
202 		WRITE_FPR(18, value);
203 		break;
204 	case 19:
205 		WRITE_FPR(19, value);
206 		break;
207 	case 20:
208 		WRITE_FPR(20, value);
209 		break;
210 	case 21:
211 		WRITE_FPR(21, value);
212 		break;
213 	case 22:
214 		WRITE_FPR(22, value);
215 		break;
216 	case 23:
217 		WRITE_FPR(23, value);
218 		break;
219 	case 24:
220 		WRITE_FPR(24, value);
221 		break;
222 	case 25:
223 		WRITE_FPR(25, value);
224 		break;
225 	case 26:
226 		WRITE_FPR(26, value);
227 		break;
228 	case 27:
229 		WRITE_FPR(27, value);
230 		break;
231 	case 28:
232 		WRITE_FPR(28, value);
233 		break;
234 	case 29:
235 		WRITE_FPR(29, value);
236 		break;
237 	case 30:
238 		WRITE_FPR(30, value);
239 		break;
240 	case 31:
241 		WRITE_FPR(31, value);
242 		break;
243 	default:
244 		panic("unexpected idx '%d'", idx);
245 	}
246 #undef WRITE_FPR
247 }
248 
emulate_load_store_insn(struct pt_regs * regs,void __user * addr,unsigned int * pc)249 void emulate_load_store_insn(struct pt_regs *regs, void __user *addr, unsigned int *pc)
250 {
251 	bool fp = false;
252 	bool sign, write;
253 	bool user = user_mode(regs);
254 	unsigned int res, size = 0;
255 	unsigned long value = 0;
256 	union loongarch_instruction insn;
257 
258 	perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, 0);
259 
260 	__get_inst(&insn.word, pc, user);
261 
262 	switch (insn.reg2i12_format.opcode) {
263 	case ldh_op:
264 		size = 2;
265 		sign = true;
266 		write = false;
267 		break;
268 	case ldhu_op:
269 		size = 2;
270 		sign = false;
271 		write = false;
272 		break;
273 	case sth_op:
274 		size = 2;
275 		sign = true;
276 		write = true;
277 		break;
278 	case ldw_op:
279 		size = 4;
280 		sign = true;
281 		write = false;
282 		break;
283 	case ldwu_op:
284 		size = 4;
285 		sign = false;
286 		write = false;
287 		break;
288 	case stw_op:
289 		size = 4;
290 		sign = true;
291 		write = true;
292 		break;
293 	case ldd_op:
294 		size = 8;
295 		sign = true;
296 		write = false;
297 		break;
298 	case std_op:
299 		size = 8;
300 		sign = true;
301 		write = true;
302 		break;
303 	case flds_op:
304 		size = 4;
305 		fp = true;
306 		sign = true;
307 		write = false;
308 		break;
309 	case fsts_op:
310 		size = 4;
311 		fp = true;
312 		sign = true;
313 		write = true;
314 		break;
315 	case fldd_op:
316 		size = 8;
317 		fp = true;
318 		sign = true;
319 		write = false;
320 		break;
321 	case fstd_op:
322 		size = 8;
323 		fp = true;
324 		sign = true;
325 		write = true;
326 		break;
327 	}
328 
329 	switch (insn.reg2i14_format.opcode) {
330 	case ldptrw_op:
331 		size = 4;
332 		sign = true;
333 		write = false;
334 		break;
335 	case stptrw_op:
336 		size = 4;
337 		sign = true;
338 		write = true;
339 		break;
340 	case ldptrd_op:
341 		size = 8;
342 		sign = true;
343 		write = false;
344 		break;
345 	case stptrd_op:
346 		size = 8;
347 		sign = true;
348 		write = true;
349 		break;
350 	}
351 
352 	switch (insn.reg3_format.opcode) {
353 	case ldxh_op:
354 		size = 2;
355 		sign = true;
356 		write = false;
357 		break;
358 	case ldxhu_op:
359 		size = 2;
360 		sign = false;
361 		write = false;
362 		break;
363 	case stxh_op:
364 		size = 2;
365 		sign = true;
366 		write = true;
367 		break;
368 	case ldxw_op:
369 		size = 4;
370 		sign = true;
371 		write = false;
372 		break;
373 	case ldxwu_op:
374 		size = 4;
375 		sign = false;
376 		write = false;
377 		break;
378 	case stxw_op:
379 		size = 4;
380 		sign = true;
381 		write = true;
382 		break;
383 	case ldxd_op:
384 		size = 8;
385 		sign = true;
386 		write = false;
387 		break;
388 	case stxd_op:
389 		size = 8;
390 		sign = true;
391 		write = true;
392 		break;
393 	case fldxs_op:
394 		size = 4;
395 		fp = true;
396 		sign = true;
397 		write = false;
398 		break;
399 	case fstxs_op:
400 		size = 4;
401 		fp = true;
402 		sign = true;
403 		write = true;
404 		break;
405 	case fldxd_op:
406 		size = 8;
407 		fp = true;
408 		sign = true;
409 		write = false;
410 		break;
411 	case fstxd_op:
412 		size = 8;
413 		fp = true;
414 		sign = true;
415 		write = true;
416 		break;
417 	}
418 
419 	if (!size)
420 		goto sigbus;
421 	if (user && !access_ok(addr, size))
422 		goto sigbus;
423 
424 	if (!write) {
425 		res = unaligned_read(addr, &value, size, sign);
426 		if (res)
427 			goto fault;
428 
429 		/* Rd is the same field in any formats */
430 		if (!fp)
431 			regs->regs[insn.reg3_format.rd] = value;
432 		else {
433 			if (is_fpu_owner())
434 				write_fpr(insn.reg3_format.rd, value);
435 			else
436 				set_fpr64(&current->thread.fpu.fpr[insn.reg3_format.rd], 0, value);
437 		}
438 	} else {
439 		/* Rd is the same field in any formats */
440 		if (!fp)
441 			value = regs->regs[insn.reg3_format.rd];
442 		else {
443 			if (is_fpu_owner())
444 				value = read_fpr(insn.reg3_format.rd);
445 			else
446 				value = get_fpr64(&current->thread.fpu.fpr[insn.reg3_format.rd], 0);
447 		}
448 
449 		res = unaligned_write(addr, value, size);
450 		if (res)
451 			goto fault;
452 	}
453 
454 #ifdef CONFIG_DEBUG_FS
455 	if (user)
456 		unaligned_instructions_user++;
457 	else
458 		unaligned_instructions_kernel++;
459 #endif
460 
461 	compute_return_era(regs);
462 
463 	return;
464 
465 fault:
466 	/* Did we have an exception handler installed? */
467 	if (fixup_exception(regs))
468 		return;
469 
470 	die_if_kernel("Unhandled kernel unaligned access", regs);
471 	force_sig(SIGSEGV);
472 
473 	return;
474 
475 sigbus:
476 	die_if_kernel("Unhandled kernel unaligned access", regs);
477 	force_sig(SIGBUS);
478 
479 	return;
480 }
481 
482 #ifdef CONFIG_DEBUG_FS
debugfs_unaligned(void)483 static int __init debugfs_unaligned(void)
484 {
485 	struct dentry *d;
486 
487 	d = debugfs_create_dir("loongarch", NULL);
488 
489 	debugfs_create_u32("unaligned_instructions_user",
490 				S_IRUGO, d, &unaligned_instructions_user);
491 	debugfs_create_u32("unaligned_instructions_kernel",
492 				S_IRUGO, d, &unaligned_instructions_kernel);
493 
494 	return 0;
495 }
496 arch_initcall(debugfs_unaligned);
497 #endif
498