diff options
Diffstat (limited to 'team-update-from-net-next.patch')
-rw-r--r-- | team-update-from-net-next.patch | 1839 |
1 files changed, 0 insertions, 1839 deletions
diff --git a/team-update-from-net-next.patch b/team-update-from-net-next.patch deleted file mode 100644 index 14794ce08..000000000 --- a/team-update-from-net-next.patch +++ /dev/null @@ -1,1839 +0,0 @@ -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 |