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