BPF trampoline is the critical infrastructure of the BPF subsystem, acting as a mediator between kernel functions and BPF programs. Numerous important features, such as using BPF program for zero overhead kernel introspection, rely on this key component. We can't wait to support bpf trampoline on RV64. The related tests have passed, as well as the test_verifier with no new failure ceses. Signed-off-by: Pu Lehui <pulehui@huawei.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Tested-by: Björn Töpel <bjorn@rivosinc.com> Acked-by: Björn Töpel <bjorn@rivosinc.com> Link: https://lore.kernel.org/bpf/20230215135205.1411105-5-pulehui@huaweicloud.com
1754 lines
44 KiB
C
1754 lines
44 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* BPF JIT compiler for RV64G
|
|
*
|
|
* Copyright(c) 2019 Björn Töpel <bjorn.topel@gmail.com>
|
|
*
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/bpf.h>
|
|
#include <linux/filter.h>
|
|
#include <linux/memory.h>
|
|
#include <linux/stop_machine.h>
|
|
#include "bpf_jit.h"
|
|
|
|
#define RV_REG_TCC RV_REG_A6
|
|
#define RV_REG_TCC_SAVED RV_REG_S6 /* Store A6 in S6 if program do calls */
|
|
|
|
static const int regmap[] = {
|
|
[BPF_REG_0] = RV_REG_A5,
|
|
[BPF_REG_1] = RV_REG_A0,
|
|
[BPF_REG_2] = RV_REG_A1,
|
|
[BPF_REG_3] = RV_REG_A2,
|
|
[BPF_REG_4] = RV_REG_A3,
|
|
[BPF_REG_5] = RV_REG_A4,
|
|
[BPF_REG_6] = RV_REG_S1,
|
|
[BPF_REG_7] = RV_REG_S2,
|
|
[BPF_REG_8] = RV_REG_S3,
|
|
[BPF_REG_9] = RV_REG_S4,
|
|
[BPF_REG_FP] = RV_REG_S5,
|
|
[BPF_REG_AX] = RV_REG_T0,
|
|
};
|
|
|
|
static const int pt_regmap[] = {
|
|
[RV_REG_A0] = offsetof(struct pt_regs, a0),
|
|
[RV_REG_A1] = offsetof(struct pt_regs, a1),
|
|
[RV_REG_A2] = offsetof(struct pt_regs, a2),
|
|
[RV_REG_A3] = offsetof(struct pt_regs, a3),
|
|
[RV_REG_A4] = offsetof(struct pt_regs, a4),
|
|
[RV_REG_A5] = offsetof(struct pt_regs, a5),
|
|
[RV_REG_S1] = offsetof(struct pt_regs, s1),
|
|
[RV_REG_S2] = offsetof(struct pt_regs, s2),
|
|
[RV_REG_S3] = offsetof(struct pt_regs, s3),
|
|
[RV_REG_S4] = offsetof(struct pt_regs, s4),
|
|
[RV_REG_S5] = offsetof(struct pt_regs, s5),
|
|
[RV_REG_T0] = offsetof(struct pt_regs, t0),
|
|
};
|
|
|
|
enum {
|
|
RV_CTX_F_SEEN_TAIL_CALL = 0,
|
|
RV_CTX_F_SEEN_CALL = RV_REG_RA,
|
|
RV_CTX_F_SEEN_S1 = RV_REG_S1,
|
|
RV_CTX_F_SEEN_S2 = RV_REG_S2,
|
|
RV_CTX_F_SEEN_S3 = RV_REG_S3,
|
|
RV_CTX_F_SEEN_S4 = RV_REG_S4,
|
|
RV_CTX_F_SEEN_S5 = RV_REG_S5,
|
|
RV_CTX_F_SEEN_S6 = RV_REG_S6,
|
|
};
|
|
|
|
static u8 bpf_to_rv_reg(int bpf_reg, struct rv_jit_context *ctx)
|
|
{
|
|
u8 reg = regmap[bpf_reg];
|
|
|
|
switch (reg) {
|
|
case RV_CTX_F_SEEN_S1:
|
|
case RV_CTX_F_SEEN_S2:
|
|
case RV_CTX_F_SEEN_S3:
|
|
case RV_CTX_F_SEEN_S4:
|
|
case RV_CTX_F_SEEN_S5:
|
|
case RV_CTX_F_SEEN_S6:
|
|
__set_bit(reg, &ctx->flags);
|
|
}
|
|
return reg;
|
|
};
|
|
|
|
static bool seen_reg(int reg, struct rv_jit_context *ctx)
|
|
{
|
|
switch (reg) {
|
|
case RV_CTX_F_SEEN_CALL:
|
|
case RV_CTX_F_SEEN_S1:
|
|
case RV_CTX_F_SEEN_S2:
|
|
case RV_CTX_F_SEEN_S3:
|
|
case RV_CTX_F_SEEN_S4:
|
|
case RV_CTX_F_SEEN_S5:
|
|
case RV_CTX_F_SEEN_S6:
|
|
return test_bit(reg, &ctx->flags);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void mark_fp(struct rv_jit_context *ctx)
|
|
{
|
|
__set_bit(RV_CTX_F_SEEN_S5, &ctx->flags);
|
|
}
|
|
|
|
static void mark_call(struct rv_jit_context *ctx)
|
|
{
|
|
__set_bit(RV_CTX_F_SEEN_CALL, &ctx->flags);
|
|
}
|
|
|
|
static bool seen_call(struct rv_jit_context *ctx)
|
|
{
|
|
return test_bit(RV_CTX_F_SEEN_CALL, &ctx->flags);
|
|
}
|
|
|
|
static void mark_tail_call(struct rv_jit_context *ctx)
|
|
{
|
|
__set_bit(RV_CTX_F_SEEN_TAIL_CALL, &ctx->flags);
|
|
}
|
|
|
|
static bool seen_tail_call(struct rv_jit_context *ctx)
|
|
{
|
|
return test_bit(RV_CTX_F_SEEN_TAIL_CALL, &ctx->flags);
|
|
}
|
|
|
|
static u8 rv_tail_call_reg(struct rv_jit_context *ctx)
|
|
{
|
|
mark_tail_call(ctx);
|
|
|
|
if (seen_call(ctx)) {
|
|
__set_bit(RV_CTX_F_SEEN_S6, &ctx->flags);
|
|
return RV_REG_S6;
|
|
}
|
|
return RV_REG_A6;
|
|
}
|
|
|
|
static bool is_32b_int(s64 val)
|
|
{
|
|
return -(1L << 31) <= val && val < (1L << 31);
|
|
}
|
|
|
|
static bool in_auipc_jalr_range(s64 val)
|
|
{
|
|
/*
|
|
* auipc+jalr can reach any signed PC-relative offset in the range
|
|
* [-2^31 - 2^11, 2^31 - 2^11).
|
|
*/
|
|
return (-(1L << 31) - (1L << 11)) <= val &&
|
|
val < ((1L << 31) - (1L << 11));
|
|
}
|
|
|
|
/* Emit fixed-length instructions for address */
|
|
static int emit_addr(u8 rd, u64 addr, bool extra_pass, struct rv_jit_context *ctx)
|
|
{
|
|
u64 ip = (u64)(ctx->insns + ctx->ninsns);
|
|
s64 off = addr - ip;
|
|
s64 upper = (off + (1 << 11)) >> 12;
|
|
s64 lower = off & 0xfff;
|
|
|
|
if (extra_pass && !in_auipc_jalr_range(off)) {
|
|
pr_err("bpf-jit: target offset 0x%llx is out of range\n", off);
|
|
return -ERANGE;
|
|
}
|
|
|
|
emit(rv_auipc(rd, upper), ctx);
|
|
emit(rv_addi(rd, rd, lower), ctx);
|
|
return 0;
|
|
}
|
|
|
|
/* Emit variable-length instructions for 32-bit and 64-bit imm */
|
|
static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx)
|
|
{
|
|
/* Note that the immediate from the add is sign-extended,
|
|
* which means that we need to compensate this by adding 2^12,
|
|
* when the 12th bit is set. A simpler way of doing this, and
|
|
* getting rid of the check, is to just add 2**11 before the
|
|
* shift. The "Loading a 32-Bit constant" example from the
|
|
* "Computer Organization and Design, RISC-V edition" book by
|
|
* Patterson/Hennessy highlights this fact.
|
|
*
|
|
* This also means that we need to process LSB to MSB.
|
|
*/
|
|
s64 upper = (val + (1 << 11)) >> 12;
|
|
/* Sign-extend lower 12 bits to 64 bits since immediates for li, addiw,
|
|
* and addi are signed and RVC checks will perform signed comparisons.
|
|
*/
|
|
s64 lower = ((val & 0xfff) << 52) >> 52;
|
|
int shift;
|
|
|
|
if (is_32b_int(val)) {
|
|
if (upper)
|
|
emit_lui(rd, upper, ctx);
|
|
|
|
if (!upper) {
|
|
emit_li(rd, lower, ctx);
|
|
return;
|
|
}
|
|
|
|
emit_addiw(rd, rd, lower, ctx);
|
|
return;
|
|
}
|
|
|
|
shift = __ffs(upper);
|
|
upper >>= shift;
|
|
shift += 12;
|
|
|
|
emit_imm(rd, upper, ctx);
|
|
|
|
emit_slli(rd, rd, shift, ctx);
|
|
if (lower)
|
|
emit_addi(rd, rd, lower, ctx);
|
|
}
|
|
|
|
static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
|
|
{
|
|
int stack_adjust = ctx->stack_size, store_offset = stack_adjust - 8;
|
|
|
|
if (seen_reg(RV_REG_RA, ctx)) {
|
|
emit_ld(RV_REG_RA, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
emit_ld(RV_REG_FP, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
if (seen_reg(RV_REG_S1, ctx)) {
|
|
emit_ld(RV_REG_S1, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S2, ctx)) {
|
|
emit_ld(RV_REG_S2, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S3, ctx)) {
|
|
emit_ld(RV_REG_S3, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S4, ctx)) {
|
|
emit_ld(RV_REG_S4, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S5, ctx)) {
|
|
emit_ld(RV_REG_S5, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S6, ctx)) {
|
|
emit_ld(RV_REG_S6, store_offset, RV_REG_SP, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
|
|
emit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);
|
|
/* Set return value. */
|
|
if (!is_tail_call)
|
|
emit_mv(RV_REG_A0, RV_REG_A5, ctx);
|
|
emit_jalr(RV_REG_ZERO, is_tail_call ? RV_REG_T3 : RV_REG_RA,
|
|
is_tail_call ? 20 : 0, /* skip reserved nops and TCC init */
|
|
ctx);
|
|
}
|
|
|
|
static void emit_bcc(u8 cond, u8 rd, u8 rs, int rvoff,
|
|
struct rv_jit_context *ctx)
|
|
{
|
|
switch (cond) {
|
|
case BPF_JEQ:
|
|
emit(rv_beq(rd, rs, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JGT:
|
|
emit(rv_bltu(rs, rd, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JLT:
|
|
emit(rv_bltu(rd, rs, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JGE:
|
|
emit(rv_bgeu(rd, rs, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JLE:
|
|
emit(rv_bgeu(rs, rd, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JNE:
|
|
emit(rv_bne(rd, rs, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JSGT:
|
|
emit(rv_blt(rs, rd, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JSLT:
|
|
emit(rv_blt(rd, rs, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JSGE:
|
|
emit(rv_bge(rd, rs, rvoff >> 1), ctx);
|
|
return;
|
|
case BPF_JSLE:
|
|
emit(rv_bge(rs, rd, rvoff >> 1), ctx);
|
|
}
|
|
}
|
|
|
|
static void emit_branch(u8 cond, u8 rd, u8 rs, int rvoff,
|
|
struct rv_jit_context *ctx)
|
|
{
|
|
s64 upper, lower;
|
|
|
|
if (is_13b_int(rvoff)) {
|
|
emit_bcc(cond, rd, rs, rvoff, ctx);
|
|
return;
|
|
}
|
|
|
|
/* Adjust for jal */
|
|
rvoff -= 4;
|
|
|
|
/* Transform, e.g.:
|
|
* bne rd,rs,foo
|
|
* to
|
|
* beq rd,rs,<.L1>
|
|
* (auipc foo)
|
|
* jal(r) foo
|
|
* .L1
|
|
*/
|
|
cond = invert_bpf_cond(cond);
|
|
if (is_21b_int(rvoff)) {
|
|
emit_bcc(cond, rd, rs, 8, ctx);
|
|
emit(rv_jal(RV_REG_ZERO, rvoff >> 1), ctx);
|
|
return;
|
|
}
|
|
|
|
/* 32b No need for an additional rvoff adjustment, since we
|
|
* get that from the auipc at PC', where PC = PC' + 4.
|
|
*/
|
|
upper = (rvoff + (1 << 11)) >> 12;
|
|
lower = rvoff & 0xfff;
|
|
|
|
emit_bcc(cond, rd, rs, 12, ctx);
|
|
emit(rv_auipc(RV_REG_T1, upper), ctx);
|
|
emit(rv_jalr(RV_REG_ZERO, RV_REG_T1, lower), ctx);
|
|
}
|
|
|
|
static void emit_zext_32(u8 reg, struct rv_jit_context *ctx)
|
|
{
|
|
emit_slli(reg, reg, 32, ctx);
|
|
emit_srli(reg, reg, 32, ctx);
|
|
}
|
|
|
|
static int emit_bpf_tail_call(int insn, struct rv_jit_context *ctx)
|
|
{
|
|
int tc_ninsn, off, start_insn = ctx->ninsns;
|
|
u8 tcc = rv_tail_call_reg(ctx);
|
|
|
|
/* a0: &ctx
|
|
* a1: &array
|
|
* a2: index
|
|
*
|
|
* if (index >= array->map.max_entries)
|
|
* goto out;
|
|
*/
|
|
tc_ninsn = insn ? ctx->offset[insn] - ctx->offset[insn - 1] :
|
|
ctx->offset[0];
|
|
emit_zext_32(RV_REG_A2, ctx);
|
|
|
|
off = offsetof(struct bpf_array, map.max_entries);
|
|
if (is_12b_check(off, insn))
|
|
return -1;
|
|
emit(rv_lwu(RV_REG_T1, off, RV_REG_A1), ctx);
|
|
off = ninsns_rvoff(tc_ninsn - (ctx->ninsns - start_insn));
|
|
emit_branch(BPF_JGE, RV_REG_A2, RV_REG_T1, off, ctx);
|
|
|
|
/* if (--TCC < 0)
|
|
* goto out;
|
|
*/
|
|
emit_addi(RV_REG_TCC, tcc, -1, ctx);
|
|
off = ninsns_rvoff(tc_ninsn - (ctx->ninsns - start_insn));
|
|
emit_branch(BPF_JSLT, RV_REG_TCC, RV_REG_ZERO, off, ctx);
|
|
|
|
/* prog = array->ptrs[index];
|
|
* if (!prog)
|
|
* goto out;
|
|
*/
|
|
emit_slli(RV_REG_T2, RV_REG_A2, 3, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_A1, ctx);
|
|
off = offsetof(struct bpf_array, ptrs);
|
|
if (is_12b_check(off, insn))
|
|
return -1;
|
|
emit_ld(RV_REG_T2, off, RV_REG_T2, ctx);
|
|
off = ninsns_rvoff(tc_ninsn - (ctx->ninsns - start_insn));
|
|
emit_branch(BPF_JEQ, RV_REG_T2, RV_REG_ZERO, off, ctx);
|
|
|
|
/* goto *(prog->bpf_func + 4); */
|
|
off = offsetof(struct bpf_prog, bpf_func);
|
|
if (is_12b_check(off, insn))
|
|
return -1;
|
|
emit_ld(RV_REG_T3, off, RV_REG_T2, ctx);
|
|
__build_epilogue(true, ctx);
|
|
return 0;
|
|
}
|
|
|
|
static void init_regs(u8 *rd, u8 *rs, const struct bpf_insn *insn,
|
|
struct rv_jit_context *ctx)
|
|
{
|
|
u8 code = insn->code;
|
|
|
|
switch (code) {
|
|
case BPF_JMP | BPF_JA:
|
|
case BPF_JMP | BPF_CALL:
|
|
case BPF_JMP | BPF_EXIT:
|
|
case BPF_JMP | BPF_TAIL_CALL:
|
|
break;
|
|
default:
|
|
*rd = bpf_to_rv_reg(insn->dst_reg, ctx);
|
|
}
|
|
|
|
if (code & (BPF_ALU | BPF_X) || code & (BPF_ALU64 | BPF_X) ||
|
|
code & (BPF_JMP | BPF_X) || code & (BPF_JMP32 | BPF_X) ||
|
|
code & BPF_LDX || code & BPF_STX)
|
|
*rs = bpf_to_rv_reg(insn->src_reg, ctx);
|
|
}
|
|
|
|
static void emit_zext_32_rd_rs(u8 *rd, u8 *rs, struct rv_jit_context *ctx)
|
|
{
|
|
emit_mv(RV_REG_T2, *rd, ctx);
|
|
emit_zext_32(RV_REG_T2, ctx);
|
|
emit_mv(RV_REG_T1, *rs, ctx);
|
|
emit_zext_32(RV_REG_T1, ctx);
|
|
*rd = RV_REG_T2;
|
|
*rs = RV_REG_T1;
|
|
}
|
|
|
|
static void emit_sext_32_rd_rs(u8 *rd, u8 *rs, struct rv_jit_context *ctx)
|
|
{
|
|
emit_addiw(RV_REG_T2, *rd, 0, ctx);
|
|
emit_addiw(RV_REG_T1, *rs, 0, ctx);
|
|
*rd = RV_REG_T2;
|
|
*rs = RV_REG_T1;
|
|
}
|
|
|
|
static void emit_zext_32_rd_t1(u8 *rd, struct rv_jit_context *ctx)
|
|
{
|
|
emit_mv(RV_REG_T2, *rd, ctx);
|
|
emit_zext_32(RV_REG_T2, ctx);
|
|
emit_zext_32(RV_REG_T1, ctx);
|
|
*rd = RV_REG_T2;
|
|
}
|
|
|
|
static void emit_sext_32_rd(u8 *rd, struct rv_jit_context *ctx)
|
|
{
|
|
emit_addiw(RV_REG_T2, *rd, 0, ctx);
|
|
*rd = RV_REG_T2;
|
|
}
|
|
|
|
static int emit_jump_and_link(u8 rd, s64 rvoff, bool fixed_addr,
|
|
struct rv_jit_context *ctx)
|
|
{
|
|
s64 upper, lower;
|
|
|
|
if (rvoff && fixed_addr && is_21b_int(rvoff)) {
|
|
emit(rv_jal(rd, rvoff >> 1), ctx);
|
|
return 0;
|
|
} else if (in_auipc_jalr_range(rvoff)) {
|
|
upper = (rvoff + (1 << 11)) >> 12;
|
|
lower = rvoff & 0xfff;
|
|
emit(rv_auipc(RV_REG_T1, upper), ctx);
|
|
emit(rv_jalr(rd, RV_REG_T1, lower), ctx);
|
|
return 0;
|
|
}
|
|
|
|
pr_err("bpf-jit: target offset 0x%llx is out of range\n", rvoff);
|
|
return -ERANGE;
|
|
}
|
|
|
|
static bool is_signed_bpf_cond(u8 cond)
|
|
{
|
|
return cond == BPF_JSGT || cond == BPF_JSLT ||
|
|
cond == BPF_JSGE || cond == BPF_JSLE;
|
|
}
|
|
|
|
static int emit_call(u64 addr, bool fixed_addr, struct rv_jit_context *ctx)
|
|
{
|
|
s64 off = 0;
|
|
u64 ip;
|
|
|
|
if (addr && ctx->insns) {
|
|
ip = (u64)(long)(ctx->insns + ctx->ninsns);
|
|
off = addr - ip;
|
|
}
|
|
|
|
return emit_jump_and_link(RV_REG_RA, off, fixed_addr, ctx);
|
|
}
|
|
|
|
static void emit_atomic(u8 rd, u8 rs, s16 off, s32 imm, bool is64,
|
|
struct rv_jit_context *ctx)
|
|
{
|
|
u8 r0;
|
|
int jmp_offset;
|
|
|
|
if (off) {
|
|
if (is_12b_int(off)) {
|
|
emit_addi(RV_REG_T1, rd, off, ctx);
|
|
} else {
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rd, ctx);
|
|
}
|
|
rd = RV_REG_T1;
|
|
}
|
|
|
|
switch (imm) {
|
|
/* lock *(u32/u64 *)(dst_reg + off16) <op>= src_reg */
|
|
case BPF_ADD:
|
|
emit(is64 ? rv_amoadd_d(RV_REG_ZERO, rs, rd, 0, 0) :
|
|
rv_amoadd_w(RV_REG_ZERO, rs, rd, 0, 0), ctx);
|
|
break;
|
|
case BPF_AND:
|
|
emit(is64 ? rv_amoand_d(RV_REG_ZERO, rs, rd, 0, 0) :
|
|
rv_amoand_w(RV_REG_ZERO, rs, rd, 0, 0), ctx);
|
|
break;
|
|
case BPF_OR:
|
|
emit(is64 ? rv_amoor_d(RV_REG_ZERO, rs, rd, 0, 0) :
|
|
rv_amoor_w(RV_REG_ZERO, rs, rd, 0, 0), ctx);
|
|
break;
|
|
case BPF_XOR:
|
|
emit(is64 ? rv_amoxor_d(RV_REG_ZERO, rs, rd, 0, 0) :
|
|
rv_amoxor_w(RV_REG_ZERO, rs, rd, 0, 0), ctx);
|
|
break;
|
|
/* src_reg = atomic_fetch_<op>(dst_reg + off16, src_reg) */
|
|
case BPF_ADD | BPF_FETCH:
|
|
emit(is64 ? rv_amoadd_d(rs, rs, rd, 0, 0) :
|
|
rv_amoadd_w(rs, rs, rd, 0, 0), ctx);
|
|
if (!is64)
|
|
emit_zext_32(rs, ctx);
|
|
break;
|
|
case BPF_AND | BPF_FETCH:
|
|
emit(is64 ? rv_amoand_d(rs, rs, rd, 0, 0) :
|
|
rv_amoand_w(rs, rs, rd, 0, 0), ctx);
|
|
if (!is64)
|
|
emit_zext_32(rs, ctx);
|
|
break;
|
|
case BPF_OR | BPF_FETCH:
|
|
emit(is64 ? rv_amoor_d(rs, rs, rd, 0, 0) :
|
|
rv_amoor_w(rs, rs, rd, 0, 0), ctx);
|
|
if (!is64)
|
|
emit_zext_32(rs, ctx);
|
|
break;
|
|
case BPF_XOR | BPF_FETCH:
|
|
emit(is64 ? rv_amoxor_d(rs, rs, rd, 0, 0) :
|
|
rv_amoxor_w(rs, rs, rd, 0, 0), ctx);
|
|
if (!is64)
|
|
emit_zext_32(rs, ctx);
|
|
break;
|
|
/* src_reg = atomic_xchg(dst_reg + off16, src_reg); */
|
|
case BPF_XCHG:
|
|
emit(is64 ? rv_amoswap_d(rs, rs, rd, 0, 0) :
|
|
rv_amoswap_w(rs, rs, rd, 0, 0), ctx);
|
|
if (!is64)
|
|
emit_zext_32(rs, ctx);
|
|
break;
|
|
/* r0 = atomic_cmpxchg(dst_reg + off16, r0, src_reg); */
|
|
case BPF_CMPXCHG:
|
|
r0 = bpf_to_rv_reg(BPF_REG_0, ctx);
|
|
emit(is64 ? rv_addi(RV_REG_T2, r0, 0) :
|
|
rv_addiw(RV_REG_T2, r0, 0), ctx);
|
|
emit(is64 ? rv_lr_d(r0, 0, rd, 0, 0) :
|
|
rv_lr_w(r0, 0, rd, 0, 0), ctx);
|
|
jmp_offset = ninsns_rvoff(8);
|
|
emit(rv_bne(RV_REG_T2, r0, jmp_offset >> 1), ctx);
|
|
emit(is64 ? rv_sc_d(RV_REG_T3, rs, rd, 0, 0) :
|
|
rv_sc_w(RV_REG_T3, rs, rd, 0, 0), ctx);
|
|
jmp_offset = ninsns_rvoff(-6);
|
|
emit(rv_bne(RV_REG_T3, 0, jmp_offset >> 1), ctx);
|
|
emit(rv_fence(0x3, 0x3), ctx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define BPF_FIXUP_OFFSET_MASK GENMASK(26, 0)
|
|
#define BPF_FIXUP_REG_MASK GENMASK(31, 27)
|
|
|
|
bool ex_handler_bpf(const struct exception_table_entry *ex,
|
|
struct pt_regs *regs)
|
|
{
|
|
off_t offset = FIELD_GET(BPF_FIXUP_OFFSET_MASK, ex->fixup);
|
|
int regs_offset = FIELD_GET(BPF_FIXUP_REG_MASK, ex->fixup);
|
|
|
|
*(unsigned long *)((void *)regs + pt_regmap[regs_offset]) = 0;
|
|
regs->epc = (unsigned long)&ex->fixup - offset;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* For accesses to BTF pointers, add an entry to the exception table */
|
|
static int add_exception_handler(const struct bpf_insn *insn,
|
|
struct rv_jit_context *ctx,
|
|
int dst_reg, int insn_len)
|
|
{
|
|
struct exception_table_entry *ex;
|
|
unsigned long pc;
|
|
off_t offset;
|
|
|
|
if (!ctx->insns || !ctx->prog->aux->extable || BPF_MODE(insn->code) != BPF_PROBE_MEM)
|
|
return 0;
|
|
|
|
if (WARN_ON_ONCE(ctx->nexentries >= ctx->prog->aux->num_exentries))
|
|
return -EINVAL;
|
|
|
|
if (WARN_ON_ONCE(insn_len > ctx->ninsns))
|
|
return -EINVAL;
|
|
|
|
if (WARN_ON_ONCE(!rvc_enabled() && insn_len == 1))
|
|
return -EINVAL;
|
|
|
|
ex = &ctx->prog->aux->extable[ctx->nexentries];
|
|
pc = (unsigned long)&ctx->insns[ctx->ninsns - insn_len];
|
|
|
|
offset = pc - (long)&ex->insn;
|
|
if (WARN_ON_ONCE(offset >= 0 || offset < INT_MIN))
|
|
return -ERANGE;
|
|
ex->insn = offset;
|
|
|
|
/*
|
|
* Since the extable follows the program, the fixup offset is always
|
|
* negative and limited to BPF_JIT_REGION_SIZE. Store a positive value
|
|
* to keep things simple, and put the destination register in the upper
|
|
* bits. We don't need to worry about buildtime or runtime sort
|
|
* modifying the upper bits because the table is already sorted, and
|
|
* isn't part of the main exception table.
|
|
*/
|
|
offset = (long)&ex->fixup - (pc + insn_len * sizeof(u16));
|
|
if (!FIELD_FIT(BPF_FIXUP_OFFSET_MASK, offset))
|
|
return -ERANGE;
|
|
|
|
ex->fixup = FIELD_PREP(BPF_FIXUP_OFFSET_MASK, offset) |
|
|
FIELD_PREP(BPF_FIXUP_REG_MASK, dst_reg);
|
|
ex->type = EX_TYPE_BPF;
|
|
|
|
ctx->nexentries++;
|
|
return 0;
|
|
}
|
|
|
|
static int gen_call_or_nops(void *target, void *ip, u32 *insns)
|
|
{
|
|
s64 rvoff;
|
|
int i, ret;
|
|
struct rv_jit_context ctx;
|
|
|
|
ctx.ninsns = 0;
|
|
ctx.insns = (u16 *)insns;
|
|
|
|
if (!target) {
|
|
for (i = 0; i < 4; i++)
|
|
emit(rv_nop(), &ctx);
|
|
return 0;
|
|
}
|
|
|
|
rvoff = (s64)(target - (ip + 4));
|
|
emit(rv_sd(RV_REG_SP, -8, RV_REG_RA), &ctx);
|
|
ret = emit_jump_and_link(RV_REG_RA, rvoff, false, &ctx);
|
|
if (ret)
|
|
return ret;
|
|
emit(rv_ld(RV_REG_RA, -8, RV_REG_SP), &ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gen_jump_or_nops(void *target, void *ip, u32 *insns)
|
|
{
|
|
s64 rvoff;
|
|
struct rv_jit_context ctx;
|
|
|
|
ctx.ninsns = 0;
|
|
ctx.insns = (u16 *)insns;
|
|
|
|
if (!target) {
|
|
emit(rv_nop(), &ctx);
|
|
emit(rv_nop(), &ctx);
|
|
return 0;
|
|
}
|
|
|
|
rvoff = (s64)(target - ip);
|
|
return emit_jump_and_link(RV_REG_ZERO, rvoff, false, &ctx);
|
|
}
|
|
|
|
int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type,
|
|
void *old_addr, void *new_addr)
|
|
{
|
|
u32 old_insns[4], new_insns[4];
|
|
bool is_call = poke_type == BPF_MOD_CALL;
|
|
int (*gen_insns)(void *target, void *ip, u32 *insns);
|
|
int ninsns = is_call ? 4 : 2;
|
|
int ret;
|
|
|
|
if (!is_bpf_text_address((unsigned long)ip))
|
|
return -ENOTSUPP;
|
|
|
|
gen_insns = is_call ? gen_call_or_nops : gen_jump_or_nops;
|
|
|
|
ret = gen_insns(old_addr, ip, old_insns);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (memcmp(ip, old_insns, ninsns * 4))
|
|
return -EFAULT;
|
|
|
|
ret = gen_insns(new_addr, ip, new_insns);
|
|
if (ret)
|
|
return ret;
|
|
|
|
cpus_read_lock();
|
|
mutex_lock(&text_mutex);
|
|
if (memcmp(ip, new_insns, ninsns * 4))
|
|
ret = patch_text(ip, new_insns, ninsns);
|
|
mutex_unlock(&text_mutex);
|
|
cpus_read_unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void store_args(int nregs, int args_off, struct rv_jit_context *ctx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nregs; i++) {
|
|
emit_sd(RV_REG_FP, -args_off, RV_REG_A0 + i, ctx);
|
|
args_off -= 8;
|
|
}
|
|
}
|
|
|
|
static void restore_args(int nregs, int args_off, struct rv_jit_context *ctx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nregs; i++) {
|
|
emit_ld(RV_REG_A0 + i, -args_off, RV_REG_FP, ctx);
|
|
args_off -= 8;
|
|
}
|
|
}
|
|
|
|
static int invoke_bpf_prog(struct bpf_tramp_link *l, int args_off, int retval_off,
|
|
int run_ctx_off, bool save_ret, struct rv_jit_context *ctx)
|
|
{
|
|
int ret, branch_off;
|
|
struct bpf_prog *p = l->link.prog;
|
|
int cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie);
|
|
|
|
if (l->cookie) {
|
|
emit_imm(RV_REG_T1, l->cookie, ctx);
|
|
emit_sd(RV_REG_FP, -run_ctx_off + cookie_off, RV_REG_T1, ctx);
|
|
} else {
|
|
emit_sd(RV_REG_FP, -run_ctx_off + cookie_off, RV_REG_ZERO, ctx);
|
|
}
|
|
|
|
/* arg1: prog */
|
|
emit_imm(RV_REG_A0, (const s64)p, ctx);
|
|
/* arg2: &run_ctx */
|
|
emit_addi(RV_REG_A1, RV_REG_FP, -run_ctx_off, ctx);
|
|
ret = emit_call((const u64)bpf_trampoline_enter(p), true, ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* if (__bpf_prog_enter(prog) == 0)
|
|
* goto skip_exec_of_prog;
|
|
*/
|
|
branch_off = ctx->ninsns;
|
|
/* nop reserved for conditional jump */
|
|
emit(rv_nop(), ctx);
|
|
|
|
/* store prog start time */
|
|
emit_mv(RV_REG_S1, RV_REG_A0, ctx);
|
|
|
|
/* arg1: &args_off */
|
|
emit_addi(RV_REG_A0, RV_REG_FP, -args_off, ctx);
|
|
if (!p->jited)
|
|
/* arg2: progs[i]->insnsi for interpreter */
|
|
emit_imm(RV_REG_A1, (const s64)p->insnsi, ctx);
|
|
ret = emit_call((const u64)p->bpf_func, true, ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (save_ret)
|
|
emit_sd(RV_REG_FP, -retval_off, regmap[BPF_REG_0], ctx);
|
|
|
|
/* update branch with beqz */
|
|
if (ctx->insns) {
|
|
int offset = ninsns_rvoff(ctx->ninsns - branch_off);
|
|
u32 insn = rv_beq(RV_REG_A0, RV_REG_ZERO, offset >> 1);
|
|
*(u32 *)(ctx->insns + branch_off) = insn;
|
|
}
|
|
|
|
/* arg1: prog */
|
|
emit_imm(RV_REG_A0, (const s64)p, ctx);
|
|
/* arg2: prog start time */
|
|
emit_mv(RV_REG_A1, RV_REG_S1, ctx);
|
|
/* arg3: &run_ctx */
|
|
emit_addi(RV_REG_A2, RV_REG_FP, -run_ctx_off, ctx);
|
|
ret = emit_call((const u64)bpf_trampoline_exit(p), true, ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im,
|
|
const struct btf_func_model *m,
|
|
struct bpf_tramp_links *tlinks,
|
|
void *func_addr, u32 flags,
|
|
struct rv_jit_context *ctx)
|
|
{
|
|
int i, ret, offset;
|
|
int *branches_off = NULL;
|
|
int stack_size = 0, nregs = m->nr_args;
|
|
int retaddr_off, fp_off, retval_off, args_off;
|
|
int nregs_off, ip_off, run_ctx_off, sreg_off;
|
|
struct bpf_tramp_links *fentry = &tlinks[BPF_TRAMP_FENTRY];
|
|
struct bpf_tramp_links *fexit = &tlinks[BPF_TRAMP_FEXIT];
|
|
struct bpf_tramp_links *fmod_ret = &tlinks[BPF_TRAMP_MODIFY_RETURN];
|
|
void *orig_call = func_addr;
|
|
bool save_ret;
|
|
u32 insn;
|
|
|
|
/* Generated trampoline stack layout:
|
|
*
|
|
* FP - 8 [ RA of parent func ] return address of parent
|
|
* function
|
|
* FP - retaddr_off [ RA of traced func ] return address of traced
|
|
* function
|
|
* FP - fp_off [ FP of parent func ]
|
|
*
|
|
* FP - retval_off [ return value ] BPF_TRAMP_F_CALL_ORIG or
|
|
* BPF_TRAMP_F_RET_FENTRY_RET
|
|
* [ argN ]
|
|
* [ ... ]
|
|
* FP - args_off [ arg1 ]
|
|
*
|
|
* FP - nregs_off [ regs count ]
|
|
*
|
|
* FP - ip_off [ traced func ] BPF_TRAMP_F_IP_ARG
|
|
*
|
|
* FP - run_ctx_off [ bpf_tramp_run_ctx ]
|
|
*
|
|
* FP - sreg_off [ callee saved reg ]
|
|
*
|
|
* [ pads ] pads for 16 bytes alignment
|
|
*/
|
|
|
|
if (flags & (BPF_TRAMP_F_ORIG_STACK | BPF_TRAMP_F_SHARE_IPMODIFY))
|
|
return -ENOTSUPP;
|
|
|
|
/* extra regiters for struct arguments */
|
|
for (i = 0; i < m->nr_args; i++)
|
|
if (m->arg_flags[i] & BTF_FMODEL_STRUCT_ARG)
|
|
nregs += round_up(m->arg_size[i], 8) / 8 - 1;
|
|
|
|
/* 8 arguments passed by registers */
|
|
if (nregs > 8)
|
|
return -ENOTSUPP;
|
|
|
|
/* room for parent function return address */
|
|
stack_size += 8;
|
|
|
|
stack_size += 8;
|
|
retaddr_off = stack_size;
|
|
|
|
stack_size += 8;
|
|
fp_off = stack_size;
|
|
|
|
save_ret = flags & (BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_RET_FENTRY_RET);
|
|
if (save_ret) {
|
|
stack_size += 8;
|
|
retval_off = stack_size;
|
|
}
|
|
|
|
stack_size += nregs * 8;
|
|
args_off = stack_size;
|
|
|
|
stack_size += 8;
|
|
nregs_off = stack_size;
|
|
|
|
if (flags & BPF_TRAMP_F_IP_ARG) {
|
|
stack_size += 8;
|
|
ip_off = stack_size;
|
|
}
|
|
|
|
stack_size += round_up(sizeof(struct bpf_tramp_run_ctx), 8);
|
|
run_ctx_off = stack_size;
|
|
|
|
stack_size += 8;
|
|
sreg_off = stack_size;
|
|
|
|
stack_size = round_up(stack_size, 16);
|
|
|
|
emit_addi(RV_REG_SP, RV_REG_SP, -stack_size, ctx);
|
|
|
|
emit_sd(RV_REG_SP, stack_size - retaddr_off, RV_REG_RA, ctx);
|
|
emit_sd(RV_REG_SP, stack_size - fp_off, RV_REG_FP, ctx);
|
|
|
|
emit_addi(RV_REG_FP, RV_REG_SP, stack_size, ctx);
|
|
|
|
/* callee saved register S1 to pass start time */
|
|
emit_sd(RV_REG_FP, -sreg_off, RV_REG_S1, ctx);
|
|
|
|
/* store ip address of the traced function */
|
|
if (flags & BPF_TRAMP_F_IP_ARG) {
|
|
emit_imm(RV_REG_T1, (const s64)func_addr, ctx);
|
|
emit_sd(RV_REG_FP, -ip_off, RV_REG_T1, ctx);
|
|
}
|
|
|
|
emit_li(RV_REG_T1, nregs, ctx);
|
|
emit_sd(RV_REG_FP, -nregs_off, RV_REG_T1, ctx);
|
|
|
|
store_args(nregs, args_off, ctx);
|
|
|
|
/* skip to actual body of traced function */
|
|
if (flags & BPF_TRAMP_F_SKIP_FRAME)
|
|
orig_call += 16;
|
|
|
|
if (flags & BPF_TRAMP_F_CALL_ORIG) {
|
|
emit_imm(RV_REG_A0, (const s64)im, ctx);
|
|
ret = emit_call((const u64)__bpf_tramp_enter, true, ctx);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < fentry->nr_links; i++) {
|
|
ret = invoke_bpf_prog(fentry->links[i], args_off, retval_off, run_ctx_off,
|
|
flags & BPF_TRAMP_F_RET_FENTRY_RET, ctx);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (fmod_ret->nr_links) {
|
|
branches_off = kcalloc(fmod_ret->nr_links, sizeof(int), GFP_KERNEL);
|
|
if (!branches_off)
|
|
return -ENOMEM;
|
|
|
|
/* cleanup to avoid garbage return value confusion */
|
|
emit_sd(RV_REG_FP, -retval_off, RV_REG_ZERO, ctx);
|
|
for (i = 0; i < fmod_ret->nr_links; i++) {
|
|
ret = invoke_bpf_prog(fmod_ret->links[i], args_off, retval_off,
|
|
run_ctx_off, true, ctx);
|
|
if (ret)
|
|
goto out;
|
|
emit_ld(RV_REG_T1, -retval_off, RV_REG_FP, ctx);
|
|
branches_off[i] = ctx->ninsns;
|
|
/* nop reserved for conditional jump */
|
|
emit(rv_nop(), ctx);
|
|
}
|
|
}
|
|
|
|
if (flags & BPF_TRAMP_F_CALL_ORIG) {
|
|
restore_args(nregs, args_off, ctx);
|
|
ret = emit_call((const u64)orig_call, true, ctx);
|
|
if (ret)
|
|
goto out;
|
|
emit_sd(RV_REG_FP, -retval_off, RV_REG_A0, ctx);
|
|
im->ip_after_call = ctx->insns + ctx->ninsns;
|
|
/* 2 nops reserved for auipc+jalr pair */
|
|
emit(rv_nop(), ctx);
|
|
emit(rv_nop(), ctx);
|
|
}
|
|
|
|
/* update branches saved in invoke_bpf_mod_ret with bnez */
|
|
for (i = 0; ctx->insns && i < fmod_ret->nr_links; i++) {
|
|
offset = ninsns_rvoff(ctx->ninsns - branches_off[i]);
|
|
insn = rv_bne(RV_REG_T1, RV_REG_ZERO, offset >> 1);
|
|
*(u32 *)(ctx->insns + branches_off[i]) = insn;
|
|
}
|
|
|
|
for (i = 0; i < fexit->nr_links; i++) {
|
|
ret = invoke_bpf_prog(fexit->links[i], args_off, retval_off,
|
|
run_ctx_off, false, ctx);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
if (flags & BPF_TRAMP_F_CALL_ORIG) {
|
|
im->ip_epilogue = ctx->insns + ctx->ninsns;
|
|
emit_imm(RV_REG_A0, (const s64)im, ctx);
|
|
ret = emit_call((const u64)__bpf_tramp_exit, true, ctx);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
if (flags & BPF_TRAMP_F_RESTORE_REGS)
|
|
restore_args(nregs, args_off, ctx);
|
|
|
|
if (save_ret)
|
|
emit_ld(RV_REG_A0, -retval_off, RV_REG_FP, ctx);
|
|
|
|
emit_ld(RV_REG_S1, -sreg_off, RV_REG_FP, ctx);
|
|
|
|
if (flags & BPF_TRAMP_F_SKIP_FRAME)
|
|
/* return address of parent function */
|
|
emit_ld(RV_REG_RA, stack_size - 8, RV_REG_SP, ctx);
|
|
else
|
|
/* return address of traced function */
|
|
emit_ld(RV_REG_RA, stack_size - retaddr_off, RV_REG_SP, ctx);
|
|
|
|
emit_ld(RV_REG_FP, stack_size - fp_off, RV_REG_SP, ctx);
|
|
emit_addi(RV_REG_SP, RV_REG_SP, stack_size, ctx);
|
|
|
|
emit_jalr(RV_REG_ZERO, RV_REG_RA, 0, ctx);
|
|
|
|
ret = ctx->ninsns;
|
|
out:
|
|
kfree(branches_off);
|
|
return ret;
|
|
}
|
|
|
|
int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image,
|
|
void *image_end, const struct btf_func_model *m,
|
|
u32 flags, struct bpf_tramp_links *tlinks,
|
|
void *func_addr)
|
|
{
|
|
int ret;
|
|
struct rv_jit_context ctx;
|
|
|
|
ctx.ninsns = 0;
|
|
ctx.insns = NULL;
|
|
ret = __arch_prepare_bpf_trampoline(im, m, tlinks, func_addr, flags, &ctx);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ninsns_rvoff(ret) > (long)image_end - (long)image)
|
|
return -EFBIG;
|
|
|
|
ctx.ninsns = 0;
|
|
ctx.insns = image;
|
|
ret = __arch_prepare_bpf_trampoline(im, m, tlinks, func_addr, flags, &ctx);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
bpf_flush_icache(ctx.insns, ctx.insns + ctx.ninsns);
|
|
|
|
return ninsns_rvoff(ret);
|
|
}
|
|
|
|
int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
|
|
bool extra_pass)
|
|
{
|
|
bool is64 = BPF_CLASS(insn->code) == BPF_ALU64 ||
|
|
BPF_CLASS(insn->code) == BPF_JMP;
|
|
int s, e, rvoff, ret, i = insn - ctx->prog->insnsi;
|
|
struct bpf_prog_aux *aux = ctx->prog->aux;
|
|
u8 rd = -1, rs = -1, code = insn->code;
|
|
s16 off = insn->off;
|
|
s32 imm = insn->imm;
|
|
|
|
init_regs(&rd, &rs, insn, ctx);
|
|
|
|
switch (code) {
|
|
/* dst = src */
|
|
case BPF_ALU | BPF_MOV | BPF_X:
|
|
case BPF_ALU64 | BPF_MOV | BPF_X:
|
|
if (imm == 1) {
|
|
/* Special mov32 for zext */
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
}
|
|
emit_mv(rd, rs, ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
|
|
/* dst = dst OP src */
|
|
case BPF_ALU | BPF_ADD | BPF_X:
|
|
case BPF_ALU64 | BPF_ADD | BPF_X:
|
|
emit_add(rd, rd, rs, ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_SUB | BPF_X:
|
|
case BPF_ALU64 | BPF_SUB | BPF_X:
|
|
if (is64)
|
|
emit_sub(rd, rd, rs, ctx);
|
|
else
|
|
emit_subw(rd, rd, rs, ctx);
|
|
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_AND | BPF_X:
|
|
case BPF_ALU64 | BPF_AND | BPF_X:
|
|
emit_and(rd, rd, rs, ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_OR | BPF_X:
|
|
case BPF_ALU64 | BPF_OR | BPF_X:
|
|
emit_or(rd, rd, rs, ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_XOR | BPF_X:
|
|
case BPF_ALU64 | BPF_XOR | BPF_X:
|
|
emit_xor(rd, rd, rs, ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_MUL | BPF_X:
|
|
case BPF_ALU64 | BPF_MUL | BPF_X:
|
|
emit(is64 ? rv_mul(rd, rd, rs) : rv_mulw(rd, rd, rs), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_DIV | BPF_X:
|
|
case BPF_ALU64 | BPF_DIV | BPF_X:
|
|
emit(is64 ? rv_divu(rd, rd, rs) : rv_divuw(rd, rd, rs), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_MOD | BPF_X:
|
|
case BPF_ALU64 | BPF_MOD | BPF_X:
|
|
emit(is64 ? rv_remu(rd, rd, rs) : rv_remuw(rd, rd, rs), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_LSH | BPF_X:
|
|
case BPF_ALU64 | BPF_LSH | BPF_X:
|
|
emit(is64 ? rv_sll(rd, rd, rs) : rv_sllw(rd, rd, rs), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_RSH | BPF_X:
|
|
case BPF_ALU64 | BPF_RSH | BPF_X:
|
|
emit(is64 ? rv_srl(rd, rd, rs) : rv_srlw(rd, rd, rs), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_ARSH | BPF_X:
|
|
case BPF_ALU64 | BPF_ARSH | BPF_X:
|
|
emit(is64 ? rv_sra(rd, rd, rs) : rv_sraw(rd, rd, rs), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
|
|
/* dst = -dst */
|
|
case BPF_ALU | BPF_NEG:
|
|
case BPF_ALU64 | BPF_NEG:
|
|
emit_sub(rd, RV_REG_ZERO, rd, ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
|
|
/* dst = BSWAP##imm(dst) */
|
|
case BPF_ALU | BPF_END | BPF_FROM_LE:
|
|
switch (imm) {
|
|
case 16:
|
|
emit_slli(rd, rd, 48, ctx);
|
|
emit_srli(rd, rd, 48, ctx);
|
|
break;
|
|
case 32:
|
|
if (!aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case 64:
|
|
/* Do nothing */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case BPF_ALU | BPF_END | BPF_FROM_BE:
|
|
emit_li(RV_REG_T2, 0, ctx);
|
|
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
emit_slli(RV_REG_T2, RV_REG_T2, 8, ctx);
|
|
emit_srli(rd, rd, 8, ctx);
|
|
if (imm == 16)
|
|
goto out_be;
|
|
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
emit_slli(RV_REG_T2, RV_REG_T2, 8, ctx);
|
|
emit_srli(rd, rd, 8, ctx);
|
|
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
emit_slli(RV_REG_T2, RV_REG_T2, 8, ctx);
|
|
emit_srli(rd, rd, 8, ctx);
|
|
if (imm == 32)
|
|
goto out_be;
|
|
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
emit_slli(RV_REG_T2, RV_REG_T2, 8, ctx);
|
|
emit_srli(rd, rd, 8, ctx);
|
|
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
emit_slli(RV_REG_T2, RV_REG_T2, 8, ctx);
|
|
emit_srli(rd, rd, 8, ctx);
|
|
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
emit_slli(RV_REG_T2, RV_REG_T2, 8, ctx);
|
|
emit_srli(rd, rd, 8, ctx);
|
|
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
emit_slli(RV_REG_T2, RV_REG_T2, 8, ctx);
|
|
emit_srli(rd, rd, 8, ctx);
|
|
out_be:
|
|
emit_andi(RV_REG_T1, rd, 0xff, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, RV_REG_T1, ctx);
|
|
|
|
emit_mv(rd, RV_REG_T2, ctx);
|
|
break;
|
|
|
|
/* dst = imm */
|
|
case BPF_ALU | BPF_MOV | BPF_K:
|
|
case BPF_ALU64 | BPF_MOV | BPF_K:
|
|
emit_imm(rd, imm, ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
|
|
/* dst = dst OP imm */
|
|
case BPF_ALU | BPF_ADD | BPF_K:
|
|
case BPF_ALU64 | BPF_ADD | BPF_K:
|
|
if (is_12b_int(imm)) {
|
|
emit_addi(rd, rd, imm, ctx);
|
|
} else {
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit_add(rd, rd, RV_REG_T1, ctx);
|
|
}
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_SUB | BPF_K:
|
|
case BPF_ALU64 | BPF_SUB | BPF_K:
|
|
if (is_12b_int(-imm)) {
|
|
emit_addi(rd, rd, -imm, ctx);
|
|
} else {
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit_sub(rd, rd, RV_REG_T1, ctx);
|
|
}
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_AND | BPF_K:
|
|
case BPF_ALU64 | BPF_AND | BPF_K:
|
|
if (is_12b_int(imm)) {
|
|
emit_andi(rd, rd, imm, ctx);
|
|
} else {
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit_and(rd, rd, RV_REG_T1, ctx);
|
|
}
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_OR | BPF_K:
|
|
case BPF_ALU64 | BPF_OR | BPF_K:
|
|
if (is_12b_int(imm)) {
|
|
emit(rv_ori(rd, rd, imm), ctx);
|
|
} else {
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit_or(rd, rd, RV_REG_T1, ctx);
|
|
}
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_XOR | BPF_K:
|
|
case BPF_ALU64 | BPF_XOR | BPF_K:
|
|
if (is_12b_int(imm)) {
|
|
emit(rv_xori(rd, rd, imm), ctx);
|
|
} else {
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit_xor(rd, rd, RV_REG_T1, ctx);
|
|
}
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_MUL | BPF_K:
|
|
case BPF_ALU64 | BPF_MUL | BPF_K:
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit(is64 ? rv_mul(rd, rd, RV_REG_T1) :
|
|
rv_mulw(rd, rd, RV_REG_T1), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_DIV | BPF_K:
|
|
case BPF_ALU64 | BPF_DIV | BPF_K:
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit(is64 ? rv_divu(rd, rd, RV_REG_T1) :
|
|
rv_divuw(rd, rd, RV_REG_T1), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_MOD | BPF_K:
|
|
case BPF_ALU64 | BPF_MOD | BPF_K:
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit(is64 ? rv_remu(rd, rd, RV_REG_T1) :
|
|
rv_remuw(rd, rd, RV_REG_T1), ctx);
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_LSH | BPF_K:
|
|
case BPF_ALU64 | BPF_LSH | BPF_K:
|
|
emit_slli(rd, rd, imm, ctx);
|
|
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_RSH | BPF_K:
|
|
case BPF_ALU64 | BPF_RSH | BPF_K:
|
|
if (is64)
|
|
emit_srli(rd, rd, imm, ctx);
|
|
else
|
|
emit(rv_srliw(rd, rd, imm), ctx);
|
|
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
case BPF_ALU | BPF_ARSH | BPF_K:
|
|
case BPF_ALU64 | BPF_ARSH | BPF_K:
|
|
if (is64)
|
|
emit_srai(rd, rd, imm, ctx);
|
|
else
|
|
emit(rv_sraiw(rd, rd, imm), ctx);
|
|
|
|
if (!is64 && !aux->verifier_zext)
|
|
emit_zext_32(rd, ctx);
|
|
break;
|
|
|
|
/* JUMP off */
|
|
case BPF_JMP | BPF_JA:
|
|
rvoff = rv_offset(i, off, ctx);
|
|
ret = emit_jump_and_link(RV_REG_ZERO, rvoff, true, ctx);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
|
|
/* IF (dst COND src) JUMP off */
|
|
case BPF_JMP | BPF_JEQ | BPF_X:
|
|
case BPF_JMP32 | BPF_JEQ | BPF_X:
|
|
case BPF_JMP | BPF_JGT | BPF_X:
|
|
case BPF_JMP32 | BPF_JGT | BPF_X:
|
|
case BPF_JMP | BPF_JLT | BPF_X:
|
|
case BPF_JMP32 | BPF_JLT | BPF_X:
|
|
case BPF_JMP | BPF_JGE | BPF_X:
|
|
case BPF_JMP32 | BPF_JGE | BPF_X:
|
|
case BPF_JMP | BPF_JLE | BPF_X:
|
|
case BPF_JMP32 | BPF_JLE | BPF_X:
|
|
case BPF_JMP | BPF_JNE | BPF_X:
|
|
case BPF_JMP32 | BPF_JNE | BPF_X:
|
|
case BPF_JMP | BPF_JSGT | BPF_X:
|
|
case BPF_JMP32 | BPF_JSGT | BPF_X:
|
|
case BPF_JMP | BPF_JSLT | BPF_X:
|
|
case BPF_JMP32 | BPF_JSLT | BPF_X:
|
|
case BPF_JMP | BPF_JSGE | BPF_X:
|
|
case BPF_JMP32 | BPF_JSGE | BPF_X:
|
|
case BPF_JMP | BPF_JSLE | BPF_X:
|
|
case BPF_JMP32 | BPF_JSLE | BPF_X:
|
|
case BPF_JMP | BPF_JSET | BPF_X:
|
|
case BPF_JMP32 | BPF_JSET | BPF_X:
|
|
rvoff = rv_offset(i, off, ctx);
|
|
if (!is64) {
|
|
s = ctx->ninsns;
|
|
if (is_signed_bpf_cond(BPF_OP(code)))
|
|
emit_sext_32_rd_rs(&rd, &rs, ctx);
|
|
else
|
|
emit_zext_32_rd_rs(&rd, &rs, ctx);
|
|
e = ctx->ninsns;
|
|
|
|
/* Adjust for extra insns */
|
|
rvoff -= ninsns_rvoff(e - s);
|
|
}
|
|
|
|
if (BPF_OP(code) == BPF_JSET) {
|
|
/* Adjust for and */
|
|
rvoff -= 4;
|
|
emit_and(RV_REG_T1, rd, rs, ctx);
|
|
emit_branch(BPF_JNE, RV_REG_T1, RV_REG_ZERO, rvoff,
|
|
ctx);
|
|
} else {
|
|
emit_branch(BPF_OP(code), rd, rs, rvoff, ctx);
|
|
}
|
|
break;
|
|
|
|
/* IF (dst COND imm) JUMP off */
|
|
case BPF_JMP | BPF_JEQ | BPF_K:
|
|
case BPF_JMP32 | BPF_JEQ | BPF_K:
|
|
case BPF_JMP | BPF_JGT | BPF_K:
|
|
case BPF_JMP32 | BPF_JGT | BPF_K:
|
|
case BPF_JMP | BPF_JLT | BPF_K:
|
|
case BPF_JMP32 | BPF_JLT | BPF_K:
|
|
case BPF_JMP | BPF_JGE | BPF_K:
|
|
case BPF_JMP32 | BPF_JGE | BPF_K:
|
|
case BPF_JMP | BPF_JLE | BPF_K:
|
|
case BPF_JMP32 | BPF_JLE | BPF_K:
|
|
case BPF_JMP | BPF_JNE | BPF_K:
|
|
case BPF_JMP32 | BPF_JNE | BPF_K:
|
|
case BPF_JMP | BPF_JSGT | BPF_K:
|
|
case BPF_JMP32 | BPF_JSGT | BPF_K:
|
|
case BPF_JMP | BPF_JSLT | BPF_K:
|
|
case BPF_JMP32 | BPF_JSLT | BPF_K:
|
|
case BPF_JMP | BPF_JSGE | BPF_K:
|
|
case BPF_JMP32 | BPF_JSGE | BPF_K:
|
|
case BPF_JMP | BPF_JSLE | BPF_K:
|
|
case BPF_JMP32 | BPF_JSLE | BPF_K:
|
|
rvoff = rv_offset(i, off, ctx);
|
|
s = ctx->ninsns;
|
|
if (imm) {
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
rs = RV_REG_T1;
|
|
} else {
|
|
/* If imm is 0, simply use zero register. */
|
|
rs = RV_REG_ZERO;
|
|
}
|
|
if (!is64) {
|
|
if (is_signed_bpf_cond(BPF_OP(code)))
|
|
emit_sext_32_rd(&rd, ctx);
|
|
else
|
|
emit_zext_32_rd_t1(&rd, ctx);
|
|
}
|
|
e = ctx->ninsns;
|
|
|
|
/* Adjust for extra insns */
|
|
rvoff -= ninsns_rvoff(e - s);
|
|
emit_branch(BPF_OP(code), rd, rs, rvoff, ctx);
|
|
break;
|
|
|
|
case BPF_JMP | BPF_JSET | BPF_K:
|
|
case BPF_JMP32 | BPF_JSET | BPF_K:
|
|
rvoff = rv_offset(i, off, ctx);
|
|
s = ctx->ninsns;
|
|
if (is_12b_int(imm)) {
|
|
emit_andi(RV_REG_T1, rd, imm, ctx);
|
|
} else {
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
emit_and(RV_REG_T1, rd, RV_REG_T1, ctx);
|
|
}
|
|
/* For jset32, we should clear the upper 32 bits of t1, but
|
|
* sign-extension is sufficient here and saves one instruction,
|
|
* as t1 is used only in comparison against zero.
|
|
*/
|
|
if (!is64 && imm < 0)
|
|
emit_addiw(RV_REG_T1, RV_REG_T1, 0, ctx);
|
|
e = ctx->ninsns;
|
|
rvoff -= ninsns_rvoff(e - s);
|
|
emit_branch(BPF_JNE, RV_REG_T1, RV_REG_ZERO, rvoff, ctx);
|
|
break;
|
|
|
|
/* function call */
|
|
case BPF_JMP | BPF_CALL:
|
|
{
|
|
bool fixed_addr;
|
|
u64 addr;
|
|
|
|
mark_call(ctx);
|
|
ret = bpf_jit_get_func_addr(ctx->prog, insn, extra_pass,
|
|
&addr, &fixed_addr);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = emit_call(addr, fixed_addr, ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
emit_mv(bpf_to_rv_reg(BPF_REG_0, ctx), RV_REG_A0, ctx);
|
|
break;
|
|
}
|
|
/* tail call */
|
|
case BPF_JMP | BPF_TAIL_CALL:
|
|
if (emit_bpf_tail_call(i, ctx))
|
|
return -1;
|
|
break;
|
|
|
|
/* function return */
|
|
case BPF_JMP | BPF_EXIT:
|
|
if (i == ctx->prog->len - 1)
|
|
break;
|
|
|
|
rvoff = epilogue_offset(ctx);
|
|
ret = emit_jump_and_link(RV_REG_ZERO, rvoff, true, ctx);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
|
|
/* dst = imm64 */
|
|
case BPF_LD | BPF_IMM | BPF_DW:
|
|
{
|
|
struct bpf_insn insn1 = insn[1];
|
|
u64 imm64;
|
|
|
|
imm64 = (u64)insn1.imm << 32 | (u32)imm;
|
|
if (bpf_pseudo_func(insn)) {
|
|
/* fixed-length insns for extra jit pass */
|
|
ret = emit_addr(rd, imm64, extra_pass, ctx);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
emit_imm(rd, imm64, ctx);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* LDX: 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:
|
|
case BPF_LDX | BPF_PROBE_MEM | BPF_B:
|
|
case BPF_LDX | BPF_PROBE_MEM | BPF_H:
|
|
case BPF_LDX | BPF_PROBE_MEM | BPF_W:
|
|
case BPF_LDX | BPF_PROBE_MEM | BPF_DW:
|
|
{
|
|
int insn_len, insns_start;
|
|
|
|
switch (BPF_SIZE(code)) {
|
|
case BPF_B:
|
|
if (is_12b_int(off)) {
|
|
insns_start = ctx->ninsns;
|
|
emit(rv_lbu(rd, off, rs), ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rs, ctx);
|
|
insns_start = ctx->ninsns;
|
|
emit(rv_lbu(rd, 0, RV_REG_T1), ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
if (insn_is_zext(&insn[1]))
|
|
return 1;
|
|
break;
|
|
case BPF_H:
|
|
if (is_12b_int(off)) {
|
|
insns_start = ctx->ninsns;
|
|
emit(rv_lhu(rd, off, rs), ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rs, ctx);
|
|
insns_start = ctx->ninsns;
|
|
emit(rv_lhu(rd, 0, RV_REG_T1), ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
if (insn_is_zext(&insn[1]))
|
|
return 1;
|
|
break;
|
|
case BPF_W:
|
|
if (is_12b_int(off)) {
|
|
insns_start = ctx->ninsns;
|
|
emit(rv_lwu(rd, off, rs), ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rs, ctx);
|
|
insns_start = ctx->ninsns;
|
|
emit(rv_lwu(rd, 0, RV_REG_T1), ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
if (insn_is_zext(&insn[1]))
|
|
return 1;
|
|
break;
|
|
case BPF_DW:
|
|
if (is_12b_int(off)) {
|
|
insns_start = ctx->ninsns;
|
|
emit_ld(rd, off, rs, ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rs, ctx);
|
|
insns_start = ctx->ninsns;
|
|
emit_ld(rd, 0, RV_REG_T1, ctx);
|
|
insn_len = ctx->ninsns - insns_start;
|
|
break;
|
|
}
|
|
|
|
ret = add_exception_handler(insn, ctx, rd, insn_len);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
}
|
|
/* speculation barrier */
|
|
case BPF_ST | BPF_NOSPEC:
|
|
break;
|
|
|
|
/* ST: *(size *)(dst + off) = imm */
|
|
case BPF_ST | BPF_MEM | BPF_B:
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
if (is_12b_int(off)) {
|
|
emit(rv_sb(rd, off, RV_REG_T1), ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T2, off, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, rd, ctx);
|
|
emit(rv_sb(RV_REG_T2, 0, RV_REG_T1), ctx);
|
|
break;
|
|
|
|
case BPF_ST | BPF_MEM | BPF_H:
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
if (is_12b_int(off)) {
|
|
emit(rv_sh(rd, off, RV_REG_T1), ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T2, off, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, rd, ctx);
|
|
emit(rv_sh(RV_REG_T2, 0, RV_REG_T1), ctx);
|
|
break;
|
|
case BPF_ST | BPF_MEM | BPF_W:
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
if (is_12b_int(off)) {
|
|
emit_sw(rd, off, RV_REG_T1, ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T2, off, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, rd, ctx);
|
|
emit_sw(RV_REG_T2, 0, RV_REG_T1, ctx);
|
|
break;
|
|
case BPF_ST | BPF_MEM | BPF_DW:
|
|
emit_imm(RV_REG_T1, imm, ctx);
|
|
if (is_12b_int(off)) {
|
|
emit_sd(rd, off, RV_REG_T1, ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T2, off, ctx);
|
|
emit_add(RV_REG_T2, RV_REG_T2, rd, ctx);
|
|
emit_sd(RV_REG_T2, 0, RV_REG_T1, ctx);
|
|
break;
|
|
|
|
/* STX: *(size *)(dst + off) = src */
|
|
case BPF_STX | BPF_MEM | BPF_B:
|
|
if (is_12b_int(off)) {
|
|
emit(rv_sb(rd, off, rs), ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rd, ctx);
|
|
emit(rv_sb(RV_REG_T1, 0, rs), ctx);
|
|
break;
|
|
case BPF_STX | BPF_MEM | BPF_H:
|
|
if (is_12b_int(off)) {
|
|
emit(rv_sh(rd, off, rs), ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rd, ctx);
|
|
emit(rv_sh(RV_REG_T1, 0, rs), ctx);
|
|
break;
|
|
case BPF_STX | BPF_MEM | BPF_W:
|
|
if (is_12b_int(off)) {
|
|
emit_sw(rd, off, rs, ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rd, ctx);
|
|
emit_sw(RV_REG_T1, 0, rs, ctx);
|
|
break;
|
|
case BPF_STX | BPF_MEM | BPF_DW:
|
|
if (is_12b_int(off)) {
|
|
emit_sd(rd, off, rs, ctx);
|
|
break;
|
|
}
|
|
|
|
emit_imm(RV_REG_T1, off, ctx);
|
|
emit_add(RV_REG_T1, RV_REG_T1, rd, ctx);
|
|
emit_sd(RV_REG_T1, 0, rs, ctx);
|
|
break;
|
|
case BPF_STX | BPF_ATOMIC | BPF_W:
|
|
case BPF_STX | BPF_ATOMIC | BPF_DW:
|
|
emit_atomic(rd, rs, off, imm,
|
|
BPF_SIZE(code) == BPF_DW, ctx);
|
|
break;
|
|
default:
|
|
pr_err("bpf-jit: unknown opcode %02x\n", code);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bpf_jit_build_prologue(struct rv_jit_context *ctx)
|
|
{
|
|
int i, stack_adjust = 0, store_offset, bpf_stack_adjust;
|
|
|
|
bpf_stack_adjust = round_up(ctx->prog->aux->stack_depth, 16);
|
|
if (bpf_stack_adjust)
|
|
mark_fp(ctx);
|
|
|
|
if (seen_reg(RV_REG_RA, ctx))
|
|
stack_adjust += 8;
|
|
stack_adjust += 8; /* RV_REG_FP */
|
|
if (seen_reg(RV_REG_S1, ctx))
|
|
stack_adjust += 8;
|
|
if (seen_reg(RV_REG_S2, ctx))
|
|
stack_adjust += 8;
|
|
if (seen_reg(RV_REG_S3, ctx))
|
|
stack_adjust += 8;
|
|
if (seen_reg(RV_REG_S4, ctx))
|
|
stack_adjust += 8;
|
|
if (seen_reg(RV_REG_S5, ctx))
|
|
stack_adjust += 8;
|
|
if (seen_reg(RV_REG_S6, ctx))
|
|
stack_adjust += 8;
|
|
|
|
stack_adjust = round_up(stack_adjust, 16);
|
|
stack_adjust += bpf_stack_adjust;
|
|
|
|
store_offset = stack_adjust - 8;
|
|
|
|
/* reserve 4 nop insns */
|
|
for (i = 0; i < 4; i++)
|
|
emit(rv_nop(), ctx);
|
|
|
|
/* First instruction is always setting the tail-call-counter
|
|
* (TCC) register. This instruction is skipped for tail calls.
|
|
* Force using a 4-byte (non-compressed) instruction.
|
|
*/
|
|
emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
|
|
|
|
emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
|
|
|
|
if (seen_reg(RV_REG_RA, ctx)) {
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_RA, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_FP, ctx);
|
|
store_offset -= 8;
|
|
if (seen_reg(RV_REG_S1, ctx)) {
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_S1, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S2, ctx)) {
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_S2, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S3, ctx)) {
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_S3, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S4, ctx)) {
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_S4, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S5, ctx)) {
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_S5, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
if (seen_reg(RV_REG_S6, ctx)) {
|
|
emit_sd(RV_REG_SP, store_offset, RV_REG_S6, ctx);
|
|
store_offset -= 8;
|
|
}
|
|
|
|
emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
|
|
|
|
if (bpf_stack_adjust)
|
|
emit_addi(RV_REG_S5, RV_REG_SP, bpf_stack_adjust, ctx);
|
|
|
|
/* Program contains calls and tail calls, so RV_REG_TCC need
|
|
* to be saved across calls.
|
|
*/
|
|
if (seen_tail_call(ctx) && seen_call(ctx))
|
|
emit_mv(RV_REG_TCC_SAVED, RV_REG_TCC, ctx);
|
|
|
|
ctx->stack_size = stack_adjust;
|
|
}
|
|
|
|
void bpf_jit_build_epilogue(struct rv_jit_context *ctx)
|
|
{
|
|
__build_epilogue(false, ctx);
|
|
}
|