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
simu_pc(struct pt_regs * regs,union loongarch_instruction insn)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
simu_branch(struct pt_regs * regs,union loongarch_instruction insn)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
larch_insn_read(void * addr,u32 * insnp)136 int larch_insn_read(void *addr, u32 *insnp)
137 {
138 int ret;
139 u32 val;
140
141 ret = copy_from_kernel_nofault(&val, addr, LOONGARCH_INSN_SIZE);
142 if (!ret)
143 *insnp = val;
144
145 return ret;
146 }
147
larch_insn_write(void * addr,u32 insn)148 int larch_insn_write(void *addr, u32 insn)
149 {
150 int ret;
151 unsigned long flags = 0;
152
153 raw_spin_lock_irqsave(&patch_lock, flags);
154 ret = copy_to_kernel_nofault(addr, &insn, LOONGARCH_INSN_SIZE);
155 raw_spin_unlock_irqrestore(&patch_lock, flags);
156
157 return ret;
158 }
159
larch_insn_patch_text(void * addr,u32 insn)160 int larch_insn_patch_text(void *addr, u32 insn)
161 {
162 int ret;
163 u32 *tp = addr;
164
165 if ((unsigned long)tp & 3)
166 return -EINVAL;
167
168 ret = larch_insn_write(tp, insn);
169 if (!ret)
170 flush_icache_range((unsigned long)tp,
171 (unsigned long)tp + LOONGARCH_INSN_SIZE);
172
173 return ret;
174 }
175
larch_insn_gen_nop(void)176 u32 larch_insn_gen_nop(void)
177 {
178 return INSN_NOP;
179 }
180
larch_insn_gen_b(unsigned long pc,unsigned long dest)181 u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
182 {
183 long offset = dest - pc;
184 union loongarch_instruction insn;
185
186 if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
187 pr_warn("The generated b instruction is out of range.\n");
188 return INSN_BREAK;
189 }
190
191 emit_b(&insn, offset >> 2);
192
193 return insn.word;
194 }
195
larch_insn_gen_bl(unsigned long pc,unsigned long dest)196 u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
197 {
198 long offset = dest - pc;
199 union loongarch_instruction insn;
200
201 if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
202 pr_warn("The generated bl instruction is out of range.\n");
203 return INSN_BREAK;
204 }
205
206 emit_bl(&insn, offset >> 2);
207
208 return insn.word;
209 }
210
larch_insn_gen_or(enum loongarch_gpr rd,enum loongarch_gpr rj,enum loongarch_gpr rk)211 u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongarch_gpr rk)
212 {
213 union loongarch_instruction insn;
214
215 emit_or(&insn, rd, rj, rk);
216
217 return insn.word;
218 }
219
larch_insn_gen_move(enum loongarch_gpr rd,enum loongarch_gpr rj)220 u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
221 {
222 return larch_insn_gen_or(rd, rj, 0);
223 }
224
larch_insn_gen_lu12iw(enum loongarch_gpr rd,int imm)225 u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
226 {
227 union loongarch_instruction insn;
228
229 emit_lu12iw(&insn, rd, imm);
230
231 return insn.word;
232 }
233
larch_insn_gen_lu32id(enum loongarch_gpr rd,int imm)234 u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
235 {
236 union loongarch_instruction insn;
237
238 emit_lu32id(&insn, rd, imm);
239
240 return insn.word;
241 }
242
larch_insn_gen_lu52id(enum loongarch_gpr rd,enum loongarch_gpr rj,int imm)243 u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
244 {
245 union loongarch_instruction insn;
246
247 emit_lu52id(&insn, rd, rj, imm);
248
249 return insn.word;
250 }
251
larch_insn_gen_jirl(enum loongarch_gpr rd,enum loongarch_gpr rj,unsigned long pc,unsigned long dest)252 u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest)
253 {
254 union loongarch_instruction insn;
255
256 emit_jirl(&insn, rj, rd, (dest - pc) >> 2);
257
258 return insn.word;
259 }
260