1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
5 #include <linux/sizes.h>
6 #include <linux/uaccess.h>
8 #include <asm/cacheflush.h>
11 static DEFINE_RAW_SPINLOCK(patch_lock);
13 void simu_pc(struct pt_regs *regs, union loongarch_instruction insn)
15 unsigned long pc = regs->csr_era;
16 unsigned int rd = insn.reg1i20_format.rd;
17 unsigned int imm = insn.reg1i20_format.immediate;
20 pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
24 switch (insn.reg1i20_format.opcode) {
26 regs->regs[rd] = pc + sign_extend64(imm << 2, 21);
29 regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
32 regs->regs[rd] = pc + sign_extend64(imm << 18, 37);
35 regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
36 regs->regs[rd] &= ~((1 << 12) - 1);
39 pr_info("%s: unknown opcode\n", __func__);
43 regs->csr_era += LOONGARCH_INSN_SIZE;
46 void simu_branch(struct pt_regs *regs, union loongarch_instruction insn)
48 unsigned int imm, imm_l, imm_h, rd, rj;
49 unsigned long pc = regs->csr_era;
52 pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
56 imm_l = insn.reg0i26_format.immediate_l;
57 imm_h = insn.reg0i26_format.immediate_h;
58 switch (insn.reg0i26_format.opcode) {
60 regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
63 regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
64 regs->regs[1] = pc + LOONGARCH_INSN_SIZE;
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) {
73 if (regs->regs[rj] == 0)
74 regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
76 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
79 if (regs->regs[rj] != 0)
80 regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
82 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
86 imm = insn.reg2i16_format.immediate;
87 rj = insn.reg2i16_format.rj;
88 rd = insn.reg2i16_format.rd;
89 switch (insn.reg2i16_format.opcode) {
91 if (regs->regs[rj] == regs->regs[rd])
92 regs->csr_era = pc + sign_extend64(imm << 2, 17);
94 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
97 if (regs->regs[rj] != regs->regs[rd])
98 regs->csr_era = pc + sign_extend64(imm << 2, 17);
100 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
103 if ((long)regs->regs[rj] < (long)regs->regs[rd])
104 regs->csr_era = pc + sign_extend64(imm << 2, 17);
106 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
109 if ((long)regs->regs[rj] >= (long)regs->regs[rd])
110 regs->csr_era = pc + sign_extend64(imm << 2, 17);
112 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
115 if (regs->regs[rj] < regs->regs[rd])
116 regs->csr_era = pc + sign_extend64(imm << 2, 17);
118 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
121 if (regs->regs[rj] >= regs->regs[rd])
122 regs->csr_era = pc + sign_extend64(imm << 2, 17);
124 regs->csr_era = pc + LOONGARCH_INSN_SIZE;
127 regs->csr_era = regs->regs[rj] + sign_extend64(imm << 2, 17);
128 regs->regs[rd] = pc + LOONGARCH_INSN_SIZE;
131 pr_info("%s: unknown opcode\n", __func__);
136 bool insns_not_supported(union loongarch_instruction insn)
138 switch (insn.reg3_format.opcode) {
139 case amswapw_op ... ammindbdu_op:
140 pr_notice("atomic memory access instructions are not supported\n");
144 switch (insn.reg2i14_format.opcode) {
149 pr_notice("ll and sc instructions are not supported\n");
153 switch (insn.reg1i21_format.opcode) {
155 pr_notice("bceqz and bcnez instructions are not supported\n");
162 bool insns_need_simulation(union loongarch_instruction insn)
164 if (is_pc_ins(&insn))
167 if (is_branch_ins(&insn))
173 void arch_simulate_insn(union loongarch_instruction insn, struct pt_regs *regs)
175 if (is_pc_ins(&insn))
177 else if (is_branch_ins(&insn))
178 simu_branch(regs, insn);
181 int larch_insn_read(void *addr, u32 *insnp)
186 ret = copy_from_kernel_nofault(&val, addr, LOONGARCH_INSN_SIZE);
193 int larch_insn_write(void *addr, u32 insn)
196 unsigned long flags = 0;
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);
205 int larch_insn_patch_text(void *addr, u32 insn)
210 if ((unsigned long)tp & 3)
213 ret = larch_insn_write(tp, insn);
215 flush_icache_range((unsigned long)tp,
216 (unsigned long)tp + LOONGARCH_INSN_SIZE);
221 u32 larch_insn_gen_nop(void)
226 u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
228 long offset = dest - pc;
229 union loongarch_instruction insn;
231 if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
232 pr_warn("The generated b instruction is out of range.\n");
236 emit_b(&insn, offset >> 2);
241 u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
243 long offset = dest - pc;
244 union loongarch_instruction insn;
246 if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
247 pr_warn("The generated bl instruction is out of range.\n");
251 emit_bl(&insn, offset >> 2);
256 u32 larch_insn_gen_break(int imm)
258 union loongarch_instruction insn;
260 if (imm < 0 || imm >= SZ_32K) {
261 pr_warn("The generated break instruction is out of range.\n");
265 emit_break(&insn, imm);
270 u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongarch_gpr rk)
272 union loongarch_instruction insn;
274 emit_or(&insn, rd, rj, rk);
279 u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
281 return larch_insn_gen_or(rd, rj, 0);
284 u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
286 union loongarch_instruction insn;
288 if (imm < -SZ_512K || imm >= SZ_512K) {
289 pr_warn("The generated lu12i.w instruction is out of range.\n");
293 emit_lu12iw(&insn, rd, imm);
298 u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
300 union loongarch_instruction insn;
302 if (imm < -SZ_512K || imm >= SZ_512K) {
303 pr_warn("The generated lu32i.d instruction is out of range.\n");
307 emit_lu32id(&insn, rd, imm);
312 u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
314 union loongarch_instruction insn;
316 if (imm < -SZ_2K || imm >= SZ_2K) {
317 pr_warn("The generated lu52i.d instruction is out of range.\n");
321 emit_lu52id(&insn, rd, rj, imm);
326 u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
328 union loongarch_instruction insn;
330 if ((imm & 3) || imm < -SZ_128K || imm >= SZ_128K) {
331 pr_warn("The generated jirl instruction is out of range.\n");
335 emit_jirl(&insn, rj, rd, imm >> 2);