xref: /openbmc/linux/arch/csky/abiv1/alignment.c (revision 2e1661d2673667d886cd40ad9f414cb6db48d8da)
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 
8081860b9SGuo Ren static int align_enable = 1;
9081860b9SGuo Ren static int align_count;
10081860b9SGuo Ren 
11081860b9SGuo Ren static inline uint32_t get_ptreg(struct pt_regs *regs, uint32_t rx)
12081860b9SGuo Ren {
13081860b9SGuo Ren 	return rx == 15 ? regs->lr : *((uint32_t *)&(regs->a0) - 2 + rx);
14081860b9SGuo Ren }
15081860b9SGuo Ren 
16081860b9SGuo Ren static inline void put_ptreg(struct pt_regs *regs, uint32_t rx, uint32_t val)
17081860b9SGuo Ren {
18081860b9SGuo Ren 	if (rx == 15)
19081860b9SGuo Ren 		regs->lr = val;
20081860b9SGuo Ren 	else
21081860b9SGuo Ren 		*((uint32_t *)&(regs->a0) - 2 + rx) = val;
22081860b9SGuo Ren }
23081860b9SGuo Ren 
24081860b9SGuo Ren /*
25081860b9SGuo Ren  * Get byte-value from addr and set it to *valp.
26081860b9SGuo Ren  *
27081860b9SGuo Ren  * Success: return 0
28081860b9SGuo Ren  * Failure: return 1
29081860b9SGuo Ren  */
30081860b9SGuo Ren static int ldb_asm(uint32_t addr, uint32_t *valp)
31081860b9SGuo Ren {
32081860b9SGuo Ren 	uint32_t val;
33081860b9SGuo Ren 	int err;
34081860b9SGuo Ren 
3596d4f267SLinus Torvalds 	if (!access_ok((void *)addr, 1))
36081860b9SGuo Ren 		return 1;
37081860b9SGuo Ren 
38081860b9SGuo Ren 	asm volatile (
39081860b9SGuo Ren 		"movi	%0, 0\n"
40081860b9SGuo Ren 		"1:\n"
41081860b9SGuo Ren 		"ldb	%1, (%2)\n"
42081860b9SGuo Ren 		"br	3f\n"
43081860b9SGuo Ren 		"2:\n"
44081860b9SGuo Ren 		"movi	%0, 1\n"
45081860b9SGuo Ren 		"br	3f\n"
46081860b9SGuo Ren 		".section __ex_table,\"a\"\n"
47081860b9SGuo Ren 		".align 2\n"
48081860b9SGuo Ren 		".long	1b, 2b\n"
49081860b9SGuo Ren 		".previous\n"
50081860b9SGuo Ren 		"3:\n"
51081860b9SGuo Ren 		: "=&r"(err), "=r"(val)
52081860b9SGuo Ren 		: "r" (addr)
53081860b9SGuo Ren 	);
54081860b9SGuo Ren 
55081860b9SGuo Ren 	*valp = val;
56081860b9SGuo Ren 
57081860b9SGuo Ren 	return err;
58081860b9SGuo Ren }
59081860b9SGuo Ren 
60081860b9SGuo Ren /*
61081860b9SGuo Ren  * Put byte-value to addr.
62081860b9SGuo Ren  *
63081860b9SGuo Ren  * Success: return 0
64081860b9SGuo Ren  * Failure: return 1
65081860b9SGuo Ren  */
66081860b9SGuo Ren static int stb_asm(uint32_t addr, uint32_t val)
67081860b9SGuo Ren {
68081860b9SGuo Ren 	int err;
69081860b9SGuo Ren 
7096d4f267SLinus Torvalds 	if (!access_ok((void *)addr, 1))
71081860b9SGuo Ren 		return 1;
72081860b9SGuo Ren 
73081860b9SGuo Ren 	asm volatile (
74081860b9SGuo Ren 		"movi	%0, 0\n"
75081860b9SGuo Ren 		"1:\n"
76081860b9SGuo Ren 		"stb	%1, (%2)\n"
77081860b9SGuo Ren 		"br	3f\n"
78081860b9SGuo Ren 		"2:\n"
79081860b9SGuo Ren 		"movi	%0, 1\n"
80081860b9SGuo Ren 		"br	3f\n"
81081860b9SGuo Ren 		".section __ex_table,\"a\"\n"
82081860b9SGuo Ren 		".align 2\n"
83081860b9SGuo Ren 		".long	1b, 2b\n"
84081860b9SGuo Ren 		".previous\n"
85081860b9SGuo Ren 		"3:\n"
86081860b9SGuo Ren 		: "=&r"(err)
87081860b9SGuo Ren 		: "r"(val), "r" (addr)
88081860b9SGuo Ren 	);
89081860b9SGuo Ren 
90081860b9SGuo Ren 	return err;
91081860b9SGuo Ren }
92081860b9SGuo Ren 
93081860b9SGuo Ren /*
94081860b9SGuo Ren  * Get half-word from [rx + imm]
95081860b9SGuo Ren  *
96081860b9SGuo Ren  * Success: return 0
97081860b9SGuo Ren  * Failure: return 1
98081860b9SGuo Ren  */
99081860b9SGuo Ren static int ldh_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
100081860b9SGuo Ren {
101081860b9SGuo Ren 	uint32_t byte0, byte1;
102081860b9SGuo Ren 
103081860b9SGuo Ren 	if (ldb_asm(addr, &byte0))
104081860b9SGuo Ren 		return 1;
105081860b9SGuo Ren 	addr += 1;
106081860b9SGuo Ren 	if (ldb_asm(addr, &byte1))
107081860b9SGuo Ren 		return 1;
108081860b9SGuo Ren 
109081860b9SGuo Ren 	byte0 |= byte1 << 8;
110081860b9SGuo Ren 	put_ptreg(regs, rz, byte0);
111081860b9SGuo Ren 
112081860b9SGuo Ren 	return 0;
113081860b9SGuo Ren }
114081860b9SGuo Ren 
115081860b9SGuo Ren /*
116081860b9SGuo Ren  * Store half-word to [rx + imm]
117081860b9SGuo Ren  *
118081860b9SGuo Ren  * Success: return 0
119081860b9SGuo Ren  * Failure: return 1
120081860b9SGuo Ren  */
121081860b9SGuo Ren static int sth_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
122081860b9SGuo Ren {
123081860b9SGuo Ren 	uint32_t byte0, byte1;
124081860b9SGuo Ren 
125081860b9SGuo Ren 	byte0 = byte1 = get_ptreg(regs, rz);
126081860b9SGuo Ren 
127081860b9SGuo Ren 	byte0 &= 0xff;
128081860b9SGuo Ren 
129081860b9SGuo Ren 	if (stb_asm(addr, byte0))
130081860b9SGuo Ren 		return 1;
131081860b9SGuo Ren 
132081860b9SGuo Ren 	addr += 1;
133081860b9SGuo Ren 	byte1 = (byte1 >> 8) & 0xff;
134081860b9SGuo Ren 	if (stb_asm(addr, byte1))
135081860b9SGuo Ren 		return 1;
136081860b9SGuo Ren 
137081860b9SGuo Ren 	return 0;
138081860b9SGuo Ren }
139081860b9SGuo Ren 
140081860b9SGuo Ren /*
141081860b9SGuo Ren  * Get word from [rx + imm]
142081860b9SGuo Ren  *
143081860b9SGuo Ren  * Success: return 0
144081860b9SGuo Ren  * Failure: return 1
145081860b9SGuo Ren  */
146081860b9SGuo Ren static int ldw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
147081860b9SGuo Ren {
148081860b9SGuo Ren 	uint32_t byte0, byte1, byte2, byte3;
149081860b9SGuo Ren 
150081860b9SGuo Ren 	if (ldb_asm(addr, &byte0))
151081860b9SGuo Ren 		return 1;
152081860b9SGuo Ren 
153081860b9SGuo Ren 	addr += 1;
154081860b9SGuo Ren 	if (ldb_asm(addr, &byte1))
155081860b9SGuo Ren 		return 1;
156081860b9SGuo Ren 
157081860b9SGuo Ren 	addr += 1;
158081860b9SGuo Ren 	if (ldb_asm(addr, &byte2))
159081860b9SGuo Ren 		return 1;
160081860b9SGuo Ren 
161081860b9SGuo Ren 	addr += 1;
162081860b9SGuo Ren 	if (ldb_asm(addr, &byte3))
163081860b9SGuo Ren 		return 1;
164081860b9SGuo Ren 
165081860b9SGuo Ren 	byte0 |= byte1 << 8;
166081860b9SGuo Ren 	byte0 |= byte2 << 16;
167081860b9SGuo Ren 	byte0 |= byte3 << 24;
168081860b9SGuo Ren 
169081860b9SGuo Ren 	put_ptreg(regs, rz, byte0);
170081860b9SGuo Ren 
171081860b9SGuo Ren 	return 0;
172081860b9SGuo Ren }
173081860b9SGuo Ren 
174081860b9SGuo Ren /*
175081860b9SGuo Ren  * Store word to [rx + imm]
176081860b9SGuo Ren  *
177081860b9SGuo Ren  * Success: return 0
178081860b9SGuo Ren  * Failure: return 1
179081860b9SGuo Ren  */
180081860b9SGuo Ren static int stw_c(struct pt_regs *regs, uint32_t rz, uint32_t addr)
181081860b9SGuo Ren {
182081860b9SGuo Ren 	uint32_t byte0, byte1, byte2, byte3;
183081860b9SGuo Ren 
184081860b9SGuo Ren 	byte0 = byte1 = byte2 = byte3 = get_ptreg(regs, rz);
185081860b9SGuo Ren 
186081860b9SGuo Ren 	byte0 &= 0xff;
187081860b9SGuo Ren 
188081860b9SGuo Ren 	if (stb_asm(addr, byte0))
189081860b9SGuo Ren 		return 1;
190081860b9SGuo Ren 
191081860b9SGuo Ren 	addr += 1;
192081860b9SGuo Ren 	byte1 = (byte1 >> 8) & 0xff;
193081860b9SGuo Ren 	if (stb_asm(addr, byte1))
194081860b9SGuo Ren 		return 1;
195081860b9SGuo Ren 
196081860b9SGuo Ren 	addr += 1;
197081860b9SGuo Ren 	byte2 = (byte2 >> 16) & 0xff;
198081860b9SGuo Ren 	if (stb_asm(addr, byte2))
199081860b9SGuo Ren 		return 1;
200081860b9SGuo Ren 
201081860b9SGuo Ren 	addr += 1;
202081860b9SGuo Ren 	byte3 = (byte3 >> 24) & 0xff;
203081860b9SGuo Ren 	if (stb_asm(addr, byte3))
204081860b9SGuo Ren 		return 1;
205081860b9SGuo Ren 
206081860b9SGuo Ren 	align_count++;
207081860b9SGuo Ren 
208081860b9SGuo Ren 	return 0;
209081860b9SGuo Ren }
210081860b9SGuo Ren 
211081860b9SGuo Ren extern int fixup_exception(struct pt_regs *regs);
212081860b9SGuo Ren 
213081860b9SGuo Ren #define OP_LDH 0xc000
214081860b9SGuo Ren #define OP_STH 0xd000
215081860b9SGuo Ren #define OP_LDW 0x8000
216081860b9SGuo Ren #define OP_STW 0x9000
217081860b9SGuo Ren 
218081860b9SGuo Ren void csky_alignment(struct pt_regs *regs)
219081860b9SGuo Ren {
220081860b9SGuo Ren 	int ret;
221081860b9SGuo Ren 	uint16_t tmp;
222081860b9SGuo Ren 	uint32_t opcode = 0;
223081860b9SGuo Ren 	uint32_t rx     = 0;
224081860b9SGuo Ren 	uint32_t rz     = 0;
225081860b9SGuo Ren 	uint32_t imm    = 0;
226081860b9SGuo Ren 	uint32_t addr   = 0;
227081860b9SGuo Ren 
228081860b9SGuo Ren 	if (!user_mode(regs))
229081860b9SGuo Ren 		goto bad_area;
230081860b9SGuo Ren 
231081860b9SGuo Ren 	ret = get_user(tmp, (uint16_t *)instruction_pointer(regs));
232081860b9SGuo Ren 	if (ret) {
233081860b9SGuo Ren 		pr_err("%s get_user failed.\n", __func__);
234081860b9SGuo Ren 		goto bad_area;
235081860b9SGuo Ren 	}
236081860b9SGuo Ren 
237081860b9SGuo Ren 	opcode = (uint32_t)tmp;
238081860b9SGuo Ren 
239081860b9SGuo Ren 	rx  = opcode & 0xf;
240081860b9SGuo Ren 	imm = (opcode >> 4) & 0xf;
241081860b9SGuo Ren 	rz  = (opcode >> 8) & 0xf;
242081860b9SGuo Ren 	opcode &= 0xf000;
243081860b9SGuo Ren 
244081860b9SGuo Ren 	if (rx == 0 || rx == 1 || rz == 0 || rz == 1)
245081860b9SGuo Ren 		goto bad_area;
246081860b9SGuo Ren 
247081860b9SGuo Ren 	switch (opcode) {
248081860b9SGuo Ren 	case OP_LDH:
249081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 1);
250081860b9SGuo Ren 		ret = ldh_c(regs, rz, addr);
251081860b9SGuo Ren 		break;
252081860b9SGuo Ren 	case OP_LDW:
253081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 2);
254081860b9SGuo Ren 		ret = ldw_c(regs, rz, addr);
255081860b9SGuo Ren 		break;
256081860b9SGuo Ren 	case OP_STH:
257081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 1);
258081860b9SGuo Ren 		ret = sth_c(regs, rz, addr);
259081860b9SGuo Ren 		break;
260081860b9SGuo Ren 	case OP_STW:
261081860b9SGuo Ren 		addr = get_ptreg(regs, rx) + (imm << 2);
262081860b9SGuo Ren 		ret = stw_c(regs, rz, addr);
263081860b9SGuo Ren 		break;
264081860b9SGuo Ren 	}
265081860b9SGuo Ren 
266081860b9SGuo Ren 	if (ret)
267081860b9SGuo Ren 		goto bad_area;
268081860b9SGuo Ren 
269081860b9SGuo Ren 	regs->pc += 2;
270081860b9SGuo Ren 
271081860b9SGuo Ren 	return;
272081860b9SGuo Ren 
273081860b9SGuo Ren bad_area:
274081860b9SGuo Ren 	if (!user_mode(regs)) {
275081860b9SGuo Ren 		if (fixup_exception(regs))
276081860b9SGuo Ren 			return;
277081860b9SGuo Ren 
278081860b9SGuo Ren 		bust_spinlocks(1);
279081860b9SGuo Ren 		pr_alert("%s opcode: %x, rz: %d, rx: %d, imm: %d, addr: %x.\n",
280081860b9SGuo Ren 				__func__, opcode, rz, rx, imm, addr);
281081860b9SGuo Ren 		show_regs(regs);
282081860b9SGuo Ren 		bust_spinlocks(0);
283081860b9SGuo Ren 		do_exit(SIGKILL);
284081860b9SGuo Ren 	}
285081860b9SGuo Ren 
286*2e1661d2SEric W. Biederman 	force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)addr);
287081860b9SGuo Ren }
288081860b9SGuo Ren 
289081860b9SGuo Ren static struct ctl_table alignment_tbl[4] = {
290081860b9SGuo Ren 	{
291081860b9SGuo Ren 		.procname = "enable",
292081860b9SGuo Ren 		.data = &align_enable,
293081860b9SGuo Ren 		.maxlen = sizeof(align_enable),
294081860b9SGuo Ren 		.mode = 0666,
295081860b9SGuo Ren 		.proc_handler = &proc_dointvec
296081860b9SGuo Ren 	},
297081860b9SGuo Ren 	{
298081860b9SGuo Ren 		.procname = "count",
299081860b9SGuo Ren 		.data = &align_count,
300081860b9SGuo Ren 		.maxlen = sizeof(align_count),
301081860b9SGuo Ren 		.mode = 0666,
302081860b9SGuo Ren 		.proc_handler = &proc_dointvec
303081860b9SGuo Ren 	},
304081860b9SGuo Ren 	{}
305081860b9SGuo Ren };
306081860b9SGuo Ren 
307081860b9SGuo Ren static struct ctl_table sysctl_table[2] = {
308081860b9SGuo Ren 	{
309081860b9SGuo Ren 	 .procname = "csky_alignment",
310081860b9SGuo Ren 	 .mode = 0555,
311081860b9SGuo Ren 	 .child = alignment_tbl},
312081860b9SGuo Ren 	{}
313081860b9SGuo Ren };
314081860b9SGuo Ren 
315081860b9SGuo Ren static struct ctl_path sysctl_path[2] = {
316081860b9SGuo Ren 	{.procname = "csky"},
317081860b9SGuo Ren 	{}
318081860b9SGuo Ren };
319081860b9SGuo Ren 
320081860b9SGuo Ren static int __init csky_alignment_init(void)
321081860b9SGuo Ren {
322081860b9SGuo Ren 	register_sysctl_paths(sysctl_path, sysctl_table);
323081860b9SGuo Ren 	return 0;
324081860b9SGuo Ren }
325081860b9SGuo Ren 
326081860b9SGuo Ren arch_initcall(csky_alignment_init);
327