mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-28 22:17:12 +08:00
979 lines
25 KiB
C
979 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* BPF JIT compiler for LoongArch
|
|
*
|
|
* Copyright (C) 2021 Loongson Technology Corporation Limited
|
|
*/
|
|
#include "ebpf_jit.h"
|
|
|
|
#define TMP_REG_1 (MAX_BPF_JIT_REG + 0)
|
|
#define TMP_REG_2 (MAX_BPF_JIT_REG + 1)
|
|
#define TMP_REG_3 (MAX_BPF_JIT_REG + 2)
|
|
#define REG_TCC (MAX_BPF_JIT_REG + 3)
|
|
#define TCC_SAVED (MAX_BPF_JIT_REG + 4)
|
|
|
|
#define SAVE_RA BIT(0)
|
|
#define SAVE_TCC BIT(1)
|
|
|
|
static const int regmap[] = {
|
|
/* return value from in-kernel function, and exit value for eBPF program */
|
|
[BPF_REG_0] = LOONGARCH_GPR_A5,
|
|
/* arguments from eBPF program to in-kernel function */
|
|
[BPF_REG_1] = LOONGARCH_GPR_A0,
|
|
[BPF_REG_2] = LOONGARCH_GPR_A1,
|
|
[BPF_REG_3] = LOONGARCH_GPR_A2,
|
|
[BPF_REG_4] = LOONGARCH_GPR_A3,
|
|
[BPF_REG_5] = LOONGARCH_GPR_A4,
|
|
/* callee saved registers that in-kernel function will preserve */
|
|
[BPF_REG_6] = LOONGARCH_GPR_S0,
|
|
[BPF_REG_7] = LOONGARCH_GPR_S1,
|
|
[BPF_REG_8] = LOONGARCH_GPR_S2,
|
|
[BPF_REG_9] = LOONGARCH_GPR_S3,
|
|
/* read-only frame pointer to access stack */
|
|
[BPF_REG_FP] = LOONGARCH_GPR_S4,
|
|
/* temporary register for blinding constants */
|
|
[BPF_REG_AX] = LOONGARCH_GPR_T0,
|
|
/* temporary register for internal BPF JIT */
|
|
[TMP_REG_1] = LOONGARCH_GPR_T1,
|
|
[TMP_REG_2] = LOONGARCH_GPR_T2,
|
|
[TMP_REG_3] = LOONGARCH_GPR_T3,
|
|
/* tail call */
|
|
[REG_TCC] = LOONGARCH_GPR_A6,
|
|
/* store A6 in S5 if program do calls */
|
|
[TCC_SAVED] = LOONGARCH_GPR_S5,
|
|
};
|
|
|
|
static void mark_call(struct jit_ctx *ctx)
|
|
{
|
|
ctx->flags |= SAVE_RA;
|
|
}
|
|
|
|
static void mark_tail_call(struct jit_ctx *ctx)
|
|
{
|
|
ctx->flags |= SAVE_TCC;
|
|
}
|
|
|
|
static bool seen_call(struct jit_ctx *ctx)
|
|
{
|
|
return (ctx->flags & SAVE_RA);
|
|
}
|
|
|
|
static bool seen_tail_call(struct jit_ctx *ctx)
|
|
{
|
|
return (ctx->flags & SAVE_TCC);
|
|
}
|
|
|
|
static u8 tail_call_reg(struct jit_ctx *ctx)
|
|
{
|
|
if (seen_call(ctx))
|
|
return regmap[TCC_SAVED];
|
|
|
|
return regmap[REG_TCC];
|
|
}
|
|
|
|
/*
|
|
* eBPF prog stack layout:
|
|
*
|
|
* high
|
|
* original $sp ------------> +-------------------------+ <--LOONGARCH_GPR_FP
|
|
* | $ra |
|
|
* +-------------------------+
|
|
* | $fp |
|
|
* +-------------------------+
|
|
* | $s0 |
|
|
* +-------------------------+
|
|
* | $s1 |
|
|
* +-------------------------+
|
|
* | $s2 |
|
|
* +-------------------------+
|
|
* | $s3 |
|
|
* +-------------------------+
|
|
* | $s4 |
|
|
* +-------------------------+
|
|
* | $s5 |
|
|
* +-------------------------+ <--BPF_REG_FP
|
|
* | prog->aux->stack_depth |
|
|
* | (optional) |
|
|
* current $sp -------------> +-------------------------+
|
|
* low
|
|
*/
|
|
static void build_prologue(struct jit_ctx *ctx)
|
|
{
|
|
int stack_adjust = 0, store_offset, bpf_stack_adjust;
|
|
|
|
bpf_stack_adjust = round_up(ctx->prog->aux->stack_depth, 16);
|
|
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_RA */
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_FP */
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_S0 */
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_S1 */
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_S2 */
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_S3 */
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_S4 */
|
|
stack_adjust += sizeof(long); /* LOONGARCH_GPR_S5 */
|
|
|
|
stack_adjust = round_up(stack_adjust, 16);
|
|
stack_adjust += bpf_stack_adjust;
|
|
|
|
/*
|
|
* First instruction initializes the tail call count (TCC).
|
|
* On tail call we skip this instruction, and the TCC is
|
|
* passed in REG_TCC from the caller.
|
|
*/
|
|
emit_insn(ctx, addid, regmap[REG_TCC], LOONGARCH_GPR_ZERO, MAX_TAIL_CALL_CNT);
|
|
|
|
emit_insn(ctx, addid, LOONGARCH_GPR_SP, LOONGARCH_GPR_SP, -stack_adjust);
|
|
|
|
store_offset = stack_adjust - sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_RA, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
store_offset -= sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_FP, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
store_offset -= sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_S0, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
store_offset -= sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_S1, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
store_offset -= sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_S2, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
store_offset -= sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_S3, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
store_offset -= sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_S4, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
store_offset -= sizeof(long);
|
|
emit_insn(ctx, std, LOONGARCH_GPR_S5, LOONGARCH_GPR_SP, store_offset);
|
|
|
|
emit_insn(ctx, addid, LOONGARCH_GPR_FP, LOONGARCH_GPR_SP, stack_adjust);
|
|
|
|
if (bpf_stack_adjust)
|
|
emit_insn(ctx, addid, regmap[BPF_REG_FP], LOONGARCH_GPR_SP, bpf_stack_adjust);
|
|
|
|
/*
|
|
* Program contains calls and tail calls, so REG_TCC need
|
|
* to be saved across calls.
|
|
*/
|
|
if (seen_tail_call(ctx) && seen_call(ctx))
|
|
move_reg(ctx, regmap[TCC_SAVED], regmap[REG_TCC]);
|
|
|
|
ctx->stack_size = stack_adjust;
|
|
}
|
|
|
|
static void __build_epilogue(struct jit_ctx *ctx, bool is_tail_call)
|
|
{
|
|
int stack_adjust = ctx->stack_size;
|
|
int load_offset;
|
|
|
|
load_offset = stack_adjust - sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_RA, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
load_offset -= sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_FP, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
load_offset -= sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_S0, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
load_offset -= sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_S1, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
load_offset -= sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_S2, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
load_offset -= sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_S3, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
load_offset -= sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_S4, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
load_offset -= sizeof(long);
|
|
emit_insn(ctx, ldd, LOONGARCH_GPR_S5, LOONGARCH_GPR_SP, load_offset);
|
|
|
|
emit_insn(ctx, addid, LOONGARCH_GPR_SP, LOONGARCH_GPR_SP, stack_adjust);
|
|
|
|
if (!is_tail_call) {
|
|
/* Set return value */
|
|
move_reg(ctx, LOONGARCH_GPR_V0, regmap[BPF_REG_0]);
|
|
/* Return to the caller */
|
|
emit_insn(ctx, jirl, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_RA, 0);
|
|
} else {
|
|
/*
|
|
* Call the next bpf prog and skip the first instruction
|
|
* of TCC initialization.
|
|
*/
|
|
emit_insn(ctx, jirl, LOONGARCH_GPR_ZERO, regmap[TMP_REG_3], 1);
|
|
}
|
|
}
|
|
|
|
void build_epilogue(struct jit_ctx *ctx)
|
|
{
|
|
__build_epilogue(ctx, false);
|
|
}
|
|
|
|
/* initialized on the first pass of build_body() */
|
|
static int out_offset = -1;
|
|
static int emit_bpf_tail_call(struct jit_ctx *ctx)
|
|
{
|
|
int off;
|
|
u8 tcc = tail_call_reg(ctx);
|
|
u8 a1 = LOONGARCH_GPR_A1;
|
|
u8 a2 = LOONGARCH_GPR_A2;
|
|
u8 tmp1 = regmap[TMP_REG_1];
|
|
u8 tmp2 = regmap[TMP_REG_2];
|
|
u8 tmp3 = regmap[TMP_REG_3];
|
|
const int idx0 = ctx->idx;
|
|
|
|
#define cur_offset (ctx->idx - idx0)
|
|
#define jmp_offset (out_offset - (cur_offset))
|
|
|
|
/*
|
|
* a0: &ctx
|
|
* a1: &array
|
|
* a2: index
|
|
*
|
|
* if (index >= array->map.max_entries)
|
|
* goto out;
|
|
*/
|
|
off = offsetof(struct bpf_array, map.max_entries);
|
|
emit_insn(ctx, ldwu, tmp1, a1, off);
|
|
/* bgeu $a2, $t1, jmp_offset */
|
|
emit_tailcall_jump(ctx, BPF_JGE, a2, tmp1, jmp_offset);
|
|
|
|
/*
|
|
* if (TCC-- < 0)
|
|
* goto out;
|
|
*/
|
|
emit_insn(ctx, addid, tmp1, tcc, -1);
|
|
emit_tailcall_jump(ctx, BPF_JSLT, tcc, LOONGARCH_GPR_ZERO, jmp_offset);
|
|
|
|
/*
|
|
* prog = array->ptrs[index];
|
|
* if (!prog)
|
|
* goto out;
|
|
*/
|
|
emit_insn(ctx, sllid, tmp2, a2, 3);
|
|
emit_insn(ctx, addd, tmp2, tmp2, a1);
|
|
off = offsetof(struct bpf_array, ptrs);
|
|
emit_insn(ctx, ldd, tmp2, tmp2, off);
|
|
/* beq $t2, $zero, jmp_offset */
|
|
emit_tailcall_jump(ctx, BPF_JEQ, tmp2, LOONGARCH_GPR_ZERO, jmp_offset);
|
|
|
|
/* goto *(prog->bpf_func + 4); */
|
|
off = offsetof(struct bpf_prog, bpf_func);
|
|
emit_insn(ctx, ldd, tmp3, tmp2, off);
|
|
move_reg(ctx, tcc, tmp1);
|
|
__build_epilogue(ctx, true);
|
|
|
|
/* out: */
|
|
if (out_offset == -1)
|
|
out_offset = cur_offset;
|
|
if (cur_offset != out_offset) {
|
|
pr_err_once("tail_call out_offset = %d, expected %d!\n",
|
|
cur_offset, out_offset);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
#undef cur_offset
|
|
#undef jmp_offset
|
|
}
|
|
|
|
static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool extra_pass)
|
|
{
|
|
bool is32 = (BPF_CLASS(insn->code) == BPF_ALU);
|
|
const u8 code = insn->code;
|
|
const u8 cond = BPF_OP(code);
|
|
const u8 dst = regmap[insn->dst_reg];
|
|
const u8 src = regmap[insn->src_reg];
|
|
const u8 tmp = regmap[TMP_REG_1];
|
|
const u8 tmp2 = regmap[TMP_REG_2];
|
|
const s16 off = insn->off;
|
|
const s32 imm = insn->imm;
|
|
int i = insn - ctx->prog->insnsi;
|
|
int jmp_offset;
|
|
bool func_addr_fixed;
|
|
u64 func_addr;
|
|
u64 imm64;
|
|
int ret;
|
|
|
|
switch (code) {
|
|
/* dst = src */
|
|
case BPF_ALU | BPF_MOV | BPF_X:
|
|
case BPF_ALU64 | BPF_MOV | BPF_X:
|
|
move_reg(ctx, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = imm */
|
|
case BPF_ALU | BPF_MOV | BPF_K:
|
|
case BPF_ALU64 | BPF_MOV | BPF_K:
|
|
move_imm32(ctx, dst, imm, is32);
|
|
break;
|
|
|
|
/* dst = dst + src */
|
|
case BPF_ALU | BPF_ADD | BPF_X:
|
|
case BPF_ALU64 | BPF_ADD | BPF_X:
|
|
emit_insn(ctx, addd, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst + imm */
|
|
case BPF_ALU | BPF_ADD | BPF_K:
|
|
case BPF_ALU64 | BPF_ADD | BPF_K:
|
|
if (is_signed_imm12(imm)) {
|
|
emit_insn(ctx, addid, dst, dst, imm);
|
|
} else {
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, addd, dst, dst, tmp);
|
|
}
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst - src */
|
|
case BPF_ALU | BPF_SUB | BPF_X:
|
|
case BPF_ALU64 | BPF_SUB | BPF_X:
|
|
emit_insn(ctx, subd, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst - imm */
|
|
case BPF_ALU | BPF_SUB | BPF_K:
|
|
case BPF_ALU64 | BPF_SUB | BPF_K:
|
|
if (is_signed_imm12(-imm)) {
|
|
emit_insn(ctx, addid, dst, dst, -imm);
|
|
} else {
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, subd, dst, dst, tmp);
|
|
}
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst * src */
|
|
case BPF_ALU | BPF_MUL | BPF_X:
|
|
case BPF_ALU64 | BPF_MUL | BPF_X:
|
|
emit_insn(ctx, muld, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst * imm */
|
|
case BPF_ALU | BPF_MUL | BPF_K:
|
|
case BPF_ALU64 | BPF_MUL | BPF_K:
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, muld, dst, dst, tmp);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst / src */
|
|
case BPF_ALU | BPF_DIV | BPF_X:
|
|
case BPF_ALU64 | BPF_DIV | BPF_X:
|
|
emit_insn(ctx, divdu, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst / imm */
|
|
case BPF_ALU | BPF_DIV | BPF_K:
|
|
case BPF_ALU64 | BPF_DIV | BPF_K:
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, divdu, dst, dst, tmp);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst % src */
|
|
case BPF_ALU | BPF_MOD | BPF_X:
|
|
case BPF_ALU64 | BPF_MOD | BPF_X:
|
|
emit_insn(ctx, moddu, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst % imm */
|
|
case BPF_ALU | BPF_MOD | BPF_K:
|
|
case BPF_ALU64 | BPF_MOD | BPF_K:
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, moddu, dst, dst, tmp);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = -dst */
|
|
case BPF_ALU | BPF_NEG:
|
|
case BPF_ALU64 | BPF_NEG:
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, subd, dst, LOONGARCH_GPR_ZERO, dst);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst & src */
|
|
case BPF_ALU | BPF_AND | BPF_X:
|
|
case BPF_ALU64 | BPF_AND | BPF_X:
|
|
emit_insn(ctx, and, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst & imm */
|
|
case BPF_ALU | BPF_AND | BPF_K:
|
|
case BPF_ALU64 | BPF_AND | BPF_K:
|
|
if (is_unsigned_imm12(imm)) {
|
|
emit_insn(ctx, andi, dst, dst, imm);
|
|
} else {
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, and, dst, dst, tmp);
|
|
}
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst | src */
|
|
case BPF_ALU | BPF_OR | BPF_X:
|
|
case BPF_ALU64 | BPF_OR | BPF_X:
|
|
emit_insn(ctx, or, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst | imm */
|
|
case BPF_ALU | BPF_OR | BPF_K:
|
|
case BPF_ALU64 | BPF_OR | BPF_K:
|
|
if (is_unsigned_imm12(imm)) {
|
|
emit_insn(ctx, ori, dst, dst, imm);
|
|
} else {
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, or, dst, dst, tmp);
|
|
}
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst ^ src */
|
|
case BPF_ALU | BPF_XOR | BPF_X:
|
|
case BPF_ALU64 | BPF_XOR | BPF_X:
|
|
emit_insn(ctx, xor, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
/* dst = dst ^ imm */
|
|
case BPF_ALU | BPF_XOR | BPF_K:
|
|
case BPF_ALU64 | BPF_XOR | BPF_K:
|
|
if (is_unsigned_imm12(imm)) {
|
|
emit_insn(ctx, xori, dst, dst, imm);
|
|
} else {
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, xor, dst, dst, tmp);
|
|
}
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
|
|
/* dst = dst << src (logical) */
|
|
case BPF_ALU | BPF_LSH | BPF_X:
|
|
emit_insn(ctx, sllw, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case BPF_ALU64 | BPF_LSH | BPF_X:
|
|
emit_insn(ctx, slld, dst, dst, src);
|
|
break;
|
|
/* dst = dst << imm (logical) */
|
|
case BPF_ALU | BPF_LSH | BPF_K:
|
|
emit_insn(ctx, slliw, dst, dst, imm);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case BPF_ALU64 | BPF_LSH | BPF_K:
|
|
emit_insn(ctx, sllid, dst, dst, imm);
|
|
break;
|
|
|
|
/* dst = dst >> src (logical) */
|
|
case BPF_ALU | BPF_RSH | BPF_X:
|
|
emit_insn(ctx, srlw, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case BPF_ALU64 | BPF_RSH | BPF_X:
|
|
emit_insn(ctx, srld, dst, dst, src);
|
|
break;
|
|
/* dst = dst >> imm (logical) */
|
|
case BPF_ALU | BPF_RSH | BPF_K:
|
|
emit_insn(ctx, srliw, dst, dst, imm);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case BPF_ALU64 | BPF_RSH | BPF_K:
|
|
emit_insn(ctx, srlid, dst, dst, imm);
|
|
break;
|
|
|
|
/* dst = dst >> src (arithmetic) */
|
|
case BPF_ALU | BPF_ARSH | BPF_X:
|
|
emit_insn(ctx, sraw, dst, dst, src);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case BPF_ALU64 | BPF_ARSH | BPF_X:
|
|
emit_insn(ctx, srad, dst, dst, src);
|
|
break;
|
|
/* dst = dst >> imm (arithmetic) */
|
|
case BPF_ALU | BPF_ARSH | BPF_K:
|
|
emit_insn(ctx, sraiw, dst, dst, imm);
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case BPF_ALU64 | BPF_ARSH | BPF_K:
|
|
emit_insn(ctx, sraid, dst, dst, imm);
|
|
break;
|
|
|
|
/* dst = BSWAP##imm(dst) */
|
|
case BPF_ALU | BPF_END | BPF_FROM_LE:
|
|
switch (imm) {
|
|
case 16:
|
|
/* zero-extend 16 bits into 64 bits */
|
|
emit_insn(ctx, sllid, dst, dst, 48);
|
|
emit_insn(ctx, srlid, dst, dst, 48);
|
|
break;
|
|
case 32:
|
|
/* zero-extend 32 bits into 64 bits */
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case 64:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
break;
|
|
case BPF_ALU | BPF_END | BPF_FROM_BE:
|
|
switch (imm) {
|
|
case 16:
|
|
emit_insn(ctx, revb2h, dst, dst);
|
|
/* zero-extend 16 bits into 64 bits */
|
|
emit_insn(ctx, sllid, dst, dst, 48);
|
|
emit_insn(ctx, srlid, dst, dst, 48);
|
|
break;
|
|
case 32:
|
|
emit_insn(ctx, revb2w, dst, dst);
|
|
/* zero-extend 32 bits into 64 bits */
|
|
emit_zext_32(ctx, dst, is32);
|
|
break;
|
|
case 64:
|
|
emit_insn(ctx, revbd, dst, dst);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
/* PC += off if dst cond src */
|
|
case BPF_JMP | BPF_JEQ | BPF_X:
|
|
case BPF_JMP | BPF_JNE | BPF_X:
|
|
case BPF_JMP | BPF_JGT | BPF_X:
|
|
case BPF_JMP | BPF_JGE | BPF_X:
|
|
case BPF_JMP | BPF_JLT | BPF_X:
|
|
case BPF_JMP | BPF_JLE | BPF_X:
|
|
case BPF_JMP | BPF_JSGT | BPF_X:
|
|
case BPF_JMP | BPF_JSGE | BPF_X:
|
|
case BPF_JMP | BPF_JSLT | BPF_X:
|
|
case BPF_JMP | BPF_JSLE | BPF_X:
|
|
jmp_offset = bpf2la_offset(i, off, ctx);
|
|
emit_cond_jump(ctx, cond, dst, src, jmp_offset);
|
|
break;
|
|
|
|
/* PC += off if dst cond imm */
|
|
case BPF_JMP | BPF_JEQ | BPF_K:
|
|
case BPF_JMP | BPF_JNE | BPF_K:
|
|
case BPF_JMP | BPF_JGT | BPF_K:
|
|
case BPF_JMP | BPF_JGE | BPF_K:
|
|
case BPF_JMP | BPF_JLT | BPF_K:
|
|
case BPF_JMP | BPF_JLE | BPF_K:
|
|
case BPF_JMP | BPF_JSGT | BPF_K:
|
|
case BPF_JMP | BPF_JSGE | BPF_K:
|
|
case BPF_JMP | BPF_JSLT | BPF_K:
|
|
case BPF_JMP | BPF_JSLE | BPF_K:
|
|
jmp_offset = bpf2la_offset(i, off, ctx);
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_cond_jump(ctx, cond, dst, tmp, jmp_offset);
|
|
break;
|
|
|
|
/* PC += off if dst & src */
|
|
case BPF_JMP | BPF_JSET | BPF_X:
|
|
jmp_offset = bpf2la_offset(i, off, ctx);
|
|
emit_insn(ctx, and, tmp, dst, src);
|
|
emit_cond_jump(ctx, cond, tmp, LOONGARCH_GPR_ZERO, jmp_offset);
|
|
break;
|
|
/* PC += off if dst & imm */
|
|
case BPF_JMP | BPF_JSET | BPF_K:
|
|
jmp_offset = bpf2la_offset(i, off, ctx);
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
emit_insn(ctx, and, tmp, dst, tmp);
|
|
emit_cond_jump(ctx, cond, tmp, LOONGARCH_GPR_ZERO, jmp_offset);
|
|
break;
|
|
|
|
/* PC += off */
|
|
case BPF_JMP | BPF_JA:
|
|
jmp_offset = bpf2la_offset(i, off, ctx);
|
|
emit_uncond_jump(ctx, jmp_offset, false);
|
|
break;
|
|
|
|
/* function call */
|
|
case BPF_JMP | BPF_CALL:
|
|
mark_call(ctx);
|
|
ret = bpf_jit_get_func_addr(ctx->prog, insn, extra_pass,
|
|
&func_addr, &func_addr_fixed);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
move_imm64(ctx, tmp, func_addr, is32);
|
|
emit_insn(ctx, jirl, LOONGARCH_GPR_RA, tmp, 0);
|
|
move_reg(ctx, regmap[BPF_REG_0], LOONGARCH_GPR_V0);
|
|
break;
|
|
|
|
/* tail call */
|
|
case BPF_JMP | BPF_TAIL_CALL:
|
|
mark_tail_call(ctx);
|
|
if (emit_bpf_tail_call(ctx))
|
|
return -EINVAL;
|
|
break;
|
|
|
|
/* function return */
|
|
case BPF_JMP | BPF_EXIT:
|
|
emit_sext_32(ctx, regmap[BPF_REG_0]);
|
|
/*
|
|
* Optimization: when last instruction is EXIT,
|
|
* simply fallthrough to epilogue.
|
|
*/
|
|
if (i == ctx->prog->len - 1)
|
|
break;
|
|
|
|
jmp_offset = epilogue_offset(ctx);
|
|
emit_uncond_jump(ctx, jmp_offset, true);
|
|
break;
|
|
|
|
/* dst = imm64 */
|
|
case BPF_LD | BPF_IMM | BPF_DW:
|
|
imm64 = (u64)(insn + 1)->imm << 32 | (u32)insn->imm;
|
|
move_imm64(ctx, dst, imm64, is32);
|
|
return 1;
|
|
|
|
/* dst = *(size *)(src + off) */
|
|
case BPF_LDX | BPF_MEM | BPF_B:
|
|
case BPF_LDX | BPF_MEM | BPF_H:
|
|
case BPF_LDX | BPF_MEM | BPF_W:
|
|
case BPF_LDX | BPF_MEM | BPF_DW:
|
|
if (is_signed_imm12(off)) {
|
|
switch (BPF_SIZE(code)) {
|
|
case BPF_B:
|
|
emit_insn(ctx, ldbu, dst, src, off);
|
|
break;
|
|
case BPF_H:
|
|
emit_insn(ctx, ldhu, dst, src, off);
|
|
break;
|
|
case BPF_W:
|
|
emit_insn(ctx, ldwu, dst, src, off);
|
|
break;
|
|
case BPF_DW:
|
|
emit_insn(ctx, ldd, dst, src, off);
|
|
break;
|
|
}
|
|
} else {
|
|
move_imm32(ctx, tmp, off, is32);
|
|
switch (BPF_SIZE(code)) {
|
|
case BPF_B:
|
|
emit_insn(ctx, ldxbu, dst, src, tmp);
|
|
break;
|
|
case BPF_H:
|
|
emit_insn(ctx, ldxhu, dst, src, tmp);
|
|
break;
|
|
case BPF_W:
|
|
emit_insn(ctx, ldxwu, dst, src, tmp);
|
|
break;
|
|
case BPF_DW:
|
|
emit_insn(ctx, ldxd, dst, src, tmp);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* *(size *)(dst + off) = imm */
|
|
case BPF_ST | BPF_MEM | BPF_B:
|
|
case BPF_ST | BPF_MEM | BPF_H:
|
|
case BPF_ST | BPF_MEM | BPF_W:
|
|
case BPF_ST | BPF_MEM | BPF_DW:
|
|
move_imm32(ctx, tmp, imm, is32);
|
|
if (is_signed_imm12(off)) {
|
|
switch (BPF_SIZE(code)) {
|
|
case BPF_B:
|
|
emit_insn(ctx, stb, tmp, dst, off);
|
|
break;
|
|
case BPF_H:
|
|
emit_insn(ctx, sth, tmp, dst, off);
|
|
break;
|
|
case BPF_W:
|
|
emit_insn(ctx, stw, tmp, dst, off);
|
|
break;
|
|
case BPF_DW:
|
|
emit_insn(ctx, std, tmp, dst, off);
|
|
break;
|
|
}
|
|
} else {
|
|
move_imm32(ctx, tmp2, off, is32);
|
|
switch (BPF_SIZE(code)) {
|
|
case BPF_B:
|
|
emit_insn(ctx, stxb, tmp, dst, tmp2);
|
|
break;
|
|
case BPF_H:
|
|
emit_insn(ctx, stxh, tmp, dst, tmp2);
|
|
break;
|
|
case BPF_W:
|
|
emit_insn(ctx, stxw, tmp, dst, tmp2);
|
|
break;
|
|
case BPF_DW:
|
|
emit_insn(ctx, stxd, tmp, dst, tmp2);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* *(size *)(dst + off) = src */
|
|
case BPF_STX | BPF_MEM | BPF_B:
|
|
case BPF_STX | BPF_MEM | BPF_H:
|
|
case BPF_STX | BPF_MEM | BPF_W:
|
|
case BPF_STX | BPF_MEM | BPF_DW:
|
|
if (is_signed_imm12(off)) {
|
|
switch (BPF_SIZE(code)) {
|
|
case BPF_B:
|
|
emit_insn(ctx, stb, src, dst, off);
|
|
break;
|
|
case BPF_H:
|
|
emit_insn(ctx, sth, src, dst, off);
|
|
break;
|
|
case BPF_W:
|
|
emit_insn(ctx, stw, src, dst, off);
|
|
break;
|
|
case BPF_DW:
|
|
emit_insn(ctx, std, src, dst, off);
|
|
break;
|
|
}
|
|
} else {
|
|
move_imm32(ctx, tmp, off, is32);
|
|
switch (BPF_SIZE(code)) {
|
|
case BPF_B:
|
|
emit_insn(ctx, stxb, src, dst, tmp);
|
|
break;
|
|
case BPF_H:
|
|
emit_insn(ctx, stxh, src, dst, tmp);
|
|
break;
|
|
case BPF_W:
|
|
emit_insn(ctx, stxw, src, dst, tmp);
|
|
break;
|
|
case BPF_DW:
|
|
emit_insn(ctx, stxd, src, dst, tmp);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* atomic_add: lock *(size *)(dst + off) += src */
|
|
case BPF_STX | BPF_XADD | BPF_W:
|
|
case BPF_STX | BPF_XADD | BPF_DW:
|
|
if (insn->imm != BPF_ADD) {
|
|
pr_err_once("unknown atomic op code %02x\n", insn->imm);
|
|
return -EINVAL;
|
|
}
|
|
|
|
move_imm32(ctx, tmp, off, is32);
|
|
emit_insn(ctx, addd, tmp, dst, tmp);
|
|
switch (BPF_SIZE(insn->code)) {
|
|
case BPF_W:
|
|
emit_insn(ctx, amaddw, tmp2, src, tmp);
|
|
break;
|
|
case BPF_DW:
|
|
emit_insn(ctx, amaddd, tmp2, src, tmp);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
pr_err("bpf_jit: unknown opcode %02x\n", code);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int build_body(struct jit_ctx *ctx, bool extra_pass)
|
|
{
|
|
const struct bpf_prog *prog = ctx->prog;
|
|
int i;
|
|
|
|
for (i = 0; i < prog->len; i++) {
|
|
const struct bpf_insn *insn = &prog->insnsi[i];
|
|
int ret;
|
|
|
|
if (!ctx->image)
|
|
ctx->offset[i] = ctx->idx;
|
|
|
|
ret = build_insn(insn, ctx, extra_pass);
|
|
if (ret > 0) {
|
|
i++;
|
|
if (!ctx->image)
|
|
ctx->offset[i] = ctx->idx;
|
|
continue;
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (!ctx->image)
|
|
ctx->offset[i] = ctx->idx;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void bpf_flush_icache(void *start, void *end)
|
|
{
|
|
flush_icache_range((unsigned long)start, (unsigned long)end);
|
|
}
|
|
|
|
/* Fill space with illegal instructions */
|
|
static void jit_fill_hole(void *area, unsigned int size)
|
|
{
|
|
u32 *ptr;
|
|
|
|
/* We are guaranteed to have aligned memory */
|
|
for (ptr = area; size >= sizeof(u32); size -= sizeof(u32))
|
|
*ptr++ = INSN_BREAK;
|
|
}
|
|
|
|
static int validate_code(struct jit_ctx *ctx)
|
|
{
|
|
int i;
|
|
union loongarch_instruction insn;
|
|
|
|
for (i = 0; i < ctx->idx; i++) {
|
|
insn = ctx->image[i];
|
|
/* Check INSN_BREAK */
|
|
if (insn.word == INSN_BREAK)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
|
|
{
|
|
struct bpf_prog *tmp, *orig_prog = prog;
|
|
struct bpf_binary_header *header;
|
|
struct jit_data *jit_data;
|
|
struct jit_ctx ctx;
|
|
bool tmp_blinded = false;
|
|
bool extra_pass = false;
|
|
int image_size;
|
|
u8 *image_ptr;
|
|
|
|
/*
|
|
* If BPF JIT was not enabled then we must fall back to
|
|
* the interpreter.
|
|
*/
|
|
if (!prog->jit_requested)
|
|
return orig_prog;
|
|
|
|
tmp = bpf_jit_blind_constants(prog);
|
|
/*
|
|
* If blinding was requested and we failed during blinding,
|
|
* we must fall back to the interpreter. Otherwise, we save
|
|
* the new JITed code.
|
|
*/
|
|
if (IS_ERR(tmp))
|
|
return orig_prog;
|
|
if (tmp != prog) {
|
|
tmp_blinded = true;
|
|
prog = tmp;
|
|
}
|
|
|
|
jit_data = prog->aux->jit_data;
|
|
if (!jit_data) {
|
|
jit_data = kzalloc(sizeof(*jit_data), GFP_KERNEL);
|
|
if (!jit_data) {
|
|
prog = orig_prog;
|
|
goto out;
|
|
}
|
|
prog->aux->jit_data = jit_data;
|
|
}
|
|
if (jit_data->ctx.offset) {
|
|
ctx = jit_data->ctx;
|
|
image_ptr = jit_data->image;
|
|
header = jit_data->header;
|
|
extra_pass = true;
|
|
image_size = sizeof(u32) * ctx.idx;
|
|
goto skip_init_ctx;
|
|
}
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.prog = prog;
|
|
|
|
ctx.offset = kcalloc(prog->len + 1, sizeof(*ctx.offset), GFP_KERNEL);
|
|
if (!ctx.offset) {
|
|
prog = orig_prog;
|
|
goto out_off;
|
|
}
|
|
|
|
/* 1. Initial fake pass to compute ctx->idx and set ctx->flags */
|
|
if (build_body(&ctx, extra_pass)) {
|
|
prog = orig_prog;
|
|
goto out_off;
|
|
}
|
|
build_prologue(&ctx);
|
|
ctx.epilogue_offset = ctx.idx;
|
|
build_epilogue(&ctx);
|
|
|
|
/* Now we know the actual image size.
|
|
* As each LoongArch instruction is of length 32bit,
|
|
* we are translating number of JITed intructions into
|
|
* the size required to store these JITed code.
|
|
*/
|
|
image_size = sizeof(u32) * ctx.idx;
|
|
/* Now we know the size of the structure to make */
|
|
header = bpf_jit_binary_alloc(image_size, &image_ptr,
|
|
sizeof(u32), jit_fill_hole);
|
|
if (!header) {
|
|
prog = orig_prog;
|
|
goto out_off;
|
|
}
|
|
|
|
/* 2. Now, the actual pass to generate final JIT code */
|
|
ctx.image = (union loongarch_instruction *)image_ptr;
|
|
skip_init_ctx:
|
|
ctx.idx = 0;
|
|
|
|
build_prologue(&ctx);
|
|
if (build_body(&ctx, extra_pass)) {
|
|
bpf_jit_binary_free(header);
|
|
prog = orig_prog;
|
|
goto out_off;
|
|
}
|
|
build_epilogue(&ctx);
|
|
|
|
/* 3. Extra pass to validate JITed code */
|
|
if (validate_code(&ctx)) {
|
|
bpf_jit_binary_free(header);
|
|
prog = orig_prog;
|
|
goto out_off;
|
|
}
|
|
|
|
/* And we're done */
|
|
if (bpf_jit_enable > 1)
|
|
bpf_jit_dump(prog->len, image_size, 2, ctx.image);
|
|
|
|
/* Update the icache */
|
|
bpf_flush_icache(header, ctx.image + ctx.idx);
|
|
|
|
if (!prog->is_func || extra_pass) {
|
|
if (extra_pass && ctx.idx != jit_data->ctx.idx) {
|
|
pr_err_once("multi-func JIT bug %d != %d\n",
|
|
ctx.idx, jit_data->ctx.idx);
|
|
bpf_jit_binary_free(header);
|
|
prog->bpf_func = NULL;
|
|
prog->jited = 0;
|
|
goto out_off;
|
|
}
|
|
bpf_jit_binary_lock_ro(header);
|
|
} else {
|
|
jit_data->ctx = ctx;
|
|
jit_data->image = image_ptr;
|
|
jit_data->header = header;
|
|
}
|
|
prog->bpf_func = (void *)ctx.image;
|
|
prog->jited = 1;
|
|
prog->jited_len = image_size;
|
|
|
|
if (!prog->is_func || extra_pass) {
|
|
out_off:
|
|
kfree(ctx.offset);
|
|
kfree(jit_data);
|
|
prog->aux->jit_data = NULL;
|
|
}
|
|
out:
|
|
if (tmp_blinded)
|
|
bpf_jit_prog_release_other(prog, prog == orig_prog ?
|
|
tmp : orig_prog);
|
|
|
|
out_offset = -1;
|
|
return prog;
|
|
}
|