LoongArch: Simulate branch and PC* instructions
authorTiezhu Yang <yangtiezhu@loongson.cn>
Sat, 25 Feb 2023 07:52:57 +0000 (15:52 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Sat, 25 Feb 2023 14:12:17 +0000 (22:12 +0800)
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
Tested-by: Jeff Xie <xiehuan09@gmail.com>
Co-developed-by: Jinyang He <hejinyang@loongson.cn>
Signed-off-by: Jinyang He <hejinyang@loongson.cn>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/include/asm/inst.h
arch/loongarch/include/asm/ptrace.h
arch/loongarch/kernel/inst.c

index 36cc5eb4f852b017051febc98e3379b19655581a..3ade9ff2bb8ff3bfd4d7fd718c94846126fdcd5f 100644 (file)
@@ -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,
 };
@@ -389,6 +391,9 @@ static inline bool is_self_loop_ins(union loongarch_instruction *ip, struct pt_r
        return false;
 }
 
+void simu_pc(struct pt_regs *regs, union loongarch_instruction insn);
+void simu_branch(struct pt_regs *regs, union loongarch_instruction insn);
+
 int larch_insn_read(void *addr, u32 *insnp);
 int larch_insn_write(void *addr, u32 insn);
 int larch_insn_patch_text(void *addr, u32 insn);
index 6eab4393b108fd71704f9156961725b62dacddcb..d761db943335ce707e9f7059e677abcc05195a16 100644 (file)
@@ -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>
 
index badc590870423433495616c47e53022e4cf96f35..258ef267cd306fd97a27f8aced0931634b518561 100644 (file)
 
 static DEFINE_RAW_SPINLOCK(patch_lock);
 
+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_extend64(imm << 2, 21);
+               break;
+       case pcaddu12i_op:
+               regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
+               break;
+       case pcaddu18i_op:
+               regs->regs[rd] = pc + sign_extend64(imm << 18, 37);
+               break;
+       case pcalau12i_op:
+               regs->regs[rd] = pc + sign_extend64(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;
+}
+
+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_extend64((imm_h << 16 | imm_l) << 2, 27);
+               return;
+       case bl_op:
+               regs->csr_era = pc + sign_extend64((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_extend64((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_extend64((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_extend64(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_extend64(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_extend64(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_extend64(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_extend64(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_extend64(imm << 2, 17);
+               else
+                       regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+               break;
+       case jirl_op:
+               regs->csr_era = regs->regs[rj] + sign_extend64(imm << 2, 17);
+               regs->regs[rd] = pc + LOONGARCH_INSN_SIZE;
+               break;
+       default:
+               pr_info("%s: unknown opcode\n", __func__);
+               return;
+       }
+}
+
 int larch_insn_read(void *addr, u32 *insnp)
 {
        int ret;