xref: /openbmc/linux/arch/loongarch/kernel/inst.c (revision 476ec641)
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 
11 static DEFINE_RAW_SPINLOCK(patch_lock);
12 
13 void 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 
46 void 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 
136 bool 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 
162 bool 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 
173 void 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 
181 int 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 
193 int 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 
205 int 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 
221 u32 larch_insn_gen_nop(void)
222 {
223 	return INSN_NOP;
224 }
225 
226 u32 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 
241 u32 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 
256 u32 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 
270 u32 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 
279 u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
280 {
281 	return larch_insn_gen_or(rd, rj, 0);
282 }
283 
284 u32 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 
298 u32 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 
312 u32 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 
326 u32 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