1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
4 */
5#include <linux/sizes.h>
6#include <linux/uaccess.h>
7
8#include <asm/cacheflush.h>
9#include <asm/inst.h>
10
11static DEFINE_RAW_SPINLOCK(patch_lock);
12
13void simu_pc(struct pt_regs *regs, union loongarch_instruction insn)
14{
15	unsigned long pc = regs->csr_era;
16	unsigned int rd = insn.reg1i20_format.rd;
17	unsigned int imm = insn.reg1i20_format.immediate;
18
19	if (pc & 3) {
20		pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
21		return;
22	}
23
24	switch (insn.reg1i20_format.opcode) {
25	case pcaddi_op:
26		regs->regs[rd] = pc + sign_extend64(imm << 2, 21);
27		break;
28	case pcaddu12i_op:
29		regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
30		break;
31	case pcaddu18i_op:
32		regs->regs[rd] = pc + sign_extend64(imm << 18, 37);
33		break;
34	case pcalau12i_op:
35		regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
36		regs->regs[rd] &= ~((1 << 12) - 1);
37		break;
38	default:
39		pr_info("%s: unknown opcode\n", __func__);
40		return;
41	}
42
43	regs->csr_era += LOONGARCH_INSN_SIZE;
44}
45
46void simu_branch(struct pt_regs *regs, union loongarch_instruction insn)
47{
48	unsigned int imm, imm_l, imm_h, rd, rj;
49	unsigned long pc = regs->csr_era;
50
51	if (pc & 3) {
52		pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
53		return;
54	}
55
56	imm_l = insn.reg0i26_format.immediate_l;
57	imm_h = insn.reg0i26_format.immediate_h;
58	switch (insn.reg0i26_format.opcode) {
59	case b_op:
60		regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
61		return;
62	case bl_op:
63		regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
64		regs->regs[1] = pc + LOONGARCH_INSN_SIZE;
65		return;
66	}
67
68	imm_l = insn.reg1i21_format.immediate_l;
69	imm_h = insn.reg1i21_format.immediate_h;
70	rj = insn.reg1i21_format.rj;
71	switch (insn.reg1i21_format.opcode) {
72	case beqz_op:
73		if (regs->regs[rj] == 0)
74			regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
75		else
76			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
77		return;
78	case bnez_op:
79		if (regs->regs[rj] != 0)
80			regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
81		else
82			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
83		return;
84	}
85
86	imm = insn.reg2i16_format.immediate;
87	rj = insn.reg2i16_format.rj;
88	rd = insn.reg2i16_format.rd;
89	switch (insn.reg2i16_format.opcode) {
90	case beq_op:
91		if (regs->regs[rj] == regs->regs[rd])
92			regs->csr_era = pc + sign_extend64(imm << 2, 17);
93		else
94			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
95		break;
96	case bne_op:
97		if (regs->regs[rj] != regs->regs[rd])
98			regs->csr_era = pc + sign_extend64(imm << 2, 17);
99		else
100			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
101		break;
102	case blt_op:
103		if ((long)regs->regs[rj] < (long)regs->regs[rd])
104			regs->csr_era = pc + sign_extend64(imm << 2, 17);
105		else
106			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
107		break;
108	case bge_op:
109		if ((long)regs->regs[rj] >= (long)regs->regs[rd])
110			regs->csr_era = pc + sign_extend64(imm << 2, 17);
111		else
112			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
113		break;
114	case bltu_op:
115		if (regs->regs[rj] < regs->regs[rd])
116			regs->csr_era = pc + sign_extend64(imm << 2, 17);
117		else
118			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
119		break;
120	case bgeu_op:
121		if (regs->regs[rj] >= regs->regs[rd])
122			regs->csr_era = pc + sign_extend64(imm << 2, 17);
123		else
124			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
125		break;
126	case jirl_op:
127		regs->csr_era = regs->regs[rj] + sign_extend64(imm << 2, 17);
128		regs->regs[rd] = pc + LOONGARCH_INSN_SIZE;
129		break;
130	default:
131		pr_info("%s: unknown opcode\n", __func__);
132		return;
133	}
134}
135
136bool insns_not_supported(union loongarch_instruction insn)
137{
138	switch (insn.reg3_format.opcode) {
139	case amswapw_op ... ammindbdu_op:
140		pr_notice("atomic memory access instructions are not supported\n");
141		return true;
142	}
143
144	switch (insn.reg2i14_format.opcode) {
145	case llw_op:
146	case lld_op:
147	case scw_op:
148	case scd_op:
149		pr_notice("ll and sc instructions are not supported\n");
150		return true;
151	}
152
153	switch (insn.reg1i21_format.opcode) {
154	case bceqz_op:
155		pr_notice("bceqz and bcnez instructions are not supported\n");
156		return true;
157	}
158
159	return false;
160}
161
162bool insns_need_simulation(union loongarch_instruction insn)
163{
164	if (is_pc_ins(&insn))
165		return true;
166
167	if (is_branch_ins(&insn))
168		return true;
169
170	return false;
171}
172
173void arch_simulate_insn(union loongarch_instruction insn, struct pt_regs *regs)
174{
175	if (is_pc_ins(&insn))
176		simu_pc(regs, insn);
177	else if (is_branch_ins(&insn))
178		simu_branch(regs, insn);
179}
180
181int larch_insn_read(void *addr, u32 *insnp)
182{
183	int ret;
184	u32 val;
185
186	ret = copy_from_kernel_nofault(&val, addr, LOONGARCH_INSN_SIZE);
187	if (!ret)
188		*insnp = val;
189
190	return ret;
191}
192
193int larch_insn_write(void *addr, u32 insn)
194{
195	int ret;
196	unsigned long flags = 0;
197
198	raw_spin_lock_irqsave(&patch_lock, flags);
199	ret = copy_to_kernel_nofault(addr, &insn, LOONGARCH_INSN_SIZE);
200	raw_spin_unlock_irqrestore(&patch_lock, flags);
201
202	return ret;
203}
204
205int larch_insn_patch_text(void *addr, u32 insn)
206{
207	int ret;
208	u32 *tp = addr;
209
210	if ((unsigned long)tp & 3)
211		return -EINVAL;
212
213	ret = larch_insn_write(tp, insn);
214	if (!ret)
215		flush_icache_range((unsigned long)tp,
216				   (unsigned long)tp + LOONGARCH_INSN_SIZE);
217
218	return ret;
219}
220
221u32 larch_insn_gen_nop(void)
222{
223	return INSN_NOP;
224}
225
226u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
227{
228	long offset = dest - pc;
229	union loongarch_instruction insn;
230
231	if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
232		pr_warn("The generated b instruction is out of range.\n");
233		return INSN_BREAK;
234	}
235
236	emit_b(&insn, offset >> 2);
237
238	return insn.word;
239}
240
241u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
242{
243	long offset = dest - pc;
244	union loongarch_instruction insn;
245
246	if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
247		pr_warn("The generated bl instruction is out of range.\n");
248		return INSN_BREAK;
249	}
250
251	emit_bl(&insn, offset >> 2);
252
253	return insn.word;
254}
255
256u32 larch_insn_gen_break(int imm)
257{
258	union loongarch_instruction insn;
259
260	if (imm < 0 || imm >= SZ_32K) {
261		pr_warn("The generated break instruction is out of range.\n");
262		return INSN_BREAK;
263	}
264
265	emit_break(&insn, imm);
266
267	return insn.word;
268}
269
270u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongarch_gpr rk)
271{
272	union loongarch_instruction insn;
273
274	emit_or(&insn, rd, rj, rk);
275
276	return insn.word;
277}
278
279u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
280{
281	return larch_insn_gen_or(rd, rj, 0);
282}
283
284u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
285{
286	union loongarch_instruction insn;
287
288	if (imm < -SZ_512K || imm >= SZ_512K) {
289		pr_warn("The generated lu12i.w instruction is out of range.\n");
290		return INSN_BREAK;
291	}
292
293	emit_lu12iw(&insn, rd, imm);
294
295	return insn.word;
296}
297
298u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
299{
300	union loongarch_instruction insn;
301
302	if (imm < -SZ_512K || imm >= SZ_512K) {
303		pr_warn("The generated lu32i.d instruction is out of range.\n");
304		return INSN_BREAK;
305	}
306
307	emit_lu32id(&insn, rd, imm);
308
309	return insn.word;
310}
311
312u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
313{
314	union loongarch_instruction insn;
315
316	if (imm < -SZ_2K || imm >= SZ_2K) {
317		pr_warn("The generated lu52i.d instruction is out of range.\n");
318		return INSN_BREAK;
319	}
320
321	emit_lu52id(&insn, rd, rj, imm);
322
323	return insn.word;
324}
325
326u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
327{
328	union loongarch_instruction insn;
329
330	if ((imm & 3) || imm < -SZ_128K || imm >= SZ_128K) {
331		pr_warn("The generated jirl instruction is out of range.\n");
332		return INSN_BREAK;
333	}
334
335	emit_jirl(&insn, rj, rd, imm >> 2);
336
337	return insn.word;
338}
339