Use AI to generate the missing BPF_LD | BPF_ABS and BPF_LD | BPF_IND instruction support to the arm64 JIT. These are needed for packet data access in programs produced by rte_bpf_convert(). Without this the JIT rejects converted cBPF programs with "invalid opcode 0x30".
Uses a fast path for single-segment mbufs and falls back to __rte_pktmbuf_read() for multi-segment packets, matching the approach used by the x86 JIT. Bugzilla ID: 1427 Signed-off-by: Stephen Hemminger <[email protected]> --- lib/bpf/bpf_jit_arm64.c | 301 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) diff --git a/lib/bpf/bpf_jit_arm64.c b/lib/bpf/bpf_jit_arm64.c index a04ef33a9c..dff101dbba 100644 --- a/lib/bpf/bpf_jit_arm64.c +++ b/lib/bpf/bpf_jit_arm64.c @@ -3,11 +3,13 @@ */ #include <errno.h> +#include <stddef.h> #include <stdbool.h> #include <stdlib.h> #include <rte_common.h> #include <rte_byteorder.h> +#include <rte_mbuf.h> #include "bpf_impl.h" @@ -43,6 +45,7 @@ struct a64_jit_ctx { uint32_t program_start; /* Program index, Just after prologue */ uint32_t program_sz; /* Program size. Found in first pass */ uint8_t foundcall; /* Found EBPF_CALL class code in eBPF pgm */ + uint8_t foundldmbuf; /* Found BPF_ABS/BPF_IND in eBPF pgm */ }; static int @@ -1137,12 +1140,293 @@ check_program_has_call(struct a64_jit_ctx *ctx, struct rte_bpf *bpf) switch (op) { /* Call imm */ case (BPF_JMP | EBPF_CALL): + /* + * BPF_ABS/BPF_IND instructions may need to call + * __rte_pktmbuf_read() in the slow path, so treat + * them as having a call for register save purposes. + */ + case (BPF_LD | BPF_ABS | BPF_B): + case (BPF_LD | BPF_ABS | BPF_H): + case (BPF_LD | BPF_ABS | BPF_W): + case (BPF_LD | BPF_IND | BPF_B): + case (BPF_LD | BPF_IND | BPF_H): + case (BPF_LD | BPF_IND | BPF_W): ctx->foundcall = 1; + ctx->foundldmbuf = 1; return; } } } +/* + * Emit SUBS (extended register) for comparing with zero-extended halfword. + * Used to compare: data_len - offset against size. + * CMP (imm): SUBS Xd, Xn, #imm + */ +static void +emit_cmp_imm(struct a64_jit_ctx *ctx, bool is64, uint8_t rn, uint16_t imm12) +{ + uint32_t insn, imm; + int32_t simm; + + imm = mask_imm(12, imm12); + simm = (int32_t)imm12; + insn = RTE_SHIFT_VAL32(is64, 31); + insn |= RTE_SHIFT_VAL32(1, 30); /* SUB */ + insn |= RTE_SHIFT_VAL32(1, 29); /* set flags */ + insn |= UINT32_C(0x11000000); + insn |= A64_ZR; /* Rd = ZR for CMP */ + insn |= RTE_SHIFT_VAL32(rn, 5); + insn |= RTE_SHIFT_VAL32(imm, 10); + + emit_insn(ctx, insn, check_reg(rn) || check_imm(12, simm)); +} + +/* + * Emit a load from [rn + unsigned immediate offset]. + * LDR (unsigned offset): size determined by sz parameter. + */ +static void +emit_ldr_uimm(struct a64_jit_ctx *ctx, uint8_t sz, uint8_t rt, uint8_t rn, + uint16_t uimm) +{ + uint32_t insn, scale, imm; + int32_t simm; + + /* Determine scale from BPF size encoding */ + if (sz == BPF_B) + scale = 0; + else if (sz == BPF_H) + scale = 1; + else if (sz == BPF_W) + scale = 2; + else /* EBPF_DW */ + scale = 3; + + /* imm12 is the offset divided by the access size */ + imm = uimm >> scale; + simm = (int32_t)imm; + + insn = RTE_SHIFT_VAL32(scale, 30); + insn |= UINT32_C(0x39400000); /* LDR (unsigned offset) base */ + insn |= RTE_SHIFT_VAL32(mask_imm(12, imm), 10); + insn |= RTE_SHIFT_VAL32(rn, 5); + insn |= rt; + + emit_insn(ctx, insn, check_reg(rt) || check_reg(rn) || + check_imm(12, simm) || check_ls_sz(sz)); +} + +/* + * Emit code for BPF_LD | BPF_ABS and BPF_LD | BPF_IND packet loads. + * + * These instructions load data from the packet referenced by the mbuf + * pointer implicitly held in EBPF_REG_6. The algorithm mirrors the + * inline rte_pktmbuf_read(): + * + * fast path (single-segment, contiguous): + * off = imm (BPF_ABS) + * off = src_reg + imm (BPF_IND) + * if (off + size <= mbuf->data_len) + * R0 = *(type *)(mbuf->buf_addr + mbuf->data_off + off) + * R0 = bswap(R0) // network to host byte order + * else goto slow_path + * + * slow path (multi-segment or out-of-bounds): + * ptr = __rte_pktmbuf_read(mbuf, off, size, tmp_buf_on_stack) + * if (ptr == NULL) goto exit + * R0 = *(type *)ptr + * R0 = bswap(R0) + */ +static void +emit_ld_mbuf(struct a64_jit_ctx *ctx, uint8_t op, uint8_t src, + int32_t imm, uint8_t tmp1, uint8_t tmp2) +{ + uint8_t r0, r6; + uint8_t mode, opsz; + uint32_t sz; + int32_t exit_off; + + r0 = ebpf_to_a64_reg(ctx, EBPF_REG_0); + r6 = ebpf_to_a64_reg(ctx, EBPF_REG_6); + + mode = BPF_MODE(op); + opsz = BPF_SIZE(op); + sz = bpf_size(opsz); + + /* + * Compute the packet offset in tmp1. + * BPF_ABS: off = imm + * BPF_IND: off = src_reg + imm + */ + emit_mov_imm(ctx, 1, tmp1, imm); + if (mode == BPF_IND) + emit_add(ctx, 1, tmp1, src); + + /* + * Fast path: check if access is within the first segment. + * Load mbuf->data_len (uint16_t) into tmp2. + */ + emit_ldr_uimm(ctx, BPF_H, tmp2, r6, + offsetof(struct rte_mbuf, data_len)); + /* Zero-extend tmp2 to 64-bit (data_len is 16-bit) */ + emit_bitfield(ctx, 1, tmp2, tmp2, 0, 15, A64_UBFM); + + /* tmp2 = data_len - off */ + emit_sub(ctx, 1, tmp2, tmp1); + + /* + * If data_len - off < sz, the access extends beyond the first + * segment. Branch to the slow path. + * CMP tmp2, #sz; B.LT slow_path + * + * The slow path is a fixed number of instructions ahead. + * Fast path remaining instructions (counted below): + * emit_ldr_uimm (buf_addr) 1 + * emit_ldr_uimm (data_off) 1 + * emit_bitfield (zext) 1 + * emit_add (+ data_off) 1 + * emit_add (+ off) 1 + * emit_ldr (load value) 1 + * emit_rev / emit_zero_extend 1 or 2 (max 2) + * emit_b (skip slow) 1 + * Total fast path after branch: max 9 instructions + * + * We branch forward to the slow path start. + * Calculate as: the b_cond instruction itself is at current idx, + * then 9 instructions of fast path follow, so slow path starts + * at +10 from the b_cond (but b_cond offset is in instructions + * relative to itself, so +10). + * + * Use B.LT for signed comparison: if data_len < off, tmp2 went + * negative so this catches both insufficient length and negative + * offset results. + */ + emit_cmp_imm(ctx, 1, tmp2, sz); + /* Count of fast path instructions to skip: varies by byte-swap */ + { + /* + * For BPF_B: no swap needed, just zero_extend 64 = nop + * For BPF_H: rev16 + zero_extend = 2 insns + * For BPF_W: rev32 = 1 insn + * To simplify, use a fixed forward offset. + * Fast path body: 6 insns (load ptr computation + ldr) + + * swap (0 for B, 2 for H, 1 for W) + 1 branch = 7..9 + * + * Use the two-pass approach: first pass calculates offsets, + * second pass emits. We track the instruction index. + */ + uint32_t fast_start, fast_end, slow_start, slow_end; + + fast_start = ctx->idx; + + /* Placeholder: emit B.LT with a dummy offset (will be fixed) */ + emit_b_cond(ctx, A64_LT, 0); + + /* === Fast path body === */ + + /* Load buf_addr (void *, 8 bytes at offset 0) into r0 */ + emit_ldr_uimm(ctx, EBPF_DW, r0, r6, + offsetof(struct rte_mbuf, buf_addr)); + + /* Load data_off (uint16_t) into tmp2 */ + emit_ldr_uimm(ctx, BPF_H, tmp2, r6, + offsetof(struct rte_mbuf, data_off)); + /* Zero-extend data_off to 64-bit */ + emit_bitfield(ctx, 1, tmp2, tmp2, 0, 15, A64_UBFM); + + /* r0 = buf_addr + data_off */ + emit_add(ctx, 1, r0, tmp2); + /* r0 = buf_addr + data_off + off */ + emit_add(ctx, 1, r0, tmp1); + + /* Load the actual value: r0 = *(size *)r0 */ + emit_mov_imm(ctx, 1, tmp2, 0); + emit_ldr(ctx, opsz, r0, r0, tmp2); + + /* Byte-swap from network order to host order */ + if (opsz == BPF_H) + emit_rev(ctx, r0, 16); + else if (opsz == BPF_W) + emit_rev(ctx, r0, 32); + /* BPF_B: no swap needed */ + + /* Skip the slow path */ + emit_b(ctx, 0); /* placeholder */ + fast_end = ctx->idx; + + /* === Slow path === */ + slow_start = ctx->idx; + + /* + * Call __rte_pktmbuf_read(mbuf, off, len, buf). + * ARM64 calling convention: R0-R3 are arguments. + * R0 = mbuf (EBPF_REG_6) + * R1 = off (tmp1) + * R2 = len (sz) + * R3 = buf (stack pointer, we use current SP as temp buf) + * + * The caller-save EBPF registers R1-R5 are scratch anyway + * for BPF_ABS/BPF_IND instructions per the BPF spec. + */ + + /* R0 = mbuf */ + emit_mov_64(ctx, A64_R(0), r6); + /* R1 = off (already in tmp1, move to A64_R(1)) */ + emit_mov_64(ctx, A64_R(1), tmp1); + /* R2 = size */ + emit_mov_imm(ctx, 0, A64_R(2), sz); + /* R3 = address of temp buffer on the stack (use SP) */ + emit_mov_64(ctx, A64_R(3), A64_SP); + + /* Call __rte_pktmbuf_read */ + emit_mov_imm(ctx, 1, tmp2, (uint64_t)__rte_pktmbuf_read); + emit_blr(ctx, tmp2); + + /* Check return value: if NULL, exit with 0 */ + /* R0 has the return value (pointer or NULL) */ + emit_cbnz(ctx, 1, A64_R(0), 3); + emit_mov_imm(ctx, 1, r0, 0); + exit_off = (ctx->program_start + ctx->program_sz) - ctx->idx; + emit_b(ctx, exit_off); + + /* Load value from returned pointer */ + emit_mov_imm(ctx, 1, tmp2, 0); + emit_ldr(ctx, opsz, A64_R(0), A64_R(0), tmp2); + + /* Byte-swap from network order to host order */ + if (opsz == BPF_H) + emit_rev(ctx, A64_R(0), 16); + else if (opsz == BPF_W) + emit_rev(ctx, A64_R(0), 32); + /* BPF_B: no swap needed */ + + /* Move result to EBPF R0 register */ + emit_mov_64(ctx, r0, A64_R(0)); + + slow_end = ctx->idx; + + /* + * Now fix up the forward branches that were emitted with + * placeholder offsets. We re-emit them with correct offsets. + * + * The B.LT at fast_start should jump to slow_start. + * The B at fast_end-1 should jump to slow_end. + */ + if (ctx->ins != NULL) { + /* Fix B.LT: offset from fast_start to slow_start */ + ctx->idx = fast_start; + emit_b_cond(ctx, A64_LT, + (int32_t)(slow_start - fast_start)); + + /* Fix B: offset from fast_end-1 to slow_end */ + ctx->idx = fast_end - 1; + emit_b(ctx, (int32_t)(slow_end - (fast_end - 1))); + } + ctx->idx = slow_end; + } +} + /* * Walk through eBPF code and translate them to arm64 one. */ @@ -1162,6 +1446,13 @@ emit(struct a64_jit_ctx *ctx, struct rte_bpf *bpf) ctx->idx = 0; /* arm64 SP must be aligned to 16 */ ctx->stack_sz = RTE_ALIGN_MUL_CEIL(bpf->stack_sz, 16); + /* + * BPF_ABS/BPF_IND instructions need a temporary buffer on the + * stack for __rte_pktmbuf_read(). Reserve at least 16 bytes + * (aligned) below the BPF stack for this purpose. + */ + if (ctx->foundldmbuf && ctx->stack_sz == 0) + ctx->stack_sz = 16; tmp1 = ebpf_to_a64_reg(ctx, TMP_REG_1); tmp2 = ebpf_to_a64_reg(ctx, TMP_REG_2); tmp3 = ebpf_to_a64_reg(ctx, TMP_REG_3); @@ -1338,6 +1629,16 @@ emit(struct a64_jit_ctx *ctx, struct rte_bpf *bpf) emit_mov_imm(ctx, 1, dst, u64); i++; break; + /* R0 = ntoh(*(size *)(mbuf->pkt_data + imm)) */ + case (BPF_LD | BPF_ABS | BPF_B): + case (BPF_LD | BPF_ABS | BPF_H): + case (BPF_LD | BPF_ABS | BPF_W): + /* R0 = ntoh(*(size *)(mbuf->pkt_data + src + imm)) */ + case (BPF_LD | BPF_IND | BPF_B): + case (BPF_LD | BPF_IND | BPF_H): + case (BPF_LD | BPF_IND | BPF_W): + emit_ld_mbuf(ctx, op, src, imm, tmp1, tmp2); + break; /* *(size *)(dst + off) = src */ case (BPF_STX | BPF_MEM | BPF_B): case (BPF_STX | BPF_MEM | BPF_H): -- 2.51.0

