Skip to content

Commit

Permalink
Merge branch 'bpf-nfp-jit-adjust-head-support'
Browse files Browse the repository at this point in the history
Jakub Kicinski says:

====================
This small set adds support for bpf_xdp_adjust_head() to the offload.
Since we have access to unmodified BPF bytecode translating calls is
pretty trivial.  First part of the series adds handling of BPF
capabilities included in the FW in TLV format.  The last two patches
add adjust head support in the nfp verifier and jit, and a small
optimization in case we can guarantee the constant adjustment
will always meet adjustment constaints.
====================

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
  • Loading branch information
borkmann committed Dec 15, 2017
2 parents 7310c23 + 8231f84 commit 1c45597
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 0 deletions.
54 changes: 54 additions & 0 deletions drivers/net/ethernet/netronome/nfp/bpf/fw.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2017 Netronome Systems, Inc.
*
* This software is dual licensed under the GNU General License Version 2,
* June 1991 as shown in the file COPYING in the top-level directory of this
* source tree or the BSD 2-Clause License provided below. You have the
* option to license this software under the complete terms of either license.
*
* The BSD 2-Clause License:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

#ifndef NFP_BPF_FW_H
#define NFP_BPF_FW_H 1

#include <linux/bitops.h>
#include <linux/types.h>

enum bpf_cap_tlv_type {
NFP_BPF_CAP_TYPE_ADJUST_HEAD = 2,
};

struct nfp_bpf_cap_tlv_adjust_head {
__le32 flags;
__le32 off_min;
__le32 off_max;
__le32 guaranteed_sub;
__le32 guaranteed_add;
};

#define NFP_BPF_ADJUST_HEAD_NO_META BIT(0)

#endif
107 changes: 107 additions & 0 deletions drivers/net/ethernet/netronome/nfp/bpf/jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#define pr_fmt(fmt) "NFP net bpf: " fmt

#include <linux/bug.h>
#include <linux/kernel.h>
#include <linux/bpf.h>
#include <linux/filter.h>
Expand Down Expand Up @@ -87,6 +88,18 @@ static unsigned int nfp_prog_current_offset(struct nfp_prog *nfp_prog)
return nfp_prog->start_off + nfp_prog->prog_len;
}

static bool
nfp_prog_confirm_current_offset(struct nfp_prog *nfp_prog, unsigned int off)
{
/* If there is a recorded error we may have dropped instructions;
* that doesn't have to be due to translator bug, and the translation
* will fail anyway, so just return OK.
*/
if (nfp_prog->error)
return true;
return !WARN_ON_ONCE(nfp_prog_current_offset(nfp_prog) != off);
}

static unsigned int
nfp_prog_offset_to_index(struct nfp_prog *nfp_prog, unsigned int offset)
{
Expand Down Expand Up @@ -1196,6 +1209,86 @@ static void wrp_end32(struct nfp_prog *nfp_prog, swreg reg_in, u8 gpr_out)
SHF_SC_R_ROT, 16);
}

