xref: /openbmc/linux/arch/csky/abiv1/alignment.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
1081860b9SGuo Ren // SPDX-License-Identifier: GPL-2.0
2081860b9SGuo Ren // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.
3081860b9SGuo Ren 
4081860b9SGuo Ren #include <linux/kernel.h>
5081860b9SGuo Ren #include <linux/uaccess.h>
6081860b9SGuo Ren #include <linux/ptrace.h>
7081860b9SGuo Ren 
8c7e6f0e9SGuo Ren static int align_kern_enable = 1;
9c7e6f0e9SGuo Ren static int align_usr_enable = 1;
10c7e6f0e9SGuo Ren static int align_kern_count = 0;
11c7e6f0e9SGuo Ren static int align_usr_count = 0;
12081860b9SGuo Ren 
get_ptreg(struct pt_regs * regs,uint32_t rx)13081860b9SGuo Ren static inline uint32_t get_ptreg(struct pt_regs *regs, uint32_t rx)
14081860b9SGuo Ren {
15081860b9SGuo Ren 	return rx == 15 ? regs->lr : *((uint32_t *)&(regs->a0) - 2 + rx);
16081860b9SGuo Ren }
17081860b9SGuo Ren 
put_ptreg(struct pt_regs * regs,uint32_t rx,uint32_t val)18081860b9SGuo Ren static inline void put_ptreg(struct pt_regs *regs, uint32_t rx, uint32_t val)
19081860b9SGuo Ren {
20081860b9SGuo Ren 	if (rx == 15)
21081860b9SGuo Ren 		regs->lr = val;
22081860b9SGuo Ren 	else
23081860b9SGuo Ren 		*((uint32_t *)&(regs->a0) - 2 + rx) = val;
24081860b9SGuo Ren }
25081860b9SGuo Ren 
26081860b9SGuo Ren /*
27081860b9SGuo Ren  * Get byte-value from addr and set it to *valp.
28081860b9SGuo Ren  *
29081860b9SGuo Ren  * Success: return 0
30081860b9SGuo Ren  * Failure: return 1
31081860b9SGuo Ren  */
ldb_asm(uint32_t addr,uint32_t * valp)32081860b9SGuo Ren static int ldb_asm(uint32_t addr, uint32_t *valp)
33081860b9SGuo Ren {
34081860b9SGuo Ren 	uint32_t val;
35081860b9SGuo Ren 	int err;
36081860b9SGuo Ren 
37081860b9SGuo Ren 	asm volatile (
38081860b9SGuo Ren 		"movi	%0, 0\n"
39081860b9SGuo Ren 		"1:\n"
40081860b9SGuo Ren 		"ldb	%1, (%2)\n"
41081860b9SGuo Ren 		"br	3f\n"
42081860b9SGuo Ren 		"2:\n"
43081860b9SGuo Ren 		"movi	%0, 1\n"
44081860b9SGuo Ren 		"br	3f\n"
45081860b9SGuo Ren 		".section __ex_table,\"a\"\n"
46081860b9SGuo Ren 		".align 2\n"
47081860b9SGuo Ren 		".long	1b, 2b\n"
48081860b9SGuo Ren 		".previous\n"
49081860b9SGuo Ren 		"3:\n"
50081860b9SGuo Ren 		: "=&r"(err), "=r"(val)
51081860b9SGuo Ren 		: "r" (addr)
52081860b9SGuo Ren 	);
53081860b9SGuo Ren 
54081860b9SGuo Ren 	*valp = val;
55081860b9SGuo Ren 
56081860b9SGuo Ren 	return err;
57081860b9SGuo Ren }
58081860b9SGuo Ren 
59081860b9SGuo Ren /*
60081860b9SGuo Ren  * Put byte-value to addr.
61081860b9SGuo Ren  *
62081860b9SGuo Ren  * Success: return 0
63081860b9SGuo Ren  * Failure: return 1
64081860b9SGuo Ren  */
stb_asm(uint32_t addr,uint32_t val)65081860b9SGuo Ren static int stb_asm(uint32_t addr, uint32_t val)
66081860b9SGuo Ren {
67081860b9SGuo Ren 	int err;
68081860b9SGuo Ren 
69081860b9SGuo Ren 	asm volatile (
70081860b9SGuo Ren 		"movi	%0, 0\n"
71081860b9SGuo Ren 		"1:\n"
72081860b9SGuo Ren 		"stb	%1, (%2)\n"
73081860b9SGuo Ren 		"br	3f\n"
74081860b9SGuo Ren 		"2:\n"
75081860b9SGuo Ren 		"movi	%0, 1\n"
76081860b9SGuo Ren 		"br	3f\n"
77081860b9SGuo Ren 		".section __ex_table,\"a\"\n"
78081860b9SGuo Ren 		".align 2\n"
79081860b9SGuo Ren 		".long	1b, 2b\n"
80081860b9SGuo Ren 		".previous\n"
81081860b9SGuo Ren 		"3:\n"
82081860b9SGuo Ren 		: "=&r"(err)
83081860b9SGuo Ren 		: "r"(val), "r" (addr)
84081860b9SGuo Ren 	);
85081860b9SGuo Ren 
86081860b9SGuo Ren 	return err;
87081860b9SGuo Ren }
88081860b9SGuo Ren 
89081860b9SGuo Ren /*
90081860b9SGuo Ren  * Get half-word from [rx + imm]
91081860b9SGuo Ren  *
92081860b9SGuo Ren  * Success: return 0
93081860b9SGuo Ren  * Failure: return 1
94081860b9SGuo Ren  */
ldh_c(struct pt_regs * regs,uint32_t rz,uint32_t addr)95081860b9SGuo Ren static int ldh_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
96081860b9SGuo Ren {
97081860b9SGuo Ren 	uint32_t byte0, byte1;
98081860b9SGuo Ren 
99081860b9SGuo Ren 	if (ldb_asm(addr, &byte0))
100081860b9SGuo Ren 		return 1;
101081860b9SGuo Ren 	addr += 1;
102081860b9SGuo Ren 	if (ldb_asm(addr, &byte1))
103081860b9SGuo Ren 		return 1;
104081860b9SGuo Ren 
105081860b9SGuo Ren 	byte0 |= byte1 << 8;
106081860b9SGuo Ren 	put_ptreg(regs, rz, byte0);
107081860b9SGuo Ren 
108081860b9SGuo Ren 	return 0;
109081860b9SGuo Ren }
110081860b9SGuo Ren 
111081860b9SGuo Ren /*
112081860b9SGuo Ren  * Store half-word to [rx + imm]
113081860b9SGuo Ren  *
114081860b9SGuo Ren  * Success: return 0
115081860b9SGuo Ren  * Failure: return 1
116081860b9SGuo Ren  */
sth_c(struct pt_regs * regs,uint32_t rz,uint32_t addr)117081860b9SGuo Ren static int sth_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
118081860b9SGuo Ren {
119081860b9SGuo Ren 	uint32_t byte0, byte1;
120081860b9SGuo Ren 
121081860b9SGuo Ren 	byte0 = byte1 = get_ptreg(regs, rz);
122081860b9SGuo Ren 
123081860b9SGuo Ren 	byte0 &= 0xff;
124081860b9SGuo Ren 
125081860b9SGuo Ren 	if (stb_asm(addr, byte0))
126081860b9SGuo Ren 		return 1;
127081860b9SGuo Ren 
128081860b9SGuo Ren 	addr += 1;
129081860b9SGuo Ren 	byte1 = (byte1 >> 8) & 0xff;
130081860b9SGuo Ren 	if (stb_asm(addr, byte1))
131081860b9SGuo Ren 		return 1;
132081860b9SGuo Ren 
133081860b9SGuo Ren 	return 0;
134081860b9SGuo Ren }
135081860b9SGuo Ren 
136081860b9SGuo Ren /*
137081860b9SGuo Ren  * Get word from [rx + imm]
138081860b9SGuo Ren  *
139081860b9SGuo Ren  * Success: return 0
140081860b9SGuo Ren  * Failure: return 1
141081860b9SGuo Ren  */
ldw_c(struct pt_regs * regs,uint32_t rz,uint32_t addr)142081860b9SGuo Ren static int ldw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
143081860b9SGuo Ren {
144081860b9SGuo Ren 	uint32_t byte0, byte1, byte2, byte3;
145081860b9SGuo Ren 
146081860b9SGuo Ren 	if (ldb_asm(addr, &byte0))
147081860b9SGuo Ren 		return 1;
148081860b9SGuo Ren 
149081860b9SGuo Ren 	addr += 1;
150081860b9SGuo Ren 	if (ldb_asm(addr, &byte1))
151081860b9SGuo Ren 		return 1;
152081860b9SGuo Ren 
153081860b9SGuo Ren 	addr += 1;
154081860b9SGuo Ren 	if (ldb_asm(addr, &byte2))
155081860b9SGuo Ren 		return 1;
156081860b9SGuo Ren 
157081860b9SGuo Ren 	addr += 1;
158081860b9SGuo Ren 	if (ldb_asm(addr, &byte3))
159081860b9SGuo Ren 		return 1;
160081860b9SGuo Ren 
161081860b9SGuo Ren 	byte0 |= byte1 << 8;
162081860b9SGuo Ren 	byte0 |= byte2 << 16;
163081860b9SGuo Ren 	byte0 |= byte3 << 24;
164081860b9SGuo Ren 
165081860b9SGuo Ren 	put_ptreg(regs, rz, byte0);
166081860b9SGuo Ren 
167081860b9SGuo Ren 	return 0;
168081860b9SGuo Ren }
169081860b9SGuo Ren 
170081860b9SGuo Ren /*
171081860b9SGuo Ren  * Store word to [rx + imm]
172081860b9SGuo Ren  *
173081860b9SGuo Ren  * Success: return 0
174081860b9SGuo Ren  * Failure: return 1
175081860b9SGuo Ren  */
stw_c(struct pt_regs * regs,uint32_t rz,uint32_t addr)176081860b9SGuo Ren static int stw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
177081860b9SGuo Ren {
178081860b9SGuo Ren 	uint32_t byte0, byte1, byte2, byte3;
179081860b9SGuo Ren 
180081860b9SGuo Ren 	byte0 = byte1 = byte2 = byte3 = get_ptreg(regs, rz);
181081860b9SGuo Ren 
182081860b9SGuo Ren 	byte0 &= 0xff;
183081860b9SGuo Ren 
184081860b9SGuo Ren 	if (stb_asm(addr, byte0))
185081860b9SGuo Ren 		return 1;
186081860b9SGuo Ren 
187081860b9SGuo Ren 	addr += 1;
188081860b9SGuo Ren 	byte1 = (byte1 >> 8) & 0xff;
189081860b9SGuo Ren 	if (stb_asm(addr, byte1))
190081860b9SGuo Ren 		return 1;
191081860b9SGuo Ren 
192081860b9SGuo Ren 	addr += 1;
193081860b9SGuo Ren 	byte2 = (byte2 >> 16) & 0xff;
194081860b9SGuo Ren 	if (stb_asm(addr, byte2))
195081860b9SGuo Ren 		return 1;
196081860b9SGuo Ren 
197081860b9SGuo Ren 	addr += 1;
198081860b9SGuo Ren 	byte3 = (byte3 >> 24) & 0xff;
199081860b9SGuo Ren 	if (stb_asm(addr, byte3))
200081860b9SGuo Ren 		return 1;
201081860b9SGuo Ren 
202081860b9SGuo Ren 	return 0;
203081860b9SGuo Ren }
204081860b9SGuo Ren 
205081860b9SGuo Ren extern int fixup_exception(struct pt_regs *regs);
206081860b9SGuo Ren 
207081860b9SGuo Ren #define OP_LDH 0xc000
208081860b9SGuo Ren #define OP_STH 0xd000
209081860b9SGuo Ren #define OP_LDW 0x8000
210081860b9SGuo Ren #define OP_STW 0x9000
211081860b9SGuo Ren 
csky_alignment(struct pt_regs * regs)212081860b9SGuo Ren void csky_alignment(struct pt_regs *regs)
213081860b9SGuo Ren {
214081860b9SGuo Ren 	int ret;
215081860b9SGuo Ren 	uint16_t tmp;
216081860b9SGuo Ren 	uint32_t opcode = 0;
217081860b9SGuo Ren 	uint32_t rx     = 0;
218081860b9SGuo Ren 	uint32_t rz     = 0;
219081860b9SGuo Ren 	uint32_t imm    = 0;
220081860b9SGuo Ren 	uint32_t addr   = 0;
221081860b9SGuo Ren 
222081860b9SGuo Ren 	if (!user_mode(regs))
223c7e6f0e9SGuo Ren 		goto kernel_area;
224c7e6f0e9SGuo Ren 
225c7e6f0e9SGuo Ren 	if (!align_usr_enable) {
226c7e6f0e9SGuo Ren 		pr_err("%s user disabled.\n", __func__);
227081860b9SGuo Ren 		goto bad_area;
228c7e6f0e9SGuo Ren 	}
229c7e6f0e9SGuo Ren 
230c7e6f0e9SGuo Ren 	align_usr_count++;
231081860b9SGuo Ren 
232081860b9SGuo Ren 	ret = get_user(tmp, (uint16_t *)instruction_pointer(regs));
233081860b9SGuo Ren 	if (ret) {
234081860b9SGuo Ren 		pr_err("%s get_user failed.\n", __func__);
235081860b9SGuo Ren 		goto bad_area;
236081860b9SGuo Ren 	}
237081860b9SGuo Ren 
238c7e6f0e9SGuo Ren 	goto good_area;
239c7e6f0e9SGuo Ren 
240c7e6f0e9SGuo Ren kernel_area:
241c7e6f0e9SGuo Ren 	if (!align_kern_enable) {
242c7e6f0e9SGuo Ren 		pr_err("%s kernel disabled.\n", __func__);
243c7e6f0e9SGuo Ren 		goto bad_area;
244c7e6f0e9SGuo Ren 	}
245c7e6f0e9SGuo Ren 
246c7e6f0e9SGuo Ren 	align_kern_count++;
247c7e6f0e9SGuo Ren 
248c7e6f0e9SGuo Ren 	tmp = *(uint16_t *)instruction_pointer(regs);
249c7e6f0e9SGuo Ren 
250c7e6f0e9SGuo Ren good_area:
251081860b9SGuo Ren 	opcode = (uint32_t)tmp;
252081860b9SGuo Ren 
253081860b9SGuo Ren 	rx  = opcode & 0xf;
254081860b9SGuo Ren 	imm = (opcode >> 4) & 0xf;
255081860b9SGuo Ren 	rz  = (opcode >> 8) & 0xf;
256081860b9SGuo Ren 	opcode &= 0xf000;
257081860b9SGuo Ren 
258081860b9SGuo Ren 	if (rx == 0 || rx == 1 || rz == 0 || rz == 1)
259081860b9SGuo Ren 		goto bad_area;
260081860b9SGuo Ren 
261081860b9SGuo Ren 	switch (opcode) {
262081860b9SGuo Ren 	case OP_LDH:
263081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 1);
264081860b9SGuo Ren 		ret = ldh_c(regs, rz, addr);
265081860b9SGuo Ren 		break;
266081860b9SGuo Ren 	case OP_LDW:
267081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 2);
268081860b9SGuo Ren 		ret = ldw_c(regs, rz, addr);
269081860b9SGuo Ren 		break;
270081860b9SGuo Ren 	case OP_STH:
271081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 1);
272081860b9SGuo Ren 		ret = sth_c(regs, rz, addr);
273081860b9SGuo Ren 		break;
274081860b9SGuo Ren 	case OP_STW:
275081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 2);
276081860b9SGuo Ren 		ret = stw_c(regs, rz, addr);
277081860b9SGuo Ren 		break;
278081860b9SGuo Ren 	}
279081860b9SGuo Ren 
280081860b9SGuo Ren 	if (ret)
281081860b9SGuo Ren 		goto bad_area;
282081860b9SGuo Ren 
283081860b9SGuo Ren 	regs->pc += 2;
284081860b9SGuo Ren 
285081860b9SGuo Ren 	return;
286081860b9SGuo Ren 
287081860b9SGuo Ren bad_area:
288081860b9SGuo Ren 	if (!user_mode(regs)) {
289081860b9SGuo Ren 		if (fixup_exception(regs))
290081860b9SGuo Ren 			return;
291081860b9SGuo Ren 
292081860b9SGuo Ren 		bust_spinlocks(1);
293081860b9SGuo Ren 		pr_alert("%s opcode: %x, rz: %d, rx: %d, imm: %d, addr: %x.\n",
294081860b9SGuo Ren 				__func__, opcode, rz, rx, imm, addr);
295081860b9SGuo Ren 		show_regs(regs);
296081860b9SGuo Ren 		bust_spinlocks(0);
297751971afSNathan Chancellor 		make_task_dead(SIGKILL);
298081860b9SGuo Ren 	}
299081860b9SGuo Ren 
3002e1661d2SEric W. Biederman 	force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)addr);
301081860b9SGuo Ren }
302081860b9SGuo Ren 
303c7e6f0e9SGuo Ren static struct ctl_table alignment_tbl[5] = {
304081860b9SGuo Ren 	{
305c7e6f0e9SGuo Ren 		.procname = "kernel_enable",
306c7e6f0e9SGuo Ren 		.data = &align_kern_enable,
307c7e6f0e9SGuo Ren 		.maxlen = sizeof(align_kern_enable),
308081860b9SGuo Ren 		.mode = 0666,
309081860b9SGuo Ren 		.proc_handler = &proc_dointvec
310081860b9SGuo Ren 	},
311081860b9SGuo Ren 	{
312c7e6f0e9SGuo Ren 		.procname = "user_enable",
313c7e6f0e9SGuo Ren 		.data = &align_usr_enable,
314c7e6f0e9SGuo Ren 		.maxlen = sizeof(align_usr_enable),
315c7e6f0e9SGuo Ren 		.mode = 0666,
316c7e6f0e9SGuo Ren 		.proc_handler = &proc_dointvec
317c7e6f0e9SGuo Ren 	},
318c7e6f0e9SGuo Ren 	{
319c7e6f0e9SGuo Ren 		.procname = "kernel_count",
320c7e6f0e9SGuo Ren 		.data = &align_kern_count,
321c7e6f0e9SGuo Ren 		.maxlen = sizeof(align_kern_count),
322c7e6f0e9SGuo Ren 		.mode = 0666,
323c7e6f0e9SGuo Ren 		.proc_handler = &proc_dointvec
324c7e6f0e9SGuo Ren 	},
325c7e6f0e9SGuo Ren 	{
326c7e6f0e9SGuo Ren 		.procname = "user_count",
327c7e6f0e9SGuo Ren 		.data = &align_usr_count,
328c7e6f0e9SGuo Ren 		.maxlen = sizeof(align_usr_count),
329081860b9SGuo Ren 		.mode = 0666,
330081860b9SGuo Ren 		.proc_handler = &proc_dointvec
331081860b9SGuo Ren 	},
332081860b9SGuo Ren 	{}
333081860b9SGuo Ren };
334081860b9SGuo Ren 
csky_alignment_init(void)335081860b9SGuo Ren static int __init csky_alignment_init(void)
336081860b9SGuo Ren {
337*adf11ea8SLuis Chamberlain 	register_sysctl_init("csky/csky_alignment", alignment_tbl);
338081860b9SGuo Ren 	return 0;
339081860b9SGuo Ren }
340081860b9SGuo Ren 
341081860b9SGuo Ren arch_initcall(csky_alignment_init);
342