xref: /linux/arch/nios2/kernel/misaligned.c (revision 82ed08dd1b0e0e0728f9188f66795c49dffe437d)
1*82ed08ddSLey Foon Tan /*
2*82ed08ddSLey Foon Tan  *  linux/arch/nios2/kernel/misaligned.c
3*82ed08ddSLey Foon Tan  *
4*82ed08ddSLey Foon Tan  *  basic emulation for mis-aligned accesses on the NIOS II cpu
5*82ed08ddSLey Foon Tan  *  modelled after the version for arm in arm/alignment.c
6*82ed08ddSLey Foon Tan  *
7*82ed08ddSLey Foon Tan  *  Brad Parker <brad@heeltoe.com>
8*82ed08ddSLey Foon Tan  *  Copyright (C) 2010 Ambient Corporation
9*82ed08ddSLey Foon Tan  *  Copyright (c) 2010 Altera Corporation, San Jose, California, USA.
10*82ed08ddSLey Foon Tan  *  Copyright (c) 2010 Arrow Electronics, Inc.
11*82ed08ddSLey Foon Tan  *
12*82ed08ddSLey Foon Tan  * This file is subject to the terms and conditions of the GNU General
13*82ed08ddSLey Foon Tan  * Public License.  See the file COPYING in the main directory of
14*82ed08ddSLey Foon Tan  * this archive for more details.
15*82ed08ddSLey Foon Tan  */
16*82ed08ddSLey Foon Tan 
17*82ed08ddSLey Foon Tan #include <linux/errno.h>
18*82ed08ddSLey Foon Tan #include <linux/string.h>
19*82ed08ddSLey Foon Tan #include <linux/proc_fs.h>
20*82ed08ddSLey Foon Tan #include <linux/init.h>
21*82ed08ddSLey Foon Tan #include <linux/sched.h>
22*82ed08ddSLey Foon Tan #include <linux/uaccess.h>
23*82ed08ddSLey Foon Tan #include <linux/seq_file.h>
24*82ed08ddSLey Foon Tan 
25*82ed08ddSLey Foon Tan #include <asm/traps.h>
26*82ed08ddSLey Foon Tan #include <asm/unaligned.h>
27*82ed08ddSLey Foon Tan 
28*82ed08ddSLey Foon Tan /* instructions we emulate */
29*82ed08ddSLey Foon Tan #define INST_LDHU	0x0b
30*82ed08ddSLey Foon Tan #define INST_STH	0x0d
31*82ed08ddSLey Foon Tan #define INST_LDH	0x0f
32*82ed08ddSLey Foon Tan #define INST_STW	0x15
33*82ed08ddSLey Foon Tan #define INST_LDW	0x17
34*82ed08ddSLey Foon Tan 
35*82ed08ddSLey Foon Tan static unsigned long ma_user, ma_kern, ma_skipped, ma_half, ma_word;
36*82ed08ddSLey Foon Tan 
37*82ed08ddSLey Foon Tan static unsigned int ma_usermode;
38*82ed08ddSLey Foon Tan #define UM_WARN		0x01
39*82ed08ddSLey Foon Tan #define UM_FIXUP	0x02
40*82ed08ddSLey Foon Tan #define UM_SIGNAL	0x04
41*82ed08ddSLey Foon Tan #define KM_WARN		0x08
42*82ed08ddSLey Foon Tan 
43*82ed08ddSLey Foon Tan /* see arch/nios2/include/asm/ptrace.h */
44*82ed08ddSLey Foon Tan static u8 sys_stack_frame_reg_offset[] = {
45*82ed08ddSLey Foon Tan 	/* struct pt_regs */
46*82ed08ddSLey Foon Tan 	8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5, 6, 7, 0,
47*82ed08ddSLey Foon Tan 	/* struct switch_stack */
48*82ed08ddSLey Foon Tan 	16, 17, 18, 19, 20, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0
49*82ed08ddSLey Foon Tan };
50*82ed08ddSLey Foon Tan 
51*82ed08ddSLey Foon Tan static int reg_offsets[32];
52*82ed08ddSLey Foon Tan 
53*82ed08ddSLey Foon Tan static inline u32 get_reg_val(struct pt_regs *fp, int reg)
54*82ed08ddSLey Foon Tan {
55*82ed08ddSLey Foon Tan 	u8 *p = ((u8 *)fp) + reg_offsets[reg];
56*82ed08ddSLey Foon Tan 
57*82ed08ddSLey Foon Tan 	return *(u32 *)p;
58*82ed08ddSLey Foon Tan }
59*82ed08ddSLey Foon Tan 
60*82ed08ddSLey Foon Tan static inline void put_reg_val(struct pt_regs *fp, int reg, u32 val)
61*82ed08ddSLey Foon Tan {
62*82ed08ddSLey Foon Tan 	u8 *p = ((u8 *)fp) + reg_offsets[reg];
63*82ed08ddSLey Foon Tan 	*(u32 *)p = val;
64*82ed08ddSLey Foon Tan }
65*82ed08ddSLey Foon Tan 
66*82ed08ddSLey Foon Tan /*
67*82ed08ddSLey Foon Tan  * (mis)alignment handler
68*82ed08ddSLey Foon Tan  */
69*82ed08ddSLey Foon Tan asmlinkage void handle_unaligned_c(struct pt_regs *fp, int cause)
70*82ed08ddSLey Foon Tan {
71*82ed08ddSLey Foon Tan 	u32 isn, addr, val;
72*82ed08ddSLey Foon Tan 	int in_kernel;
73*82ed08ddSLey Foon Tan 	u8 a, b, d0, d1, d2, d3;
74*82ed08ddSLey Foon Tan 	u16 imm16;
75*82ed08ddSLey Foon Tan 	unsigned int fault;
76*82ed08ddSLey Foon Tan 
77*82ed08ddSLey Foon Tan 	/* back up one instruction */
78*82ed08ddSLey Foon Tan 	fp->ea -= 4;
79*82ed08ddSLey Foon Tan 
80*82ed08ddSLey Foon Tan 	if (fixup_exception(fp)) {
81*82ed08ddSLey Foon Tan 		ma_skipped++;
82*82ed08ddSLey Foon Tan 		return;
83*82ed08ddSLey Foon Tan 	}
84*82ed08ddSLey Foon Tan 
85*82ed08ddSLey Foon Tan 	in_kernel = !user_mode(fp);
86*82ed08ddSLey Foon Tan 
87*82ed08ddSLey Foon Tan 	isn = *(unsigned long *)(fp->ea);
88*82ed08ddSLey Foon Tan 
89*82ed08ddSLey Foon Tan 	fault = 0;
90*82ed08ddSLey Foon Tan 
91*82ed08ddSLey Foon Tan 	/* do fixup if in kernel or mode turned on */
92*82ed08ddSLey Foon Tan 	if (in_kernel || (ma_usermode & UM_FIXUP)) {
93*82ed08ddSLey Foon Tan 		/* decompose instruction */
94*82ed08ddSLey Foon Tan 		a = (isn >> 27) & 0x1f;
95*82ed08ddSLey Foon Tan 		b = (isn >> 22) & 0x1f;
96*82ed08ddSLey Foon Tan 		imm16 = (isn >> 6) & 0xffff;
97*82ed08ddSLey Foon Tan 		addr = get_reg_val(fp, a) + imm16;
98*82ed08ddSLey Foon Tan 
99*82ed08ddSLey Foon Tan 		/* do fixup to saved registers */
100*82ed08ddSLey Foon Tan 		switch (isn & 0x3f) {
101*82ed08ddSLey Foon Tan 		case INST_LDHU:
102*82ed08ddSLey Foon Tan 			fault |= __get_user(d0, (u8 *)(addr+0));
103*82ed08ddSLey Foon Tan 			fault |= __get_user(d1, (u8 *)(addr+1));
104*82ed08ddSLey Foon Tan 			val = (d1 << 8) | d0;
105*82ed08ddSLey Foon Tan 			put_reg_val(fp, b, val);
106*82ed08ddSLey Foon Tan 			ma_half++;
107*82ed08ddSLey Foon Tan 			break;
108*82ed08ddSLey Foon Tan 		case INST_STH:
109*82ed08ddSLey Foon Tan 			val = get_reg_val(fp, b);
110*82ed08ddSLey Foon Tan 			d1 = val >> 8;
111*82ed08ddSLey Foon Tan 			d0 = val >> 0;
112*82ed08ddSLey Foon Tan 
113*82ed08ddSLey Foon Tan 			pr_debug("sth: ra=%d (%08x) rb=%d (%08x), imm16 %04x addr %08x val %08x\n",
114*82ed08ddSLey Foon Tan 				a, get_reg_val(fp, a),
115*82ed08ddSLey Foon Tan 				b, get_reg_val(fp, b),
116*82ed08ddSLey Foon Tan 				imm16, addr, val);
117*82ed08ddSLey Foon Tan 
118*82ed08ddSLey Foon Tan 			if (in_kernel) {
119*82ed08ddSLey Foon Tan 				*(u8 *)(addr+0) = d0;
120*82ed08ddSLey Foon Tan 				*(u8 *)(addr+1) = d1;
121*82ed08ddSLey Foon Tan 			} else {
122*82ed08ddSLey Foon Tan 				fault |= __put_user(d0, (u8 *)(addr+0));
123*82ed08ddSLey Foon Tan 				fault |= __put_user(d1, (u8 *)(addr+1));
124*82ed08ddSLey Foon Tan 			}
125*82ed08ddSLey Foon Tan 			ma_half++;
126*82ed08ddSLey Foon Tan 			break;
127*82ed08ddSLey Foon Tan 		case INST_LDH:
128*82ed08ddSLey Foon Tan 			fault |= __get_user(d0, (u8 *)(addr+0));
129*82ed08ddSLey Foon Tan 			fault |= __get_user(d1, (u8 *)(addr+1));
130*82ed08ddSLey Foon Tan 			val = (short)((d1 << 8) | d0);
131*82ed08ddSLey Foon Tan 			put_reg_val(fp, b, val);
132*82ed08ddSLey Foon Tan 			ma_half++;
133*82ed08ddSLey Foon Tan 			break;
134*82ed08ddSLey Foon Tan 		case INST_STW:
135*82ed08ddSLey Foon Tan 			val = get_reg_val(fp, b);
136*82ed08ddSLey Foon Tan 			d3 = val >> 24;
137*82ed08ddSLey Foon Tan 			d2 = val >> 16;
138*82ed08ddSLey Foon Tan 			d1 = val >> 8;
139*82ed08ddSLey Foon Tan 			d0 = val >> 0;
140*82ed08ddSLey Foon Tan 			if (in_kernel) {
141*82ed08ddSLey Foon Tan 				*(u8 *)(addr+0) = d0;
142*82ed08ddSLey Foon Tan 				*(u8 *)(addr+1) = d1;
143*82ed08ddSLey Foon Tan 				*(u8 *)(addr+2) = d2;
144*82ed08ddSLey Foon Tan 				*(u8 *)(addr+3) = d3;
145*82ed08ddSLey Foon Tan 			} else {
146*82ed08ddSLey Foon Tan 				fault |= __put_user(d0, (u8 *)(addr+0));
147*82ed08ddSLey Foon Tan 				fault |= __put_user(d1, (u8 *)(addr+1));
148*82ed08ddSLey Foon Tan 				fault |= __put_user(d2, (u8 *)(addr+2));
149*82ed08ddSLey Foon Tan 				fault |= __put_user(d3, (u8 *)(addr+3));
150*82ed08ddSLey Foon Tan 			}
151*82ed08ddSLey Foon Tan 			ma_word++;
152*82ed08ddSLey Foon Tan 			break;
153*82ed08ddSLey Foon Tan 		case INST_LDW:
154*82ed08ddSLey Foon Tan 			fault |= __get_user(d0, (u8 *)(addr+0));
155*82ed08ddSLey Foon Tan 			fault |= __get_user(d1, (u8 *)(addr+1));
156*82ed08ddSLey Foon Tan 			fault |= __get_user(d2, (u8 *)(addr+2));
157*82ed08ddSLey Foon Tan 			fault |= __get_user(d3, (u8 *)(addr+3));
158*82ed08ddSLey Foon Tan 			val = (d3 << 24) | (d2 << 16) | (d1 << 8) | d0;
159*82ed08ddSLey Foon Tan 			put_reg_val(fp, b, val);
160*82ed08ddSLey Foon Tan 			ma_word++;
161*82ed08ddSLey Foon Tan 			break;
162*82ed08ddSLey Foon Tan 		}
163*82ed08ddSLey Foon Tan 	}
164*82ed08ddSLey Foon Tan 
165*82ed08ddSLey Foon Tan 	addr = RDCTL(CTL_BADADDR);
166*82ed08ddSLey Foon Tan 	cause >>= 2;
167*82ed08ddSLey Foon Tan 
168*82ed08ddSLey Foon Tan 	if (fault) {
169*82ed08ddSLey Foon Tan 		if (in_kernel) {
170*82ed08ddSLey Foon Tan 			pr_err("fault during kernel misaligned fixup @ %#lx; addr 0x%08x; isn=0x%08x\n",
171*82ed08ddSLey Foon Tan 				fp->ea, (unsigned int)addr,
172*82ed08ddSLey Foon Tan 				(unsigned int)isn);
173*82ed08ddSLey Foon Tan 		} else {
174*82ed08ddSLey Foon Tan 			pr_err("fault during user misaligned fixup @ %#lx; isn=%08x addr=0x%08x sp=0x%08lx pid=%d\n",
175*82ed08ddSLey Foon Tan 				fp->ea,
176*82ed08ddSLey Foon Tan 				(unsigned int)isn, addr, fp->sp,
177*82ed08ddSLey Foon Tan 				current->pid);
178*82ed08ddSLey Foon Tan 
179*82ed08ddSLey Foon Tan 			_exception(SIGSEGV, fp, SEGV_MAPERR, fp->ea);
180*82ed08ddSLey Foon Tan 			return;
181*82ed08ddSLey Foon Tan 		}
182*82ed08ddSLey Foon Tan 	}
183*82ed08ddSLey Foon Tan 
184*82ed08ddSLey Foon Tan 	/*
185*82ed08ddSLey Foon Tan 	 * kernel mode -
186*82ed08ddSLey Foon Tan 	 *  note exception and skip bad instruction (return)
187*82ed08ddSLey Foon Tan 	 */
188*82ed08ddSLey Foon Tan 	if (in_kernel) {
189*82ed08ddSLey Foon Tan 		ma_kern++;
190*82ed08ddSLey Foon Tan 		fp->ea += 4;
191*82ed08ddSLey Foon Tan 
192*82ed08ddSLey Foon Tan 		if (ma_usermode & KM_WARN) {
193*82ed08ddSLey Foon Tan 			pr_err("kernel unaligned access @ %#lx; BADADDR 0x%08x; cause=%d, isn=0x%08x\n",
194*82ed08ddSLey Foon Tan 				fp->ea,
195*82ed08ddSLey Foon Tan 				(unsigned int)addr, cause,
196*82ed08ddSLey Foon Tan 				(unsigned int)isn);
197*82ed08ddSLey Foon Tan 			/* show_regs(fp); */
198*82ed08ddSLey Foon Tan 		}
199*82ed08ddSLey Foon Tan 
200*82ed08ddSLey Foon Tan 		return;
201*82ed08ddSLey Foon Tan 	}
202*82ed08ddSLey Foon Tan 
203*82ed08ddSLey Foon Tan 	ma_user++;
204*82ed08ddSLey Foon Tan 
205*82ed08ddSLey Foon Tan 	/*
206*82ed08ddSLey Foon Tan 	 * user mode -
207*82ed08ddSLey Foon Tan 	 *  possibly warn,
208*82ed08ddSLey Foon Tan 	 *  possibly send SIGBUS signal to process
209*82ed08ddSLey Foon Tan 	 */
210*82ed08ddSLey Foon Tan 	if (ma_usermode & UM_WARN) {
211*82ed08ddSLey Foon Tan 		pr_err("user unaligned access @ %#lx; isn=0x%08lx ea=0x%08lx ra=0x%08lx sp=0x%08lx\n",
212*82ed08ddSLey Foon Tan 			(unsigned long)addr, (unsigned long)isn,
213*82ed08ddSLey Foon Tan 			fp->ea, fp->ra, fp->sp);
214*82ed08ddSLey Foon Tan 	}
215*82ed08ddSLey Foon Tan 
216*82ed08ddSLey Foon Tan 	if (ma_usermode & UM_SIGNAL)
217*82ed08ddSLey Foon Tan 		_exception(SIGBUS, fp, BUS_ADRALN, fp->ea);
218*82ed08ddSLey Foon Tan 	else
219*82ed08ddSLey Foon Tan 		fp->ea += 4;	/* else advance */
220*82ed08ddSLey Foon Tan }
221*82ed08ddSLey Foon Tan 
222*82ed08ddSLey Foon Tan static void __init misaligned_calc_reg_offsets(void)
223*82ed08ddSLey Foon Tan {
224*82ed08ddSLey Foon Tan 	int i, r, offset;
225*82ed08ddSLey Foon Tan 
226*82ed08ddSLey Foon Tan 	/* pre-calc offsets of registers on sys call stack frame */
227*82ed08ddSLey Foon Tan 	offset = 0;
228*82ed08ddSLey Foon Tan 
229*82ed08ddSLey Foon Tan 	/* struct pt_regs */
230*82ed08ddSLey Foon Tan 	for (i = 0; i < 16; i++) {
231*82ed08ddSLey Foon Tan 		r = sys_stack_frame_reg_offset[i];
232*82ed08ddSLey Foon Tan 		reg_offsets[r] = offset;
233*82ed08ddSLey Foon Tan 		offset += 4;
234*82ed08ddSLey Foon Tan 	}
235*82ed08ddSLey Foon Tan 
236*82ed08ddSLey Foon Tan 	/* struct switch_stack */
237*82ed08ddSLey Foon Tan 	offset = -sizeof(struct switch_stack);
238*82ed08ddSLey Foon Tan 	for (i = 16; i < 32; i++) {
239*82ed08ddSLey Foon Tan 		r = sys_stack_frame_reg_offset[i];
240*82ed08ddSLey Foon Tan 		reg_offsets[r] = offset;
241*82ed08ddSLey Foon Tan 		offset += 4;
242*82ed08ddSLey Foon Tan 	}
243*82ed08ddSLey Foon Tan }
244*82ed08ddSLey Foon Tan 
245*82ed08ddSLey Foon Tan 
246*82ed08ddSLey Foon Tan static int __init misaligned_init(void)
247*82ed08ddSLey Foon Tan {
248*82ed08ddSLey Foon Tan 	/* default mode - silent fix */
249*82ed08ddSLey Foon Tan 	ma_usermode = UM_FIXUP | KM_WARN;
250*82ed08ddSLey Foon Tan 
251*82ed08ddSLey Foon Tan 	misaligned_calc_reg_offsets();
252*82ed08ddSLey Foon Tan 
253*82ed08ddSLey Foon Tan 	return 0;
254*82ed08ddSLey Foon Tan }
255*82ed08ddSLey Foon Tan 
256*82ed08ddSLey Foon Tan fs_initcall(misaligned_init);
257