static int adjust_head(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
swreg tmp = imm_a(nfp_prog), tmp_len = imm_b(nfp_prog);
struct nfp_bpf_cap_adjust_head *adjust_head;
u32 ret_einval, end;

adjust_head = &nfp_prog->bpf->adjust_head;

/* Optimized version - 5 vs 14 cycles */
if (nfp_prog->adjust_head_location != UINT_MAX) {
if (WARN_ON_ONCE(nfp_prog->adjust_head_location != meta->n))
return -EINVAL;

emit_alu(nfp_prog, pptr_reg(nfp_prog),
reg_a(2 * 2), ALU_OP_ADD, pptr_reg(nfp_prog));
emit_alu(nfp_prog, plen_reg(nfp_prog),
plen_reg(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));
emit_alu(nfp_prog, pv_len(nfp_prog),
pv_len(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));

wrp_immed(nfp_prog, reg_both(0), 0);
wrp_immed(nfp_prog, reg_both(1), 0);

/* TODO: when adjust head is guaranteed to succeed we can
* also eliminate the following if (r0 == 0) branch.
*/

return 0;
}

ret_einval = nfp_prog_current_offset(nfp_prog) + 14;
end = ret_einval + 2;

/* We need to use a temp because offset is just a part of the pkt ptr */
emit_alu(nfp_prog, tmp,
reg_a(2 * 2), ALU_OP_ADD_2B, pptr_reg(nfp_prog));

/* Validate result will fit within FW datapath constraints */
emit_alu(nfp_prog, reg_none(),
tmp, ALU_OP_SUB, reg_imm(adjust_head->off_min));
emit_br(nfp_prog, BR_BLO, ret_einval, 0);
emit_alu(nfp_prog, reg_none(),
reg_imm(adjust_head->off_max), ALU_OP_SUB, tmp);
emit_br(nfp_prog, BR_BLO, ret_einval, 0);

/* Validate the length is at least ETH_HLEN */
emit_alu(nfp_prog, tmp_len,
plen_reg(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));
emit_alu(nfp_prog, reg_none(),
tmp_len, ALU_OP_SUB, reg_imm(ETH_HLEN));
emit_br(nfp_prog, BR_BMI, ret_einval, 0);

/* Load the ret code */
wrp_immed(nfp_prog, reg_both(0), 0);
wrp_immed(nfp_prog, reg_both(1), 0);

/* Modify the packet metadata */
emit_ld_field(nfp_prog, pptr_reg(nfp_prog), 0x3, tmp, SHF_SC_NONE, 0);

/* Skip over the -EINVAL ret code (defer 2) */
emit_br_def(nfp_prog, end, 2);

emit_alu(nfp_prog, plen_reg(nfp_prog),
plen_reg(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));
emit_alu(nfp_prog, pv_len(nfp_prog),
pv_len(nfp_prog), ALU_OP_SUB, reg_a(2 * 2));

/* return -EINVAL target */
if (!nfp_prog_confirm_current_offset(nfp_prog, ret_einval))
return -EINVAL;

wrp_immed(nfp_prog, reg_both(0), -22);
wrp_immed(nfp_prog, reg_both(1), ~0);

if (!nfp_prog_confirm_current_offset(nfp_prog, end))
return -EINVAL;

return 0;
}

