blob: 3478860d40232c07997b4699513534b6a0ed952a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/****************************************************************************
* Driver for Solarflare network controllers and boards
* Copyright 2019 Solarflare Communications Inc.
* Copyright 2020-2022 Xilinx Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation, incorporated herein by reference.
*/
#include <net/pkt_cls.h>
#include "tc.h"
#include "tc_bindings.h"
#include "mae.h"
#include "ef100_rep.h"
#include "efx.h"
#define EFX_EFV_PF NULL
/* Look up the representor information (efv) for a device.
* May return NULL for the PF (us), or an error pointer for a device that
* isn't supported as a TC offload endpoint
*/
static struct efx_rep *efx_tc_flower_lookup_efv(struct efx_nic *efx,
struct net_device *dev)
{
struct efx_rep *efv;
if (!dev)
return ERR_PTR(-EOPNOTSUPP);
/* Is it us (the PF)? */
if (dev == efx->net_dev)
return EFX_EFV_PF;
/* Is it an efx vfrep at all? */
if (dev->netdev_ops != &efx_ef100_rep_netdev_ops)
return ERR_PTR(-EOPNOTSUPP);
/* Is it ours? We don't support TC rules that include another
* EF100's netdevices (not even on another port of the same NIC).
*/
efv = netdev_priv(dev);
if (efv->parent != efx)
return ERR_PTR(-EOPNOTSUPP);
return efv;
}
/* Convert a driver-internal vport ID into an external device (wire or VF) */
static s64 efx_tc_flower_external_mport(struct efx_nic *efx, struct efx_rep *efv)
{
u32 mport;
if (IS_ERR(efv))
return PTR_ERR(efv);
if (!efv) /* device is PF (us) */
efx_mae_mport_wire(efx, &mport);
else /* device is repr */
efx_mae_mport_mport(efx, efv->mport, &mport);
return mport;
}
static const struct rhashtable_params efx_tc_match_action_ht_params = {
.key_len = sizeof(unsigned long),
.key_offset = offsetof(struct efx_tc_flow_rule, cookie),
.head_offset = offsetof(struct efx_tc_flow_rule, linkage),
};
static void efx_tc_free_action_set(struct efx_nic *efx,
struct efx_tc_action_set *act, bool in_hw)
{
/* Failure paths calling this on the 'running action' set in_hw=false,
* because if the alloc had succeeded we'd've put it in acts.list and
* not still have it in act.
*/
if (in_hw) {
efx_mae_free_action_set(efx, act->fw_id);
/* in_hw is true iff we are on an acts.list; make sure to
* remove ourselves from that list before we are freed.
*/
list_del(&act->list);
}
kfree(act);
}
static void efx_tc_free_action_set_list(struct efx_nic *efx,
struct efx_tc_action_set_list *acts,
bool in_hw)
{
struct efx_tc_action_set *act, *next;
/* Failure paths set in_hw=false, because usually the acts didn't get
* to efx_mae_alloc_action_set_list(); if they did, the failure tree
* has a separate efx_mae_free_action_set_list() before calling us.
*/
if (in_hw)
efx_mae_free_action_set_list(efx, acts);
/* Any act that's on the list will be in_hw even if the list isn't */
list_for_each_entry_safe(act, next, &acts->list, list)
efx_tc_free_action_set(efx, act, true);
/* Don't kfree, as acts is embedded inside a struct efx_tc_flow_rule */
}
static void efx_tc_delete_rule(struct efx_nic *efx, struct efx_tc_flow_rule *rule)
{
efx_mae_delete_rule(efx, rule->fw_id);
/* Release entries in subsidiary tables */
efx_tc_free_action_set_list(efx, &rule->acts, true);
rule->fw_id = MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL;
}
static void efx_tc_flow_free(void *ptr, void *arg)
{
struct efx_tc_flow_rule *rule = ptr;
struct efx_nic *efx = arg;
netif_err(efx, drv, efx->net_dev,
"tc rule %lx still present at teardown, removing\n",
rule->cookie);
efx_mae_delete_rule(efx, rule->fw_id);
/* Release entries in subsidiary tables */
efx_tc_free_action_set_list(efx, &rule->acts, true);
kfree(rule);
}
static int efx_tc_flower_parse_match(struct efx_nic *efx,
struct flow_rule *rule,
struct efx_tc_match *match,
struct netlink_ext_ack *extack)
{
struct flow_dissector *dissector = rule->match.dissector;
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
struct flow_match_control fm;
flow_rule_match_control(rule, &fm);
if (fm.mask->flags) {
efx_tc_err(efx, "Unsupported match on control.flags %#x\n",
fm.mask->flags);
NL_SET_ERR_MSG_MOD(extack, "Unsupported match on control.flags");
return -EOPNOTSUPP;
}
}
if (dissector->used_keys &
~(BIT(FLOW_DISSECTOR_KEY_CONTROL) |
BIT(FLOW_DISSECTOR_KEY_BASIC))) {
efx_tc_err(efx, "Unsupported flower keys %#x\n", dissector->used_keys);
NL_SET_ERR_MSG_MOD(extack, "Unsupported flower keys encountered");
return -EOPNOTSUPP;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_match_basic fm;
flow_rule_match_basic(rule, &fm);
if (fm.mask->n_proto) {
EFX_TC_ERR_MSG(efx, extack, "Unsupported eth_proto match\n");
return -EOPNOTSUPP;
}
if (fm.mask->ip_proto) {
EFX_TC_ERR_MSG(efx, extack, "Unsupported ip_proto match\n");
return -EOPNOTSUPP;
}
}
return 0;
}
static int efx_tc_flower_replace(struct efx_nic *efx,
struct net_device *net_dev,
struct flow_cls_offload *tc,
struct efx_rep *efv)
{
struct flow_rule *fr = flow_cls_offload_flow_rule(tc);
struct netlink_ext_ack *extack = tc->common.extack;
struct efx_tc_flow_rule *rule = NULL, *old;
struct efx_tc_action_set *act = NULL;
const struct flow_action_entry *fa;
struct efx_rep *from_efv, *to_efv;
struct efx_tc_match match;
s64 rc;
int i;
if (!tc_can_offload_extack(efx->net_dev, extack))
return -EOPNOTSUPP;
if (WARN_ON(!efx->tc))
return -ENETDOWN;
if (WARN_ON(!efx->tc->up))
return -ENETDOWN;
from_efv = efx_tc_flower_lookup_efv(efx, net_dev);
if (IS_ERR(from_efv)) {
/* Might be a tunnel decap rule from an indirect block.
* Support for those not implemented yet.
*/
return -EOPNOTSUPP;
}
if (efv != from_efv) {
/* can't happen */
efx_tc_err(efx, "for %s efv is %snull but from_efv is %snull\n",
netdev_name(net_dev), efv ? "non-" : "",
from_efv ? "non-" : "");
if (efv)
NL_SET_ERR_MSG_MOD(extack, "vfrep filter has PF net_dev (can't happen)");
else
NL_SET_ERR_MSG_MOD(extack, "PF filter has vfrep net_dev (can't happen)");
return -EINVAL;
}
/* Parse match */
memset(&match, 0, sizeof(match));
rc = efx_tc_flower_external_mport(efx, from_efv);
if (rc < 0) {
EFX_TC_ERR_MSG(efx, extack, "Failed to identify ingress m-port");
return rc;
}
match.value.ingress_port = rc;
match.mask.ingress_port = ~0;
rc = efx_tc_flower_parse_match(efx, fr, &match, extack);
if (rc)
return rc;
if (tc->common.chain_index) {
EFX_TC_ERR_MSG(efx, extack, "No support for nonzero chain_index");
return -EOPNOTSUPP;
}
match.mask.recirc_id = 0xff;
rc = efx_mae_match_check_caps(efx, &match.mask, extack);
if (rc)
return rc;
rule = kzalloc(sizeof(*rule), GFP_USER);
if (!rule)
return -ENOMEM;
INIT_LIST_HEAD(&rule->acts.list);
rule->cookie = tc->cookie;
old = rhashtable_lookup_get_insert_fast(&efx->tc->match_action_ht,
&rule->linkage,
efx_tc_match_action_ht_params);
if (old) {
netif_dbg(efx, drv, efx->net_dev,
"Already offloaded rule (cookie %lx)\n", tc->cookie);
rc = -EEXIST;
NL_SET_ERR_MSG_MOD(extack, "Rule already offloaded");
goto release;
}
/* Parse actions */
act = kzalloc(sizeof(*act), GFP_USER);
if (!act) {
rc = -ENOMEM;
goto release;
}
flow_action_for_each(i, fa, &fr->action) {
struct efx_tc_action_set save;
if (!act) {
/* more actions after a non-pipe action */
EFX_TC_ERR_MSG(efx, extack, "Action follows non-pipe action");
rc = -EINVAL;
goto release;
}
switch (fa->id) {
case FLOW_ACTION_DROP:
rc = efx_mae_alloc_action_set(efx, act);
if (rc) {
EFX_TC_ERR_MSG(efx, extack, "Failed to write action set to hw (drop)");
goto release;
}
list_add_tail(&act->list, &rule->acts.list);
act = NULL; /* end of the line */
break;
case FLOW_ACTION_REDIRECT:
case FLOW_ACTION_MIRRED:
save = *act;
to_efv = efx_tc_flower_lookup_efv(efx, fa->dev);
if (IS_ERR(to_efv)) {
EFX_TC_ERR_MSG(efx, extack, "Mirred egress device not on switch");
rc = PTR_ERR(to_efv);
goto release;
}
rc = efx_tc_flower_external_mport(efx, to_efv);
if (rc < 0) {
EFX_TC_ERR_MSG(efx, extack, "Failed to identify egress m-port");
goto release;
}
act->dest_mport = rc;
act->deliver = 1;
rc = efx_mae_alloc_action_set(efx, act);
if (rc) {
EFX_TC_ERR_MSG(efx, extack, "Failed to write action set to hw (mirred)");
goto release;
}
list_add_tail(&act->list, &rule->acts.list);
act = NULL;
if (fa->id == FLOW_ACTION_REDIRECT)
break; /* end of the line */
/* Mirror, so continue on with saved act */
act = kzalloc(sizeof(*act), GFP_USER);
if (!act) {
rc = -ENOMEM;
goto release;
}
*act = save;
break;
default:
efx_tc_err(efx, "Unhandled action %u\n", fa->id);
rc = -EOPNOTSUPP;
NL_SET_ERR_MSG_MOD(extack, "Unsupported action");
goto release;
}
}
if (act) {
/* Not shot/redirected, so deliver to default dest */
if (from_efv == EFX_EFV_PF)
/* Rule applies to traffic from the wire,
* and default dest is thus the PF
*/
efx_mae_mport_uplink(efx, &act->dest_mport);
else
/* Representor, so rule applies to traffic from
* representee, and default dest is thus the rep.
* All reps use the same mport for delivery
*/
efx_mae_mport_mport(efx, efx->tc->reps_mport_id,
&act->dest_mport);
act->deliver = 1;
rc = efx_mae_alloc_action_set(efx, act);
if (rc) {
EFX_TC_ERR_MSG(efx, extack, "Failed to write action set to hw (deliver)");
goto release;
}
list_add_tail(&act->list, &rule->acts.list);
act = NULL; /* Prevent double-free in error path */
}
netif_dbg(efx, drv, efx->net_dev,
"Successfully parsed filter (cookie %lx)\n",
tc->cookie);
rule->match = match;
rc = efx_mae_alloc_action_set_list(efx, &rule->acts);
if (rc) {
EFX_TC_ERR_MSG(efx, extack, "Failed to write action set list to hw");
goto release;
}
rc = efx_mae_insert_rule(efx, &rule->match, EFX_TC_PRIO_TC,
rule->acts.fw_id, &rule->fw_id);
if (rc) {
EFX_TC_ERR_MSG(efx, extack, "Failed to insert rule in hw");
goto release_acts;
}
return 0;
release_acts:
efx_mae_free_action_set_list(efx, &rule->acts);
release:
/* We failed to insert the rule, so free up any entries we created in
* subsidiary tables.
*/
if (act)
efx_tc_free_action_set(efx, act, false);
if (rule) {
rhashtable_remove_fast(&efx->tc->match_action_ht,
&rule->linkage,
efx_tc_match_action_ht_params);
efx_tc_free_action_set_list(efx, &rule->acts, false);
}
kfree(rule);
return rc;
}
static int efx_tc_flower_destroy(struct efx_nic *efx,
struct net_device *net_dev,
struct flow_cls_offload *tc)
{
struct netlink_ext_ack *extack = tc->common.extack;
struct efx_tc_flow_rule *rule;
rule = rhashtable_lookup_fast(&efx->tc->match_action_ht, &tc->cookie,
efx_tc_match_action_ht_params);
if (!rule) {
/* Only log a message if we're the ingress device. Otherwise
* it's a foreign filter and we might just not have been
* interested (e.g. we might not have been the egress device
* either).
*/
if (!IS_ERR(efx_tc_flower_lookup_efv(efx, net_dev)))
netif_warn(efx, drv, efx->net_dev,
"Filter %lx not found to remove\n", tc->cookie);
NL_SET_ERR_MSG_MOD(extack, "Flow cookie not found in offloaded rules");
return -ENOENT;
}
/* Remove it from HW */
efx_tc_delete_rule(efx, rule);
/* Delete it from SW */
rhashtable_remove_fast(&efx->tc->match_action_ht, &rule->linkage,
efx_tc_match_action_ht_params);
netif_dbg(efx, drv, efx->net_dev, "Removed filter %lx\n", rule->cookie);
kfree(rule);
return 0;
}
int efx_tc_flower(struct efx_nic *efx, struct net_device *net_dev,
struct flow_cls_offload *tc, struct efx_rep *efv)
{
int rc;
if (!efx->tc)
return -EOPNOTSUPP;
mutex_lock(&efx->tc->mutex);
switch (tc->command) {
case FLOW_CLS_REPLACE:
rc = efx_tc_flower_replace(efx, net_dev, tc, efv);
break;
case FLOW_CLS_DESTROY:
rc = efx_tc_flower_destroy(efx, net_dev, tc);
break;
default:
rc = -EOPNOTSUPP;
break;
}
mutex_unlock(&efx->tc->mutex);
return rc;
}
static int efx_tc_configure_default_rule(struct efx_nic *efx, u32 ing_port,
u32 eg_port, struct efx_tc_flow_rule *rule)
{
struct efx_tc_action_set_list *acts = &rule->acts;
struct efx_tc_match *match = &rule->match;
struct efx_tc_action_set *act;
int rc;
match->value.ingress_port = ing_port;
match->mask.ingress_port = ~0;
act = kzalloc(sizeof(*act), GFP_KERNEL);
if (!act)
return -ENOMEM;
act->deliver = 1;
act->dest_mport = eg_port;
rc = efx_mae_alloc_action_set(efx, act);
if (rc)
goto fail1;
EFX_WARN_ON_PARANOID(!list_empty(&acts->list));
list_add_tail(&act->list, &acts->list);
rc = efx_mae_alloc_action_set_list(efx, acts);
if (rc)
goto fail2;
rc = efx_mae_insert_rule(efx, match, EFX_TC_PRIO_DFLT,
acts->fw_id, &rule->fw_id);
if (rc)
goto fail3;
return 0;
fail3:
efx_mae_free_action_set_list(efx, acts);
fail2:
list_del(&act->list);
efx_mae_free_action_set(efx, act->fw_id);
fail1:
kfree(act);
return rc;
}
static int efx_tc_configure_default_rule_pf(struct efx_nic *efx)
{
struct efx_tc_flow_rule *rule = &efx->tc->dflt.pf;
u32 ing_port, eg_port;
efx_mae_mport_uplink(efx, &ing_port);
efx_mae_mport_wire(efx, &eg_port);
return efx_tc_configure_default_rule(efx, ing_port, eg_port, rule);
}
static int efx_tc_configure_default_rule_wire(struct efx_nic *efx)
{
struct efx_tc_flow_rule *rule = &efx->tc->dflt.wire;
u32 ing_port, eg_port;
efx_mae_mport_wire(efx, &ing_port);
efx_mae_mport_uplink(efx, &eg_port);
return efx_tc_configure_default_rule(efx, ing_port, eg_port, rule);
}
int efx_tc_configure_default_rule_rep(struct efx_rep *efv)
{
struct efx_tc_flow_rule *rule = &efv->dflt;
struct efx_nic *efx = efv->parent;
u32 ing_port, eg_port;
efx_mae_mport_mport(efx, efv->mport, &ing_port);
efx_mae_mport_mport(efx, efx->tc->reps_mport_id, &eg_port);
return efx_tc_configure_default_rule(efx, ing_port, eg_port, rule);
}
void efx_tc_deconfigure_default_rule(struct efx_nic *efx,
struct efx_tc_flow_rule *rule)
{
if (rule->fw_id != MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL)
efx_tc_delete_rule(efx, rule);
rule->fw_id = MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL;
}
static int efx_tc_configure_rep_mport(struct efx_nic *efx)
{
u32 rep_mport_label;
int rc;
rc = efx_mae_allocate_mport(efx, &efx->tc->reps_mport_id, &rep_mport_label);
if (rc)
return rc;
pci_dbg(efx->pci_dev, "created rep mport 0x%08x (0x%04x)\n",
efx->tc->reps_mport_id, rep_mport_label);
/* Use mport *selector* as vport ID */
efx_mae_mport_mport(efx, efx->tc->reps_mport_id,
&efx->tc->reps_mport_vport_id);
return 0;
}
static void efx_tc_deconfigure_rep_mport(struct efx_nic *efx)
{
efx_mae_free_mport(efx, efx->tc->reps_mport_id);
efx->tc->reps_mport_id = MAE_MPORT_SELECTOR_NULL;
}
int efx_tc_insert_rep_filters(struct efx_nic *efx)
{
struct efx_filter_spec promisc, allmulti;
int rc;
if (efx->type->is_vf)
return 0;
if (!efx->tc)
return 0;
efx_filter_init_rx(&promisc, EFX_FILTER_PRI_REQUIRED, 0, 0);
efx_filter_set_uc_def(&promisc);
efx_filter_set_vport_id(&promisc, efx->tc->reps_mport_vport_id);
rc = efx_filter_insert_filter(efx, &promisc, false);
if (rc < 0)
return rc;
efx->tc->reps_filter_uc = rc;
efx_filter_init_rx(&allmulti, EFX_FILTER_PRI_REQUIRED, 0, 0);
efx_filter_set_mc_def(&allmulti);
efx_filter_set_vport_id(&allmulti, efx->tc->reps_mport_vport_id);
rc = efx_filter_insert_filter(efx, &allmulti, false);
if (rc < 0)
return rc;
efx->tc->reps_filter_mc = rc;
return 0;
}
void efx_tc_remove_rep_filters(struct efx_nic *efx)
{
if (efx->type->is_vf)
return;
if (!efx->tc)
return;
if (efx->tc->reps_filter_mc >= 0)
efx_filter_remove_id_safe(efx, EFX_FILTER_PRI_REQUIRED, efx->tc->reps_filter_mc);
efx->tc->reps_filter_mc = -1;
if (efx->tc->reps_filter_uc >= 0)
efx_filter_remove_id_safe(efx, EFX_FILTER_PRI_REQUIRED, efx->tc->reps_filter_uc);
efx->tc->reps_filter_uc = -1;
}
int efx_init_tc(struct efx_nic *efx)
{
int rc;
rc = efx_mae_get_caps(efx, efx->tc->caps);
if (rc)
return rc;
if (efx->tc->caps->match_field_count > MAE_NUM_FIELDS)
/* Firmware supports some match fields the driver doesn't know
* about. Not fatal, unless any of those fields are required
* (MAE_FIELD_SUPPORTED_MATCH_ALWAYS) but if so we don't know.
*/
netif_warn(efx, probe, efx->net_dev,
"FW reports additional match fields %u\n",
efx->tc->caps->match_field_count);
if (efx->tc->caps->action_prios < EFX_TC_PRIO__NUM) {
netif_err(efx, probe, efx->net_dev,
"Too few action prios supported (have %u, need %u)\n",
efx->tc->caps->action_prios, EFX_TC_PRIO__NUM);
return -EIO;
}
rc = efx_tc_configure_default_rule_pf(efx);
if (rc)
return rc;
rc = efx_tc_configure_default_rule_wire(efx);
if (rc)
return rc;
rc = efx_tc_configure_rep_mport(efx);
if (rc)
return rc;
efx->tc->up = true;
rc = flow_indr_dev_register(efx_tc_indr_setup_cb, efx);
if (rc)
return rc;
return 0;
}
void efx_fini_tc(struct efx_nic *efx)
{
/* We can get called even if efx_init_struct_tc() failed */
if (!efx->tc)
return;
if (efx->tc->up)
flow_indr_dev_unregister(efx_tc_indr_setup_cb, efx, efx_tc_block_unbind);
efx_tc_deconfigure_rep_mport(efx);
efx_tc_deconfigure_default_rule(efx, &efx->tc->dflt.pf);
efx_tc_deconfigure_default_rule(efx, &efx->tc->dflt.wire);
efx->tc->up = false;
}
int efx_init_struct_tc(struct efx_nic *efx)
{
int rc;
if (efx->type->is_vf)
return 0;
efx->tc = kzalloc(sizeof(*efx->tc), GFP_KERNEL);
if (!efx->tc)
return -ENOMEM;
efx->tc->caps = kzalloc(sizeof(struct mae_caps), GFP_KERNEL);
if (!efx->tc->caps) {
rc = -ENOMEM;
goto fail_alloc_caps;
}
INIT_LIST_HEAD(&efx->tc->block_list);
mutex_init(&efx->tc->mutex);
rc = rhashtable_init(&efx->tc->match_action_ht, &efx_tc_match_action_ht_params);
if (rc < 0)
goto fail_match_action_ht;
efx->tc->reps_filter_uc = -1;
efx->tc->reps_filter_mc = -1;
INIT_LIST_HEAD(&efx->tc->dflt.pf.acts.list);
efx->tc->dflt.pf.fw_id = MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL;
INIT_LIST_HEAD(&efx->tc->dflt.wire.acts.list);
efx->tc->dflt.wire.fw_id = MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL;
return 0;
fail_match_action_ht:
mutex_destroy(&efx->tc->mutex);
kfree(efx->tc->caps);
fail_alloc_caps:
kfree(efx->tc);
efx->tc = NULL;
return rc;
}
void efx_fini_struct_tc(struct efx_nic *efx)
{
if (!efx->tc)
return;
mutex_lock(&efx->tc->mutex);
EFX_WARN_ON_PARANOID(efx->tc->dflt.pf.fw_id !=
MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL);
EFX_WARN_ON_PARANOID(efx->tc->dflt.wire.fw_id !=
MC_CMD_MAE_ACTION_RULE_INSERT_OUT_ACTION_RULE_ID_NULL);
rhashtable_free_and_destroy(&efx->tc->match_action_ht, efx_tc_flow_free,
efx);
mutex_unlock(&efx->tc->mutex);
mutex_destroy(&efx->tc->mutex);
kfree(efx->tc->caps);
kfree(efx->tc);
efx->tc = NULL;
}