xref: /netbsd-src/sys/arch/powerpc/powerpc/fix_unaligned.c (revision 6dbfdbfdddffe025bafdb50830f76dceaaee73ca)
1 /*	$NetBSD: fix_unaligned.c,v 1.2 2022/06/02 00:32:14 rin Exp $	*/
2 
3 /*
4  * Copyright (c) 2022 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Rin Okuyama.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * Routines to fix unaligned memory access for userland process.
34  *
35  * Intended mainly for PPC_IBM403 at the moment:
36  *
37  * - Fetch and decode insn; 403 does not have DSISR.
38  *
39  * - Only for integer insn; unaligned floating-point load/store are taken
40  *   care of by FPU emulator. (Support for FPU insn should be trivial.)
41  *
42  * Also note:
43  *
44  * - For invalid forms, behaviors are undefined and not documented in
45  *   processor manuals. Here, we mimic what described in
46  *   "AIX 7.2 Assembler language reference":
47  *
48  *   - For "u" variants, ra is not updated if ra == 0 (or rd for load).
49  *
50  *   - Fix for {l,st}mw is disabled by default.
51  */
52 
53 #include <sys/cdefs.h>
54 __KERNEL_RCSID(0, "$NetBSD: fix_unaligned.c,v 1.2 2022/06/02 00:32:14 rin Exp $");
55 
56 #include "opt_ddb.h"
57 #include "opt_ppcarch.h"
58 
59 #include <sys/param.h>
60 #include <sys/types.h>
61 #include <sys/evcnt.h>
62 #include <sys/siginfo.h>
63 #include <sys/systm.h>
64 
65 #include <powerpc/frame.h>
66 #include <powerpc/instr.h>
67 #include <powerpc/trap.h>
68 
69 #define	UA_EVCNT_ATTACH(name)						\
70     static struct evcnt unaligned_ev_##name =				\
71 	EVCNT_INITIALIZER(EVCNT_TYPE_TRAP, NULL, "unaligned", #name);	\
72 	EVCNT_ATTACH_STATIC(unaligned_ev_##name)
73 
74 #define	UA_EVCNT_INCR(name)	unaligned_ev_##name.ev_count++
75 
76 UA_EVCNT_ATTACH(lwz);
77 UA_EVCNT_ATTACH(lwzu);
78 UA_EVCNT_ATTACH(stw);
79 UA_EVCNT_ATTACH(stwu);
80 UA_EVCNT_ATTACH(lhz);
81 UA_EVCNT_ATTACH(lhzu);
82 UA_EVCNT_ATTACH(lha);
83 UA_EVCNT_ATTACH(lhau);
84 UA_EVCNT_ATTACH(sth);
85 UA_EVCNT_ATTACH(sthu);
86 
87 UA_EVCNT_ATTACH(lwzx);
88 UA_EVCNT_ATTACH(lwzux);
89 UA_EVCNT_ATTACH(stwx);
90 UA_EVCNT_ATTACH(stwux);
91 UA_EVCNT_ATTACH(lhzx);
92 UA_EVCNT_ATTACH(lhzux);
93 UA_EVCNT_ATTACH(lhax);
94 UA_EVCNT_ATTACH(lhaux);
95 UA_EVCNT_ATTACH(sthx);
96 UA_EVCNT_ATTACH(sthux);
97 UA_EVCNT_ATTACH(lwbrx);
98 UA_EVCNT_ATTACH(stwbrx);
99 UA_EVCNT_ATTACH(lhbrx);
100 UA_EVCNT_ATTACH(sthbrx);
101 
102 UA_EVCNT_ATTACH(lmw);
103 UA_EVCNT_ATTACH(stmw);
104 
105 UA_EVCNT_ATTACH(isi);
106 UA_EVCNT_ATTACH(dsi);
107 UA_EVCNT_ATTACH(unknown);
108 UA_EVCNT_ATTACH(invalid);
109 
110 #if 0
111 #define	UNALIGNED_DEBUG 1
112 #define	FIX_UNALIGNED_LSTMW 1
113 #endif
114 
115 #if defined(UNALIGNED_DEBUG)
116 int unaligned_debug = 1;
117 #elif defined(DEBUG)
118 int unaligned_debug = 0;
119 #endif
120 
121 #if defined(UNALIGNED_DEBUG) || defined(DEBUG)
122 #define DPRINTF(fmt, args...)						\
123     do {								\
124 	if (unaligned_debug)						\
125 		printf("%s: " fmt, __func__, ##args);			\
126     } while (0)
127 #else
128 #define	DPRINTF(fmt, args...)	__nothing
129 #endif
130 
131 #if defined(DDB) && (defined(UNALIGNED_DEBUG) || defined(DEBUG))
132 extern vaddr_t opc_disasm(vaddr_t, int);
133 #define	DISASM(tf, insn)						\
134     do {								\
135 	if (unaligned_debug)						\
136 		opc_disasm((tf)->tf_srr0, (insn)->i_int);		\
137     } while (0)
138 #else
139 #define	DISASM(tf, insn)	__nothing
140 #endif
141 
142 static bool emul_unaligned(struct trapframe *, ksiginfo_t *,
143     const union instr *);
144 static bool do_lst(struct trapframe *, const union instr *, int);
145 #ifdef FIX_UNALIGNED_LSTMW
146 static bool do_lstmw(struct trapframe *, const union instr *, int);
147 #endif
148 
149 bool
fix_unaligned(struct trapframe * tf,ksiginfo_t * ksi)150 fix_unaligned(struct trapframe *tf, ksiginfo_t *ksi)
151 {
152 	union instr insn;
153 	int ret;
154 
155 	KSI_INIT_TRAP(ksi);
156 
157 	ret = ufetch_32((uint32_t *)tf->tf_srr0, (uint32_t *)&insn.i_int);
158 	if (ret) {
159 		UA_EVCNT_INCR(isi);
160 		DPRINTF("EXC_ISI: ret: %d, srr0: 0x%08lx dear: 0x%08lx\n",
161 		    ret, tf->tf_srr0, tf->tf_dear);
162 		ksi->ksi_signo = SIGSEGV;
163 		ksi->ksi_trap = EXC_ISI;
164 		ksi->ksi_code = SEGV_MAPERR;
165 		ksi->ksi_addr = (void *)tf->tf_srr0;
166 		return true;
167 	}
168 
169 	if (emul_unaligned(tf, ksi, &insn))
170 		return true;
171 
172 	CTASSERT(sizeof(insn) == 4);	/* It was broken before... */
173 	tf->tf_srr0 += sizeof(insn);
174 	return false;
175 }
176 
177 #define	UAF_STORE	0
178 #define	UAF_LOAD	__BIT(0)
179 #define	UAF_HALF	__BIT(1)
180 #define	UAF_ALGEBRA	__BIT(2)
181 #define	UAF_REVERSE	__BIT(3)
182 #define	UAF_UPDATE	__BIT(4)
183 
184 static bool
emul_unaligned(struct trapframe * tf,ksiginfo_t * ksi,const union instr * insn)185 emul_unaligned(struct trapframe *tf, ksiginfo_t *ksi, const union instr *insn)
186 {
187 	int flags;
188 
189 	switch (insn->i_any.i_opcd) {
190 	case OPC_LWZ:
191 		UA_EVCNT_INCR(lwz);
192 		flags = UAF_LOAD;
193 		break;
194 
195 	case OPC_LWZU:
196 		UA_EVCNT_INCR(lwzu);
197 		flags = UAF_LOAD | UAF_UPDATE;
198 		break;
199 
200 	case OPC_STW:
201 		UA_EVCNT_INCR(stw);
202 		flags = UAF_STORE;
203 		break;
204 
205 	case OPC_STWU:
206 		UA_EVCNT_INCR(stwu);
207 		flags = UAF_STORE | UAF_UPDATE;
208 		break;
209 
210 	case OPC_LHZ:
211 		UA_EVCNT_INCR(lhz);
212 		flags = UAF_LOAD | UAF_HALF;
213 		break;
214 
215 	case OPC_LHZU:
216 		UA_EVCNT_INCR(lhzu);
217 		flags = UAF_LOAD | UAF_HALF | UAF_UPDATE;
218 		break;
219 
220 	case OPC_LHA:
221 		UA_EVCNT_INCR(lha);
222 		flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA;
223 		break;
224 
225 	case OPC_LHAU:
226 		UA_EVCNT_INCR(lhau);
227 		flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA | UAF_UPDATE;
228 		break;
229 
230 	case OPC_STH:
231 		UA_EVCNT_INCR(sth);
232 		flags = UAF_STORE | UAF_HALF;
233 		break;
234 
235 	case OPC_STHU:
236 		UA_EVCNT_INCR(sthu);
237 		flags = UAF_STORE | UAF_HALF | UAF_UPDATE;
238 		break;
239 
240 	case OPC_integer_31:
241 		switch (insn->i_x.i_xo) {
242 		case OPC31_LWZX:
243 			UA_EVCNT_INCR(lwzx);
244 			flags = UAF_LOAD;
245 			break;
246 
247 		case OPC31_LWZUX:
248 			UA_EVCNT_INCR(lwzux);
249 			flags = UAF_LOAD | UAF_UPDATE;
250 			break;
251 
252 		case OPC31_STWX:
253 			UA_EVCNT_INCR(stwx);
254 			flags = UAF_STORE;
255 			break;
256 
257 		case OPC31_STWUX:
258 			UA_EVCNT_INCR(stwux);
259 			flags = UAF_STORE | UAF_UPDATE;
260 			break;
261 
262 		case OPC31_LHZX:
263 			UA_EVCNT_INCR(lhzx);
264 			flags = UAF_LOAD | UAF_HALF;
265 			break;
266 
267 		case OPC31_LHZUX:
268 			UA_EVCNT_INCR(lhzux);
269 			flags = UAF_LOAD | UAF_HALF | UAF_UPDATE;
270 			break;
271 
272 		case OPC31_LHAX:
273 			UA_EVCNT_INCR(lhax);
274 			flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA;
275 			break;
276 
277 		case OPC31_LHAUX:
278 			UA_EVCNT_INCR(lhaux);
279 			flags = UAF_LOAD | UAF_HALF | UAF_ALGEBRA | UAF_UPDATE;
280 			break;
281 
282 		case OPC31_STHX:
283 			UA_EVCNT_INCR(sthx);
284 			flags = UAF_STORE | UAF_HALF;
285 			break;
286 
287 		case OPC31_STHUX:
288 			UA_EVCNT_INCR(sthux);
289 			flags = UAF_STORE | UAF_HALF | UAF_UPDATE;
290 			break;
291 
292 		case OPC31_LWBRX:
293 			UA_EVCNT_INCR(lwbrx);
294 			flags = UAF_LOAD | UAF_REVERSE;
295 			break;
296 
297 		case OPC31_STWBRX:
298 			UA_EVCNT_INCR(stwbrx);
299 			flags = UAF_STORE | UAF_REVERSE;
300 			break;
301 
302 		case OPC31_LHBRX:
303 			UA_EVCNT_INCR(lhbrx);
304 			flags = UAF_LOAD | UAF_HALF | UAF_REVERSE;
305 			break;
306 
307 		case OPC31_STHBRX:
308 			UA_EVCNT_INCR(sthbrx);
309 			flags = UAF_STORE | UAF_HALF | UAF_REVERSE;
310 			break;
311 
312 		default:
313 			UA_EVCNT_INCR(unknown);
314 			goto unknown;
315 		}
316 		break;
317 
318 	case OPC_LMW:
319 		UA_EVCNT_INCR(lmw);
320 #ifdef FIX_UNALIGNED_LSTMW
321 		flags = UAF_LOAD;
322 		if (do_lstmw(tf, insn, flags))
323 			goto fault;
324 		return false;
325 #else
326 		goto unknown;
327 #endif
328 
329 	case OPC_STMW:
330 		UA_EVCNT_INCR(stmw);
331 #ifdef FIX_UNALIGNED_LSTMW
332 		flags = UAF_STORE;
333 		if (do_lstmw(tf, insn, flags))
334 			goto fault;
335 		return false;
336 #else
337 		goto unknown;
338 #endif
339 
340 	default:
341 		UA_EVCNT_INCR(unknown);
342  unknown:
343 		DPRINTF("unknown: srr0: 0x%08lx dear: 0x%08lx "
344 		    "insn: 0x%08x (opcd: 0x%02x, xo: 0x%03x) ",
345 		    tf->tf_srr0, tf->tf_dear,
346 		    insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
347 		DISASM(tf, insn);
348 		ksi->ksi_signo = SIGBUS;
349 		ksi->ksi_trap = EXC_ALI;
350 		ksi->ksi_addr = (void *)tf->tf_dear;
351 		return true;
352 	}
353 
354 	if (do_lst(tf, insn, flags)) {
355 #ifdef FIX_UNALIGNED_LSTMW
356  fault:
357 #endif
358 		UA_EVCNT_INCR(dsi);
359 		ksi->ksi_signo = SIGSEGV;
360 		ksi->ksi_trap = EXC_DSI;
361 		ksi->ksi_code = SEGV_MAPERR;
362 		ksi->ksi_addr = (void *)tf->tf_dear;
363 		return true;
364 	}
365 
366 	return false;
367 }
368 
369 #define	SIGN_EXT(u16, algebra)						\
370     ((u16) | (((algebra) && (u16) >= 0x8000) ? 0xffff0000 : 0))
371 
372 /*
373  * We support formats D and X, but don't care which;
374  * fault address is in dear.
375  */
376 static bool
do_lst(struct trapframe * tf,const union instr * insn,int flags)377 do_lst(struct trapframe *tf, const union instr *insn, int flags)
378 {
379 	const bool load    = flags & UAF_LOAD,
380 		   half    = flags & UAF_HALF,
381 		   algebra = flags & UAF_ALGEBRA,
382 		   reverse = flags & UAF_REVERSE,
383 		   update  = flags & UAF_UPDATE;
384 	uint8_t * const dear = (uint8_t *)tf->tf_dear;
385 	uint32_t u32;
386 	uint16_t u16;
387 	int rs, ra, ret;
388 
389 	rs = insn->i_d.i_rs;	/* same as i_[dx].i_r[sd] */
390 
391 	if (load) {
392 		if (half)
393 			ret = copyin(dear, &u16, sizeof(u16));
394 		else
395 			ret = copyin(dear, &u32, sizeof(u32));
396 	} else {
397 		if (half) {
398 			u16 = (uint16_t)tf->tf_fixreg[rs];
399 			if (reverse)
400 				u16 = bswap16(u16);
401 			ret = copyout(&u16, dear, sizeof(u16));
402 		} else {
403 			u32 = tf->tf_fixreg[rs];
404 			if (reverse)
405 				u32 = bswap32(u32);
406 			ret = copyout(&u32, dear, sizeof(u32));
407 		}
408 	}
409 
410 	if (ret)
411 		goto fault;
412 
413 	if (load) {
414 		if (half)
415 			tf->tf_fixreg[rs] = reverse ?
416 			    bswap16(u16) : SIGN_EXT(u16, algebra);
417 		else
418 			tf->tf_fixreg[rs] = reverse ?
419 			    bswap32(u32) : u32;
420 	}
421 
422 	if (update) {
423 		ra = insn->i_d.i_ra;	/* same as i_x.i_ra */
424 		/*
425 		 * XXX
426 		 * ra == 0 (or ra == rd for load) is invalid (undefined).
427 		 * Mimic what AIX 7.2 describes.
428 		 */
429 		if (ra == 0 || (load && ra == rs)) {
430 			UA_EVCNT_INCR(invalid);
431 			DPRINTF("invalid: rs: %d ra: %d "
432 			    "srr0: 0x%08lx dear: 0x%08x "
433 			    "insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
434 			    rs, ra, tf->tf_srr0, (uint32_t)dear,
435 			    insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
436 			DISASM(tf, insn);
437 			/* XXX discard */
438 		} else
439 			tf->tf_fixreg[ra] = (__register_t)dear;
440 	}
441 
442 	return false;
443 
444  fault:
445 	DPRINTF("fault: ret: %d srr0: 0x%08lx dear: 0x%08x "
446 	    "insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
447 	    ret, tf->tf_srr0, (uint32_t)dear,
448 	    insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
449 	DISASM(tf, insn);
450 	return true;
451 }
452 
453 #ifdef FIX_UNALIGNED_LSTMW
454 static bool
do_lstmw(struct trapframe * tf,const union instr * insn,int flags)455 do_lstmw(struct trapframe *tf, const union instr *insn, int flags)
456 {
457 	const bool load = flags & UAF_LOAD;
458 	const size_t size = sizeof(tf->tf_fixreg[0]);
459 	uint8_t *ea;
460 	uint32_t u32;
461 	int rs, ra, r, ret;
462 
463 	/*
464 	 * XXX
465 	 * Can we always assume ea == tf->tf_dear? (True for 403 although...)
466 	 */
467 	rs = insn->i_d.i_rs;
468 	ra = insn->i_d.i_ra;
469 	ea = (uint8_t *)(insn->i_d.i_d + (ra ? tf->tf_fixreg[ra] : 0));
470 
471 	for (r = rs; r < 32; r++) {
472 		if (load)
473 			ret = copyin(ea, &u32, size);
474 		else
475 			ret = copyout(&tf->tf_fixreg[r], ea, size);
476 
477 		if (ret)
478 			goto fault;
479 
480 		if (load) {
481 			/*
482 			 * XXX
483 			 * r == ra is invalid (undefined); Mimic what
484 			 * AIX 7.2 describes for POWER processors.
485 			 */
486 			if (r == ra) {
487 				UA_EVCNT_INCR(invalid);
488 				DPRINTF("invalid: rs: %d ra: %d r: %d "
489 				    "srr0: 0x%08lx dear: 0x%08lx (ea: 0x%08x) "
490 				    "insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
491 				    rs, ra, r,
492 				    tf->tf_srr0, tf->tf_dear, (uint32_t)ea,
493 				    insn->i_int, insn->i_any.i_opcd,
494 				    insn->i_x.i_xo);
495 				DISASM(tf, insn);
496 				if (r == 0) {
497 					/* XXX load anyway */
498 					tf->tf_fixreg[r] = u32;
499 				} else {
500 					/* XXX discard */
501 				}
502 			} else
503 				tf->tf_fixreg[r] = u32;
504 		}
505 
506 		ea += size;
507 	}
508 
509 	return false;
510 
511  fault:
512 	DPRINTF("fault: ret: %d rs: %d r: %d "
513 	    "srr0: 0x%08lx dear: 0x%08lx (ea: 0x%08x) "
514 	    "insn: 0x%08x (opcd: 0x%02x xo: 0x%03x) ",
515 	    ret, rs, r, tf->tf_srr0, tf->tf_dear, (uint32_t)ea,
516 	    insn->i_int, insn->i_any.i_opcd, insn->i_x.i_xo);
517 	DISASM(tf, insn);
518 	return true;
519 }
520 #endif /* FIX_UNALIGNED_LSTMW */
521