7a9e3a0
Update team driver to latest net-next.
7a9e3a0
7a9e3a0
Split patches available here:
7a9e3a0
http://people.redhat.com/jpirko/f18_team_update/
7a9e3a0
7a9e3a0
David S. Miller (1):
7a9e3a0
  team: Revert previous two changes.
7a9e3a0
7a9e3a0
Jiri Pirko (25):
7a9e3a0
  team: make team_mode struct const
7a9e3a0
  team: for nomode use dummy struct team_mode
7a9e3a0
  team: add mode priv to port
7a9e3a0
  team: lb: push hash counting into separate function
7a9e3a0
  team: allow read/write-only options
7a9e3a0
  team: introduce array options
7a9e3a0
  team: comments: s/net\/drivers\/team/drivers\/net\/team/
7a9e3a0
  team: push array_index and port into separate structure
7a9e3a0
  team: allow async option changes
7a9e3a0
  team: fix error path in team_nl_fill_options_get()
7a9e3a0
  team: fix error path in team_nl_fill_port_list_get()
7a9e3a0
  team: allow to specify one option instance to be send to userspace
7a9e3a0
  team: pass NULL to __team_option_inst_add() instead of 0
7a9e3a0
  team: add port_[enabled/disabled] mode callbacks
7a9e3a0
  team: lb: introduce infrastructure for userspace driven tx
7a9e3a0
    loadbalancing
7a9e3a0
  team: implement multipart netlink messages for options transfers
7a9e3a0
  team: ensure correct order of netlink messages delivery
7a9e3a0
  team: allow to send multiple set events in one message
7a9e3a0
  team: use rcu_dereference_bh() in tx path
7a9e3a0
  team: use rcu_access_pointer to access RCU pointer by writer
7a9e3a0
  team: use RCU_INIT_POINTER for NULL assignment of RCU pointer
7a9e3a0
  team: do RCU update path fixups
7a9e3a0
  team: fix team_adjust_ops with regard to enabled ports
7a9e3a0
  team: do not allow to map disabled ports
7a9e3a0
  team: remove unused rcu_head field from team_port struct
7a9e3a0
7a9e3a0
 drivers/net/team/team.c                   |  577 ++++++++++++++++++-----------
7a9e3a0
 drivers/net/team/team_mode_activebackup.c |   14 +-
7a9e3a0
 drivers/net/team/team_mode_loadbalance.c  |  543 +++++++++++++++++++++++++--
7a9e3a0
 drivers/net/team/team_mode_roundrobin.c   |    4 +-
7a9e3a0
 include/linux/if_team.h                   |   25 +-
7a9e3a0
 5 files changed, 919 insertions(+), 244 deletions(-)
7a9e3a0
7a9e3a0
Signed-off-by: Jiri Pirko <jpirko@redhat.com>
7a9e3a0
7a9e3a0
diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c
7a9e3a0
index c61ae35..5350eea 100644
7a9e3a0
--- a/drivers/net/team/team.c
7a9e3a0
+++ b/drivers/net/team/team.c
7a9e3a0
@@ -1,5 +1,5 @@
7a9e3a0
 /*
7a9e3a0
- * net/drivers/team/team.c - Network team device driver
7a9e3a0
+ * drivers/net/team/team.c - Network team device driver
7a9e3a0
  * Copyright (c) 2011 Jiri Pirko <jpirko@redhat.com>
7a9e3a0
  *
7a9e3a0
  * This program is free software; you can redistribute it and/or modify
7a9e3a0
@@ -82,14 +82,16 @@ static void team_refresh_port_linkup(struct team_port *port)
7a9e3a0
 						   port->state.linkup;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
+
7a9e3a0
 /*******************
7a9e3a0
  * Options handling
7a9e3a0
  *******************/
7a9e3a0
 
7a9e3a0
 struct team_option_inst { /* One for each option instance */
7a9e3a0
 	struct list_head list;
7a9e3a0
+	struct list_head tmp_list;
7a9e3a0
 	struct team_option *option;
7a9e3a0
-	struct team_port *port; /* != NULL if per-port */
7a9e3a0
+	struct team_option_inst_info info;
7a9e3a0
 	bool changed;
7a9e3a0
 	bool removed;
7a9e3a0
 };
