[v7,1/4] LoongArch: Simulate branch and PC instructions
Commit Message
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 | 12 ++++
arch/loongarch/include/asm/ptrace.h | 1 +
arch/loongarch/kernel/inst.c | 123 ++++++++++++++++++++++++++++++++++++
3 files changed, 136 insertions(+)
Comments
Hi, Tiezhu,
On Fri, Dec 2, 2022 at 9:08 PM Tiezhu Yang <yangtiezhu@loongson.cn> 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 | 12 ++++
> arch/loongarch/include/asm/ptrace.h | 1 +
> arch/loongarch/kernel/inst.c | 123 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 136 insertions(+)
>
> diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h
> index 6cd994d..a91798b 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,
> };
> @@ -366,6 +368,16 @@ u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm);
> 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 unsigned long sign_extended(unsigned long val, unsigned int idx)
> +{
> + if (val & (1UL << idx))
> + return ~((1UL << (idx + 1)) - 1) | val;
> + else
> + return ((1UL << (idx + 1)) - 1) & val;
> +}
You can use existing __SIGNEX and its friends rather than reinvent them.
Huacai
>
> static inline bool signed_imm_check(long val, unsigned int bit)
> {
> 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..aaaf9de 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_extended((imm_h << 16 | imm_l) << 2, 27);
> + return;
> + case bl_op:
> + regs->csr_era = pc + sign_extended((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_extended((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_extended((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_extended(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_extended(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_extended(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_extended(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_extended(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_extended(imm << 2, 17);
> + else
> + regs->csr_era = pc + LOONGARCH_INSN_SIZE;
> + break;
> + case jirl_op:
> + regs->csr_era = regs->regs[rj] + sign_extended(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_extended(imm << 2, 21);
> + break;
> + case pcaddu12i_op:
> + regs->regs[rd] = pc + sign_extended(imm << 12, 31);
> + break;
> + case pcaddu18i_op:
> + regs->regs[rd] = pc + sign_extended(imm << 18, 37);
> + break;
> + case pcalau12i_op:
> + regs->regs[rd] = pc + sign_extended(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 12/07/2022 11:06 AM, Huacai Chen wrote:
> Hi, Tiezhu,
>
> On Fri, Dec 2, 2022 at 9:08 PM Tiezhu Yang <yangtiezhu@loongson.cn> wrote:
>>
>> According to LoongArch Reference Manual, simulate branch and
>> PC instructions, this is preparation for later patch.
>>
...
>> +static inline unsigned long sign_extended(unsigned long val, unsigned int idx)
>> +{
>> + if (val & (1UL << idx))
>> + return ~((1UL << (idx + 1)) - 1) | val;
>> + else
>> + return ((1UL << (idx + 1)) - 1) & val;
>> +}
> You can use existing __SIGNEX and its friends rather than reinvent them.
Thanks for your reminder.
In my opinion, this static inline function sign_extended()
is much more clear and readable than the macro __SIGNEX()
defined in alternative.c, the helper function bs_dest_*()
seems redundant too, use "pc + sign_extended()" is a more
straightforward way to simulate instruction according to
the ISA Manual, so here I prefer to keep it as is.
Additionally, we can use sign_extended() instead of __SIGNEX()
in alternative.c, the __SIGNEX() related code can be removed
in a seperate patch in some day.
Thanks,
Tiezhu
@@ -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,
};
@@ -366,6 +368,16 @@ u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm);
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 unsigned long sign_extended(unsigned long val, unsigned int idx)
+{
+ if (val & (1UL << idx))
+ return ~((1UL << (idx + 1)) - 1) | val;
+ else
+ return ((1UL << (idx + 1)) - 1) & val;
+}
static inline bool signed_imm_check(long val, unsigned int bit)
{
@@ -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>
@@ -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_extended((imm_h << 16 | imm_l) << 2, 27);
+ return;
+ case bl_op:
+ regs->csr_era = pc + sign_extended((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_extended((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_extended((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_extended(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_extended(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_extended(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_extended(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_extended(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_extended(imm << 2, 17);
+ else
+ regs->csr_era = pc + LOONGARCH_INSN_SIZE;
+ break;
+ case jirl_op:
+ regs->csr_era = regs->regs[rj] + sign_extended(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_extended(imm << 2, 21);
+ break;
+ case pcaddu12i_op:
+ regs->regs[rd] = pc + sign_extended(imm << 12, 31);
+ break;
+ case pcaddu18i_op:
+ regs->regs[rd] = pc + sign_extended(imm << 18, 37);
+ break;
+ case pcalau12i_op:
+ regs->regs[rd] = pc + sign_extended(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;
+}