xref: /openbmc/linux/arch/arc/kernel/unaligned.c (revision 275876e2)
1 /*
2  * Copyright (C) 2011-2012 Synopsys (www.synopsys.com)
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * vineetg : May 2011
9  *  -Adapted (from .26 to .35)
10  *  -original contribution by Tim.yao@amlogic.com
11  *
12  */
13 
14 #include <linux/types.h>
15 #include <linux/ptrace.h>
16 #include <linux/uaccess.h>
17 #include <asm/disasm.h>
18 
19 #ifdef CONFIG_CPU_BIG_ENDIAN
20 #define BE		1
21 #define FIRST_BYTE_16	"swap %1, %1\n swape %1, %1\n"
22 #define FIRST_BYTE_32	"swape %1, %1\n"
23 #else
24 #define BE		0
25 #define FIRST_BYTE_16
26 #define FIRST_BYTE_32
27 #endif
28 
29 #define __get8_unaligned_check(val, addr, err)		\
30 	__asm__(					\
31 	"1:	ldb.ab	%1, [%2, 1]\n"			\
32 	"2:\n"						\
33 	"	.section .fixup,\"ax\"\n"		\
34 	"	.align	4\n"				\
35 	"3:	mov	%0, 1\n"			\
36 	"	b	2b\n"				\
37 	"	.previous\n"				\
38 	"	.section __ex_table,\"a\"\n"		\
39 	"	.align	4\n"				\
40 	"	.long	1b, 3b\n"			\
41 	"	.previous\n"				\
42 	: "=r" (err), "=&r" (val), "=r" (addr)		\
43 	: "0" (err), "2" (addr))
44 
45 #define get16_unaligned_check(val, addr)		\
46 	do {						\
47 		unsigned int err = 0, v, a = addr;	\
48 		__get8_unaligned_check(v, a, err);	\
49 		val =  v << ((BE) ? 8 : 0);		\
50 		__get8_unaligned_check(v, a, err);	\
51 		val |= v << ((BE) ? 0 : 8);		\
52 		if (err)				\
53 			goto fault;			\
54 	} while (0)
55 
56 #define get32_unaligned_check(val, addr)		\
57 	do {						\
58 		unsigned int err = 0, v, a = addr;	\
59 		__get8_unaligned_check(v, a, err);	\
60 		val =  v << ((BE) ? 24 : 0);		\
61 		__get8_unaligned_check(v, a, err);	\
62 		val |= v << ((BE) ? 16 : 8);		\
63 		__get8_unaligned_check(v, a, err);	\
64 		val |= v << ((BE) ? 8 : 16);		\
65 		__get8_unaligned_check(v, a, err);	\
66 		val |= v << ((BE) ? 0 : 24);		\
67 		if (err)				\
68 			goto fault;			\
69 	} while (0)
70 
71 #define put16_unaligned_check(val, addr)		\
72 	do {						\
73 		unsigned int err = 0, v = val, a = addr;\
74 							\
75 		__asm__(				\
76 		FIRST_BYTE_16				\
77 		"1:	stb.ab	%1, [%2, 1]\n"		\
78 		"	lsr %1, %1, 8\n"		\
79 		"2:	stb	%1, [%2]\n"		\
80 		"3:\n"					\
81 		"	.section .fixup,\"ax\"\n"	\
82 		"	.align	4\n"			\
83 		"4:	mov	%0, 1\n"		\
84 		"	b	3b\n"			\
85 		"	.previous\n"			\
86 		"	.section __ex_table,\"a\"\n"	\
87 		"	.align	4\n"			\
88 		"	.long	1b, 4b\n"		\
89 		"	.long	2b, 4b\n"		\
90 		"	.previous\n"			\
91 		: "=r" (err), "=&r" (v), "=&r" (a)	\
92 		: "0" (err), "1" (v), "2" (a));		\
93 							\
94 		if (err)				\
95 			goto fault;			\
96 	} while (0)
97 
98 #define put32_unaligned_check(val, addr)		\
99 	do {						\
100 		unsigned int err = 0, v = val, a = addr;\
101 							\
102 		__asm__(				\
103 		FIRST_BYTE_32				\
104 		"1:	stb.ab	%1, [%2, 1]\n"		\
105 		"	lsr %1, %1, 8\n"		\
106 		"2:	stb.ab	%1, [%2, 1]\n"		\
107 		"	lsr %1, %1, 8\n"		\
108 		"3:	stb.ab	%1, [%2, 1]\n"		\
109 		"	lsr %1, %1, 8\n"		\
110 		"4:	stb	%1, [%2]\n"		\
111 		"5:\n"					\
112 		"	.section .fixup,\"ax\"\n"	\
113 		"	.align	4\n"			\
114 		"6:	mov	%0, 1\n"		\
115 		"	b	5b\n"			\
116 		"	.previous\n"			\
117 		"	.section __ex_table,\"a\"\n"	\
118 		"	.align	4\n"			\
119 		"	.long	1b, 6b\n"		\
120 		"	.long	2b, 6b\n"		\
121 		"	.long	3b, 6b\n"		\
122 		"	.long	4b, 6b\n"		\
123 		"	.previous\n"			\
124 		: "=r" (err), "=&r" (v), "=&r" (a)	\
125 		: "0" (err), "1" (v), "2" (a));		\
126 							\
127 		if (err)				\
128 			goto fault;			\
129 	} while (0)
130 
131 /* sysctl hooks */
132 int unaligned_enabled __read_mostly = 1;	/* Enabled by default */
133 int no_unaligned_warning __read_mostly = 1;	/* Only 1 warning by default */
134 
135 static void fixup_load(struct disasm_state *state, struct pt_regs *regs,
136 			struct callee_regs *cregs)
137 {
138 	int val;
139 
140 	/* register write back */
141 	if ((state->aa == 1) || (state->aa == 2)) {
142 		set_reg(state->wb_reg, state->src1 + state->src2, regs, cregs);
143 
144 		if (state->aa == 2)
145 			state->src2 = 0;
146 	}
147 
148 	if (state->zz == 0) {
149 		get32_unaligned_check(val, state->src1 + state->src2);
150 	} else {
151 		get16_unaligned_check(val, state->src1 + state->src2);
152 
153 		if (state->x)
154 			val = (val << 16) >> 16;
155 	}
156 
157 	if (state->pref == 0)
158 		set_reg(state->dest, val, regs, cregs);
159 
160 	return;
161 
162 fault:	state->fault = 1;
163 }
164 
165 static void fixup_store(struct disasm_state *state, struct pt_regs *regs,
166 			struct callee_regs *cregs)
167 {
168 	/* register write back */
169 	if ((state->aa == 1) || (state->aa == 2)) {
170 		set_reg(state->wb_reg, state->src2 + state->src3, regs, cregs);
171 
172 		if (state->aa == 3)
173 			state->src3 = 0;
174 	} else if (state->aa == 3) {
175 		if (state->zz == 2) {
176 			set_reg(state->wb_reg, state->src2 + (state->src3 << 1),
177 				regs, cregs);
178 		} else if (!state->zz) {
179 			set_reg(state->wb_reg, state->src2 + (state->src3 << 2),
180 				regs, cregs);
181 		} else {
182 			goto fault;
183 		}
184 	}
185 
186 	/* write fix-up */
187 	if (!state->zz)
188 		put32_unaligned_check(state->src1, state->src2 + state->src3);
189 	else
190 		put16_unaligned_check(state->src1, state->src2 + state->src3);
191 
192 	return;
193 
194 fault:	state->fault = 1;
195 }
196 
197 /*
198  * Handle an unaligned access
199  * Returns 0 if successfully handled, 1 if some error happened
200  */
201 int misaligned_fixup(unsigned long address, struct pt_regs *regs,
202 		     struct callee_regs *cregs)
203 {
204 	struct disasm_state state;
205 	char buf[TASK_COMM_LEN];
206 
207 	/* handle user mode only and only if enabled by sysadmin */
208 	if (!user_mode(regs) || !unaligned_enabled)
209 		return 1;
210 
211 	if (no_unaligned_warning) {
212 		pr_warn_once("%s(%d) made unaligned access which was emulated"
213 			     " by kernel assist\n. This can degrade application"
214 			     " performance significantly\n. To enable further"
215 			     " logging of such instances, please \n"
216 			     " echo 0 > /proc/sys/kernel/ignore-unaligned-usertrap\n",
217 			     get_task_comm(buf, current), task_pid_nr(current));
218 	} else {
219 		/* Add rate limiting if it gets down to it */
220 		pr_warn("%s(%d): unaligned access to/from 0x%lx by PC: 0x%lx\n",
221 			get_task_comm(buf, current), task_pid_nr(current),
222 			address, regs->ret);
223 
224 	}
225 
226 	disasm_instr(regs->ret, &state, 1, regs, cregs);
227 
228 	if (state.fault)
229 		goto fault;
230 
231 	/* ldb/stb should not have unaligned exception */
232 	if ((state.zz == 1) || (state.di))
233 		goto fault;
234 
235 	if (!state.write)
236 		fixup_load(&state, regs, cregs);
237 	else
238 		fixup_store(&state, regs, cregs);
239 
240 	if (state.fault)
241 		goto fault;
242 
243 	if (delay_mode(regs)) {
244 		regs->ret = regs->bta;
245 		regs->status32 &= ~STATUS_DE_MASK;
246 	} else {
247 		regs->ret += state.instr_len;
248 
249 		/* handle zero-overhead-loop */
250 		if ((regs->ret == regs->lp_end) && (regs->lp_count)) {
251 			regs->ret = regs->lp_start;
252 			regs->lp_count--;
253 		}
254 	}
255 
256 	return 0;
257 
258 fault:
259 	pr_err("Alignment trap: fault in fix-up %08lx at [<%08lx>]\n",
260 		state.words[0], address);
261 
262 	return 1;
263 }
264