7a9e3a0
@@ -106,22 +108,6 @@ static struct team_option *__team_find_option(struct team *team,
7a9e3a0
 	return NULL;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
-static int __team_option_inst_add(struct team *team, struct team_option *option,
7a9e3a0
-				  struct team_port *port)
7a9e3a0
-{
7a9e3a0
-	struct team_option_inst *opt_inst;
7a9e3a0
-
7a9e3a0
-	opt_inst = kmalloc(sizeof(*opt_inst), GFP_KERNEL);
7a9e3a0
-	if (!opt_inst)
7a9e3a0
-		return -ENOMEM;
7a9e3a0
-	opt_inst->option = option;
7a9e3a0
-	opt_inst->port = port;
7a9e3a0
-	opt_inst->changed = true;
7a9e3a0
-	opt_inst->removed = false;
7a9e3a0
-	list_add_tail(&opt_inst->list, &team->option_inst_list);
7a9e3a0
-	return 0;
7a9e3a0
-}
7a9e3a0
-
7a9e3a0
 static void __team_option_inst_del(struct team_option_inst *opt_inst)
7a9e3a0
 {
7a9e3a0
 	list_del(&opt_inst->list);
7a9e3a0
@@ -139,14 +125,49 @@ static void __team_option_inst_del_option(struct team *team,
7a9e3a0
 	}
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
+static int __team_option_inst_add(struct team *team, struct team_option *option,
7a9e3a0
+				  struct team_port *port)
7a9e3a0
+{
7a9e3a0
+	struct team_option_inst *opt_inst;
7a9e3a0
+	unsigned int array_size;
7a9e3a0
+	unsigned int i;
7a9e3a0
+	int err;
7a9e3a0
+
7a9e3a0
+	array_size = option->array_size;
7a9e3a0
+	if (!array_size)
7a9e3a0
+		array_size = 1; /* No array but still need one instance */
7a9e3a0
+
7a9e3a0
+	for (i = 0; i < array_size; i++) {
7a9e3a0
+		opt_inst = kmalloc(sizeof(*opt_inst), GFP_KERNEL);
7a9e3a0
+		if (!opt_inst)
7a9e3a0
+			return -ENOMEM;
7a9e3a0
+		opt_inst->option = option;
7a9e3a0
+		opt_inst->info.port = port;
7a9e3a0
+		opt_inst->info.array_index = i;
7a9e3a0
+		opt_inst->changed = true;
7a9e3a0
+		opt_inst->removed = false;
7a9e3a0
+		list_add_tail(&opt_inst->list, &team->option_inst_list);
7a9e3a0
+		if (option->init) {
7a9e3a0
+			err = option->init(team, &opt_inst->info);
7a9e3a0
+			if (err)
7a9e3a0
+				return err;
7a9e3a0
+		}
7a9e3a0
+
7a9e3a0
+	}
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
 static int __team_option_inst_add_option(struct team *team,
7a9e3a0
 					 struct team_option *option)
7a9e3a0
 {
7a9e3a0
 	struct team_port *port;
7a9e3a0
 	int err;
7a9e3a0
 
7a9e3a0
-	if (!option->per_port)
7a9e3a0
-		return __team_option_inst_add(team, option, 0);
7a9e3a0
+	if (!option->per_port) {
7a9e3a0
+		err = __team_option_inst_add(team, option, NULL);
7a9e3a0
+		if (err)
7a9e3a0
+			goto inst_del_option;
7a9e3a0
+	}
7a9e3a0
 
7a9e3a0
 	list_for_each_entry(port, &team->port_list, list) {
7a9e3a0
 		err = __team_option_inst_add(team, option, port);
7a9e3a0
@@ -180,7 +201,7 @@ static void __team_option_inst_del_port(struct team *team,
7a9e3a0
 
7a9e3a0
 	list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) {
7a9e3a0
 		if (opt_inst->option->per_port &&
7a9e3a0
-		    opt_inst->port == port)
7a9e3a0
+		    opt_inst->info.port == port)
7a9e3a0
 			__team_option_inst_del(opt_inst);
7a9e3a0
 	}
7a9e3a0
 }
7a9e3a0
@@ -211,7 +232,7 @@ static void __team_option_inst_mark_removed_port(struct team *team,
7a9e3a0
 	struct team_option_inst *opt_inst;
7a9e3a0
 
7a9e3a0
 	list_for_each_entry(opt_inst, &team->option_inst_list, list) {
7a9e3a0
-		if (opt_inst->port == port) {
7a9e3a0
+		if (opt_inst->info.port == port) {
7a9e3a0
 			opt_inst->changed = true;
7a9e3a0
 			opt_inst->removed = true;
7a9e3a0
 		}
7a9e3a0
@@ -324,28 +345,12 @@ void team_options_unregister(struct team *team,
7a9e3a0
 }
7a9e3a0
 EXPORT_SYMBOL(team_options_unregister);
7a9e3a0
 
7a9e3a0
-static int team_option_port_add(struct team *team, struct team_port *port)
7a9e3a0
-{
7a9e3a0
-	int err;
7a9e3a0
-
7a9e3a0
-	err = __team_option_inst_add_port(team, port);
7a9e3a0
-	if (err)
7a9e3a0
-		return err;
7a9e3a0
-	__team_options_change_check(team);
7a9e3a0
-	return 0;
7a9e3a0
-}
7a9e3a0
-
7a9e3a0
-static void team_option_port_del(struct team *team, struct team_port *port)
7a9e3a0
-{
7a9e3a0
-	__team_option_inst_mark_removed_port(team, port);
7a9e3a0
-	__team_options_change_check(team);
7a9e3a0
-	__team_option_inst_del_port(team, port);
7a9e3a0
-}
7a9e3a0
-
7a9e3a0
 static int team_option_get(struct team *team,
7a9e3a0
 			   struct team_option_inst *opt_inst,
7a9e3a0
 			   struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
+	if (!opt_inst->option->getter)
7a9e3a0
+		return -EOPNOTSUPP;
7a9e3a0
 	return opt_inst->option->getter(team, ctx);
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
@@ -353,16 +358,26 @@ static int team_option_set(struct team *team,
7a9e3a0
 			   struct team_option_inst *opt_inst,
7a9e3a0
 			   struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	int err;
7a9e3a0
+	if (!opt_inst->option->setter)
7a9e3a0
+		return -EOPNOTSUPP;
7a9e3a0
+	return opt_inst->option->setter(team, ctx);
7a9e3a0
+}
7a9e3a0
 
7a9e3a0
-	err = opt_inst->option->setter(team, ctx);
7a9e3a0
-	if (err)
7a9e3a0
-		return err;
7a9e3a0
+void team_option_inst_set_change(struct team_option_inst_info *opt_inst_info)
7a9e3a0
+{
7a9e3a0
+	struct team_option_inst *opt_inst;
7a9e3a0
 
7a9e3a0
+	opt_inst = container_of(opt_inst_info, struct team_option_inst, info);
7a9e3a0
 	opt_inst->changed = true;
7a9e3a0
+}
7a9e3a0
+EXPORT_SYMBOL(team_option_inst_set_change);
7a9e3a0
+
7a9e3a0
+void team_options_change_check(struct team *team)
7a9e3a0
+{
7a9e3a0
 	__team_options_change_check(team);
7a9e3a0
-	return err;
7a9e3a0
 }
7a9e3a0
+EXPORT_SYMBOL(team_options_change_check);
7a9e3a0
+
7a9e3a0
 
7a9e3a0
 /****************
7a9e3a0
  * Mode handling
7a9e3a0
@@ -371,13 +386,18 @@ static int team_option_set(struct team *team,
7a9e3a0
 static LIST_HEAD(mode_list);
7a9e3a0
 static DEFINE_SPINLOCK(mode_list_lock);
7a9e3a0
 
7a9e3a0
-static struct team_mode *__find_mode(const char *kind)
7a9e3a0
+struct team_mode_item {
7a9e3a0
+	struct list_head list;
7a9e3a0
+	const struct team_mode *mode;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+static struct team_mode_item *__find_mode(const char *kind)
7a9e3a0
 {
7a9e3a0
-	struct team_mode *mode;
7a9e3a0
+	struct team_mode_item *mitem;
7a9e3a0
 
7a9e3a0
-	list_for_each_entry(mode, &mode_list, list) {
7a9e3a0
-		if (strcmp(mode->kind, kind) == 0)
7a9e3a0
-			return mode;
7a9e3a0
+	list_for_each_entry(mitem, &mode_list, list) {
7a9e3a0
+		if (strcmp(mitem->mode->kind, kind) == 0)
7a9e3a0
+			return mitem;
7a9e3a0
 	}
7a9e3a0
 	return NULL;
7a9e3a0
 }
7a9e3a0
@@ -392,49 +412,65 @@ static bool is_good_mode_name(const char *name)
7a9e3a0
 	return true;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
-int team_mode_register(struct team_mode *mode)
7a9e3a0
+int team_mode_register(const struct team_mode *mode)
7a9e3a0
 {
7a9e3a0
 	int err = 0;
7a9e3a0
+	struct team_mode_item *mitem;
7a9e3a0
 
7a9e3a0
 	if (!is_good_mode_name(mode->kind) ||
7a9e3a0
 	    mode->priv_size > TEAM_MODE_PRIV_SIZE)
7a9e3a0
 		return -EINVAL;
7a9e3a0
+
7a9e3a0
+	mitem = kmalloc(sizeof(*mitem), GFP_KERNEL);
7a9e3a0
+	if (!mitem)
7a9e3a0
+		return -ENOMEM;
7a9e3a0
+
7a9e3a0
 	spin_lock(&mode_list_lock);
7a9e3a0
 	if (__find_mode(mode->kind)) {
7a9e3a0
 		err = -EEXIST;
7a9e3a0
+		kfree(mitem);
7a9e3a0
 		goto unlock;
7a9e3a0
 	}
7a9e3a0
-	list_add_tail(&mode->list, &mode_list);
7a9e3a0
+	mitem->mode = mode;
7a9e3a0
+	list_add_tail(&mitem->list, &mode_list);
7a9e3a0
 unlock:
7a9e3a0
 	spin_unlock(&mode_list_lock);
7a9e3a0
 	return err;
7a9e3a0
 }
7a9e3a0
 EXPORT_SYMBOL(team_mode_register);
7a9e3a0
 
7a9e3a0
-int team_mode_unregister(struct team_mode *mode)
7a9e3a0
+void team_mode_unregister(const struct team_mode *mode)
7a9e3a0
 {
7a9e3a0
+	struct team_mode_item *mitem;
7a9e3a0
+
7a9e3a0
 	spin_lock(&mode_list_lock);
7a9e3a0
-	list_del_init(&mode->list);
7a9e3a0
+	mitem = __find_mode(mode->kind);
7a9e3a0
+	if (mitem) {
7a9e3a0
+		list_del_init(&mitem->list);
7a9e3a0
+		kfree(mitem);
7a9e3a0
+	}
7a9e3a0
 	spin_unlock(&mode_list_lock);
7a9e3a0
-	return 0;
7a9e3a0
 }
7a9e3a0
 EXPORT_SYMBOL(team_mode_unregister);
7a9e3a0
 
7a9e3a0
-static struct team_mode *team_mode_get(const char *kind)
7a9e3a0
+static const struct team_mode *team_mode_get(const char *kind)
7a9e3a0
 {
7a9e3a0
-	struct team_mode *mode;
7a9e3a0
+	struct team_mode_item *mitem;
7a9e3a0
+	const struct team_mode *mode = NULL;
7a9e3a0
 
7a9e3a0
 	spin_lock(&mode_list_lock);
7a9e3a0
-	mode = __find_mode(kind);
7a9e3a0
-	if (!mode) {
7a9e3a0
+	mitem = __find_mode(kind);
7a9e3a0
+	if (!mitem) {
7a9e3a0
 		spin_unlock(&mode_list_lock);
7a9e3a0
 		request_module("team-mode-%s", kind);
7a9e3a0
 		spin_lock(&mode_list_lock);
7a9e3a0
-		mode = __find_mode(kind);
7a9e3a0
+		mitem = __find_mode(kind);
7a9e3a0
 	}
7a9e3a0
-	if (mode)
7a9e3a0
+	if (mitem) {
7a9e3a0
+		mode = mitem->mode;
7a9e3a0
 		if (!try_module_get(mode->owner))
7a9e3a0
 			mode = NULL;
7a9e3a0
+	}
7a9e3a0
 
7a9e3a0
 	spin_unlock(&mode_list_lock);
7a9e3a0
 	return mode;
7a9e3a0
@@ -458,26 +494,45 @@ rx_handler_result_t team_dummy_receive(struct team *team,
7a9e3a0
 	return RX_HANDLER_ANOTHER;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
-static void team_adjust_ops(struct team *team)
7a9e3a0
+static const struct team_mode __team_no_mode = {
7a9e3a0
+	.kind		= "*NOMODE*",
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+static bool team_is_mode_set(struct team *team)
7a9e3a0
+{
7a9e3a0
+	return team->mode != &__team_no_mode;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void team_set_no_mode(struct team *team)
7a9e3a0
+{
7a9e3a0
+	team->mode = &__team_no_mode;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void __team_adjust_ops(struct team *team, int en_port_count)
7a9e3a0
 {
7a9e3a0
 	/*
7a9e3a0
 	 * To avoid checks in rx/tx skb paths, ensure here that non-null and
7a9e3a0
 	 * correct ops are always set.
7a9e3a0
 	 */
7a9e3a0
 
7a9e3a0
-	if (list_empty(&team->port_list) ||
7a9e3a0
-	    !team->mode || !team->mode->ops->transmit)
7a9e3a0
+	if (!en_port_count || !team_is_mode_set(team) ||
7a9e3a0
+	    !team->mode->ops->transmit)
7a9e3a0
 		team->ops.transmit = team_dummy_transmit;
7a9e3a0
 	else
7a9e3a0
 		team->ops.transmit = team->mode->ops->transmit;
7a9e3a0
 
7a9e3a0
-	if (list_empty(&team->port_list) ||
7a9e3a0
-	    !team->mode || !team->mode->ops->receive)
7a9e3a0
+	if (!en_port_count || !team_is_mode_set(team) ||
7a9e3a0
+	    !team->mode->ops->receive)
7a9e3a0
 		team->ops.receive = team_dummy_receive;
7a9e3a0
 	else
7a9e3a0
 		team->ops.receive = team->mode->ops->receive;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
+static void team_adjust_ops(struct team *team)
7a9e3a0
+{
7a9e3a0
+	__team_adjust_ops(team, team->en_port_count);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
 /*
7a9e3a0
  * We can benefit from the fact that it's ensured no port is present
7a9e3a0
  * at the time of mode change. Therefore no packets are in fly so there's no
7a9e3a0
@@ -487,7 +542,7 @@ static int __team_change_mode(struct team *team,
7a9e3a0
 			      const struct team_mode *new_mode)
7a9e3a0
 {
7a9e3a0
 	/* Check if mode was previously set and do cleanup if so */
7a9e3a0
-	if (team->mode) {
7a9e3a0
+	if (team_is_mode_set(team)) {
7a9e3a0
 		void (*exit_op)(struct team *team) = team->ops.exit;
7a9e3a0
 
7a9e3a0
 		/* Clear ops area so no callback is called any longer */
7a9e3a0
@@ -497,7 +552,7 @@ static int __team_change_mode(struct team *team,
7a9e3a0
 		if (exit_op)
7a9e3a0
 			exit_op(team);
7a9e3a0
 		team_mode_put(team->mode);
7a9e3a0
-		team->mode = NULL;
7a9e3a0
+		team_set_no_mode(team);
7a9e3a0
 		/* zero private data area */
7a9e3a0
 		memset(&team->mode_priv, 0,
7a9e3a0
 		       sizeof(struct team) - offsetof(struct team, mode_priv));
7a9e3a0
@@ -523,7 +578,7 @@ static int __team_change_mode(struct team *team,
7a9e3a0
 
7a9e3a0
 static int team_change_mode(struct team *team, const char *kind)
7a9e3a0
 {
7a9e3a0
-	struct team_mode *new_mode;
7a9e3a0
+	const struct team_mode *new_mode;
7a9e3a0
 	struct net_device *dev = team->dev;
7a9e3a0
 	int err;
7a9e3a0
 
7a9e3a0
@@ -532,7 +587,7 @@ static int team_change_mode(struct team *team, const char *kind)
7a9e3a0
 		return -EBUSY;
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
-	if (team->mode && strcmp(team->mode->kind, kind) == 0) {
7a9e3a0
+	if (team_is_mode_set(team) && strcmp(team->mode->kind, kind) == 0) {
7a9e3a0
 		netdev_err(dev, "Unable to change to the same mode the team is in\n");
7a9e3a0
 		return -EINVAL;
7a9e3a0
 	}
7a9e3a0
@@ -559,8 +614,6 @@ static int team_change_mode(struct team *team, const char *kind)
7a9e3a0
  * Rx path frame handler
7a9e3a0
  ************************/
7a9e3a0
 
7a9e3a0
-static bool team_port_enabled(struct team_port *port);
7a9e3a0
-
7a9e3a0
 /* note: already called with rcu_read_lock */
7a9e3a0
 static rx_handler_result_t team_handle_frame(struct sk_buff **pskb)
7a9e3a0
 {
7a9e3a0
@@ -618,10 +671,11 @@ static bool team_port_find(const struct team *team,
7a9e3a0
 	return false;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
-static bool team_port_enabled(struct team_port *port)
7a9e3a0
+bool team_port_enabled(struct team_port *port)
7a9e3a0
 {
7a9e3a0
 	return port->index != -1;
7a9e3a0
 }
7a9e3a0
+EXPORT_SYMBOL(team_port_enabled);
7a9e3a0
 
7a9e3a0
 /*
7a9e3a0
  * Enable/disable port by adding to enabled port hashlist and setting
7a9e3a0
@@ -637,6 +691,9 @@ static void team_port_enable(struct team *team,
7a9e3a0
 	port->index = team->en_port_count++;
7a9e3a0
 	hlist_add_head_rcu(&port->hlist,
7a9e3a0
 			   team_port_index_hash(team, port->index));
7a9e3a0
+	team_adjust_ops(team);
7a9e3a0
+	if (team->ops.port_enabled)
7a9e3a0
+		team->ops.port_enabled(team, port);
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static void __reconstruct_port_hlist(struct team *team, int rm_index)
7a9e3a0
@@ -656,14 +713,20 @@ static void __reconstruct_port_hlist(struct team *team, int rm_index)
7a9e3a0
 static void team_port_disable(struct team *team,
7a9e3a0
 			      struct team_port *port)
7a9e3a0
 {
7a9e3a0
-	int rm_index = port->index;
7a9e3a0
-
7a9e3a0
 	if (!team_port_enabled(port))
7a9e3a0
 		return;
7a9e3a0
+	if (team->ops.port_disabled)
7a9e3a0
+		team->ops.port_disabled(team, port);
7a9e3a0
 	hlist_del_rcu(&port->hlist);
7a9e3a0
-	__reconstruct_port_hlist(team, rm_index);
7a9e3a0
-	team->en_port_count--;
7a9e3a0
+	__reconstruct_port_hlist(team, port->index);
7a9e3a0
 	port->index = -1;
7a9e3a0
+	__team_adjust_ops(team, team->en_port_count - 1);
7a9e3a0
+	/*
7a9e3a0
+	 * Wait until readers see adjusted ops. This ensures that
7a9e3a0
+	 * readers never see team->en_port_count == 0
7a9e3a0
+	 */
7a9e3a0
+	synchronize_rcu();
7a9e3a0
+	team->en_port_count--;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 #define TEAM_VLAN_FEATURES (NETIF_F_ALL_CSUM | NETIF_F_SG | \
7a9e3a0
@@ -758,7 +821,8 @@ static int team_port_add(struct team *team, struct net_device *port_dev)
7a9e3a0
 		return -EBUSY;
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
-	port = kzalloc(sizeof(struct team_port), GFP_KERNEL);
7a9e3a0
+	port = kzalloc(sizeof(struct team_port) + team->mode->port_priv_size,
7a9e3a0
+		       GFP_KERNEL);
7a9e3a0
 	if (!port)
7a9e3a0
 		return -ENOMEM;
7a9e3a0
 
7a9e3a0
@@ -809,7 +873,7 @@ static int team_port_add(struct team *team, struct net_device *port_dev)
7a9e3a0
 		goto err_handler_register;
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
-	err = team_option_port_add(team, port);
7a9e3a0
+	err = __team_option_inst_add_port(team, port);
7a9e3a0
 	if (err) {
7a9e3a0
 		netdev_err(dev, "Device %s failed to add per-port options\n",
7a9e3a0
 			   portname);
7a9e3a0
@@ -819,9 +883,9 @@ static int team_port_add(struct team *team, struct net_device *port_dev)
7a9e3a0
 	port->index = -1;
7a9e3a0
 	team_port_enable(team, port);
7a9e3a0
 	list_add_tail_rcu(&port->list, &team->port_list);
7a9e3a0
-	team_adjust_ops(team);
7a9e3a0
 	__team_compute_features(team);
7a9e3a0
 	__team_port_change_check(port, !!netif_carrier_ok(port_dev));
7a9e3a0
+	__team_options_change_check(team);
7a9e3a0
 
7a9e3a0
 	netdev_info(dev, "Port device %s added\n", portname);
7a9e3a0
 
7a9e3a0
@@ -865,12 +929,13 @@ static int team_port_del(struct team *team, struct net_device *port_dev)
7a9e3a0
 		return -ENOENT;
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
+	__team_option_inst_mark_removed_port(team, port);
7a9e3a0
+	__team_options_change_check(team);
7a9e3a0
+	__team_option_inst_del_port(team, port);
7a9e3a0
 	port->removed = true;
7a9e3a0
 	__team_port_change_check(port, false);
7a9e3a0
 	team_port_disable(team, port);
7a9e3a0
 	list_del_rcu(&port->list);
7a9e3a0
-	team_adjust_ops(team);
7a9e3a0
-	team_option_port_del(team, port);
7a9e3a0
 	netdev_rx_handler_unregister(port_dev);
7a9e3a0
 	netdev_set_master(port_dev, NULL);
7a9e3a0
 	vlan_vids_del_by_dev(port_dev, dev);
7a9e3a0
@@ -891,11 +956,9 @@ static int team_port_del(struct team *team, struct net_device *port_dev)
7a9e3a0
  * Net device ops
7a9e3a0
  *****************/
7a9e3a0
 
7a9e3a0
-static const char team_no_mode_kind[] = "*NOMODE*";
7a9e3a0
-
7a9e3a0
 static int team_mode_option_get(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	ctx->data.str_val = team->mode ? team->mode->kind : team_no_mode_kind;
7a9e3a0
+	ctx->data.str_val = team->mode->kind;
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
@@ -907,39 +970,47 @@ static int team_mode_option_set(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
 static int team_port_en_option_get(struct team *team,
7a9e3a0
 				   struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	ctx->data.bool_val = team_port_enabled(ctx->port);
7a9e3a0
+	struct team_port *port = ctx->info->port;
7a9e3a0
+
7a9e3a0
+	ctx->data.bool_val = team_port_enabled(port);
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static int team_port_en_option_set(struct team *team,
7a9e3a0
 				   struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
+	struct team_port *port = ctx->info->port;
7a9e3a0
+
7a9e3a0
 	if (ctx->data.bool_val)
7a9e3a0
-		team_port_enable(team, ctx->port);
7a9e3a0
+		team_port_enable(team, port);
7a9e3a0
 	else
7a9e3a0
-		team_port_disable(team, ctx->port);
7a9e3a0
+		team_port_disable(team, port);
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static int team_user_linkup_option_get(struct team *team,
7a9e3a0
 				       struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	ctx->data.bool_val = ctx->port->user.linkup;
7a9e3a0
+	struct team_port *port = ctx->info->port;
7a9e3a0
+
7a9e3a0
+	ctx->data.bool_val = port->user.linkup;
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static int team_user_linkup_option_set(struct team *team,
7a9e3a0
 				       struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	ctx->port->user.linkup = ctx->data.bool_val;
7a9e3a0
-	team_refresh_port_linkup(ctx->port);
7a9e3a0
+	struct team_port *port = ctx->info->port;
7a9e3a0
+
7a9e3a0
+	port->user.linkup = ctx->data.bool_val;
7a9e3a0
+	team_refresh_port_linkup(port);
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static int team_user_linkup_en_option_get(struct team *team,
7a9e3a0
 					  struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	struct team_port *port = ctx->port;
7a9e3a0
+	struct team_port *port = ctx->info->port;
7a9e3a0
 
7a9e3a0
 	ctx->data.bool_val = port->user.linkup_enabled;
7a9e3a0
 	return 0;
7a9e3a0
@@ -948,10 +1019,10 @@ static int team_user_linkup_en_option_get(struct team *team,
7a9e3a0
 static int team_user_linkup_en_option_set(struct team *team,
7a9e3a0
 					  struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	struct team_port *port = ctx->port;
7a9e3a0
+	struct team_port *port = ctx->info->port;
7a9e3a0
 
7a9e3a0
 	port->user.linkup_enabled = ctx->data.bool_val;
7a9e3a0
-	team_refresh_port_linkup(ctx->port);
7a9e3a0
+	team_refresh_port_linkup(port);
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
@@ -993,6 +1064,7 @@ static int team_init(struct net_device *dev)
7a9e3a0
 
7a9e3a0
 	team->dev = dev;
7a9e3a0
 	mutex_init(&team->lock);
7a9e3a0
+	team_set_no_mode(team);
7a9e3a0
 
7a9e3a0
 	team->pcpu_stats = alloc_percpu(struct team_pcpu_stats);
7a9e3a0
 	if (!team->pcpu_stats)
7a9e3a0
@@ -1482,16 +1554,128 @@ err_fill:
7a9e3a0
 	return err;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
-static int team_nl_fill_options_get(struct sk_buff *skb,
7a9e3a0
-				    u32 pid, u32 seq, int flags,
7a9e3a0
-				    struct team *team, bool fillall)
7a9e3a0
+typedef int team_nl_send_func_t(struct sk_buff *skb,
7a9e3a0
+				struct team *team, u32 pid);
7a9e3a0
+
7a9e3a0
+static int team_nl_send_unicast(struct sk_buff *skb, struct team *team, u32 pid)
7a9e3a0
+{
7a9e3a0
+	return genlmsg_unicast(dev_net(team->dev), skb, pid);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int team_nl_fill_one_option_get(struct sk_buff *skb, struct team *team,
7a9e3a0
+				       struct team_option_inst *opt_inst)
7a9e3a0
+{
7a9e3a0
+	struct nlattr *option_item;
7a9e3a0
+	struct team_option *option = opt_inst->option;
7a9e3a0
+	struct team_option_inst_info *opt_inst_info = &opt_inst->info;
7a9e3a0
+	struct team_gsetter_ctx ctx;
7a9e3a0
+	int err;
7a9e3a0
+
7a9e3a0
+	ctx.info = opt_inst_info;
7a9e3a0
+	err = team_option_get(team, opt_inst, &ctx;;
7a9e3a0
+	if (err)
7a9e3a0
+		return err;
7a9e3a0
+
7a9e3a0
+	option_item = nla_nest_start(skb, TEAM_ATTR_ITEM_OPTION);
7a9e3a0
+	if (!option_item)
7a9e3a0
+		return -EMSGSIZE;
7a9e3a0
+
7a9e3a0
+	if (nla_put_string(skb, TEAM_ATTR_OPTION_NAME, option->name))
7a9e3a0
+		goto nest_cancel;
7a9e3a0
+	if (opt_inst_info->port &&
7a9e3a0
+	    nla_put_u32(skb, TEAM_ATTR_OPTION_PORT_IFINDEX,
7a9e3a0
+			opt_inst_info->port->dev->ifindex))
7a9e3a0
+		goto nest_cancel;
7a9e3a0
+	if (opt_inst->option->array_size &&
7a9e3a0
+	    nla_put_u32(skb, TEAM_ATTR_OPTION_ARRAY_INDEX,
7a9e3a0
+			opt_inst_info->array_index))
7a9e3a0
+		goto nest_cancel;
7a9e3a0
+
7a9e3a0
+	switch (option->type) {
7a9e3a0
+	case TEAM_OPTION_TYPE_U32:
7a9e3a0
+		if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		if (nla_put_u32(skb, TEAM_ATTR_OPTION_DATA, ctx.data.u32_val))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		break;
7a9e3a0
+	case TEAM_OPTION_TYPE_STRING:
7a9e3a0
+		if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		if (nla_put_string(skb, TEAM_ATTR_OPTION_DATA,
7a9e3a0
+				   ctx.data.str_val))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		break;
7a9e3a0
+	case TEAM_OPTION_TYPE_BINARY:
7a9e3a0
+		if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_BINARY))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		if (nla_put(skb, TEAM_ATTR_OPTION_DATA, ctx.data.bin_val.len,
7a9e3a0
+			    ctx.data.bin_val.ptr))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		break;
7a9e3a0
+	case TEAM_OPTION_TYPE_BOOL:
7a9e3a0
+		if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_FLAG))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		if (ctx.data.bool_val &&
7a9e3a0
+		    nla_put_flag(skb, TEAM_ATTR_OPTION_DATA))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		break;
7a9e3a0
+	default:
7a9e3a0
+		BUG();
7a9e3a0
+	}
7a9e3a0
+	if (opt_inst->removed && nla_put_flag(skb, TEAM_ATTR_OPTION_REMOVED))
7a9e3a0
+		goto nest_cancel;
7a9e3a0
+	if (opt_inst->changed) {
7a9e3a0
+		if (nla_put_flag(skb, TEAM_ATTR_OPTION_CHANGED))
7a9e3a0
+			goto nest_cancel;
7a9e3a0
+		opt_inst->changed = false;
7a9e3a0
+	}
7a9e3a0
+	nla_nest_end(skb, option_item);
7a9e3a0
+	return 0;
7a9e3a0
+
7a9e3a0
+nest_cancel:
7a9e3a0
+	nla_nest_cancel(skb, option_item);
7a9e3a0
+	return -EMSGSIZE;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int __send_and_alloc_skb(struct sk_buff **pskb,
7a9e3a0
+				struct team *team, u32 pid,
7a9e3a0
+				team_nl_send_func_t *send_func)
7a9e3a0
+{
7a9e3a0
+	int err;
7a9e3a0
+
7a9e3a0
+	if (*pskb) {
7a9e3a0
+		err = send_func(*pskb, team, pid);
7a9e3a0
+		if (err)
7a9e3a0
+			return err;
7a9e3a0
+	}
7a9e3a0
+	*pskb = genlmsg_new(NLMSG_DEFAULT_SIZE - GENL_HDRLEN, GFP_KERNEL);
7a9e3a0
+	if (!*pskb)
7a9e3a0
+		return -ENOMEM;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int team_nl_send_options_get(struct team *team, u32 pid, u32 seq,
7a9e3a0
+				    int flags, team_nl_send_func_t *send_func,
7a9e3a0
+				    struct list_head *sel_opt_inst_list)
7a9e3a0
 {
7a9e3a0
 	struct nlattr *option_list;
7a9e3a0
+	struct nlmsghdr *nlh;
7a9e3a0
 	void *hdr;
7a9e3a0
 	struct team_option_inst *opt_inst;
7a9e3a0
 	int err;
7a9e3a0
+	struct sk_buff *skb = NULL;
7a9e3a0
+	bool incomplete;
7a9e3a0
+	int i;
7a9e3a0
 
7a9e3a0
-	hdr = genlmsg_put(skb, pid, seq, &team_nl_family, flags,
7a9e3a0
+	opt_inst = list_first_entry(sel_opt_inst_list,
7a9e3a0
+				    struct team_option_inst, tmp_list);
7a9e3a0
+
7a9e3a0
+start_again:
7a9e3a0
+	err = __send_and_alloc_skb(&skb, team, pid, send_func);
7a9e3a0
+	if (err)
7a9e3a0
+		return err;
7a9e3a0
+
7a9e3a0
+	hdr = genlmsg_put(skb, pid, seq, &team_nl_family, flags | NLM_F_MULTI,
7a9e3a0
 			  TEAM_CMD_OPTIONS_GET);
7a9e3a0
 	if (IS_ERR(hdr))
7a9e3a0
 		return PTR_ERR(hdr);
7a9e3a0
@@ -1500,122 +1684,80 @@ static int team_nl_fill_options_get(struct sk_buff *skb,
7a9e3a0
 		goto nla_put_failure;
7a9e3a0
 	option_list = nla_nest_start(skb, TEAM_ATTR_LIST_OPTION);
7a9e3a0
 	if (!option_list)
7a9e3a0
-		return -EMSGSIZE;
7a9e3a0
-
7a9e3a0
-	list_for_each_entry(opt_inst, &team->option_inst_list, list) {
7a9e3a0
-		struct nlattr *option_item;
7a9e3a0
-		struct team_option *option = opt_inst->option;
7a9e3a0
-		struct team_gsetter_ctx ctx;
7a9e3a0
+		goto nla_put_failure;
7a9e3a0
 
7a9e3a0
-		/* Include only changed options if fill all mode is not on */
7a9e3a0
-		if (!fillall && !opt_inst->changed)
7a9e3a0
-			continue;
7a9e3a0
-		option_item = nla_nest_start(skb, TEAM_ATTR_ITEM_OPTION);
7a9e3a0
-		if (!option_item)
7a9e3a0
-			goto nla_put_failure;
7a9e3a0
-		if (nla_put_string(skb, TEAM_ATTR_OPTION_NAME, option->name))
7a9e3a0
-			goto nla_put_failure;
7a9e3a0
-		if (opt_inst->changed) {
7a9e3a0
-			if (nla_put_flag(skb, TEAM_ATTR_OPTION_CHANGED))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			opt_inst->changed = false;
7a9e3a0
-		}
7a9e3a0
-		if (opt_inst->removed &&
7a9e3a0
-		    nla_put_flag(skb, TEAM_ATTR_OPTION_REMOVED))
7a9e3a0
-			goto nla_put_failure;
7a9e3a0
-		if (opt_inst->port &&
7a9e3a0
-		    nla_put_u32(skb, TEAM_ATTR_OPTION_PORT_IFINDEX,
7a9e3a0
-				opt_inst->port->dev->ifindex))
7a9e3a0
-			goto nla_put_failure;
7a9e3a0
-		ctx.port = opt_inst->port;
7a9e3a0
-		switch (option->type) {
7a9e3a0
-		case TEAM_OPTION_TYPE_U32:
7a9e3a0
-			if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			err = team_option_get(team, opt_inst, &ctx;;
7a9e3a0
-			if (err)
7a9e3a0
-				goto errout;
7a9e3a0
-			if (nla_put_u32(skb, TEAM_ATTR_OPTION_DATA,
7a9e3a0
-					ctx.data.u32_val))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			break;
7a9e3a0
-		case TEAM_OPTION_TYPE_STRING:
7a9e3a0
-			if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			err = team_option_get(team, opt_inst, &ctx;;
7a9e3a0
-			if (err)
7a9e3a0
-				goto errout;
7a9e3a0
-			if (nla_put_string(skb, TEAM_ATTR_OPTION_DATA,
7a9e3a0
-					   ctx.data.str_val))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			break;
7a9e3a0
-		case TEAM_OPTION_TYPE_BINARY:
7a9e3a0
-			if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_BINARY))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			err = team_option_get(team, opt_inst, &ctx;;
7a9e3a0
-			if (err)
7a9e3a0
-				goto errout;
7a9e3a0
-			if (nla_put(skb, TEAM_ATTR_OPTION_DATA,
7a9e3a0
-				    ctx.data.bin_val.len, ctx.data.bin_val.ptr))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			break;
7a9e3a0
-		case TEAM_OPTION_TYPE_BOOL:
7a9e3a0
-			if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_FLAG))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			err = team_option_get(team, opt_inst, &ctx;;
7a9e3a0
-			if (err)
7a9e3a0
-				goto errout;
7a9e3a0
-			if (ctx.data.bool_val &&
7a9e3a0
-			    nla_put_flag(skb, TEAM_ATTR_OPTION_DATA))
7a9e3a0
-				goto nla_put_failure;
7a9e3a0
-			break;
7a9e3a0
-		default:
7a9e3a0
-			BUG();
7a9e3a0
+	i = 0;
7a9e3a0
+	incomplete = false;
7a9e3a0
+	list_for_each_entry_from(opt_inst, sel_opt_inst_list, tmp_list) {
7a9e3a0
+		err = team_nl_fill_one_option_get(skb, team, opt_inst);
7a9e3a0
+		if (err) {
7a9e3a0
+			if (err == -EMSGSIZE) {
7a9e3a0
+				if (!i)
7a9e3a0
+					goto errout;
7a9e3a0
+				incomplete = true;
7a9e3a0
+				break;
7a9e3a0
+			}
7a9e3a0
+			goto errout;
7a9e3a0
 		}
7a9e3a0
-		nla_nest_end(skb, option_item);
7a9e3a0
+		i++;
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
 	nla_nest_end(skb, option_list);
7a9e3a0
-	return genlmsg_end(skb, hdr);
7a9e3a0
+	genlmsg_end(skb, hdr);
7a9e3a0
+	if (incomplete)
7a9e3a0
+		goto start_again;
7a9e3a0
+
7a9e3a0
+send_done:
7a9e3a0
+	nlh = nlmsg_put(skb, pid, seq, NLMSG_DONE, 0, flags | NLM_F_MULTI);
7a9e3a0
+	if (!nlh) {
7a9e3a0
+		err = __send_and_alloc_skb(&skb, team, pid, send_func);
7a9e3a0
+		if (err)
7a9e3a0
+			goto errout;
7a9e3a0
+		goto send_done;
7a9e3a0
+	}
7a9e3a0
+
7a9e3a0
+	return send_func(skb, team, pid);
7a9e3a0
 
7a9e3a0
 nla_put_failure:
7a9e3a0
 	err = -EMSGSIZE;
7a9e3a0
 errout:
7a9e3a0
 	genlmsg_cancel(skb, hdr);
7a9e3a0
+	nlmsg_free(skb);
7a9e3a0
 	return err;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
-static int team_nl_fill_options_get_all(struct sk_buff *skb,
7a9e3a0
-					struct genl_info *info, int flags,
7a9e3a0
-					struct team *team)
7a9e3a0
-{
7a9e3a0
-	return team_nl_fill_options_get(skb, info->snd_pid,
7a9e3a0
-					info->snd_seq, NLM_F_ACK,
7a9e3a0
-					team, true);
7a9e3a0
-}
7a9e3a0
-
7a9e3a0
 static int team_nl_cmd_options_get(struct sk_buff *skb, struct genl_info *info)
7a9e3a0
 {
7a9e3a0
 	struct team *team;
7a9e3a0
+	struct team_option_inst *opt_inst;
7a9e3a0
 	int err;
7a9e3a0
+	LIST_HEAD(sel_opt_inst_list);
7a9e3a0
 
7a9e3a0
 	team = team_nl_team_get(info);
7a9e3a0
 	if (!team)
7a9e3a0
 		return -EINVAL;
7a9e3a0
 
7a9e3a0
-	err = team_nl_send_generic(info, team, team_nl_fill_options_get_all);
7a9e3a0
+	list_for_each_entry(opt_inst, &team->option_inst_list, list)
7a9e3a0
+		list_add_tail(&opt_inst->tmp_list, &sel_opt_inst_list);
7a9e3a0
+	err = team_nl_send_options_get(team, info->snd_pid, info->snd_seq,
7a9e3a0
+				       NLM_F_ACK, team_nl_send_unicast,
7a9e3a0
+				       &sel_opt_inst_list);
7a9e3a0
 
7a9e3a0
 	team_nl_team_put(team);
7a9e3a0
 
7a9e3a0
 	return err;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
+static int team_nl_send_event_options_get(struct team *team,
7a9e3a0
+					  struct list_head *sel_opt_inst_list);
7a9e3a0
+
7a9e3a0
 static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info)
7a9e3a0
 {
7a9e3a0
 	struct team *team;
7a9e3a0
 	int err = 0;
7a9e3a0
 	int i;
7a9e3a0
 	struct nlattr *nl_option;
7a9e3a0
+	LIST_HEAD(opt_inst_list);
7a9e3a0
 
7a9e3a0
 	team = team_nl_team_get(info);
7a9e3a0
 	if (!team)
7a9e3a0
@@ -1629,10 +1771,12 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info)
7a9e3a0
 
7a9e3a0
 	nla_for_each_nested(nl_option, info->attrs[TEAM_ATTR_LIST_OPTION], i) {
7a9e3a0
 		struct nlattr *opt_attrs[TEAM_ATTR_OPTION_MAX + 1];
7a9e3a0
-		struct nlattr *attr_port_ifindex;
7a9e3a0
+		struct nlattr *attr;
7a9e3a0
 		struct nlattr *attr_data;
7a9e3a0
 		enum team_option_type opt_type;
7a9e3a0
 		int opt_port_ifindex = 0; /* != 0 for per-port options */
7a9e3a0
+		u32 opt_array_index = 0;
7a9e3a0
+		bool opt_is_array = false;
7a9e3a0
 		struct team_option_inst *opt_inst;
7a9e3a0
 		char *opt_name;
7a9e3a0
 		bool opt_found = false;
7a9e3a0
@@ -1674,23 +1818,33 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info)
7a9e3a0
 		}
7a9e3a0
 
7a9e3a0
 		opt_name = nla_data(opt_attrs[TEAM_ATTR_OPTION_NAME]);
7a9e3a0
-		attr_port_ifindex = opt_attrs[TEAM_ATTR_OPTION_PORT_IFINDEX];
7a9e3a0
-		if (attr_port_ifindex)
7a9e3a0
-			opt_port_ifindex = nla_get_u32(attr_port_ifindex);
7a9e3a0
+		attr = opt_attrs[TEAM_ATTR_OPTION_PORT_IFINDEX];
7a9e3a0
+		if (attr)
7a9e3a0
+			opt_port_ifindex = nla_get_u32(attr);
7a9e3a0
+
7a9e3a0
+		attr = opt_attrs[TEAM_ATTR_OPTION_ARRAY_INDEX];
7a9e3a0
+		if (attr) {
7a9e3a0
+			opt_is_array = true;
7a9e3a0
+			opt_array_index = nla_get_u32(attr);
7a9e3a0
+		}
7a9e3a0
 
7a9e3a0
 		list_for_each_entry(opt_inst, &team->option_inst_list, list) {
7a9e3a0
 			struct team_option *option = opt_inst->option;
7a9e3a0
 			struct team_gsetter_ctx ctx;
7a9e3a0
+			struct team_option_inst_info *opt_inst_info;
7a9e3a0
 			int tmp_ifindex;
7a9e3a0
 
7a9e3a0
-			tmp_ifindex = opt_inst->port ?
7a9e3a0
-				      opt_inst->port->dev->ifindex : 0;
7a9e3a0
+			opt_inst_info = &opt_inst->info;
7a9e3a0
+			tmp_ifindex = opt_inst_info->port ?
7a9e3a0
+				      opt_inst_info->port->dev->ifindex : 0;
7a9e3a0
 			if (option->type != opt_type ||
7a9e3a0
 			    strcmp(option->name, opt_name) ||
7a9e3a0
-			    tmp_ifindex != opt_port_ifindex)
7a9e3a0
+			    tmp_ifindex != opt_port_ifindex ||
7a9e3a0
+			    (option->array_size && !opt_is_array) ||
7a9e3a0
+			    opt_inst_info->array_index != opt_array_index)
7a9e3a0
 				continue;
7a9e3a0
 			opt_found = true;
7a9e3a0
-			ctx.port = opt_inst->port;
7a9e3a0
+			ctx.info = opt_inst_info;
7a9e3a0
 			switch (opt_type) {
7a9e3a0
 			case TEAM_OPTION_TYPE_U32:
7a9e3a0
 				ctx.data.u32_val = nla_get_u32(attr_data);
7a9e3a0
@@ -1715,6 +1869,8 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info)
7a9e3a0
 			err = team_option_set(team, opt_inst, &ctx;;
7a9e3a0
 			if (err)
7a9e3a0
 				goto team_put;
7a9e3a0
+			opt_inst->changed = true;
7a9e3a0
+			list_add(&opt_inst->tmp_list, &opt_inst_list);
7a9e3a0
 		}
7a9e3a0
 		if (!opt_found) {
7a9e3a0
 			err = -ENOENT;
7a9e3a0
@@ -1722,6 +1878,8 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info)
7a9e3a0
 		}
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
+	err = team_nl_send_event_options_get(team, &opt_inst_list);
7a9e3a0
+
7a9e3a0
 team_put:
7a9e3a0
 	team_nl_team_put(team);
7a9e3a0
 
7a9e3a0
@@ -1746,7 +1904,7 @@ static int team_nl_fill_port_list_get(struct sk_buff *skb,
7a9e3a0
 		goto nla_put_failure;
7a9e3a0
 	port_list = nla_nest_start(skb, TEAM_ATTR_LIST_PORT);
7a9e3a0
 	if (!port_list)
7a9e3a0
-		return -EMSGSIZE;
7a9e3a0
+		goto nla_put_failure;
7a9e3a0
 
7a9e3a0
 	list_for_each_entry(port, &team->port_list, list) {
7a9e3a0
 		struct nlattr *port_item;
7a9e3a0
@@ -1838,27 +1996,18 @@ static struct genl_multicast_group team_change_event_mcgrp = {
7a9e3a0
 	.name = TEAM_GENL_CHANGE_EVENT_MC_GRP_NAME,
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
-static int team_nl_send_event_options_get(struct team *team)
7a9e3a0
+static int team_nl_send_multicast(struct sk_buff *skb,
7a9e3a0
+				  struct team *team, u32 pid)
7a9e3a0
 {
7a9e3a0
-	struct sk_buff *skb;
7a9e3a0
-	int err;
7a9e3a0
-	struct net *net = dev_net(team->dev);
7a9e3a0
-
7a9e3a0
-	skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
7a9e3a0
-	if (!skb)
7a9e3a0
-		return -ENOMEM;
7a9e3a0
-
7a9e3a0
-	err = team_nl_fill_options_get(skb, 0, 0, 0, team, false);
7a9e3a0
-	if (err < 0)
7a9e3a0
-		goto err_fill;
7a9e3a0
-
7a9e3a0
-	err = genlmsg_multicast_netns(net, skb, 0, team_change_event_mcgrp.id,
7a9e3a0
-				      GFP_KERNEL);
7a9e3a0
-	return err;
7a9e3a0
+	return genlmsg_multicast_netns(dev_net(team->dev), skb, 0,
7a9e3a0
+				       team_change_event_mcgrp.id, GFP_KERNEL);
7a9e3a0
+}
7a9e3a0
 
7a9e3a0
-err_fill:
7a9e3a0
-	nlmsg_free(skb);
7a9e3a0
-	return err;
7a9e3a0
+static int team_nl_send_event_options_get(struct team *team,
7a9e3a0
+					  struct list_head *sel_opt_inst_list)
7a9e3a0
+{
7a9e3a0
+	return team_nl_send_options_get(team, 0, 0, 0, team_nl_send_multicast,
7a9e3a0
+					sel_opt_inst_list);
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static int team_nl_send_event_port_list_get(struct team *team)
7a9e3a0
@@ -1918,10 +2067,17 @@ static void team_nl_fini(void)
7a9e3a0
 static void __team_options_change_check(struct team *team)
7a9e3a0
 {
7a9e3a0
 	int err;
7a9e3a0
+	struct team_option_inst *opt_inst;
7a9e3a0
+	LIST_HEAD(sel_opt_inst_list);
7a9e3a0
 
7a9e3a0
-	err = team_nl_send_event_options_get(team);
7a9e3a0
+	list_for_each_entry(opt_inst, &team->option_inst_list, list) {
7a9e3a0
+		if (opt_inst->changed)
7a9e3a0
+			list_add_tail(&opt_inst->tmp_list, &sel_opt_inst_list);
7a9e3a0
+	}
7a9e3a0
+	err = team_nl_send_event_options_get(team, &sel_opt_inst_list);
7a9e3a0
 	if (err)
7a9e3a0
-		netdev_warn(team->dev, "Failed to send options change via netlink\n");
7a9e3a0
+		netdev_warn(team->dev, "Failed to send options change via netlink (err %d)\n",
7a9e3a0
+			    err);
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 /* rtnl lock is held */
7a9e3a0
@@ -1965,6 +2121,7 @@ static void team_port_change_check(struct team_port *port, bool linkup)
7a9e3a0
 	mutex_unlock(&team->lock);
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
+
7a9e3a0
 /************************************
7a9e3a0
  * Net device notifier event handler
7a9e3a0
  ************************************/
7a9e3a0
diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c
7a9e3a0
index fd6bd03..253b8a5 100644
7a9e3a0
--- a/drivers/net/team/team_mode_activebackup.c
7a9e3a0
+++ b/drivers/net/team/team_mode_activebackup.c
7a9e3a0
@@ -1,5 +1,5 @@
7a9e3a0
 /*
7a9e3a0
- * net/drivers/team/team_mode_activebackup.c - Active-backup mode for team
7a9e3a0
+ * drivers/net/team/team_mode_activebackup.c - Active-backup mode for team
7a9e3a0
  * Copyright (c) 2011 Jiri Pirko <jpirko@redhat.com>
7a9e3a0
  *
7a9e3a0
  * This program is free software; you can redistribute it and/or modify
7a9e3a0
@@ -40,7 +40,7 @@ static bool ab_transmit(struct team *team, struct sk_buff *skb)
7a9e3a0
 {
7a9e3a0
 	struct team_port *active_port;
7a9e3a0
 
7a9e3a0
-	active_port = rcu_dereference(ab_priv(team)->active_port);
7a9e3a0
+	active_port = rcu_dereference_bh(ab_priv(team)->active_port);
7a9e3a0
 	if (unlikely(!active_port))
7a9e3a0
 		goto drop;
7a9e3a0
 	skb->dev = active_port->dev;
7a9e3a0
@@ -61,8 +61,12 @@ static void ab_port_leave(struct team *team, struct team_port *port)
7a9e3a0
 
7a9e3a0
 static int ab_active_port_get(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	if (ab_priv(team)->active_port)
7a9e3a0
-		ctx->data.u32_val = ab_priv(team)->active_port->dev->ifindex;
7a9e3a0
+	struct team_port *active_port;
7a9e3a0
+
7a9e3a0
+	active_port = rcu_dereference_protected(ab_priv(team)->active_port,
7a9e3a0
+						lockdep_is_held(&team->lock));
7a9e3a0
+	if (active_port)
7a9e3a0
+		ctx->data.u32_val = active_port->dev->ifindex;
7a9e3a0
 	else
7a9e3a0
 		ctx->data.u32_val = 0;
7a9e3a0
 	return 0;
7a9e3a0
@@ -108,7 +112,7 @@ static const struct team_mode_ops ab_mode_ops = {
7a9e3a0
 	.port_leave		= ab_port_leave,
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
-static struct team_mode ab_mode = {
7a9e3a0
+static const struct team_mode ab_mode = {
7a9e3a0
 	.kind		= "activebackup",
7a9e3a0
 	.owner		= THIS_MODULE,
7a9e3a0
 	.priv_size	= sizeof(struct ab_priv),
7a9e3a0
diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c
7a9e3a0
index 86e8183..51a4b19 100644
7a9e3a0
--- a/drivers/net/team/team_mode_loadbalance.c
7a9e3a0
+++ b/drivers/net/team/team_mode_loadbalance.c
7a9e3a0
@@ -17,34 +17,210 @@
7a9e3a0
 #include <linux/filter.h>
7a9e3a0
 #include <linux/if_team.h>
7a9e3a0
 
7a9e3a0
+struct lb_priv;
7a9e3a0
+
7a9e3a0
+typedef struct team_port *lb_select_tx_port_func_t(struct team *,
7a9e3a0
+						   struct lb_priv *,
7a9e3a0
+						   struct sk_buff *,
7a9e3a0
+						   unsigned char);
7a9e3a0
+
7a9e3a0
+#define LB_TX_HASHTABLE_SIZE 256 /* hash is a char */
7a9e3a0
+
7a9e3a0
+struct lb_stats {
7a9e3a0
+	u64 tx_bytes;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+struct lb_pcpu_stats {
7a9e3a0
+	struct lb_stats hash_stats[LB_TX_HASHTABLE_SIZE];
7a9e3a0
+	struct u64_stats_sync syncp;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+struct lb_stats_info {
7a9e3a0
+	struct lb_stats stats;
7a9e3a0
+	struct lb_stats last_stats;
7a9e3a0
+	struct team_option_inst_info *opt_inst_info;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+struct lb_port_mapping {
7a9e3a0
+	struct team_port __rcu *port;
7a9e3a0
+	struct team_option_inst_info *opt_inst_info;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+struct lb_priv_ex {
7a9e3a0
+	struct team *team;
7a9e3a0
+	struct lb_port_mapping tx_hash_to_port_mapping[LB_TX_HASHTABLE_SIZE];
7a9e3a0
+	struct sock_fprog *orig_fprog;
7a9e3a0
+	struct {
7a9e3a0
+		unsigned int refresh_interval; /* in tenths of second */
7a9e3a0
+		struct delayed_work refresh_dw;
7a9e3a0
+		struct lb_stats_info info[LB_TX_HASHTABLE_SIZE];
7a9e3a0
+	} stats;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
 struct lb_priv {
7a9e3a0
 	struct sk_filter __rcu *fp;
7a9e3a0
-	struct sock_fprog *orig_fprog;
7a9e3a0
+	lb_select_tx_port_func_t __rcu *select_tx_port_func;
7a9e3a0
+	struct lb_pcpu_stats __percpu *pcpu_stats;
7a9e3a0
+	struct lb_priv_ex *ex; /* priv extension */
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
-static struct lb_priv *lb_priv(struct team *team)
7a9e3a0
+static struct lb_priv *get_lb_priv(struct team *team)
7a9e3a0
 {
7a9e3a0
 	return (struct lb_priv *) &team->mode_priv;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
-static bool lb_transmit(struct team *team, struct sk_buff *skb)
7a9e3a0
+struct lb_port_priv {
7a9e3a0
+	struct lb_stats __percpu *pcpu_stats;
7a9e3a0
+	struct lb_stats_info stats_info;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+static struct lb_port_priv *get_lb_port_priv(struct team_port *port)
7a9e3a0
+{
7a9e3a0
+	return (struct lb_port_priv *) &port->mode_priv;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+#define LB_HTPM_PORT_BY_HASH(lp_priv, hash) \
7a9e3a0
+	(lb_priv)->ex->tx_hash_to_port_mapping[hash].port
7a9e3a0
+
7a9e3a0
+#define LB_HTPM_OPT_INST_INFO_BY_HASH(lp_priv, hash) \
7a9e3a0
+	(lb_priv)->ex->tx_hash_to_port_mapping[hash].opt_inst_info
7a9e3a0
+
7a9e3a0
+static void lb_tx_hash_to_port_mapping_null_port(struct team *team,
7a9e3a0
+						 struct team_port *port)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	bool changed = false;
7a9e3a0
+	int i;
7a9e3a0
+
7a9e3a0
+	for (i = 0; i < LB_TX_HASHTABLE_SIZE; i++) {
7a9e3a0
+		struct lb_port_mapping *pm;
7a9e3a0
+
7a9e3a0
+		pm = &lb_priv->ex->tx_hash_to_port_mapping[i];
7a9e3a0
+		if (rcu_access_pointer(pm->port) == port) {
7a9e3a0
+			RCU_INIT_POINTER(pm->port, NULL);
7a9e3a0
+			team_option_inst_set_change(pm->opt_inst_info);
7a9e3a0
+			changed = true;
7a9e3a0
+		}
7a9e3a0
+	}
7a9e3a0
+	if (changed)
7a9e3a0
+		team_options_change_check(team);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+/* Basic tx selection based solely by hash */
7a9e3a0
+static struct team_port *lb_hash_select_tx_port(struct team *team,
7a9e3a0
+						struct lb_priv *lb_priv,
7a9e3a0
+						struct sk_buff *skb,
7a9e3a0
+						unsigned char hash)
7a9e3a0
 {
7a9e3a0
-	struct sk_filter *fp;
7a9e3a0
-	struct team_port *port;
7a9e3a0
-	unsigned int hash;
7a9e3a0
 	int port_index;
7a9e3a0
 
7a9e3a0
-	fp = rcu_dereference(lb_priv(team)->fp);
7a9e3a0
-	if (unlikely(!fp))
7a9e3a0
-		goto drop;
7a9e3a0
-	hash = SK_RUN_FILTER(fp, skb);
7a9e3a0
 	port_index = hash % team->en_port_count;
7a9e3a0
-	port = team_get_port_by_index_rcu(team, port_index);
7a9e3a0
+	return team_get_port_by_index_rcu(team, port_index);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+/* Hash to port mapping select tx port */
7a9e3a0
+static struct team_port *lb_htpm_select_tx_port(struct team *team,
7a9e3a0
+						struct lb_priv *lb_priv,
7a9e3a0
+						struct sk_buff *skb,
7a9e3a0
+						unsigned char hash)
7a9e3a0
+{
7a9e3a0
+	return rcu_dereference_bh(LB_HTPM_PORT_BY_HASH(lb_priv, hash));
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+struct lb_select_tx_port {
7a9e3a0
+	char *name;
7a9e3a0
+	lb_select_tx_port_func_t *func;
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
+static const struct lb_select_tx_port lb_select_tx_port_list[] = {
7a9e3a0
+	{
7a9e3a0
+		.name = "hash",
7a9e3a0
+		.func = lb_hash_select_tx_port,
7a9e3a0
+	},
7a9e3a0
+	{
7a9e3a0
+		.name = "hash_to_port_mapping",
7a9e3a0
+		.func = lb_htpm_select_tx_port,
7a9e3a0
+	},
7a9e3a0
+};
7a9e3a0
+#define LB_SELECT_TX_PORT_LIST_COUNT ARRAY_SIZE(lb_select_tx_port_list)
7a9e3a0
+
7a9e3a0
+static char *lb_select_tx_port_get_name(lb_select_tx_port_func_t *func)
7a9e3a0
+{
7a9e3a0
+	int i;
7a9e3a0
+
7a9e3a0
+	for (i = 0; i < LB_SELECT_TX_PORT_LIST_COUNT; i++) {
7a9e3a0
+		const struct lb_select_tx_port *item;
7a9e3a0
+
7a9e3a0
+		item = &lb_select_tx_port_list[i];
7a9e3a0
+		if (item->func == func)
7a9e3a0
+			return item->name;
7a9e3a0
+	}
7a9e3a0
+	return NULL;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static lb_select_tx_port_func_t *lb_select_tx_port_get_func(const char *name)
7a9e3a0
+{
7a9e3a0
+	int i;
7a9e3a0
+
7a9e3a0
+	for (i = 0; i < LB_SELECT_TX_PORT_LIST_COUNT; i++) {
7a9e3a0
+		const struct lb_select_tx_port *item;
7a9e3a0
+
7a9e3a0
+		item = &lb_select_tx_port_list[i];
7a9e3a0
+		if (!strcmp(item->name, name))
7a9e3a0
+			return item->func;
7a9e3a0
+	}
7a9e3a0
+	return NULL;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static unsigned int lb_get_skb_hash(struct lb_priv *lb_priv,
7a9e3a0
+				    struct sk_buff *skb)
7a9e3a0
+{
7a9e3a0
+	struct sk_filter *fp;
7a9e3a0
+	uint32_t lhash;
7a9e3a0
+	unsigned char *c;
7a9e3a0
+
7a9e3a0
+	fp = rcu_dereference_bh(lb_priv->fp);
7a9e3a0
+	if (unlikely(!fp))
7a9e3a0
+		return 0;
7a9e3a0
+	lhash = SK_RUN_FILTER(fp, skb);
7a9e3a0
+	c = (char *) &lhas;;
7a9e3a0
+	return c[0] ^ c[1] ^ c[2] ^ c[3];
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void lb_update_tx_stats(unsigned int tx_bytes, struct lb_priv *lb_priv,
7a9e3a0
+			       struct lb_port_priv *lb_port_priv,
7a9e3a0
+			       unsigned char hash)
7a9e3a0
+{
7a9e3a0
+	struct lb_pcpu_stats *pcpu_stats;
7a9e3a0
+	struct lb_stats *port_stats;
7a9e3a0
+	struct lb_stats *hash_stats;
7a9e3a0
+
7a9e3a0
+	pcpu_stats = this_cpu_ptr(lb_priv->pcpu_stats);
7a9e3a0
+	port_stats = this_cpu_ptr(lb_port_priv->pcpu_stats);
7a9e3a0
+	hash_stats = &pcpu_stats->hash_stats[hash];
7a9e3a0
+	u64_stats_update_begin(&pcpu_stats->syncp);
7a9e3a0
+	port_stats->tx_bytes += tx_bytes;
7a9e3a0
+	hash_stats->tx_bytes += tx_bytes;
7a9e3a0
+	u64_stats_update_end(&pcpu_stats->syncp);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static bool lb_transmit(struct team *team, struct sk_buff *skb)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	lb_select_tx_port_func_t *select_tx_port_func;
7a9e3a0
+	struct team_port *port;
7a9e3a0
+	unsigned char hash;
7a9e3a0
+	unsigned int tx_bytes = skb->len;
7a9e3a0
+
7a9e3a0
+	hash = lb_get_skb_hash(lb_priv, skb);
7a9e3a0
+	select_tx_port_func = rcu_dereference_bh(lb_priv->select_tx_port_func);
7a9e3a0
+	port = select_tx_port_func(team, lb_priv, skb, hash);
7a9e3a0
 	if (unlikely(!port))
7a9e3a0
 		goto drop;
7a9e3a0
 	skb->dev = port->dev;
7a9e3a0
 	if (dev_queue_xmit(skb))
7a9e3a0
 		return false;
7a9e3a0
+	lb_update_tx_stats(tx_bytes, lb_priv, get_lb_port_priv(port), hash);
7a9e3a0
 	return true;
7a9e3a0
 
7a9e3a0
 drop:
7a9e3a0
@@ -54,14 +230,16 @@ drop:
7a9e3a0
 
7a9e3a0
 static int lb_bpf_func_get(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
-	if (!lb_priv(team)->orig_fprog) {
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+
7a9e3a0
+	if (!lb_priv->ex->orig_fprog) {
7a9e3a0
 		ctx->data.bin_val.len = 0;
7a9e3a0
 		ctx->data.bin_val.ptr = NULL;
7a9e3a0
 		return 0;
7a9e3a0
 	}
7a9e3a0
-	ctx->data.bin_val.len = lb_priv(team)->orig_fprog->len *
7a9e3a0
+	ctx->data.bin_val.len = lb_priv->ex->orig_fprog->len *
7a9e3a0
 				sizeof(struct sock_filter);
7a9e3a0
-	ctx->data.bin_val.ptr = lb_priv(team)->orig_fprog->filter;
7a9e3a0
+	ctx->data.bin_val.ptr = lb_priv->ex->orig_fprog->filter;
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
@@ -94,7 +272,9 @@ static void __fprog_destroy(struct sock_fprog *fprog)
7a9e3a0
 
7a9e3a0
 static int lb_bpf_func_set(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
 {
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
 	struct sk_filter *fp = NULL;
7a9e3a0
+	struct sk_filter *orig_fp;
7a9e3a0
 	struct sock_fprog *fprog = NULL;
7a9e3a0
 	int err;
7a9e3a0
 
7a9e3a0
@@ -110,14 +290,238 @@ static int lb_bpf_func_set(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
 		}
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
-	if (lb_priv(team)->orig_fprog) {
7a9e3a0
+	if (lb_priv->ex->orig_fprog) {
7a9e3a0
 		/* Clear old filter data */
7a9e3a0
-		__fprog_destroy(lb_priv(team)->orig_fprog);
7a9e3a0
-		sk_unattached_filter_destroy(lb_priv(team)->fp);
7a9e3a0
+		__fprog_destroy(lb_priv->ex->orig_fprog);
7a9e3a0
+		orig_fp = rcu_dereference_protected(lb_priv->fp,
7a9e3a0
+						lockdep_is_held(&team->lock));
7a9e3a0
+		sk_unattached_filter_destroy(orig_fp);
7a9e3a0
 	}
7a9e3a0
 
7a9e3a0
-	rcu_assign_pointer(lb_priv(team)->fp, fp);
7a9e3a0
-	lb_priv(team)->orig_fprog = fprog;
7a9e3a0
+	rcu_assign_pointer(lb_priv->fp, fp);
7a9e3a0
+	lb_priv->ex->orig_fprog = fprog;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_tx_method_get(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	lb_select_tx_port_func_t *func;
7a9e3a0
+	char *name;
7a9e3a0
+
7a9e3a0
+	func = rcu_dereference_protected(lb_priv->select_tx_port_func,
7a9e3a0
+					 lockdep_is_held(&team->lock));
7a9e3a0
+	name = lb_select_tx_port_get_name(func);
7a9e3a0
+	BUG_ON(!name);
7a9e3a0
+	ctx->data.str_val = name;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_tx_method_set(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	lb_select_tx_port_func_t *func;
7a9e3a0
+
7a9e3a0
+	func = lb_select_tx_port_get_func(ctx->data.str_val);
7a9e3a0
+	if (!func)
7a9e3a0
+		return -EINVAL;
7a9e3a0
+	rcu_assign_pointer(lb_priv->select_tx_port_func, func);
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_tx_hash_to_port_mapping_init(struct team *team,
7a9e3a0
+					   struct team_option_inst_info *info)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	unsigned char hash = info->array_index;
7a9e3a0
+
7a9e3a0
+	LB_HTPM_OPT_INST_INFO_BY_HASH(lb_priv, hash) = info;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_tx_hash_to_port_mapping_get(struct team *team,
7a9e3a0
+					  struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	struct team_port *port;
7a9e3a0
+	unsigned char hash = ctx->info->array_index;
7a9e3a0
+
7a9e3a0
+	port = LB_HTPM_PORT_BY_HASH(lb_priv, hash);
7a9e3a0
+	ctx->data.u32_val = port ? port->dev->ifindex : 0;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_tx_hash_to_port_mapping_set(struct team *team,
7a9e3a0
+					  struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	struct team_port *port;
7a9e3a0
+	unsigned char hash = ctx->info->array_index;
7a9e3a0
+
7a9e3a0
+	list_for_each_entry(port, &team->port_list, list) {
7a9e3a0
+		if (ctx->data.u32_val == port->dev->ifindex &&
7a9e3a0
+		    team_port_enabled(port)) {
7a9e3a0
+			rcu_assign_pointer(LB_HTPM_PORT_BY_HASH(lb_priv, hash),
7a9e3a0
+					   port);
7a9e3a0
+			return 0;
7a9e3a0
+		}
7a9e3a0
+	}
7a9e3a0
+	return -ENODEV;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_hash_stats_init(struct team *team,
7a9e3a0
+			      struct team_option_inst_info *info)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	unsigned char hash = info->array_index;
7a9e3a0
+
7a9e3a0
+	lb_priv->ex->stats.info[hash].opt_inst_info = info;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_hash_stats_get(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	unsigned char hash = ctx->info->array_index;
7a9e3a0
+
7a9e3a0
+	ctx->data.bin_val.ptr = &lb_priv->ex->stats.info[hash].stats;
7a9e3a0
+	ctx->data.bin_val.len = sizeof(struct lb_stats);
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_port_stats_init(struct team *team,
7a9e3a0
+			      struct team_option_inst_info *info)
7a9e3a0
+{
7a9e3a0
+	struct team_port *port = info->port;
7a9e3a0
+	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
7a9e3a0
+
7a9e3a0
+	lb_port_priv->stats_info.opt_inst_info = info;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_port_stats_get(struct team *team, struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct team_port *port = ctx->info->port;
7a9e3a0
+	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
7a9e3a0
+
7a9e3a0
+	ctx->data.bin_val.ptr = &lb_port_priv->stats_info.stats;
7a9e3a0
+	ctx->data.bin_val.len = sizeof(struct lb_stats);
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void __lb_stats_info_refresh_prepare(struct lb_stats_info *s_info)
7a9e3a0
+{
7a9e3a0
+	memcpy(&s_info->last_stats, &s_info->stats, sizeof(struct lb_stats));
7a9e3a0
+	memset(&s_info->stats, 0, sizeof(struct lb_stats));
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static bool __lb_stats_info_refresh_check(struct lb_stats_info *s_info,
7a9e3a0
+					  struct team *team)
7a9e3a0
+{
7a9e3a0
+	if (memcmp(&s_info->last_stats, &s_info->stats,
7a9e3a0
+	    sizeof(struct lb_stats))) {
7a9e3a0
+		team_option_inst_set_change(s_info->opt_inst_info);
7a9e3a0
+		return true;
7a9e3a0
+	}
7a9e3a0
+	return false;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void __lb_one_cpu_stats_add(struct lb_stats *acc_stats,
7a9e3a0
+				   struct lb_stats *cpu_stats,
7a9e3a0
+				   struct u64_stats_sync *syncp)
7a9e3a0
+{
7a9e3a0
+	unsigned int start;
7a9e3a0
+	struct lb_stats tmp;
7a9e3a0
+
7a9e3a0
+	do {
7a9e3a0
+		start = u64_stats_fetch_begin_bh(syncp);
7a9e3a0
+		tmp.tx_bytes = cpu_stats->tx_bytes;
7a9e3a0
+	} while (u64_stats_fetch_retry_bh(syncp, start));
7a9e3a0
+	acc_stats->tx_bytes += tmp.tx_bytes;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void lb_stats_refresh(struct work_struct *work)
7a9e3a0
+{
7a9e3a0
+	struct team *team;
7a9e3a0
+	struct lb_priv *lb_priv;
7a9e3a0
+	struct lb_priv_ex *lb_priv_ex;
7a9e3a0
+	struct lb_pcpu_stats *pcpu_stats;
7a9e3a0
+	struct lb_stats *stats;
7a9e3a0
+	struct lb_stats_info *s_info;
7a9e3a0
+	struct team_port *port;
7a9e3a0
+	bool changed = false;
7a9e3a0
+	int i;
7a9e3a0
+	int j;
7a9e3a0
+
7a9e3a0
+	lb_priv_ex = container_of(work, struct lb_priv_ex,
7a9e3a0
+				  stats.refresh_dw.work);
7a9e3a0
+
7a9e3a0
+	team = lb_priv_ex->team;
7a9e3a0
+	lb_priv = get_lb_priv(team);
7a9e3a0
+
7a9e3a0
+	if (!mutex_trylock(&team->lock)) {
7a9e3a0
+		schedule_delayed_work(&lb_priv_ex->stats.refresh_dw, 0);
7a9e3a0
+		return;
7a9e3a0
+	}
7a9e3a0
+
7a9e3a0
+	for (j = 0; j < LB_TX_HASHTABLE_SIZE; j++) {
7a9e3a0
+		s_info = &lb_priv->ex->stats.info[j];
7a9e3a0
+		__lb_stats_info_refresh_prepare(s_info);
7a9e3a0
+		for_each_possible_cpu(i) {
7a9e3a0
+			pcpu_stats = per_cpu_ptr(lb_priv->pcpu_stats, i);
7a9e3a0
+			stats = &pcpu_stats->hash_stats[j];
7a9e3a0
+			__lb_one_cpu_stats_add(&s_info->stats, stats,
7a9e3a0
+					       &pcpu_stats->syncp);
7a9e3a0
+		}
7a9e3a0
+		changed |= __lb_stats_info_refresh_check(s_info, team);
7a9e3a0
+	}
7a9e3a0
+
7a9e3a0
+	list_for_each_entry(port, &team->port_list, list) {
7a9e3a0
+		struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
7a9e3a0
+
7a9e3a0
+		s_info = &lb_port_priv->stats_info;
7a9e3a0
+		__lb_stats_info_refresh_prepare(s_info);
7a9e3a0
+		for_each_possible_cpu(i) {
7a9e3a0
+			pcpu_stats = per_cpu_ptr(lb_priv->pcpu_stats, i);
7a9e3a0
+			stats = per_cpu_ptr(lb_port_priv->pcpu_stats, i);
7a9e3a0
+			__lb_one_cpu_stats_add(&s_info->stats, stats,
7a9e3a0
+					       &pcpu_stats->syncp);
7a9e3a0
+		}
7a9e3a0
+		changed |= __lb_stats_info_refresh_check(s_info, team);
7a9e3a0
+	}
7a9e3a0
+
7a9e3a0
+	if (changed)
7a9e3a0
+		team_options_change_check(team);
7a9e3a0
+
7a9e3a0
+	schedule_delayed_work(&lb_priv_ex->stats.refresh_dw,
7a9e3a0
+			      (lb_priv_ex->stats.refresh_interval * HZ) / 10);
7a9e3a0
+
7a9e3a0
+	mutex_unlock(&team->lock);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_stats_refresh_interval_get(struct team *team,
7a9e3a0
+					 struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+
7a9e3a0
+	ctx->data.u32_val = lb_priv->ex->stats.refresh_interval;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_stats_refresh_interval_set(struct team *team,
7a9e3a0
+					 struct team_gsetter_ctx *ctx)
7a9e3a0
+{
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	unsigned int interval;
7a9e3a0
+
7a9e3a0
+	interval = ctx->data.u32_val;
7a9e3a0
+	if (lb_priv->ex->stats.refresh_interval == interval)
7a9e3a0
+		return 0;
7a9e3a0
+	lb_priv->ex->stats.refresh_interval = interval;
7a9e3a0
+	if (interval)
7a9e3a0
+		schedule_delayed_work(&lb_priv->ex->stats.refresh_dw, 0);
7a9e3a0
+	else
7a9e3a0
+		cancel_delayed_work(&lb_priv->ex->stats.refresh_dw);
7a9e3a0
 	return 0;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
@@ -128,30 +532,125 @@ static const struct team_option lb_options[] = {
7a9e3a0
 		.getter = lb_bpf_func_get,
7a9e3a0
 		.setter = lb_bpf_func_set,
7a9e3a0
 	},
7a9e3a0
+	{
7a9e3a0
+		.name = "lb_tx_method",
7a9e3a0
+		.type = TEAM_OPTION_TYPE_STRING,
7a9e3a0
+		.getter = lb_tx_method_get,
7a9e3a0
+		.setter = lb_tx_method_set,
7a9e3a0
+	},
7a9e3a0
+	{
7a9e3a0
+		.name = "lb_tx_hash_to_port_mapping",
7a9e3a0
+		.array_size = LB_TX_HASHTABLE_SIZE,
7a9e3a0
+		.type = TEAM_OPTION_TYPE_U32,
7a9e3a0
+		.init = lb_tx_hash_to_port_mapping_init,
7a9e3a0
+		.getter = lb_tx_hash_to_port_mapping_get,
7a9e3a0
+		.setter = lb_tx_hash_to_port_mapping_set,
7a9e3a0
+	},
7a9e3a0
+	{
7a9e3a0
+		.name = "lb_hash_stats",
7a9e3a0
+		.array_size = LB_TX_HASHTABLE_SIZE,
7a9e3a0
+		.type = TEAM_OPTION_TYPE_BINARY,
7a9e3a0
+		.init = lb_hash_stats_init,
7a9e3a0
+		.getter = lb_hash_stats_get,
7a9e3a0
+	},
7a9e3a0
+	{
7a9e3a0
+		.name = "lb_port_stats",
7a9e3a0
+		.per_port = true,
7a9e3a0
+		.type = TEAM_OPTION_TYPE_BINARY,
7a9e3a0
+		.init = lb_port_stats_init,
7a9e3a0
+		.getter = lb_port_stats_get,
7a9e3a0
+	},
7a9e3a0
+	{
7a9e3a0
+		.name = "lb_stats_refresh_interval",
7a9e3a0
+		.type = TEAM_OPTION_TYPE_U32,
7a9e3a0
+		.getter = lb_stats_refresh_interval_get,
7a9e3a0
+		.setter = lb_stats_refresh_interval_set,
7a9e3a0
+	},
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
 static int lb_init(struct team *team)
7a9e3a0
 {
7a9e3a0
-	return team_options_register(team, lb_options,
7a9e3a0
-				     ARRAY_SIZE(lb_options));
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+	lb_select_tx_port_func_t *func;
7a9e3a0
+	int err;
7a9e3a0
+
7a9e3a0
+	/* set default tx port selector */
7a9e3a0
+	func = lb_select_tx_port_get_func("hash");
7a9e3a0
+	BUG_ON(!func);
7a9e3a0
+	rcu_assign_pointer(lb_priv->select_tx_port_func, func);
7a9e3a0
+
7a9e3a0
+	lb_priv->ex = kzalloc(sizeof(*lb_priv->ex), GFP_KERNEL);
7a9e3a0
+	if (!lb_priv->ex)
7a9e3a0
+		return -ENOMEM;
7a9e3a0
+	lb_priv->ex->team = team;
7a9e3a0
+
7a9e3a0
+	lb_priv->pcpu_stats = alloc_percpu(struct lb_pcpu_stats);
7a9e3a0
+	if (!lb_priv->pcpu_stats) {
7a9e3a0
+		err = -ENOMEM;
7a9e3a0
+		goto err_alloc_pcpu_stats;
7a9e3a0
+	}
7a9e3a0
+
7a9e3a0
+	INIT_DELAYED_WORK(&lb_priv->ex->stats.refresh_dw, lb_stats_refresh);
7a9e3a0
+
7a9e3a0
+	err = team_options_register(team, lb_options, ARRAY_SIZE(lb_options));
7a9e3a0
+	if (err)
7a9e3a0
+		goto err_options_register;
7a9e3a0
+	return 0;
7a9e3a0
+
7a9e3a0
+err_options_register:
7a9e3a0
+	free_percpu(lb_priv->pcpu_stats);
7a9e3a0
+err_alloc_pcpu_stats:
7a9e3a0
+	kfree(lb_priv->ex);
7a9e3a0
+	return err;
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static void lb_exit(struct team *team)
7a9e3a0
 {
7a9e3a0
+	struct lb_priv *lb_priv = get_lb_priv(team);
7a9e3a0
+
7a9e3a0
 	team_options_unregister(team, lb_options,
7a9e3a0
 				ARRAY_SIZE(lb_options));
7a9e3a0
+	cancel_delayed_work_sync(&lb_priv->ex->stats.refresh_dw);
7a9e3a0
+	free_percpu(lb_priv->pcpu_stats);
7a9e3a0
+	kfree(lb_priv->ex);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static int lb_port_enter(struct team *team, struct team_port *port)
7a9e3a0
+{
7a9e3a0
+	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
7a9e3a0
+
7a9e3a0
+	lb_port_priv->pcpu_stats = alloc_percpu(struct lb_stats);
7a9e3a0
+	if (!lb_port_priv->pcpu_stats)
7a9e3a0
+		return -ENOMEM;
7a9e3a0
+	return 0;
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void lb_port_leave(struct team *team, struct team_port *port)
7a9e3a0
+{
7a9e3a0
+	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
7a9e3a0
+
7a9e3a0
+	free_percpu(lb_port_priv->pcpu_stats);
7a9e3a0
+}
7a9e3a0
+
7a9e3a0
+static void lb_port_disabled(struct team *team, struct team_port *port)
7a9e3a0
+{
7a9e3a0
+	lb_tx_hash_to_port_mapping_null_port(team, port);
7a9e3a0
 }
7a9e3a0
 
7a9e3a0
 static const struct team_mode_ops lb_mode_ops = {
7a9e3a0
 	.init			= lb_init,
7a9e3a0
 	.exit			= lb_exit,
7a9e3a0
+	.port_enter		= lb_port_enter,
7a9e3a0
+	.port_leave		= lb_port_leave,
7a9e3a0
+	.port_disabled		= lb_port_disabled,
7a9e3a0
 	.transmit		= lb_transmit,
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
-static struct team_mode lb_mode = {
7a9e3a0
+static const struct team_mode lb_mode = {
7a9e3a0
 	.kind		= "loadbalance",
7a9e3a0
 	.owner		= THIS_MODULE,
7a9e3a0
 	.priv_size	= sizeof(struct lb_priv),
7a9e3a0
+	.port_priv_size	= sizeof(struct lb_port_priv),
7a9e3a0
 	.ops		= &lb_mode_ops,
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
diff --git a/drivers/net/team/team_mode_roundrobin.c b/drivers/net/team/team_mode_roundrobin.c
7a9e3a0
index 6abfbdc..52dd0ec 100644
7a9e3a0
--- a/drivers/net/team/team_mode_roundrobin.c
7a9e3a0
+++ b/drivers/net/team/team_mode_roundrobin.c
7a9e3a0
@@ -1,5 +1,5 @@
7a9e3a0
 /*
7a9e3a0
- * net/drivers/team/team_mode_roundrobin.c - Round-robin mode for team
7a9e3a0
+ * drivers/net/team/team_mode_roundrobin.c - Round-robin mode for team
7a9e3a0
  * Copyright (c) 2011 Jiri Pirko <jpirko@redhat.com>
7a9e3a0
  *
7a9e3a0
  * This program is free software; you can redistribute it and/or modify
7a9e3a0
@@ -81,7 +81,7 @@ static const struct team_mode_ops rr_mode_ops = {
7a9e3a0
 	.port_change_mac	= rr_port_change_mac,
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
-static struct team_mode rr_mode = {
7a9e3a0
+static const struct team_mode rr_mode = {
7a9e3a0
 	.kind		= "roundrobin",
7a9e3a0
 	.owner		= THIS_MODULE,
7a9e3a0
 	.priv_size	= sizeof(struct rr_priv),
7a9e3a0
diff --git a/include/linux/if_team.h b/include/linux/if_team.h
7a9e3a0
index 8185f57..99efd60 100644
7a9e3a0
--- a/include/linux/if_team.h
7a9e3a0
+++ b/include/linux/if_team.h
7a9e3a0
@@ -60,9 +60,11 @@ struct team_port {
7a9e3a0
 		unsigned int mtu;
7a9e3a0
 	} orig;
7a9e3a0
 
7a9e3a0
-	struct rcu_head rcu;
7a9e3a0
+	long mode_priv[0];
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
+extern bool team_port_enabled(struct team_port *port);
7a9e3a0
+
7a9e3a0
 struct team_mode_ops {
7a9e3a0
 	int (*init)(struct team *team);
7a9e3a0
 	void (*exit)(struct team *team);
7a9e3a0
@@ -73,6 +75,8 @@ struct team_mode_ops {
7a9e3a0
 	int (*port_enter)(struct team *team, struct team_port *port);
7a9e3a0
 	void (*port_leave)(struct team *team, struct team_port *port);
7a9e3a0
 	void (*port_change_mac)(struct team *team, struct team_port *port);
7a9e3a0
+	void (*port_enabled)(struct team *team, struct team_port *port);
7a9e3a0
+	void (*port_disabled)(struct team *team, struct team_port *port);
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
 enum team_option_type {
7a9e3a0
@@ -82,6 +86,11 @@ enum team_option_type {
7a9e3a0
 	TEAM_OPTION_TYPE_BOOL,
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
+struct team_option_inst_info {
7a9e3a0
+	u32 array_index;
7a9e3a0
+	struct team_port *port; /* != NULL if per-port */
7a9e3a0
+};
7a9e3a0
+
7a9e3a0
 struct team_gsetter_ctx {
7a9e3a0
 	union {
7a9e3a0
 		u32 u32_val;
7a9e3a0
@@ -92,23 +101,28 @@ struct team_gsetter_ctx {
7a9e3a0
 		} bin_val;
7a9e3a0
 		bool bool_val;
7a9e3a0
 	} data;
7a9e3a0
-	struct team_port *port;
7a9e3a0
+	struct team_option_inst_info *info;
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
 struct team_option {
7a9e3a0
 	struct list_head list;
7a9e3a0
 	const char *name;
7a9e3a0
 	bool per_port;
7a9e3a0
+	unsigned int array_size; /* != 0 means the option is array */
7a9e3a0
 	enum team_option_type type;
7a9e3a0
+	int (*init)(struct team *team, struct team_option_inst_info *info);
7a9e3a0
 	int (*getter)(struct team *team, struct team_gsetter_ctx *ctx);
7a9e3a0
 	int (*setter)(struct team *team, struct team_gsetter_ctx *ctx);
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
+extern void team_option_inst_set_change(struct team_option_inst_info *opt_inst_info);
7a9e3a0
+extern void team_options_change_check(struct team *team);
7a9e3a0
+
7a9e3a0
 struct team_mode {
7a9e3a0
-	struct list_head list;
7a9e3a0
 	const char *kind;
7a9e3a0
 	struct module *owner;
7a9e3a0
 	size_t priv_size;
7a9e3a0
+	size_t port_priv_size;
7a9e3a0
 	const struct team_mode_ops *ops;
7a9e3a0
 };
7a9e3a0
 
7a9e3a0
@@ -178,8 +192,8 @@ extern int team_options_register(struct team *team,
7a9e3a0
 extern void team_options_unregister(struct team *team,
7a9e3a0
 				    const struct team_option *option,
7a9e3a0
 				    size_t option_count);
7a9e3a0
-extern int team_mode_register(struct team_mode *mode);
7a9e3a0
-extern int team_mode_unregister(struct team_mode *mode);
7a9e3a0
+extern int team_mode_register(const struct team_mode *mode);
7a9e3a0
+extern void team_mode_unregister(const struct team_mode *mode);
7a9e3a0
 
7a9e3a0
 #endif /* __KERNEL__ */
7a9e3a0
 
7a9e3a0
@@ -241,6 +255,7 @@ enum {
7a9e3a0
 	TEAM_ATTR_OPTION_DATA,		/* dynamic */
7a9e3a0
 	TEAM_ATTR_OPTION_REMOVED,	/* flag */
7a9e3a0
 	TEAM_ATTR_OPTION_PORT_IFINDEX,	/* u32 */ /* for per-port options */
7a9e3a0
+	TEAM_ATTR_OPTION_ARRAY_INDEX,	/* u32 */ /* for array options */
7a9e3a0
 
7a9e3a0
 	__TEAM_ATTR_OPTION_MAX,
7a9e3a0
 	TEAM_ATTR_OPTION_MAX = __TEAM_ATTR_OPTION_MAX - 1,
7a9e3a0
_______________________________________________
7a9e3a0
kernel mailing list
7a9e3a0
kernel@lists.fedoraproject.org
7a9e3a0
https://admin.fedoraproject.org/mailman/listinfo/kernel