Home | History | Annotate | Line # | Download | only in powerpc
      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
    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
    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
    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
    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