/* --- Callbacks --- */
static int mov_reg64(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
Expand Down Expand Up @@ -1930,6 +2023,17 @@ static int jne_reg(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
return wrp_test_reg(nfp_prog, meta, ALU_OP_XOR, BR_BNE);
}

static int call(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
switch (meta->insn.imm) {
case BPF_FUNC_xdp_adjust_head:
return adjust_head(nfp_prog, meta);
default:
WARN_ONCE(1, "verifier allowed unsupported function\n");
return -EOPNOTSUPP;
}
}

static int goto_out(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta)
{
wrp_br_special(nfp_prog, BR_UNC, OP_BR_GO_OUT);
Expand Down Expand Up @@ -2002,6 +2106,7 @@ static const instr_cb_t instr_cb[256] = {
[BPF_JMP | BPF_JLE | BPF_X] = jle_reg,
[BPF_JMP | BPF_JSET | BPF_X] = jset_reg,
[BPF_JMP | BPF_JNE | BPF_X] = jne_reg,
[BPF_JMP | BPF_CALL] = call,
[BPF_JMP | BPF_EXIT] = goto_out,
};

Expand All @@ -2026,6 +2131,8 @@ static int nfp_fixup_branches(struct nfp_prog *nfp_prog)
list_for_each_entry(meta, &nfp_prog->insns, l) {
if (meta->skip)
continue;
if (meta->insn.code == (BPF_JMP | BPF_CALL))
continue;
if (BPF_CLASS(meta->insn.code) != BPF_JMP)
continue;

Expand Down
115 changes: 115 additions & 0 deletions drivers/net/ethernet/netronome/nfp/bpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
#include <net/pkt_cls.h>

#include "../nfpcore/nfp_cpp.h"
#include "../nfpcore/nfp_nffw.h"
#include "../nfp_app.h"
#include "../nfp_main.h"
#include "../nfp_net.h"
#include "../nfp_port.h"
#include "fw.h"
#include "main.h"

static bool nfp_net_ebpf_capable(struct nfp_net *nn)
Expand Down Expand Up @@ -155,10 +157,123 @@ static bool nfp_bpf_tc_busy(struct nfp_app *app, struct nfp_net *nn)
return nn->dp.ctrl & NFP_NET_CFG_CTRL_BPF;
}

static int
nfp_bpf_parse_cap_adjust_head(struct nfp_app_bpf *bpf, void __iomem *value,
u32 length)
{
struct nfp_bpf_cap_tlv_adjust_head __iomem *cap = value;
struct nfp_cpp *cpp = bpf->app->pf->cpp;

if (length < sizeof(*cap)) {
nfp_err(cpp, "truncated adjust_head TLV: %d\n", length);
return -EINVAL;
}

bpf->adjust_head.flags = readl(&cap->flags);
bpf->adjust_head.off_min = readl(&cap->off_min);
bpf->adjust_head.off_max = readl(&cap->off_max);
bpf->adjust_head.guaranteed_sub = readl(&cap->guaranteed_sub);
bpf->adjust_head.guaranteed_add = readl(&cap->guaranteed_add);

if (bpf->adjust_head.off_min > bpf->adjust_head.off_max) {
nfp_err(cpp, "invalid adjust_head TLV: min > max\n");
return -EINVAL;
}
if (!FIELD_FIT(UR_REG_IMM_MAX, bpf->adjust_head.off_min) ||
!FIELD_FIT(UR_REG_IMM_MAX, bpf->adjust_head.off_max)) {
nfp_warn(cpp, "disabling adjust_head - driver expects min/max to fit in as immediates\n");
memset(&bpf->adjust_head, 0, sizeof(bpf->adjust_head));
return 0;
}

return 0;
}

static int nfp_bpf_parse_capabilities(struct nfp_app *app)
{
struct nfp_cpp *cpp = app->pf->cpp;
struct nfp_cpp_area *area;
u8 __iomem *mem, *start;

mem = nfp_rtsym_map(app->pf->rtbl, "_abi_bpf_capabilities", "bpf.cap",
8, &area);
if (IS_ERR(mem))
return PTR_ERR(mem) == -ENOENT ? 0 : PTR_ERR(mem);

start = mem;
while (mem - start + 8 < nfp_cpp_area_size(area)) {
u8 __iomem *value;
u32 type, length;

type = readl(mem);
length = readl(mem + 4);
value = mem + 8;

mem += 8 + length;
if (mem - start > nfp_cpp_area_size(area))
goto err_release_free;

switch (type) {
case NFP_BPF_CAP_TYPE_ADJUST_HEAD:
if (nfp_bpf_parse_cap_adjust_head(app->priv, value,
length))
goto err_release_free;
break;
default:
nfp_dbg(cpp, "unknown BPF capability: %d\n", type);
break;
}
}
if (mem - start != nfp_cpp_area_size(area)) {
nfp_err(cpp, "BPF capabilities left after parsing, parsed:%lu total length:%lu\n",
mem - start, nfp_cpp_area_size(area));
goto err_release_free;
}

nfp_cpp_area_release_free(area);

return 0;

err_release_free:
nfp_err(cpp, "invalid BPF capabilities at offset:%ld\n", mem - start);
nfp_cpp_area_release_free(area);
return -EINVAL;
}

static int nfp_bpf_init(struct nfp_app *app)
{
struct nfp_app_bpf *bpf;
int err;

bpf = kzalloc(sizeof(*bpf), GFP_KERNEL);
if (!bpf)
return -ENOMEM;
bpf->app = app;
app->priv = bpf;

err = nfp_bpf_parse_capabilities(app);
if (err)
goto err_free_bpf;

return 0;

err_free_bpf:
kfree(bpf);
return err;
}

static void nfp_bpf_clean(struct nfp_app *app)
{
kfree(app->priv);
}

const struct nfp_app_type app_bpf = {
.id = NFP_APP_BPF_NIC,
.name = "ebpf",

.init = nfp_bpf_init,
.clean = nfp_bpf_clean,

.extra_cap = nfp_bpf_extra_cap,

.vnic_alloc = nfp_app_nic_vnic_alloc,
Expand Down
Loading

0 comments on commit 1c45597

Please sign in to comment.