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