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