xref: /openbmc/linux/arch/csky/abiv1/alignment.c (revision 2a598d0b)
1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.
3 
4 #include <linux/kernel.h>
5 #include <linux/uaccess.h>
6 #include <linux/ptrace.h>
7 
8 static int align_kern_enable = 1;
9 static int align_usr_enable = 1;
10 static int align_kern_count = 0;
11 static int align_usr_count = 0;
12 
13 static inline uint32_t get_ptreg(struct pt_regs *regs, uint32_t rx)
14 {
15 	return rx == 15 ? regs->lr : *((uint32_t *)&(regs->a0) - 2 + rx);
16 }
17 
18 static inline void put_ptreg(struct pt_regs *regs, uint32_t rx, uint32_t val)
19 {
20 	if (rx == 15)
21 		regs->lr = val;
22 	else
23 		*((uint32_t *)&(regs->a0) - 2 + rx) = val;
24 }
25 
26 /*
27  * Get byte-value from addr and set it to *valp.
28  *
29  * Success: return 0
30  * Failure: return 1
31  */
32 static int ldb_asm(uint32_t addr, uint32_t *valp)
33 {
34 	uint32_t val;
35 	int err;
36 
37 	asm volatile (
38 		"movi	%0, 0\n"
39 		"1:\n"
40 		"ldb	%1, (%2)\n"
41 		"br	3f\n"
42 		"2:\n"
43 		"movi	%0, 1\n"
44 		"br	3f\n"
45 		".section __ex_table,\"a\"\n"
46 		".align 2\n"
47 		".long	1b, 2b\n"
48 		".previous\n"
49 		"3:\n"
50 		: "=&r"(err), "=r"(val)
51 		: "r" (addr)
52 	);
53 
54 	*valp = val;
55 
56 	return err;
57 }
58 
59 /*
60  * Put byte-value to addr.
61  *
62  * Success: return 0
63  * Failure: return 1
64  */
65 static int stb_asm(uint32_t addr, uint32_t val)
66 {
67 	int err;
68 
69 	asm volatile (
70 		"movi	%0, 0\n"
71 		"1:\n"
72 		"stb	%1, (%2)\n"
73 		"br	3f\n"
74 		"2:\n"
75 		"movi	%0, 1\n"
76 		"br	3f\n"
77 		".section __ex_table,\"a\"\n"
78 		".align 2\n"
79 		".long	1b, 2b\n"
80 		".previous\n"
81 		"3:\n"
82 		: "=&r"(err)
83 		: "r"(val), "r" (addr)
84 	);
85 
86 	return err;
87 }
88 
89 /*
90  * Get half-word from [rx + imm]
91  *
92  * Success: return 0
93  * Failure: return 1
94  */
95 static int ldh_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
96 {
97 	uint32_t byte0, byte1;
98 
99 	if (ldb_asm(addr, &byte0))
100 		return 1;
101 	addr += 1;
102 	if (ldb_asm(addr, &byte1))
103 		return 1;
104 
105 	byte0 |= byte1 << 8;
106 	put_ptreg(regs, rz, byte0);
107 
108 	return 0;
109 }
110 
111 /*
112  * Store half-word to [rx + imm]
113  *
114  * Success: return 0
115  * Failure: return 1
116  */
117 static int sth_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
118 {
119 	uint32_t byte0, byte1;
120 
121 	byte0 = byte1 = get_ptreg(regs, rz);
122 
123 	byte0 &= 0xff;
124 
125 	if (stb_asm(addr, byte0))
126 		return 1;
127 
128 	addr += 1;
129 	byte1 = (byte1 >> 8) & 0xff;
130 	if (stb_asm(addr, byte1))
131 		return 1;
132 
133 	return 0;
134 }
135 
136 /*
137  * Get word from [rx + imm]
138  *
139  * Success: return 0
140  * Failure: return 1
141  */
142 static int ldw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
143 {
144 	uint32_t byte0, byte1, byte2, byte3;
145 
146 	if (ldb_asm(addr, &byte0))
147 		return 1;
148 
149 	addr += 1;
150 	if (ldb_asm(addr, &byte1))
151 		return 1;
152 
153 	addr += 1;
154 	if (ldb_asm(addr, &byte2))
155 		return 1;
156 
157 	addr += 1;
158 	if (ldb_asm(addr, &byte3))
159 		return 1;
160 
161 	byte0 |= byte1 << 8;
162 	byte0 |= byte2 << 16;
163 	byte0 |= byte3 << 24;
164 
165 	put_ptreg(regs, rz, byte0);
166 
167 	return 0;
168 }
169 
170 /*
171  * Store word to [rx + imm]
172  *
173  * Success: return 0
174  * Failure: return 1
175  */
176 static int stw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
177 {
178 	uint32_t byte0, byte1, byte2, byte3;
179 
180 	byte0 = byte1 = byte2 = byte3 = get_ptreg(regs, rz);
181 
182 	byte0 &= 0xff;
183 
184 	if (stb_asm(addr, byte0))
185 		return 1;
186 
187 	addr += 1;
188 	byte1 = (byte1 >> 8) & 0xff;
189 	if (stb_asm(addr, byte1))
190 		return 1;
191 
192 	addr += 1;
193 	byte2 = (byte2 >> 16) & 0xff;
194 	if (stb_asm(addr, byte2))
195 		return 1;
196 
197 	addr += 1;
198 	byte3 = (byte3 >> 24) & 0xff;
199 	if (stb_asm(addr, byte3))
200 		return 1;
201 
202 	return 0;
203 }
204 
205 extern int fixup_exception(struct pt_regs *regs);
206 
207 #define OP_LDH 0xc000
208 #define OP_STH 0xd000
209 #define OP_LDW 0x8000
210 #define OP_STW 0x9000
211 
212 void csky_alignment(struct pt_regs *regs)
213 {
214 	int ret;
215 	uint16_t tmp;
216 	uint32_t opcode = 0;
217 	uint32_t rx     = 0;
218 	uint32_t rz     = 0;
219 	uint32_t imm    = 0;
220 	uint32_t addr   = 0;
221 
222 	if (!user_mode(regs))
223 		goto kernel_area;
224 
225 	if (!align_usr_enable) {
226 		pr_err("%s user disabled.\n", __func__);
227 		goto bad_area;
228 	}
229 
230 	align_usr_count++;
231 
232 	ret = get_user(tmp, (uint16_t *)instruction_pointer(regs));
233 	if (ret) {
234 		pr_err("%s get_user failed.\n", __func__);
235 		goto bad_area;
236 	}
237 
238 	goto good_area;
239 
240 kernel_area:
241 	if (!align_kern_enable) {
242 		pr_err("%s kernel disabled.\n", __func__);
243 		goto bad_area;
244 	}
245 
246 	align_kern_count++;
247 
248 	tmp = *(uint16_t *)instruction_pointer(regs);
249 
250 good_area:
251 	opcode = (uint32_t)tmp;
252 
253 	rx  = opcode & 0xf;
254 	imm = (opcode >> 4) & 0xf;
255 	rz  = (opcode >> 8) & 0xf;
256 	opcode &= 0xf000;
257 
258 	if (rx == 0 || rx == 1 || rz == 0 || rz == 1)
259 		goto bad_area;
260 
261 	switch (opcode) {
262 	case OP_LDH:
263 		addr = get_ptreg(regs, rx) + (imm << 1);
264 		ret = ldh_c(regs, rz, addr);
265 		break;
266 	case OP_LDW:
267 		addr = get_ptreg(regs, rx) + (imm << 2);
268 		ret = ldw_c(regs, rz, addr);
269 		break;
270 	case OP_STH:
271 		addr = get_ptreg(regs, rx) + (imm << 1);
272 		ret = sth_c(regs, rz, addr);
273 		break;
274 	case OP_STW:
275 		addr = get_ptreg(regs, rx) + (imm << 2);
276 		ret = stw_c(regs, rz, addr);
277 		break;
278 	}
279 
280 	if (ret)
281 		goto bad_area;
282 
283 	regs->pc += 2;
284 
285 	return;
286 
287 bad_area:
288 	if (!user_mode(regs)) {
289 		if (fixup_exception(regs))
290 			return;
291 
292 		bust_spinlocks(1);
293 		pr_alert("%s opcode: %x, rz: %d, rx: %d, imm: %d, addr: %x.\n",
294 				__func__, opcode, rz, rx, imm, addr);
295 		show_regs(regs);
296 		bust_spinlocks(0);
297 		make_task_dead(SIGKILL);
298 	}
299 
300 	force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)addr);
301 }
302 
303 static struct ctl_table alignment_tbl[5] = {
304 	{
305 		.procname = "kernel_enable",
306 		.data = &align_kern_enable,
307 		.maxlen = sizeof(align_kern_enable),
308 		.mode = 0666,
309 		.proc_handler = &proc_dointvec
310 	},
311 	{
312 		.procname = "user_enable",
313 		.data = &align_usr_enable,
314 		.maxlen = sizeof(align_usr_enable),
315 		.mode = 0666,
316 		.proc_handler = &proc_dointvec
317 	},
318 	{
319 		.procname = "kernel_count",
320 		.data = &align_kern_count,
321 		.maxlen = sizeof(align_kern_count),
322 		.mode = 0666,
323 		.proc_handler = &proc_dointvec
324 	},
325 	{
326 		.procname = "user_count",
327 		.data = &align_usr_count,
328 		.maxlen = sizeof(align_usr_count),
329 		.mode = 0666,
330 		.proc_handler = &proc_dointvec
331 	},
332 	{}
333 };
334 
335 static int __init csky_alignment_init(void)
336 {
337 	register_sysctl_init("csky/csky_alignment", alignment_tbl);
338 	return 0;
339 }
340 
341 arch_initcall(csky_alignment_init);
342