[v10,1/4] LoongArch: Simulate branch and PC instructions

Message ID 1670575981-14389-2-git-send-email-yangtiezhu@loongson.cn
State New
Headers
Series Add kprobe and kretprobe support for LoongArch |

Commit Message

Tiezhu Yang Dec. 9, 2022, 8:52 a.m. UTC
  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(+)
  

Comments

Jinyang He Dec. 21, 2022, 7:42 a.m. UTC | #1
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;
> +}
  

Patch

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;
+}