According to LoongArch Reference Manual, simulate branch and
PC instructions, this is preparation for later patch.
Link: https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#branch-instructions
Link: https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#_pcaddi_pcaddu121_pcaddu18l_pcalau12i
Co-developed-by: Jinyang He <hejinyang@loongson.cn>
Signed-off-by: Jinyang He <hejinyang@loongson.cn>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
arch/loongarch/include/asm/inst.h | 5 ++
arch/loongarch/include/asm/ptrace.h | 1 +
arch/loongarch/kernel/inst.c | 123 ++++++++++++++++++++++++++++++++++++
3 files changed, 129 insertions(+)
diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h
index c00e151..e25fd54 100644
--- a/arch/loongarch/include/asm/inst.h
+++ b/arch/loongarch/include/asm/inst.h
@@ -7,6 +7,7 @@
#include <linux/types.h>
#include <asm/asm.h>
+#include <asm/ptrace.h>
#define INSN_NOP 0x03400000
#define INSN_BREAK 0x002a0000
@@ -32,6 +33,7 @@ enum reg1i20_op {
lu12iw_op = 0x0a,
lu32id_op = 0x0b,
pcaddi_op = 0x0c,
+ pcalau12i_op = 0x0d,
pcaddu12i_op = 0x0e,
pcaddu18i_op = 0x0f,
};
@@ -367,6 +369,9 @@ u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm);
u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm);
u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest);
+void simu_branch(struct pt_regs *regs, union loongarch_instruction insn);
+void simu_pc(struct pt_regs *regs, union loongarch_instruction insn);
+
static inline bool signed_imm_check(long val, unsigned int bit)
{
return -(1L << (bit - 1)) <= val && val < (1L << (bit - 1));
diff --git a/arch/loongarch/include/asm/ptrace.h b/arch/loongarch/include/asm/ptrace.h
index 59c4608..58596c4 100644
--- a/arch/loongarch/include/asm/ptrace.h
+++ b/arch/loongarch/include/asm/ptrace.h
@@ -6,6 +6,7 @@
#define _ASM_PTRACE_H
#include <asm/page.h>
+#include <asm/irqflags.h>
#include <asm/thread_info.h>
#include <uapi/asm/ptrace.h>
diff --git a/arch/loongarch/kernel/inst.c b/arch/loongarch/kernel/inst.c
index 512579d..af48faf 100644
--- a/arch/loongarch/kernel/inst.c
+++ b/arch/loongarch/kernel/inst.c
@@ -165,3 +165,126 @@ u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned l
return insn.word;
}
+
+void simu_branch(struct pt_regs *regs, union loongarch_instruction insn)
+{
+ unsigned int imm, imm_l, imm_h, rd, rj;
+ unsigned long pc = regs->csr_era;
+
+ if (pc & 3) {
+ pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
+ return;
+ }
+
+ imm_l = insn.reg0i26_format.immediate_l;
+ imm_h = insn.reg0i26_format.immediate_h;
+ switch (insn.reg0i26_format.opcode) {
+ case b_op:
+ regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 27);
+ return;
+ case bl_op:
+ regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 27);
+ regs->regs[1] = pc + LOONGARCH_INSN_SIZE;
+ return;
+ }
+
+ imm_l = insn.reg1i21_format.immediate_l;
+ imm_h = insn.reg1i21_format.immediate_h;
+ rj = insn.reg1i21_format.rj;
+ switch (insn.reg1i21_format.opcode) {
+ case beqz_op:
+ if (regs->regs[rj] == 0)
+ regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 22);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ return;
+ case bnez_op:
+ if (regs->regs[rj] != 0)
+ regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 22);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ return;
+ }
+
+ imm = insn.reg2i16_format.immediate;
+ rj = insn.reg2i16_format.rj;
+ rd = insn.reg2i16_format.rd;
+ switch (insn.reg2i16_format.opcode) {
+ case beq_op:
+ if (regs->regs[rj] == regs->regs[rd])
+ regs->csr_era = pc + sign_extend(imm << 2, 17);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ break;
+ case bne_op:
+ if (regs->regs[rj] != regs->regs[rd])
+ regs->csr_era = pc + sign_extend(imm << 2, 17);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ break;
+ case blt_op:
+ if ((long)regs->regs[rj] < (long)regs->regs[rd])
+ regs->csr_era = pc + sign_extend(imm << 2, 17);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ break;
+ case bge_op:
+ if ((long)regs->regs[rj] >= (long)regs->regs[rd])
+ regs->csr_era = pc + sign_extend(imm << 2, 17);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ break;
+ case bltu_op:
+ if (regs->regs[rj] < regs->regs[rd])
+ regs->csr_era = pc + sign_extend(imm << 2, 17);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ break;
+ case bgeu_op:
+ if (regs->regs[rj] >= regs->regs[rd])
+ regs->csr_era = pc + sign_extend(imm << 2, 17);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ break;
+ case jirl_op:
+ regs->csr_era = regs->regs[rj] + sign_extend(imm << 2, 17);
+ regs->regs[rd] = pc + LOONGARCH_INSN_SIZE;
+ break;
+ default:
+ pr_info("%s: unknown opcode\n", __func__);
+ return;
+ }
+}
+
+void simu_pc(struct pt_regs *regs, union loongarch_instruction insn)
+{
+ unsigned long pc = regs->csr_era;
+ unsigned int rd = insn.reg1i20_format.rd;
+ unsigned int imm = insn.reg1i20_format.immediate;
+
+ if (pc & 3) {
+ pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
+ return;
+ }
+
+ switch (insn.reg1i20_format.opcode) {
+ case pcaddi_op:
+ regs->regs[rd] = pc + sign_extend(imm << 2, 21);
+ break;
+ case pcaddu12i_op:
+ regs->regs[rd] = pc + sign_extend(imm << 12, 31);
+ break;
+ case pcaddu18i_op:
+ regs->regs[rd] = pc + sign_extend(imm << 18, 37);
+ break;
+ case pcalau12i_op:
+ regs->regs[rd] = pc + sign_extend(imm << 12, 31);
+ regs->regs[rd] &= ~((1 << 12) - 1);
+ break;
+ default:
+ pr_info("%s: unknown opcode\n", __func__);
+ return;
+ }
+
+ regs->csr_era += LOONGARCH_INSN_SIZE;
+}
--
2.1.0
On 2022-12-09 16:52, Tiezhu Yang wrote: > According to LoongArch Reference Manual, simulate branch and > PC instructions, this is preparation for later patch. > > Link: https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#branch-instructions > Link: https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#_pcaddi_pcaddu121_pcaddu18l_pcalau12i > > Co-developed-by: Jinyang He <hejinyang@loongson.cn> > Signed-off-by: Jinyang He <hejinyang@loongson.cn> > Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn> > --- > arch/loongarch/include/asm/inst.h | 5 ++ > arch/loongarch/include/asm/ptrace.h | 1 + > arch/loongarch/kernel/inst.c | 123 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 129 insertions(+) > > diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h > index c00e151..e25fd54 100644 > --- a/arch/loongarch/include/asm/inst.h > +++ b/arch/loongarch/include/asm/inst.h > @@ -7,6 +7,7 @@ > > #include <linux/types.h> > #include <asm/asm.h> > +#include <asm/ptrace.h> > > #define INSN_NOP 0x03400000 > #define INSN_BREAK 0x002a0000 > @@ -32,6 +33,7 @@ enum reg1i20_op { > lu12iw_op = 0x0a, > lu32id_op = 0x0b, > pcaddi_op = 0x0c, > + pcalau12i_op = 0x0d, > pcaddu12i_op = 0x0e, > pcaddu18i_op = 0x0f, > }; > @@ -367,6 +369,9 @@ u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm); > u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm); > u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest); > > +void simu_branch(struct pt_regs *regs, union loongarch_instruction insn); > +void simu_pc(struct pt_regs *regs, union loongarch_instruction insn); > + > static inline bool signed_imm_check(long val, unsigned int bit) > { > return -(1L << (bit - 1)) <= val && val < (1L << (bit - 1)); > diff --git a/arch/loongarch/include/asm/ptrace.h b/arch/loongarch/include/asm/ptrace.h > index 59c4608..58596c4 100644 > --- a/arch/loongarch/include/asm/ptrace.h > +++ b/arch/loongarch/include/asm/ptrace.h > @@ -6,6 +6,7 @@ > #define _ASM_PTRACE_H > > #include <asm/page.h> > +#include <asm/irqflags.h> > #include <asm/thread_info.h> > #include <uapi/asm/ptrace.h> > > diff --git a/arch/loongarch/kernel/inst.c b/arch/loongarch/kernel/inst.c > index 512579d..af48faf 100644 > --- a/arch/loongarch/kernel/inst.c > +++ b/arch/loongarch/kernel/inst.c > @@ -165,3 +165,126 @@ u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned l > > return insn.word; > } > + > +void simu_branch(struct pt_regs *regs, union loongarch_instruction insn) > +{ > + unsigned int imm, imm_l, imm_h, rd, rj; > + unsigned long pc = regs->csr_era; > + > + if (pc & 3) { > + pr_warn("%s: invalid pc 0x%lx\n", __func__, pc); > + return; > + } > + > + imm_l = insn.reg0i26_format.immediate_l; > + imm_h = insn.reg0i26_format.immediate_h; > + switch (insn.reg0i26_format.opcode) { > + case b_op: > + regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 27); Hi, Tiezhu, We can use sign_extend64() in linux/bitops.h. Its assembly sequence is slli.d+srai.d, which is more efficient and clear. Jinyang > + return; > + case bl_op: > + regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 27); > + regs->regs[1] = pc + LOONGARCH_INSN_SIZE; > + return; > + } > + > + imm_l = insn.reg1i21_format.immediate_l; > + imm_h = insn.reg1i21_format.immediate_h; > + rj = insn.reg1i21_format.rj; > + switch (insn.reg1i21_format.opcode) { > + case beqz_op: > + if (regs->regs[rj] == 0) > + regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 22); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + return; > + case bnez_op: > + if (regs->regs[rj] != 0) > + regs->csr_era = pc + sign_extend((imm_h << 16 | imm_l) << 2, 22); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + return; > + } > + > + imm = insn.reg2i16_format.immediate; > + rj = insn.reg2i16_format.rj; > + rd = insn.reg2i16_format.rd; > + switch (insn.reg2i16_format.opcode) { > + case beq_op: > + if (regs->regs[rj] == regs->regs[rd]) > + regs->csr_era = pc + sign_extend(imm << 2, 17); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + break; > + case bne_op: > + if (regs->regs[rj] != regs->regs[rd]) > + regs->csr_era = pc + sign_extend(imm << 2, 17); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + break; > + case blt_op: > + if ((long)regs->regs[rj] < (long)regs->regs[rd]) > + regs->csr_era = pc + sign_extend(imm << 2, 17); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + break; > + case bge_op: > + if ((long)regs->regs[rj] >= (long)regs->regs[rd]) > + regs->csr_era = pc + sign_extend(imm << 2, 17); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + break; > + case bltu_op: > + if (regs->regs[rj] < regs->regs[rd]) > + regs->csr_era = pc + sign_extend(imm << 2, 17); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + break; > + case bgeu_op: > + if (regs->regs[rj] >= regs->regs[rd]) > + regs->csr_era = pc + sign_extend(imm << 2, 17); > + else > + regs->csr_era = pc + LOONGARCH_INSN_SIZE; > + break; > + case jirl_op: > + regs->csr_era = regs->regs[rj] + sign_extend(imm << 2, 17); > + regs->regs[rd] = pc + LOONGARCH_INSN_SIZE; > + break; > + default: > + pr_info("%s: unknown opcode\n", __func__); > + return; > + } > +} > + > +void simu_pc(struct pt_regs *regs, union loongarch_instruction insn) > +{ > + unsigned long pc = regs->csr_era; > + unsigned int rd = insn.reg1i20_format.rd; > + unsigned int imm = insn.reg1i20_format.immediate; > + > + if (pc & 3) { > + pr_warn("%s: invalid pc 0x%lx\n", __func__, pc); > + return; > + } > + > + switch (insn.reg1i20_format.opcode) { > + case pcaddi_op: > + regs->regs[rd] = pc + sign_extend(imm << 2, 21); > + break; > + case pcaddu12i_op: > + regs->regs[rd] = pc + sign_extend(imm << 12, 31); > + break; > + case pcaddu18i_op: > + regs->regs[rd] = pc + sign_extend(imm << 18, 37); > + break; > + case pcalau12i_op: > + regs->regs[rd] = pc + sign_extend(imm << 12, 31); > + regs->regs[rd] &= ~((1 << 12) - 1); > + break; > + default: > + pr_info("%s: unknown opcode\n", __func__); > + return; > + } > + > + regs->csr_era += LOONGARCH_INSN_SIZE; > +}
© 2016 - 2025 Red Hat, Inc.