274 lines
7.0 KiB
C
274 lines
7.0 KiB
C
|
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||
|
/* Copyright (C) 2018 Netronome Systems, Inc. */
|
||
|
|
||
|
#include <linux/bitfield.h>
|
||
|
#include <net/pkt_cls.h>
|
||
|
|
||
|
#include "../nfpcore/nfp_cpp.h"
|
||
|
#include "../nfp_app.h"
|
||
|
#include "../nfp_net_repr.h"
|
||
|
#include "main.h"
|
||
|
|
||
|
struct nfp_abm_u32_match {
|
||
|
u32 handle;
|
||
|
u32 band;
|
||
|
u8 mask;
|
||
|
u8 val;
|
||
|
struct list_head list;
|
||
|
};
|
||
|
|
||
|
static bool
|
||
|
nfp_abm_u32_check_knode(struct nfp_abm *abm, struct tc_cls_u32_knode *knode,
|
||
|
__be16 proto, struct netlink_ext_ack *extack)
|
||
|
{
|
||
|
struct tc_u32_key *k;
|
||
|
unsigned int tos_off;
|
||
|
|
||
|
if (knode->exts && tcf_exts_has_actions(knode->exts)) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "action offload not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->link_handle) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "linking not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->sel->flags != TC_U32_TERMINAL) {
|
||
|
NL_SET_ERR_MSG_MOD(extack,
|
||
|
"flags must be equal to TC_U32_TERMINAL");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->sel->off || knode->sel->offshift || knode->sel->offmask ||
|
||
|
knode->sel->offoff || knode->fshift) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "variable offsetting not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->sel->hoff || knode->sel->hmask) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "hashing not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->val || knode->mask) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "matching on mark not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->res && knode->res->class) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "setting non-0 class not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->res && knode->res->classid >= abm->num_bands) {
|
||
|
NL_SET_ERR_MSG_MOD(extack,
|
||
|
"classid higher than number of bands");
|
||
|
return false;
|
||
|
}
|
||
|
if (knode->sel->nkeys != 1) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "exactly one key required");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
switch (proto) {
|
||
|
case htons(ETH_P_IP):
|
||
|
tos_off = 16;
|
||
|
break;
|
||
|
case htons(ETH_P_IPV6):
|
||
|
tos_off = 20;
|
||
|
break;
|
||
|
default:
|
||
|
NL_SET_ERR_MSG_MOD(extack, "only IP and IPv6 supported as filter protocol");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
k = &knode->sel->keys[0];
|
||
|
if (k->offmask) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "offset mask - variable offsetting not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (k->off) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "only DSCP fields can be matched");
|
||
|
return false;
|
||
|
}
|
||
|
if (k->val & ~k->mask) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "mask does not cover the key");
|
||
|
return false;
|
||
|
}
|
||
|
if (be32_to_cpu(k->mask) >> tos_off & ~abm->dscp_mask) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "only high DSCP class selector bits can be used");
|
||
|
nfp_err(abm->app->cpp,
|
||
|
"u32 offload: requested mask %x FW can support only %x\n",
|
||
|
be32_to_cpu(k->mask) >> tos_off, abm->dscp_mask);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* This filter list -> map conversion is O(n * m), we expect single digit or
|
||
|
* low double digit number of prios and likewise for the filters. Also u32
|
||
|
* doesn't report stats, so it's really only setup time cost.
|
||
|
*/
|
||
|
static unsigned int
|
||
|
nfp_abm_find_band_for_prio(struct nfp_abm_link *alink, unsigned int prio)
|
||
|
{
|
||
|
struct nfp_abm_u32_match *iter;
|
||
|
|
||
|
list_for_each_entry(iter, &alink->dscp_map, list)
|
||
|
if ((prio & iter->mask) == iter->val)
|
||
|
return iter->band;
|
||
|
|
||
|
return alink->def_band;
|
||
|
}
|
||
|
|
||
|
static int nfp_abm_update_band_map(struct nfp_abm_link *alink)
|
||
|
{
|
||
|
unsigned int i, bits_per_prio, prios_per_word, base_shift;
|
||
|
struct nfp_abm *abm = alink->abm;
|
||
|
u32 field_mask;
|
||
|
|
||
|
alink->has_prio = !list_empty(&alink->dscp_map);
|
||
|
|
||
|
bits_per_prio = roundup_pow_of_two(order_base_2(abm->num_bands));
|
||
|
field_mask = (1 << bits_per_prio) - 1;
|
||
|
prios_per_word = sizeof(u32) * BITS_PER_BYTE / bits_per_prio;
|
||
|
|
||
|
/* FW mask applies from top bits */
|
||
|
base_shift = 8 - order_base_2(abm->num_prios);
|
||
|
|
||
|
for (i = 0; i < abm->num_prios; i++) {
|
||
|
unsigned int offset;
|
||
|
u32 *word;
|
||
|
u8 band;
|
||
|
|
||
|
word = &alink->prio_map[i / prios_per_word];
|
||
|
offset = (i % prios_per_word) * bits_per_prio;
|
||
|
|
||
|
band = nfp_abm_find_band_for_prio(alink, i << base_shift);
|
||
|
|
||
|
*word &= ~(field_mask << offset);
|
||
|
*word |= band << offset;
|
||
|
}
|
||
|
|
||
|
/* Qdisc offload status may change if has_prio changed */
|
||
|
nfp_abm_qdisc_offload_update(alink);
|
||
|
|
||
|
return nfp_abm_ctrl_prio_map_update(alink, alink->prio_map);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
nfp_abm_u32_knode_delete(struct nfp_abm_link *alink,
|
||
|
struct tc_cls_u32_knode *knode)
|
||
|
{
|
||
|
struct nfp_abm_u32_match *iter;
|
||
|
|
||
|
list_for_each_entry(iter, &alink->dscp_map, list)
|
||
|
if (iter->handle == knode->handle) {
|
||
|
list_del(&iter->list);
|
||
|
kfree(iter);
|
||
|
nfp_abm_update_band_map(alink);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
nfp_abm_u32_knode_replace(struct nfp_abm_link *alink,
|
||
|
struct tc_cls_u32_knode *knode,
|
||
|
__be16 proto, struct netlink_ext_ack *extack)
|
||
|
{
|
||
|
struct nfp_abm_u32_match *match = NULL, *iter;
|
||
|
unsigned int tos_off;
|
||
|
u8 mask, val;
|
||
|
int err;
|
||
|
|
||
|
if (!nfp_abm_u32_check_knode(alink->abm, knode, proto, extack))
|
||
|
goto err_delete;
|
||
|
|
||
|
tos_off = proto == htons(ETH_P_IP) ? 16 : 20;
|
||
|
|
||
|
/* Extract the DSCP Class Selector bits */
|
||
|
val = be32_to_cpu(knode->sel->keys[0].val) >> tos_off & 0xff;
|
||
|
mask = be32_to_cpu(knode->sel->keys[0].mask) >> tos_off & 0xff;
|
||
|
|
||
|
/* Check if there is no conflicting mapping and find match by handle */
|
||
|
list_for_each_entry(iter, &alink->dscp_map, list) {
|
||
|
u32 cmask;
|
||
|
|
||
|
if (iter->handle == knode->handle) {
|
||
|
match = iter;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
cmask = iter->mask & mask;
|
||
|
if ((iter->val & cmask) == (val & cmask) &&
|
||
|
iter->band != knode->res->classid) {
|
||
|
NL_SET_ERR_MSG_MOD(extack, "conflict with already offloaded filter");
|
||
|
goto err_delete;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!match) {
|
||
|
match = kzalloc(sizeof(*match), GFP_KERNEL);
|
||
|
if (!match)
|
||
|
return -ENOMEM;
|
||
|
list_add(&match->list, &alink->dscp_map);
|
||
|
}
|
||
|
match->handle = knode->handle;
|
||
|
match->band = knode->res->classid;
|
||
|
match->mask = mask;
|
||
|
match->val = val;
|
||
|
|
||
|
err = nfp_abm_update_band_map(alink);
|
||
|
if (err)
|
||
|
goto err_delete;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_delete:
|
||
|
nfp_abm_u32_knode_delete(alink, knode);
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
static int nfp_abm_setup_tc_block_cb(enum tc_setup_type type,
|
||
|
void *type_data, void *cb_priv)
|
||
|
{
|
||
|
struct tc_cls_u32_offload *cls_u32 = type_data;
|
||
|
struct nfp_repr *repr = cb_priv;
|
||
|
struct nfp_abm_link *alink;
|
||
|
|
||
|
alink = repr->app_priv;
|
||
|
|
||
|
if (type != TC_SETUP_CLSU32) {
|
||
|
NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
|
||
|
"only offload of u32 classifier supported");
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
if (!tc_cls_can_offload_and_chain0(repr->netdev, &cls_u32->common))
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
if (cls_u32->common.protocol != htons(ETH_P_IP) &&
|
||
|
cls_u32->common.protocol != htons(ETH_P_IPV6)) {
|
||
|
NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
|
||
|
"only IP and IPv6 supported as filter protocol");
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
switch (cls_u32->command) {
|
||
|
case TC_CLSU32_NEW_KNODE:
|
||
|
case TC_CLSU32_REPLACE_KNODE:
|
||
|
return nfp_abm_u32_knode_replace(alink, &cls_u32->knode,
|
||
|
cls_u32->common.protocol,
|
||
|
cls_u32->common.extack);
|
||
|
case TC_CLSU32_DELETE_KNODE:
|
||
|
nfp_abm_u32_knode_delete(alink, &cls_u32->knode);
|
||
|
return 0;
|
||
|
default:
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static LIST_HEAD(nfp_abm_block_cb_list);
|
||
|
|
||
|
int nfp_abm_setup_cls_block(struct net_device *netdev, struct nfp_repr *repr,
|
||
|
struct flow_block_offload *f)
|
||
|
{
|
||
|
return flow_block_cb_setup_simple(f, &nfp_abm_block_cb_list,
|
||
|
nfp_abm_setup_tc_block_cb,
|
||
|
repr, repr, true);
|
||
|
}
|