Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/arch/loongarch/kernel/inst.c
26442 views
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
#include <linux/set_memory.h>
8
#include <linux/stop_machine.h>
9
10
#include <asm/cacheflush.h>
11
#include <asm/inst.h>
12
13
static DEFINE_RAW_SPINLOCK(patch_lock);
14
15
void simu_pc(struct pt_regs *regs, union loongarch_instruction insn)
16
{
17
unsigned long pc = regs->csr_era;
18
unsigned int rd = insn.reg1i20_format.rd;
19
unsigned int imm = insn.reg1i20_format.immediate;
20
21
if (pc & 3) {
22
pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
23
return;
24
}
25
26
switch (insn.reg1i20_format.opcode) {
27
case pcaddi_op:
28
regs->regs[rd] = pc + sign_extend64(imm << 2, 21);
29
break;
30
case pcaddu12i_op:
31
regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
32
break;
33
case pcaddu18i_op:
34
regs->regs[rd] = pc + sign_extend64(imm << 18, 37);
35
break;
36
case pcalau12i_op:
37
regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
38
regs->regs[rd] &= ~((1 << 12) - 1);
39
break;
40
default:
41
pr_info("%s: unknown opcode\n", __func__);
42
return;
43
}
44
45
regs->csr_era += LOONGARCH_INSN_SIZE;
46
}
47
48
void simu_branch(struct pt_regs *regs, union loongarch_instruction insn)
49
{
50
unsigned int imm, imm_l, imm_h, rd, rj;
51
unsigned long pc = regs->csr_era;
52
53
if (pc & 3) {
54
pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
55
return;
56
}
57
58
imm_l = insn.reg0i26_format.immediate_l;
59
imm_h = insn.reg0i26_format.immediate_h;
60
switch (insn.reg0i26_format.opcode) {
61
case b_op:
62
regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
63
return;
64
case bl_op:
65
regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
66
regs->regs[1] = pc + LOONGARCH_INSN_SIZE;
67
return;
68
}
69
70
imm_l = insn.reg1i21_format.immediate_l;
71
imm_h = insn.reg1i21_format.immediate_h;
72
rj = insn.reg1i21_format.rj;
73
switch (insn.reg1i21_format.opcode) {
74
case beqz_op:
75
if (regs->regs[rj] == 0)
76
regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
77
else
78
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
79
return;
80
case bnez_op:
81
if (regs->regs[rj] != 0)
82
regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
83
else
84
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
85
return;
86
}
87
88
imm = insn.reg2i16_format.immediate;
89
rj = insn.reg2i16_format.rj;
90
rd = insn.reg2i16_format.rd;
91
switch (insn.reg2i16_format.opcode) {
92
case beq_op:
93
if (regs->regs[rj] == regs->regs[rd])
94
regs->csr_era = pc + sign_extend64(imm << 2, 17);
95
else
96
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
97
break;
98
case bne_op:
99
if (regs->regs[rj] != regs->regs[rd])
100
regs->csr_era = pc + sign_extend64(imm << 2, 17);
101
else
102
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
103
break;
104
case blt_op:
105
if ((long)regs->regs[rj] < (long)regs->regs[rd])
106
regs->csr_era = pc + sign_extend64(imm << 2, 17);
107
else
108
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
109
break;
110
case bge_op:
111
if ((long)regs->regs[rj] >= (long)regs->regs[rd])
112
regs->csr_era = pc + sign_extend64(imm << 2, 17);
113
else
114
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
115
break;
116
case bltu_op:
117
if (regs->regs[rj] < regs->regs[rd])
118
regs->csr_era = pc + sign_extend64(imm << 2, 17);
119
else
120
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
121
break;
122
case bgeu_op:
123
if (regs->regs[rj] >= regs->regs[rd])
124
regs->csr_era = pc + sign_extend64(imm << 2, 17);
125
else
126
regs->csr_era = pc + LOONGARCH_INSN_SIZE;
127
break;
128
case jirl_op:
129
regs->csr_era = regs->regs[rj] + sign_extend64(imm << 2, 17);
130
regs->regs[rd] = pc + LOONGARCH_INSN_SIZE;
131
break;
132
default:
133
pr_info("%s: unknown opcode\n", __func__);
134
return;
135
}
136
}
137
138
bool insns_not_supported(union loongarch_instruction insn)
139
{
140
switch (insn.reg3_format.opcode) {
141
case amswapw_op ... ammindbdu_op:
142
pr_notice("atomic memory access instructions are not supported\n");
143
return true;
144
}
145
146
switch (insn.reg2i14_format.opcode) {
147
case llw_op:
148
case lld_op:
149
case scw_op:
150
case scd_op:
151
pr_notice("ll and sc instructions are not supported\n");
152
return true;
153
}
154
155
switch (insn.reg1i21_format.opcode) {
156
case bceqz_op:
157
pr_notice("bceqz and bcnez instructions are not supported\n");
158
return true;
159
}
160
161
return false;
162
}
163
164
bool insns_need_simulation(union loongarch_instruction insn)
165
{
166
if (is_pc_ins(&insn))
167
return true;
168
169
if (is_branch_ins(&insn))
170
return true;
171
172
return false;
173
}
174
175
void arch_simulate_insn(union loongarch_instruction insn, struct pt_regs *regs)
176
{
177
if (is_pc_ins(&insn))
178
simu_pc(regs, insn);
179
else if (is_branch_ins(&insn))
180
simu_branch(regs, insn);
181
}
182
183
int larch_insn_read(void *addr, u32 *insnp)
184
{
185
int ret;
186
u32 val;
187
188
ret = copy_from_kernel_nofault(&val, addr, LOONGARCH_INSN_SIZE);
189
if (!ret)
190
*insnp = val;
191
192
return ret;
193
}
194
195
int larch_insn_write(void *addr, u32 insn)
196
{
197
int ret;
198
unsigned long flags = 0;
199
200
raw_spin_lock_irqsave(&patch_lock, flags);
201
ret = copy_to_kernel_nofault(addr, &insn, LOONGARCH_INSN_SIZE);
202
raw_spin_unlock_irqrestore(&patch_lock, flags);
203
204
return ret;
205
}
206
207
int larch_insn_patch_text(void *addr, u32 insn)
208
{
209
int ret;
210
u32 *tp = addr;
211
212
if ((unsigned long)tp & 3)
213
return -EINVAL;
214
215
ret = larch_insn_write(tp, insn);
216
if (!ret)
217
flush_icache_range((unsigned long)tp,
218
(unsigned long)tp + LOONGARCH_INSN_SIZE);
219
220
return ret;
221
}
222
223
struct insn_copy {
224
void *dst;
225
void *src;
226
size_t len;
227
unsigned int cpu;
228
};
229
230
static int text_copy_cb(void *data)
231
{
232
int ret = 0;
233
struct insn_copy *copy = data;
234
235
if (smp_processor_id() == copy->cpu) {
236
ret = copy_to_kernel_nofault(copy->dst, copy->src, copy->len);
237
if (ret)
238
pr_err("%s: operation failed\n", __func__);
239
}
240
241
flush_icache_range((unsigned long)copy->dst, (unsigned long)copy->dst + copy->len);
242
243
return ret;
244
}
245
246
int larch_insn_text_copy(void *dst, void *src, size_t len)
247
{
248
int ret = 0;
249
size_t start, end;
250
struct insn_copy copy = {
251
.dst = dst,
252
.src = src,
253
.len = len,
254
.cpu = smp_processor_id(),
255
};
256
257
start = round_down((size_t)dst, PAGE_SIZE);
258
end = round_up((size_t)dst + len, PAGE_SIZE);
259
260
set_memory_rw(start, (end - start) / PAGE_SIZE);
261
ret = stop_machine(text_copy_cb, &copy, cpu_online_mask);
262
set_memory_rox(start, (end - start) / PAGE_SIZE);
263
264
return ret;
265
}
266
267
u32 larch_insn_gen_nop(void)
268
{
269
return INSN_NOP;
270
}
271
272
u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
273
{
274
long offset = dest - pc;
275
union loongarch_instruction insn;
276
277
if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
278
pr_warn("The generated b instruction is out of range.\n");
279
return INSN_BREAK;
280
}
281
282
emit_b(&insn, offset >> 2);
283
284
return insn.word;
285
}
286
287
u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
288
{
289
long offset = dest - pc;
290
union loongarch_instruction insn;
291
292
if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
293
pr_warn("The generated bl instruction is out of range.\n");
294
return INSN_BREAK;
295
}
296
297
emit_bl(&insn, offset >> 2);
298
299
return insn.word;
300
}
301
302
u32 larch_insn_gen_break(int imm)
303
{
304
union loongarch_instruction insn;
305
306
if (imm < 0 || imm >= SZ_32K) {
307
pr_warn("The generated break instruction is out of range.\n");
308
return INSN_BREAK;
309
}
310
311
emit_break(&insn, imm);
312
313
return insn.word;
314
}
315
316
u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongarch_gpr rk)
317
{
318
union loongarch_instruction insn;
319
320
emit_or(&insn, rd, rj, rk);
321
322
return insn.word;
323
}
324
325
u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
326
{
327
return larch_insn_gen_or(rd, rj, 0);
328
}
329
330
u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
331
{
332
union loongarch_instruction insn;
333
334
if (imm < -SZ_512K || imm >= SZ_512K) {
335
pr_warn("The generated lu12i.w instruction is out of range.\n");
336
return INSN_BREAK;
337
}
338
339
emit_lu12iw(&insn, rd, imm);
340
341
return insn.word;
342
}
343
344
u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
345
{
346
union loongarch_instruction insn;
347
348
if (imm < -SZ_512K || imm >= SZ_512K) {
349
pr_warn("The generated lu32i.d instruction is out of range.\n");
350
return INSN_BREAK;
351
}
352
353
emit_lu32id(&insn, rd, imm);
354
355
return insn.word;
356
}
357
358
u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
359
{
360
union loongarch_instruction insn;
361
362
if (imm < -SZ_2K || imm >= SZ_2K) {
363
pr_warn("The generated lu52i.d instruction is out of range.\n");
364
return INSN_BREAK;
365
}
366
367
emit_lu52id(&insn, rd, rj, imm);
368
369
return insn.word;
370
}
371
372
u32 larch_insn_gen_beq(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
373
{
374
union loongarch_instruction insn;
375
376
if ((imm & 3) || imm < -SZ_128K || imm >= SZ_128K) {
377
pr_warn("The generated beq instruction is out of range.\n");
378
return INSN_BREAK;
379
}
380
381
emit_beq(&insn, rj, rd, imm >> 2);
382
383
return insn.word;
384
}
385
386
u32 larch_insn_gen_bne(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
387
{
388
union loongarch_instruction insn;
389
390
if ((imm & 3) || imm < -SZ_128K || imm >= SZ_128K) {
391
pr_warn("The generated bne instruction is out of range.\n");
392
return INSN_BREAK;
393
}
394
395
emit_bne(&insn, rj, rd, imm >> 2);
396
397
return insn.word;
398
}
399
400
u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
401
{
402
union loongarch_instruction insn;
403
404
if ((imm & 3) || imm < -SZ_128K || imm >= SZ_128K) {
405
pr_warn("The generated jirl instruction is out of range.\n");
406
return INSN_BREAK;
407
}
408
409
emit_jirl(&insn, rd, rj, imm >> 2);
410
411
return insn.word;
412
}
413
414