681 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			681 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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;
 | 
						|
	rc = flow_indr_dev_register(efx_tc_indr_setup_cb, efx);
 | 
						|
	if (rc)
 | 
						|
		return rc;
 | 
						|
	efx->tc->up = true;
 | 
						|
	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;
 | 
						|
}
 |