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
76UA_EVCNT_ATTACH(lwz);
77UA_EVCNT_ATTACH(lwzu);
78UA_EVCNT_ATTACH(stw);
79UA_EVCNT_ATTACH(stwu);
80UA_EVCNT_ATTACH(lhz);
81UA_EVCNT_ATTACH(lhzu);
82UA_EVCNT_ATTACH(lha);
83UA_EVCNT_ATTACH(lhau);
84UA_EVCNT_ATTACH(sth);
85UA_EVCNT_ATTACH(sthu);
86
87UA_EVCNT_ATTACH(lwzx);
88UA_EVCNT_ATTACH(lwzux);
89UA_EVCNT_ATTACH(stwx);
90UA_EVCNT_ATTACH(stwux);
91UA_EVCNT_ATTACH(lhzx);
92UA_EVCNT_ATTACH(lhzux);
93UA_EVCNT_ATTACH(lhax);
94UA_EVCNT_ATTACH(lhaux);
95UA_EVCNT_ATTACH(sthx);
96UA_EVCNT_ATTACH(sthux);
97UA_EVCNT_ATTACH(lwbrx);
98UA_EVCNT_ATTACH(stwbrx);
99UA_EVCNT_ATTACH(lhbrx);
100UA_EVCNT_ATTACH(sthbrx);
101
102UA_EVCNT_ATTACH(lmw);
103UA_EVCNT_ATTACH(stmw);
104
105UA_EVCNT_ATTACH(isi);
106UA_EVCNT_ATTACH(dsi);
107UA_EVCNT_ATTACH(unknown);
108UA_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)
116int unaligned_debug = 1;
117#elif defined(DEBUG)
118int 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))
132extern 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
142static bool emul_unaligned(struct trapframe *, ksiginfo_t *,
143    const union instr *);
144static bool do_lst(struct trapframe *, const union instr *, int);
145#ifdef FIX_UNALIGNED_LSTMW
146static bool do_lstmw(struct trapframe *, const union instr *, int);
147#endif
148
149bool
150fix_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
184static bool
185emul_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 */
376static bool
377do_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
454static bool
455do_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