|
|
@@ -21,6 +21,7 @@
|
|
|
#include <linux/netdevice.h>
|
|
|
#include <linux/filter.h>
|
|
|
#include <linux/init.h>
|
|
|
+#include <linux/bpf.h>
|
|
|
#include <asm/cacheflush.h>
|
|
|
#include <asm/dis.h>
|
|
|
#include "bpf_jit.h"
|
|
|
@@ -40,6 +41,8 @@ struct bpf_jit {
|
|
|
int base_ip; /* Base address for literal pool */
|
|
|
int ret0_ip; /* Address of return 0 */
|
|
|
int exit_ip; /* Address of exit */
|
|
|
+ int tail_call_start; /* Tail call start offset */
|
|
|
+ int labels[1]; /* Labels for local jumps */
|
|
|
};
|
|
|
|
|
|
#define BPF_SIZE_MAX 4096 /* Max size for program */
|
|
|
@@ -49,6 +52,7 @@ struct bpf_jit {
|
|
|
#define SEEN_RET0 4 /* ret0_ip points to a valid return 0 */
|
|
|
#define SEEN_LITERAL 8 /* code uses literals */
|
|
|
#define SEEN_FUNC 16 /* calls C functions */
|
|
|
+#define SEEN_TAIL_CALL 32 /* code uses tail calls */
|
|
|
#define SEEN_STACK (SEEN_FUNC | SEEN_MEM | SEEN_SKB)
|
|
|
|
|
|
/*
|
|
|
@@ -60,6 +64,7 @@ struct bpf_jit {
|
|
|
#define REG_L (__MAX_BPF_REG+3) /* Literal pool register */
|
|
|
#define REG_15 (__MAX_BPF_REG+4) /* Register 15 */
|
|
|
#define REG_0 REG_W0 /* Register 0 */
|
|
|
+#define REG_1 REG_W1 /* Register 1 */
|
|
|
#define REG_2 BPF_REG_1 /* Register 2 */
|
|
|
#define REG_14 BPF_REG_0 /* Register 14 */
|
|
|
|
|
|
@@ -223,6 +228,24 @@ static inline void reg_set_seen(struct bpf_jit *jit, u32 b1)
|
|
|
REG_SET_SEEN(b3); \
|
|
|
})
|
|
|
|
|
|
+#define EMIT6_PCREL_LABEL(op1, op2, b1, b2, label, mask) \
|
|
|
+({ \
|
|
|
+ int rel = (jit->labels[label] - jit->prg) >> 1; \
|
|
|
+ _EMIT6(op1 | reg(b1, b2) << 16 | (rel & 0xffff), \
|
|
|
+ op2 | mask << 12); \
|
|
|
+ REG_SET_SEEN(b1); \
|
|
|
+ REG_SET_SEEN(b2); \
|
|
|
+})
|
|
|
+
|
|
|
+#define EMIT6_PCREL_IMM_LABEL(op1, op2, b1, imm, label, mask) \
|
|
|
+({ \
|
|
|
+ int rel = (jit->labels[label] - jit->prg) >> 1; \
|
|
|
+ _EMIT6(op1 | (reg_high(b1) | mask) << 16 | \
|
|
|
+ (rel & 0xffff), op2 | (imm & 0xff) << 8); \
|
|
|
+ REG_SET_SEEN(b1); \
|
|
|
+ BUILD_BUG_ON(((unsigned long) imm) > 0xff); \
|
|
|
+})
|
|
|
+
|
|
|
#define EMIT6_PCREL(op1, op2, b1, b2, i, off, mask) \
|
|
|
({ \
|
|
|
/* Branch instruction needs 6 bytes */ \
|
|
|
@@ -286,7 +309,7 @@ static void jit_fill_hole(void *area, unsigned int size)
|
|
|
*/
|
|
|
static void save_regs(struct bpf_jit *jit, u32 rs, u32 re)
|
|
|
{
|
|
|
- u32 off = 72 + (rs - 6) * 8;
|
|
|
+ u32 off = STK_OFF_R6 + (rs - 6) * 8;
|
|
|
|
|
|
if (rs == re)
|
|
|
/* stg %rs,off(%r15) */
|
|
|
@@ -301,7 +324,7 @@ static void save_regs(struct bpf_jit *jit, u32 rs, u32 re)
|
|
|
*/
|
|
|
static void restore_regs(struct bpf_jit *jit, u32 rs, u32 re)
|
|
|
{
|
|
|
- u32 off = 72 + (rs - 6) * 8;
|
|
|
+ u32 off = STK_OFF_R6 + (rs - 6) * 8;
|
|
|
|
|
|
if (jit->seen & SEEN_STACK)
|
|
|
off += STK_OFF;
|
|
|
@@ -374,6 +397,16 @@ static void save_restore_regs(struct bpf_jit *jit, int op)
|
|
|
*/
|
|
|
static void bpf_jit_prologue(struct bpf_jit *jit)
|
|
|
{
|
|
|
+ if (jit->seen & SEEN_TAIL_CALL) {
|
|
|
+ /* xc STK_OFF_TCCNT(4,%r15),STK_OFF_TCCNT(%r15) */
|
|
|
+ _EMIT6(0xd703f000 | STK_OFF_TCCNT, 0xf000 | STK_OFF_TCCNT);
|
|
|
+ } else {
|
|
|
+ /* j tail_call_start: NOP if no tail calls are used */
|
|
|
+ EMIT4_PCREL(0xa7f40000, 6);
|
|
|
+ _EMIT2(0);
|
|
|
+ }
|
|
|
+ /* Tail calls have to skip above initialization */
|
|
|
+ jit->tail_call_start = jit->prg;
|
|
|
/* Save registers */
|
|
|
save_restore_regs(jit, REGS_SAVE);
|
|
|
/* Setup literal pool */
|
|
|
@@ -951,6 +984,75 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp, int i
|
|
|
EMIT4(0xb9040000, BPF_REG_0, REG_2);
|
|
|
break;
|
|
|
}
|
|
|
+ case BPF_JMP | BPF_CALL | BPF_X:
|
|
|
+ /*
|
|
|
+ * Implicit input:
|
|
|
+ * B1: pointer to ctx
|
|
|
+ * B2: pointer to bpf_array
|
|
|
+ * B3: index in bpf_array
|
|
|
+ */
|
|
|
+ jit->seen |= SEEN_TAIL_CALL;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * if (index >= array->map.max_entries)
|
|
|
+ * goto out;
|
|
|
+ */
|
|
|
+
|
|
|
+ /* llgf %w1,map.max_entries(%b2) */
|
|
|
+ EMIT6_DISP_LH(0xe3000000, 0x0016, REG_W1, REG_0, BPF_REG_2,
|
|
|
+ offsetof(struct bpf_array, map.max_entries));
|
|
|
+ /* clgrj %b3,%w1,0xa,label0: if %b3 >= %w1 goto out */
|
|
|
+ EMIT6_PCREL_LABEL(0xec000000, 0x0065, BPF_REG_3,
|
|
|
+ REG_W1, 0, 0xa);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * if (tail_call_cnt++ > MAX_TAIL_CALL_CNT)
|
|
|
+ * goto out;
|
|
|
+ */
|
|
|
+
|
|
|
+ if (jit->seen & SEEN_STACK)
|
|
|
+ off = STK_OFF_TCCNT + STK_OFF;
|
|
|
+ else
|
|
|
+ off = STK_OFF_TCCNT;
|
|
|
+ /* lhi %w0,1 */
|
|
|
+ EMIT4_IMM(0xa7080000, REG_W0, 1);
|
|
|
+ /* laal %w1,%w0,off(%r15) */
|
|
|
+ EMIT6_DISP_LH(0xeb000000, 0x00fa, REG_W1, REG_W0, REG_15, off);
|
|
|
+ /* clij %w1,MAX_TAIL_CALL_CNT,0x2,label0 */
|
|
|
+ EMIT6_PCREL_IMM_LABEL(0xec000000, 0x007f, REG_W1,
|
|
|
+ MAX_TAIL_CALL_CNT, 0, 0x2);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * prog = array->prog[index];
|
|
|
+ * if (prog == NULL)
|
|
|
+ * goto out;
|
|
|
+ */
|
|
|
+
|
|
|
+ /* sllg %r1,%b3,3: %r1 = index * 8 */
|
|
|
+ EMIT6_DISP_LH(0xeb000000, 0x000d, REG_1, BPF_REG_3, REG_0, 3);
|
|
|
+ /* lg %r1,prog(%b2,%r1) */
|
|
|
+ EMIT6_DISP_LH(0xe3000000, 0x0004, REG_1, BPF_REG_2,
|
|
|
+ REG_1, offsetof(struct bpf_array, prog));
|
|
|
+ /* clgij %r1,0,0x8,label0 */
|
|
|
+ EMIT6_PCREL_IMM_LABEL(0xec000000, 0x007d, REG_1, 0, 0, 0x8);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Restore registers before calling function
|
|
|
+ */
|
|
|
+ save_restore_regs(jit, REGS_RESTORE);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * goto *(prog->bpf_func + tail_call_start);
|
|
|
+ */
|
|
|
+
|
|
|
+ /* lg %r1,bpf_func(%r1) */
|
|
|
+ EMIT6_DISP_LH(0xe3000000, 0x0004, REG_1, REG_1, REG_0,
|
|
|
+ offsetof(struct bpf_prog, bpf_func));
|
|
|
+ /* bc 0xf,tail_call_start(%r1) */
|
|
|
+ _EMIT4(0x47f01000 + jit->tail_call_start);
|
|
|
+ /* out: */
|
|
|
+ jit->labels[0] = jit->prg;
|
|
|
+ break;
|
|
|
case BPF_JMP | BPF_EXIT: /* return b0 */
|
|
|
last = (i == fp->len - 1) ? 1 : 0;
|
|
|
if (last && !(jit->seen & SEEN_RET0))
|