linux/arch/loongarch/kernel/inst.c
Tiezhu Yang c1474bb0b7 LoongArch: BPF: Adjust the parameter of emit_jirl()
The branch instructions beq, bne, blt, bge, bltu, bgeu and jirl belong
to the format reg2i16, but the sequence of oprand is different for the
instruction jirl. So adjust the parameter order of emit_jirl() to make
it more readable correspond with the Instruction Set Architecture manual.

Here are the instruction formats:

  beq     rj, rd, offs16
  bne     rj, rd, offs16
  blt     rj, rd, offs16
  bge     rj, rd, offs16
  bltu    rj, rd, offs16
  bgeu    rj, rd, offs16
  jirl    rd, rj, offs16

Link: https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#branch-instructions
Suggested-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
2024-12-02 16:42:08 +08:00

338 lines
7.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/
#include <linux/sizes.h>
#include <linux/uaccess.h>
#include <asm/cacheflush.h>
#include <asm/inst.h>
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;
}
}
bool insns_not_supported(union loongarch_instruction insn)
{
switch (insn.reg3_format.opcode) {
case amswapw_op ... ammindbdu_op:
pr_notice("atomic memory access instructions are not supported\n");
return true;
}
switch (insn.reg2i14_format.opcode) {
case llw_op:
case lld_op:
case scw_op:
case scd_op:
pr_notice("ll and sc instructions are not supported\n");
return true;
}
switch (insn.reg1i21_format.opcode) {
case bceqz_op:
pr_notice("bceqz and bcnez instructions are not supported\n");
return true;
}
return false;
}
bool insns_need_simulation(union loongarch_instruction insn)
{
if (is_pc_ins(&insn))
return true;
if (is_branch_ins(&insn))
return true;
return false;
}
void arch_simulate_insn(union loongarch_instruction insn, struct pt_regs *regs)
{
if (is_pc_ins(&insn))
simu_pc(regs, insn);
else if (is_branch_ins(&insn))
simu_branch(regs, insn);
}
int larch_insn_read(void *addr, u32 *insnp)
{
int ret;
u32 val;
ret = copy_from_kernel_nofault(&val, addr, LOONGARCH_INSN_SIZE);
if (!ret)
*insnp = val;
return ret;
}
int larch_insn_write(void *addr, u32 insn)
{
int ret;
unsigned long flags = 0;
raw_spin_lock_irqsave(&patch_lock, flags);
ret = copy_to_kernel_nofault(addr, &insn, LOONGARCH_INSN_SIZE);
raw_spin_unlock_irqrestore(&patch_lock, flags);
return ret;
}
int larch_insn_patch_text(void *addr, u32 insn)
{
int ret;
u32 *tp = addr;
if ((unsigned long)tp & 3)
return -EINVAL;
ret = larch_insn_write(tp, insn);
if (!ret)
flush_icache_range((unsigned long)tp,
(unsigned long)tp + LOONGARCH_INSN_SIZE);
return ret;
}
u32 larch_insn_gen_nop(void)
{
return INSN_NOP;
}
u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
{
long offset = dest - pc;
union loongarch_instruction insn;
if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
pr_warn("The generated b instruction is out of range.\n");
return INSN_BREAK;
}
emit_b(&insn, offset >> 2);
return insn.word;
}
u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
{
long offset = dest - pc;
union loongarch_instruction insn;
if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
pr_warn("The generated bl instruction is out of range.\n");
return INSN_BREAK;
}
emit_bl(&insn, offset >> 2);
return insn.word;
}
u32 larch_insn_gen_break(int imm)
{
union loongarch_instruction insn;
if (imm < 0 || imm >= SZ_32K) {
pr_warn("The generated break instruction is out of range.\n");
return INSN_BREAK;
}
emit_break(&insn, imm);
return insn.word;
}
u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongarch_gpr rk)
{
union loongarch_instruction insn;
emit_or(&insn, rd, rj, rk);
return insn.word;
}
u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
{
return larch_insn_gen_or(rd, rj, 0);
}
u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
{
union loongarch_instruction insn;
if (imm < -SZ_512K || imm >= SZ_512K) {
pr_warn("The generated lu12i.w instruction is out of range.\n");
return INSN_BREAK;
}
emit_lu12iw(&insn, rd, imm);
return insn.word;
}
u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
{
union loongarch_instruction insn;
if (imm < -SZ_512K || imm >= SZ_512K) {
pr_warn("The generated lu32i.d instruction is out of range.\n");
return INSN_BREAK;
}
emit_lu32id(&insn, rd, imm);
return insn.word;
}
u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
{
union loongarch_instruction insn;
if (imm < -SZ_2K || imm >= SZ_2K) {
pr_warn("The generated lu52i.d instruction is out of range.\n");
return INSN_BREAK;
}
emit_lu52id(&insn, rd, rj, imm);
return insn.word;
}
u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
{
union loongarch_instruction insn;
if ((imm & 3) || imm < -SZ_128K || imm >= SZ_128K) {
pr_warn("The generated jirl instruction is out of range.\n");
return INSN_BREAK;
}
emit_jirl(&insn, rd, rj, imm >> 2);
return insn.word;
}