699 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			699 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
 | 
						|
/* Copyright (C) 2018 Netronome Systems, Inc. */
 | 
						|
 | 
						|
#include "main.h"
 | 
						|
 | 
						|
/* LAG group config flags. */
 | 
						|
#define NFP_FL_LAG_LAST			BIT(1)
 | 
						|
#define NFP_FL_LAG_FIRST		BIT(2)
 | 
						|
#define NFP_FL_LAG_DATA			BIT(3)
 | 
						|
#define NFP_FL_LAG_XON			BIT(4)
 | 
						|
#define NFP_FL_LAG_SYNC			BIT(5)
 | 
						|
#define NFP_FL_LAG_SWITCH		BIT(6)
 | 
						|
#define NFP_FL_LAG_RESET		BIT(7)
 | 
						|
 | 
						|
/* LAG port state flags. */
 | 
						|
#define NFP_PORT_LAG_LINK_UP		BIT(0)
 | 
						|
#define NFP_PORT_LAG_TX_ENABLED		BIT(1)
 | 
						|
#define NFP_PORT_LAG_CHANGED		BIT(2)
 | 
						|
 | 
						|
enum nfp_fl_lag_batch {
 | 
						|
	NFP_FL_LAG_BATCH_FIRST,
 | 
						|
	NFP_FL_LAG_BATCH_MEMBER,
 | 
						|
	NFP_FL_LAG_BATCH_FINISHED
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * struct nfp_flower_cmsg_lag_config - control message payload for LAG config
 | 
						|
 * @ctrl_flags:	Configuration flags
 | 
						|
 * @reserved:	Reserved for future use
 | 
						|
 * @ttl:	Time to live of packet - host always sets to 0xff
 | 
						|
 * @pkt_number:	Config message packet number - increment for each message
 | 
						|
 * @batch_ver:	Batch version of messages - increment for each batch of messages
 | 
						|
 * @group_id:	Group ID applicable
 | 
						|
 * @group_inst:	Group instance number - increment when group is reused
 | 
						|
 * @members:	Array of 32-bit words listing all active group members
 | 
						|
 */
 | 
						|
struct nfp_flower_cmsg_lag_config {
 | 
						|
	u8 ctrl_flags;
 | 
						|
	u8 reserved[2];
 | 
						|
	u8 ttl;
 | 
						|
	__be32 pkt_number;
 | 
						|
	__be32 batch_ver;
 | 
						|
	__be32 group_id;
 | 
						|
	__be32 group_inst;
 | 
						|
	__be32 members[];
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * struct nfp_fl_lag_group - list entry for each LAG group
 | 
						|
 * @group_id:		Assigned group ID for host/kernel sync
 | 
						|
 * @group_inst:		Group instance in case of ID reuse
 | 
						|
 * @list:		List entry
 | 
						|
 * @master_ndev:	Group master Netdev
 | 
						|
 * @dirty:		Marked if the group needs synced to HW
 | 
						|
 * @offloaded:		Marked if the group is currently offloaded to NIC
 | 
						|
 * @to_remove:		Marked if the group should be removed from NIC
 | 
						|
 * @to_destroy:		Marked if the group should be removed from driver
 | 
						|
 * @slave_cnt:		Number of slaves in group
 | 
						|
 */
 | 
						|
struct nfp_fl_lag_group {
 | 
						|
	unsigned int group_id;
 | 
						|
	u8 group_inst;
 | 
						|
	struct list_head list;
 | 
						|
	struct net_device *master_ndev;
 | 
						|
	bool dirty;
 | 
						|
	bool offloaded;
 | 
						|
	bool to_remove;
 | 
						|
	bool to_destroy;
 | 
						|
	unsigned int slave_cnt;
 | 
						|
};
 | 
						|
 | 
						|
#define NFP_FL_LAG_PKT_NUMBER_MASK	GENMASK(30, 0)
 | 
						|
#define NFP_FL_LAG_VERSION_MASK		GENMASK(22, 0)
 | 
						|
#define NFP_FL_LAG_HOST_TTL		0xff
 | 
						|
 | 
						|
/* Use this ID with zero members to ack a batch config */
 | 
						|
#define NFP_FL_LAG_SYNC_ID		0
 | 
						|
#define NFP_FL_LAG_GROUP_MIN		1 /* ID 0 reserved */
 | 
						|
#define NFP_FL_LAG_GROUP_MAX		32 /* IDs 1 to 31 are valid */
 | 
						|
 | 
						|
/* wait for more config */
 | 
						|
#define NFP_FL_LAG_DELAY		(msecs_to_jiffies(2))
 | 
						|
 | 
						|
#define NFP_FL_LAG_RETRANS_LIMIT	100 /* max retrans cmsgs to store */
 | 
						|
 | 
						|
static unsigned int nfp_fl_get_next_pkt_number(struct nfp_fl_lag *lag)
 | 
						|
{
 | 
						|
	lag->pkt_num++;
 | 
						|
	lag->pkt_num &= NFP_FL_LAG_PKT_NUMBER_MASK;
 | 
						|
 | 
						|
	return lag->pkt_num;
 | 
						|
}
 | 
						|
 | 
						|
static void nfp_fl_increment_version(struct nfp_fl_lag *lag)
 | 
						|
{
 | 
						|
	/* LSB is not considered by firmware so add 2 for each increment. */
 | 
						|
	lag->batch_ver += 2;
 | 
						|
	lag->batch_ver &= NFP_FL_LAG_VERSION_MASK;
 | 
						|
 | 
						|
	/* Zero is reserved by firmware. */
 | 
						|
	if (!lag->batch_ver)
 | 
						|
		lag->batch_ver += 2;
 | 
						|
}
 | 
						|
 | 
						|
static struct nfp_fl_lag_group *
 | 
						|
nfp_fl_lag_group_create(struct nfp_fl_lag *lag, struct net_device *master)
 | 
						|
{
 | 
						|
	struct nfp_fl_lag_group *group;
 | 
						|
	struct nfp_flower_priv *priv;
 | 
						|
	int id;
 | 
						|
 | 
						|
	priv = container_of(lag, struct nfp_flower_priv, nfp_lag);
 | 
						|
 | 
						|
	id = ida_simple_get(&lag->ida_handle, NFP_FL_LAG_GROUP_MIN,
 | 
						|
			    NFP_FL_LAG_GROUP_MAX, GFP_KERNEL);
 | 
						|
	if (id < 0) {
 | 
						|
		nfp_flower_cmsg_warn(priv->app,
 | 
						|
				     "No more bonding groups available\n");
 | 
						|
		return ERR_PTR(id);
 | 
						|
	}
 | 
						|
 | 
						|
	group = kmalloc(sizeof(*group), GFP_KERNEL);
 | 
						|
	if (!group) {
 | 
						|
		ida_simple_remove(&lag->ida_handle, id);
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
	}
 | 
						|
 | 
						|
	group->group_id = id;
 | 
						|
	group->master_ndev = master;
 | 
						|
	group->dirty = true;
 | 
						|
	group->offloaded = false;
 | 
						|
	group->to_remove = false;
 | 
						|
	group->to_destroy = false;
 | 
						|
	group->slave_cnt = 0;
 | 
						|
	group->group_inst = ++lag->global_inst;
 | 
						|
	list_add_tail(&group->list, &lag->group_list);
 | 
						|
 | 
						|
	return group;
 | 
						|
}
 | 
						|
 | 
						|
static struct nfp_fl_lag_group *
 | 
						|
nfp_fl_lag_find_group_for_master_with_lag(struct nfp_fl_lag *lag,
 | 
						|
					  struct net_device *master)
 | 
						|
{
 | 
						|
	struct nfp_fl_lag_group *entry;
 | 
						|
 | 
						|
	if (!master)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	list_for_each_entry(entry, &lag->group_list, list)
 | 
						|
		if (entry->master_ndev == master)
 | 
						|
			return entry;
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
int nfp_flower_lag_populate_pre_action(struct nfp_app *app,
 | 
						|
				       struct net_device *master,
 | 
						|
				       struct nfp_fl_pre_lag *pre_act,
 | 
						|
				       struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct nfp_flower_priv *priv = app->priv;
 | 
						|
	struct nfp_fl_lag_group *group = NULL;
 | 
						|
	__be32 temp_vers;
 | 
						|
 | 
						|
	mutex_lock(&priv->nfp_lag.lock);
 | 
						|
	group = nfp_fl_lag_find_group_for_master_with_lag(&priv->nfp_lag,
 | 
						|
							  master);
 | 
						|
	if (!group) {
 | 
						|
		mutex_unlock(&priv->nfp_lag.lock);
 | 
						|
		NL_SET_ERR_MSG_MOD(extack, "invalid entry: group does not exist for LAG action");
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	pre_act->group_id = cpu_to_be16(group->group_id);
 | 
						|
	temp_vers = cpu_to_be32(priv->nfp_lag.batch_ver <<
 | 
						|
				NFP_FL_PRE_LAG_VER_OFF);
 | 
						|
	memcpy(pre_act->lag_version, &temp_vers, 3);
 | 
						|
	pre_act->instance = group->group_inst;
 | 
						|
	mutex_unlock(&priv->nfp_lag.lock);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int nfp_flower_lag_get_output_id(struct nfp_app *app, struct net_device *master)
 | 
						|
{
 | 
						|
	struct nfp_flower_priv *priv = app->priv;
 | 
						|
	struct nfp_fl_lag_group *group = NULL;
 | 
						|
	int group_id = -ENOENT;
 | 
						|
 | 
						|
	mutex_lock(&priv->nfp_lag.lock);
 | 
						|
	group = nfp_fl_lag_find_group_for_master_with_lag(&priv->nfp_lag,
 | 
						|
							  master);
 | 
						|
	if (group)
 | 
						|
		group_id = group->group_id;
 | 
						|
	mutex_unlock(&priv->nfp_lag.lock);
 | 
						|
 | 
						|
	return group_id;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
nfp_fl_lag_config_group(struct nfp_fl_lag *lag, struct nfp_fl_lag_group *group,
 | 
						|
			struct net_device **active_members,
 | 
						|
			unsigned int member_cnt, enum nfp_fl_lag_batch *batch)
 | 
						|
{
 | 
						|
	struct nfp_flower_cmsg_lag_config *cmsg_payload;
 | 
						|
	struct nfp_flower_priv *priv;
 | 
						|
	unsigned long int flags;
 | 
						|
	unsigned int size, i;
 | 
						|
	struct sk_buff *skb;
 | 
						|
 | 
						|
	priv = container_of(lag, struct nfp_flower_priv, nfp_lag);
 | 
						|
	size = sizeof(*cmsg_payload) + sizeof(__be32) * member_cnt;
 | 
						|
	skb = nfp_flower_cmsg_alloc(priv->app, size,
 | 
						|
				    NFP_FLOWER_CMSG_TYPE_LAG_CONFIG,
 | 
						|
				    GFP_KERNEL);
 | 
						|
	if (!skb)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	cmsg_payload = nfp_flower_cmsg_get_data(skb);
 | 
						|
	flags = 0;
 | 
						|
 | 
						|
	/* Increment batch version for each new batch of config messages. */
 | 
						|
	if (*batch == NFP_FL_LAG_BATCH_FIRST) {
 | 
						|
		flags |= NFP_FL_LAG_FIRST;
 | 
						|
		nfp_fl_increment_version(lag);
 | 
						|
		*batch = NFP_FL_LAG_BATCH_MEMBER;
 | 
						|
	}
 | 
						|
 | 
						|
	/* If it is a reset msg then it is also the end of the batch. */
 | 
						|
	if (lag->rst_cfg) {
 | 
						|
		flags |= NFP_FL_LAG_RESET;
 | 
						|
		*batch = NFP_FL_LAG_BATCH_FINISHED;
 | 
						|
	}
 | 
						|
 | 
						|
	/* To signal the end of a batch, both the switch and last flags are set
 | 
						|
	 * and the the reserved SYNC group ID is used.
 | 
						|
	 */
 | 
						|
	if (*batch == NFP_FL_LAG_BATCH_FINISHED) {
 | 
						|
		flags |= NFP_FL_LAG_SWITCH | NFP_FL_LAG_LAST;
 | 
						|
		lag->rst_cfg = false;
 | 
						|
		cmsg_payload->group_id = cpu_to_be32(NFP_FL_LAG_SYNC_ID);
 | 
						|
		cmsg_payload->group_inst = 0;
 | 
						|
	} else {
 | 
						|
		cmsg_payload->group_id = cpu_to_be32(group->group_id);
 | 
						|
		cmsg_payload->group_inst = cpu_to_be32(group->group_inst);
 | 
						|
	}
 | 
						|
 | 
						|
	cmsg_payload->reserved[0] = 0;
 | 
						|
	cmsg_payload->reserved[1] = 0;
 | 
						|
	cmsg_payload->ttl = NFP_FL_LAG_HOST_TTL;
 | 
						|
	cmsg_payload->ctrl_flags = flags;
 | 
						|
	cmsg_payload->batch_ver = cpu_to_be32(lag->batch_ver);
 | 
						|
	cmsg_payload->pkt_number = cpu_to_be32(nfp_fl_get_next_pkt_number(lag));
 | 
						|
 | 
						|
	for (i = 0; i < member_cnt; i++)
 | 
						|
		cmsg_payload->members[i] =
 | 
						|
			cpu_to_be32(nfp_repr_get_port_id(active_members[i]));
 | 
						|
 | 
						|
	nfp_ctrl_tx(priv->app->ctrl, skb);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void nfp_fl_lag_do_work(struct work_struct *work)
 | 
						|
{
 | 
						|
	enum nfp_fl_lag_batch batch = NFP_FL_LAG_BATCH_FIRST;
 | 
						|
	struct nfp_fl_lag_group *entry, *storage;
 | 
						|
	struct delayed_work *delayed_work;
 | 
						|
	struct nfp_flower_priv *priv;
 | 
						|
	struct nfp_fl_lag *lag;
 | 
						|
	int err;
 | 
						|
 | 
						|
	delayed_work = to_delayed_work(work);
 | 
						|
	lag = container_of(delayed_work, struct nfp_fl_lag, work);
 | 
						|
	priv = container_of(lag, struct nfp_flower_priv, nfp_lag);
 | 
						|
 | 
						|
	mutex_lock(&lag->lock);
 | 
						|
	list_for_each_entry_safe(entry, storage, &lag->group_list, list) {
 | 
						|
		struct net_device *iter_netdev, **acti_netdevs;
 | 
						|
		struct nfp_flower_repr_priv *repr_priv;
 | 
						|
		int active_count = 0, slaves = 0;
 | 
						|
		struct nfp_repr *repr;
 | 
						|
		unsigned long *flags;
 | 
						|
 | 
						|
		if (entry->to_remove) {
 | 
						|
			/* Active count of 0 deletes group on hw. */
 | 
						|
			err = nfp_fl_lag_config_group(lag, entry, NULL, 0,
 | 
						|
						      &batch);
 | 
						|
			if (!err) {
 | 
						|
				entry->to_remove = false;
 | 
						|
				entry->offloaded = false;
 | 
						|
			} else {
 | 
						|
				nfp_flower_cmsg_warn(priv->app,
 | 
						|
						     "group delete failed\n");
 | 
						|
				schedule_delayed_work(&lag->work,
 | 
						|
						      NFP_FL_LAG_DELAY);
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			if (entry->to_destroy) {
 | 
						|
				ida_simple_remove(&lag->ida_handle,
 | 
						|
						  entry->group_id);
 | 
						|
				list_del(&entry->list);
 | 
						|
				kfree(entry);
 | 
						|
			}
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		acti_netdevs = kmalloc_array(entry->slave_cnt,
 | 
						|
					     sizeof(*acti_netdevs), GFP_KERNEL);
 | 
						|
		if (!acti_netdevs) {
 | 
						|
			schedule_delayed_work(&lag->work,
 | 
						|
					      NFP_FL_LAG_DELAY);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Include sanity check in the loop. It may be that a bond has
 | 
						|
		 * changed between processing the last notification and the
 | 
						|
		 * work queue triggering. If the number of slaves has changed
 | 
						|
		 * or it now contains netdevs that cannot be offloaded, ignore
 | 
						|
		 * the group until pending notifications are processed.
 | 
						|
		 */
 | 
						|
		rcu_read_lock();
 | 
						|
		for_each_netdev_in_bond_rcu(entry->master_ndev, iter_netdev) {
 | 
						|
			if (!nfp_netdev_is_nfp_repr(iter_netdev)) {
 | 
						|
				slaves = 0;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			repr = netdev_priv(iter_netdev);
 | 
						|
 | 
						|
			if (repr->app != priv->app) {
 | 
						|
				slaves = 0;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			slaves++;
 | 
						|
			if (slaves > entry->slave_cnt)
 | 
						|
				break;
 | 
						|
 | 
						|
			/* Check the ports for state changes. */
 | 
						|
			repr_priv = repr->app_priv;
 | 
						|
			flags = &repr_priv->lag_port_flags;
 | 
						|
 | 
						|
			if (*flags & NFP_PORT_LAG_CHANGED) {
 | 
						|
				*flags &= ~NFP_PORT_LAG_CHANGED;
 | 
						|
				entry->dirty = true;
 | 
						|
			}
 | 
						|
 | 
						|
			if ((*flags & NFP_PORT_LAG_TX_ENABLED) &&
 | 
						|
			    (*flags & NFP_PORT_LAG_LINK_UP))
 | 
						|
				acti_netdevs[active_count++] = iter_netdev;
 | 
						|
		}
 | 
						|
		rcu_read_unlock();
 | 
						|
 | 
						|
		if (slaves != entry->slave_cnt || !entry->dirty) {
 | 
						|
			kfree(acti_netdevs);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		err = nfp_fl_lag_config_group(lag, entry, acti_netdevs,
 | 
						|
					      active_count, &batch);
 | 
						|
		if (!err) {
 | 
						|
			entry->offloaded = true;
 | 
						|
			entry->dirty = false;
 | 
						|
		} else {
 | 
						|
			nfp_flower_cmsg_warn(priv->app,
 | 
						|
					     "group offload failed\n");
 | 
						|
			schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY);
 | 
						|
		}
 | 
						|
 | 
						|
		kfree(acti_netdevs);
 | 
						|
	}
 | 
						|
 | 
						|
	/* End the config batch if at least one packet has been batched. */
 | 
						|
	if (batch == NFP_FL_LAG_BATCH_MEMBER) {
 | 
						|
		batch = NFP_FL_LAG_BATCH_FINISHED;
 | 
						|
		err = nfp_fl_lag_config_group(lag, NULL, NULL, 0, &batch);
 | 
						|
		if (err)
 | 
						|
			nfp_flower_cmsg_warn(priv->app,
 | 
						|
					     "group batch end cmsg failed\n");
 | 
						|
	}
 | 
						|
 | 
						|
	mutex_unlock(&lag->lock);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
nfp_fl_lag_put_unprocessed(struct nfp_fl_lag *lag, struct sk_buff *skb)
 | 
						|
{
 | 
						|
	struct nfp_flower_cmsg_lag_config *cmsg_payload;
 | 
						|
 | 
						|
	cmsg_payload = nfp_flower_cmsg_get_data(skb);
 | 
						|
	if (be32_to_cpu(cmsg_payload->group_id) >= NFP_FL_LAG_GROUP_MAX)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	/* Drop cmsg retrans if storage limit is exceeded to prevent
 | 
						|
	 * overloading. If the fw notices that expected messages have not been
 | 
						|
	 * received in a given time block, it will request a full resync.
 | 
						|
	 */
 | 
						|
	if (skb_queue_len(&lag->retrans_skbs) >= NFP_FL_LAG_RETRANS_LIMIT)
 | 
						|
		return -ENOSPC;
 | 
						|
 | 
						|
	__skb_queue_tail(&lag->retrans_skbs, skb);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void nfp_fl_send_unprocessed(struct nfp_fl_lag *lag)
 | 
						|
{
 | 
						|
	struct nfp_flower_priv *priv;
 | 
						|
	struct sk_buff *skb;
 | 
						|
 | 
						|
	priv = container_of(lag, struct nfp_flower_priv, nfp_lag);
 | 
						|
 | 
						|
	while ((skb = __skb_dequeue(&lag->retrans_skbs)))
 | 
						|
		nfp_ctrl_tx(priv->app->ctrl, skb);
 | 
						|
}
 | 
						|
 | 
						|
bool nfp_flower_lag_unprocessed_msg(struct nfp_app *app, struct sk_buff *skb)
 | 
						|
{
 | 
						|
	struct nfp_flower_cmsg_lag_config *cmsg_payload;
 | 
						|
	struct nfp_flower_priv *priv = app->priv;
 | 
						|
	struct nfp_fl_lag_group *group_entry;
 | 
						|
	unsigned long int flags;
 | 
						|
	bool store_skb = false;
 | 
						|
	int err;
 | 
						|
 | 
						|
	cmsg_payload = nfp_flower_cmsg_get_data(skb);
 | 
						|
	flags = cmsg_payload->ctrl_flags;
 | 
						|
 | 
						|
	/* Note the intentional fall through below. If DATA and XON are both
 | 
						|
	 * set, the message will stored and sent again with the rest of the
 | 
						|
	 * unprocessed messages list.
 | 
						|
	 */
 | 
						|
 | 
						|
	/* Store */
 | 
						|
	if (flags & NFP_FL_LAG_DATA)
 | 
						|
		if (!nfp_fl_lag_put_unprocessed(&priv->nfp_lag, skb))
 | 
						|
			store_skb = true;
 | 
						|
 | 
						|
	/* Send stored */
 | 
						|
	if (flags & NFP_FL_LAG_XON)
 | 
						|
		nfp_fl_send_unprocessed(&priv->nfp_lag);
 | 
						|
 | 
						|
	/* Resend all */
 | 
						|
	if (flags & NFP_FL_LAG_SYNC) {
 | 
						|
		/* To resend all config:
 | 
						|
		 * 1) Clear all unprocessed messages
 | 
						|
		 * 2) Mark all groups dirty
 | 
						|
		 * 3) Reset NFP group config
 | 
						|
		 * 4) Schedule a LAG config update
 | 
						|
		 */
 | 
						|
 | 
						|
		__skb_queue_purge(&priv->nfp_lag.retrans_skbs);
 | 
						|
 | 
						|
		mutex_lock(&priv->nfp_lag.lock);
 | 
						|
		list_for_each_entry(group_entry, &priv->nfp_lag.group_list,
 | 
						|
				    list)
 | 
						|
			group_entry->dirty = true;
 | 
						|
 | 
						|
		err = nfp_flower_lag_reset(&priv->nfp_lag);
 | 
						|
		if (err)
 | 
						|
			nfp_flower_cmsg_warn(priv->app,
 | 
						|
					     "mem err in group reset msg\n");
 | 
						|
		mutex_unlock(&priv->nfp_lag.lock);
 | 
						|
 | 
						|
		schedule_delayed_work(&priv->nfp_lag.work, 0);
 | 
						|
	}
 | 
						|
 | 
						|
	return store_skb;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
nfp_fl_lag_schedule_group_remove(struct nfp_fl_lag *lag,
 | 
						|
				 struct nfp_fl_lag_group *group)
 | 
						|
{
 | 
						|
	group->to_remove = true;
 | 
						|
 | 
						|
	schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
nfp_fl_lag_schedule_group_delete(struct nfp_fl_lag *lag,
 | 
						|
				 struct net_device *master)
 | 
						|
{
 | 
						|
	struct nfp_fl_lag_group *group;
 | 
						|
	struct nfp_flower_priv *priv;
 | 
						|
 | 
						|
	priv = container_of(lag, struct nfp_flower_priv, nfp_lag);
 | 
						|
 | 
						|
	if (!netif_is_bond_master(master))
 | 
						|
		return;
 | 
						|
 | 
						|
	mutex_lock(&lag->lock);
 | 
						|
	group = nfp_fl_lag_find_group_for_master_with_lag(lag, master);
 | 
						|
	if (!group) {
 | 
						|
		mutex_unlock(&lag->lock);
 | 
						|
		nfp_warn(priv->app->cpp, "untracked bond got unregistered %s\n",
 | 
						|
			 netdev_name(master));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	group->to_remove = true;
 | 
						|
	group->to_destroy = true;
 | 
						|
	mutex_unlock(&lag->lock);
 | 
						|
 | 
						|
	schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
nfp_fl_lag_changeupper_event(struct nfp_fl_lag *lag,
 | 
						|
			     struct netdev_notifier_changeupper_info *info)
 | 
						|
{
 | 
						|
	struct net_device *upper = info->upper_dev, *iter_netdev;
 | 
						|
	struct netdev_lag_upper_info *lag_upper_info;
 | 
						|
	struct nfp_fl_lag_group *group;
 | 
						|
	struct nfp_flower_priv *priv;
 | 
						|
	unsigned int slave_count = 0;
 | 
						|
	bool can_offload = true;
 | 
						|
	struct nfp_repr *repr;
 | 
						|
 | 
						|
	if (!netif_is_lag_master(upper))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	priv = container_of(lag, struct nfp_flower_priv, nfp_lag);
 | 
						|
 | 
						|
	rcu_read_lock();
 | 
						|
	for_each_netdev_in_bond_rcu(upper, iter_netdev) {
 | 
						|
		if (!nfp_netdev_is_nfp_repr(iter_netdev)) {
 | 
						|
			can_offload = false;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		repr = netdev_priv(iter_netdev);
 | 
						|
 | 
						|
		/* Ensure all ports are created by the same app/on same card. */
 | 
						|
		if (repr->app != priv->app) {
 | 
						|
			can_offload = false;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		slave_count++;
 | 
						|
	}
 | 
						|
	rcu_read_unlock();
 | 
						|
 | 
						|
	lag_upper_info = info->upper_info;
 | 
						|
 | 
						|
	/* Firmware supports active/backup and L3/L4 hash bonds. */
 | 
						|
	if (lag_upper_info &&
 | 
						|
	    lag_upper_info->tx_type != NETDEV_LAG_TX_TYPE_ACTIVEBACKUP &&
 | 
						|
	    (lag_upper_info->tx_type != NETDEV_LAG_TX_TYPE_HASH ||
 | 
						|
	     (lag_upper_info->hash_type != NETDEV_LAG_HASH_L34 &&
 | 
						|
	      lag_upper_info->hash_type != NETDEV_LAG_HASH_E34 &&
 | 
						|
	      lag_upper_info->hash_type != NETDEV_LAG_HASH_UNKNOWN))) {
 | 
						|
		can_offload = false;
 | 
						|
		nfp_flower_cmsg_warn(priv->app,
 | 
						|
				     "Unable to offload tx_type %u hash %u\n",
 | 
						|
				     lag_upper_info->tx_type,
 | 
						|
				     lag_upper_info->hash_type);
 | 
						|
	}
 | 
						|
 | 
						|
	mutex_lock(&lag->lock);
 | 
						|
	group = nfp_fl_lag_find_group_for_master_with_lag(lag, upper);
 | 
						|
 | 
						|
	if (slave_count == 0 || !can_offload) {
 | 
						|
		/* Cannot offload the group - remove if previously offloaded. */
 | 
						|
		if (group && group->offloaded)
 | 
						|
			nfp_fl_lag_schedule_group_remove(lag, group);
 | 
						|
 | 
						|
		mutex_unlock(&lag->lock);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!group) {
 | 
						|
		group = nfp_fl_lag_group_create(lag, upper);
 | 
						|
		if (IS_ERR(group)) {
 | 
						|
			mutex_unlock(&lag->lock);
 | 
						|
			return PTR_ERR(group);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	group->dirty = true;
 | 
						|
	group->slave_cnt = slave_count;
 | 
						|
 | 
						|
	/* Group may have been on queue for removal but is now offloable. */
 | 
						|
	group->to_remove = false;
 | 
						|
	mutex_unlock(&lag->lock);
 | 
						|
 | 
						|
	schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
nfp_fl_lag_changels_event(struct nfp_fl_lag *lag, struct net_device *netdev,
 | 
						|
			  struct netdev_notifier_changelowerstate_info *info)
 | 
						|
{
 | 
						|
	struct netdev_lag_lower_state_info *lag_lower_info;
 | 
						|
	struct nfp_flower_repr_priv *repr_priv;
 | 
						|
	struct nfp_flower_priv *priv;
 | 
						|
	struct nfp_repr *repr;
 | 
						|
	unsigned long *flags;
 | 
						|
 | 
						|
	if (!netif_is_lag_port(netdev) || !nfp_netdev_is_nfp_repr(netdev))
 | 
						|
		return;
 | 
						|
 | 
						|
	lag_lower_info = info->lower_state_info;
 | 
						|
	if (!lag_lower_info)
 | 
						|
		return;
 | 
						|
 | 
						|
	priv = container_of(lag, struct nfp_flower_priv, nfp_lag);
 | 
						|
	repr = netdev_priv(netdev);
 | 
						|
 | 
						|
	/* Verify that the repr is associated with this app. */
 | 
						|
	if (repr->app != priv->app)
 | 
						|
		return;
 | 
						|
 | 
						|
	repr_priv = repr->app_priv;
 | 
						|
	flags = &repr_priv->lag_port_flags;
 | 
						|
 | 
						|
	mutex_lock(&lag->lock);
 | 
						|
	if (lag_lower_info->link_up)
 | 
						|
		*flags |= NFP_PORT_LAG_LINK_UP;
 | 
						|
	else
 | 
						|
		*flags &= ~NFP_PORT_LAG_LINK_UP;
 | 
						|
 | 
						|
	if (lag_lower_info->tx_enabled)
 | 
						|
		*flags |= NFP_PORT_LAG_TX_ENABLED;
 | 
						|
	else
 | 
						|
		*flags &= ~NFP_PORT_LAG_TX_ENABLED;
 | 
						|
 | 
						|
	*flags |= NFP_PORT_LAG_CHANGED;
 | 
						|
	mutex_unlock(&lag->lock);
 | 
						|
 | 
						|
	schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY);
 | 
						|
}
 | 
						|
 | 
						|
int nfp_flower_lag_netdev_event(struct nfp_flower_priv *priv,
 | 
						|
				struct net_device *netdev,
 | 
						|
				unsigned long event, void *ptr)
 | 
						|
{
 | 
						|
	struct nfp_fl_lag *lag = &priv->nfp_lag;
 | 
						|
	int err;
 | 
						|
 | 
						|
	switch (event) {
 | 
						|
	case NETDEV_CHANGEUPPER:
 | 
						|
		err = nfp_fl_lag_changeupper_event(lag, ptr);
 | 
						|
		if (err)
 | 
						|
			return NOTIFY_BAD;
 | 
						|
		return NOTIFY_OK;
 | 
						|
	case NETDEV_CHANGELOWERSTATE:
 | 
						|
		nfp_fl_lag_changels_event(lag, netdev, ptr);
 | 
						|
		return NOTIFY_OK;
 | 
						|
	case NETDEV_UNREGISTER:
 | 
						|
		nfp_fl_lag_schedule_group_delete(lag, netdev);
 | 
						|
		return NOTIFY_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	return NOTIFY_DONE;
 | 
						|
}
 | 
						|
 | 
						|
int nfp_flower_lag_reset(struct nfp_fl_lag *lag)
 | 
						|
{
 | 
						|
	enum nfp_fl_lag_batch batch = NFP_FL_LAG_BATCH_FIRST;
 | 
						|
 | 
						|
	lag->rst_cfg = true;
 | 
						|
	return nfp_fl_lag_config_group(lag, NULL, NULL, 0, &batch);
 | 
						|
}
 | 
						|
 | 
						|
void nfp_flower_lag_init(struct nfp_fl_lag *lag)
 | 
						|
{
 | 
						|
	INIT_DELAYED_WORK(&lag->work, nfp_fl_lag_do_work);
 | 
						|
	INIT_LIST_HEAD(&lag->group_list);
 | 
						|
	mutex_init(&lag->lock);
 | 
						|
	ida_init(&lag->ida_handle);
 | 
						|
 | 
						|
	__skb_queue_head_init(&lag->retrans_skbs);
 | 
						|
 | 
						|
	/* 0 is a reserved batch version so increment to first valid value. */
 | 
						|
	nfp_fl_increment_version(lag);
 | 
						|
}
 | 
						|
 | 
						|
void nfp_flower_lag_cleanup(struct nfp_fl_lag *lag)
 | 
						|
{
 | 
						|
	struct nfp_fl_lag_group *entry, *storage;
 | 
						|
 | 
						|
	cancel_delayed_work_sync(&lag->work);
 | 
						|
 | 
						|
	__skb_queue_purge(&lag->retrans_skbs);
 | 
						|
 | 
						|
	/* Remove all groups. */
 | 
						|
	mutex_lock(&lag->lock);
 | 
						|
	list_for_each_entry_safe(entry, storage, &lag->group_list, list) {
 | 
						|
		list_del(&entry->list);
 | 
						|
		kfree(entry);
 | 
						|
	}
 | 
						|
	mutex_unlock(&lag->lock);
 | 
						|
	mutex_destroy(&lag->lock);
 | 
						|
	ida_destroy(&lag->ida_handle);
 | 
						|
}
 |