team: add support for per-port options
authorJiri Pirko <jpirko@redhat.com>
Tue, 10 Apr 2012 05:15:42 +0000 (05:15 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 11 Apr 2012 14:03:51 +0000 (10:03 -0400)
This patch allows to create per-port options. That becomes handy for all
sorts of stuff, for example for userspace driven link-state, 802.3ad
implementation and so on.

Signed-off-by: Jiri Pirko <jpirko@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/team/team.c
drivers/net/team/team_mode_activebackup.c
drivers/net/team/team_mode_loadbalance.c
include/linux/if_team.h

index ea96f82..eaf8441 100644 (file)
@@ -81,7 +81,16 @@ EXPORT_SYMBOL(team_port_set_team_mac);
  * Options handling
  *******************/
 
-struct team_option *__team_find_option(struct team *team, const char *opt_name)
+struct team_option_inst { /* One for each option instance */
+       struct list_head list;
+       struct team_option *option;
+       struct team_port *port; /* != NULL if per-port */
+       bool changed;
+       bool removed;
+};
+
+static struct team_option *__team_find_option(struct team *team,
+                                             const char *opt_name)
 {
        struct team_option *option;
 
@@ -92,9 +101,121 @@ struct team_option *__team_find_option(struct team *team, const char *opt_name)
        return NULL;
 }
 
-int __team_options_register(struct team *team,
-                           const struct team_option *option,
-                           size_t option_count)
+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);
+       kfree(opt_inst);
+}
+
+static void __team_option_inst_del_option(struct team *team,
+                                         struct team_option *option)
+{
+       struct team_option_inst *opt_inst, *tmp;
+
+       list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) {
+               if (opt_inst->option == option)
+                       __team_option_inst_del(opt_inst);
+       }
+}
+
+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);
+
+       list_for_each_entry(port, &team->port_list, list) {
+               err = __team_option_inst_add(team, option, port);
+               if (err)
+                       goto inst_del_option;
+       }
+       return 0;
+
+inst_del_option:
+       __team_option_inst_del_option(team, option);
+       return err;
+}
+
+static void __team_option_inst_mark_removed_option(struct team *team,
+                                                  struct team_option *option)
+{
+       struct team_option_inst *opt_inst;
+
+       list_for_each_entry(opt_inst, &team->option_inst_list, list) {
+               if (opt_inst->option == option) {
+                       opt_inst->changed = true;
+                       opt_inst->removed = true;
+               }
+       }
+}
+
+static void __team_option_inst_del_port(struct team *team,
+                                       struct team_port *port)
+{
+       struct team_option_inst *opt_inst, *tmp;
+
+       list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) {
+               if (opt_inst->option->per_port &&
+                   opt_inst->port == port)
+                       __team_option_inst_del(opt_inst);
+       }
+}
+
+static int __team_option_inst_add_port(struct team *team,
+                                      struct team_port *port)
+{
+       struct team_option *option;
+       int err;
+
+       list_for_each_entry(option, &team->option_list, list) {
+               if (!option->per_port)
+                       continue;
+               err = __team_option_inst_add(team, option, port);
+               if (err)
+                       goto inst_del_port;
+       }
+       return 0;
+
+inst_del_port:
+       __team_option_inst_del_port(team, port);
+       return err;
+}
+
+static void __team_option_inst_mark_removed_port(struct team *team,
+                                                struct team_port *port)
+{
+       struct team_option_inst *opt_inst;
+
+       list_for_each_entry(opt_inst, &team->option_inst_list, list) {
+               if (opt_inst->port == port) {
+                       opt_inst->changed = true;
+                       opt_inst->removed = true;
+               }
+       }
+}
+
+static int __team_options_register(struct team *team,
+                                  const struct team_option *option,
+                                  size_t option_count)
 {
        int i;
        struct team_option **dst_opts;
@@ -107,26 +228,32 @@ int __team_options_register(struct team *team,
        for (i = 0; i < option_count; i++, option++) {
                if (__team_find_option(team, option->name)) {
                        err = -EEXIST;
-                       goto rollback;
+                       goto alloc_rollback;
                }
                dst_opts[i] = kmemdup(option, sizeof(*option), GFP_KERNEL);
                if (!dst_opts[i]) {
                        err = -ENOMEM;
-                       goto rollback;
+                       goto alloc_rollback;
                }
        }
 
        for (i = 0; i < option_count; i++) {
-               dst_opts[i]->changed = true;
-               dst_opts[i]->removed = false;
+               err = __team_option_inst_add_option(team, dst_opts[i]);
+               if (err)
+                       goto inst_rollback;
                list_add_tail(&dst_opts[i]->list, &team->option_list);
        }
 
        kfree(dst_opts);
        return 0;
 
-rollback:
-       for (i = 0; i < option_count; i++)
+inst_rollback:
+       for (i--; i >= 0; i--)
+               __team_option_inst_del_option(team, dst_opts[i]);
+
+       i = option_count - 1;
+alloc_rollback:
+       for (i--; i >= 0; i--)
                kfree(dst_opts[i]);
 
        kfree(dst_opts);
@@ -143,10 +270,8 @@ static void __team_options_mark_removed(struct team *team,
                struct team_option *del_opt;
 
                del_opt = __team_find_option(team, option->name);
-               if (del_opt) {
-                       del_opt->changed = true;
-                       del_opt->removed = true;
-               }
+               if (del_opt)
+                       __team_option_inst_mark_removed_option(team, del_opt);
        }
 }
 
@@ -161,6 +286,7 @@ static void __team_options_unregister(struct team *team,
 
                del_opt = __team_find_option(team, option->name);
                if (del_opt) {
+                       __team_option_inst_del_option(team, del_opt);
                        list_del(&del_opt->list);
                        kfree(del_opt);
                }
@@ -193,22 +319,42 @@ void team_options_unregister(struct team *team,
 }
 EXPORT_SYMBOL(team_options_unregister);
 
-static int team_option_get(struct team *team, struct team_option *option,
-                          void *arg)
+static int team_option_port_add(struct team *team, struct team_port *port)
 {
-       return option->getter(team, arg);
+       int err;
+
+       err = __team_option_inst_add_port(team, port);
+       if (err)
+               return err;
+       __team_options_change_check(team);
+       return 0;
 }
 
-static int team_option_set(struct team *team, struct team_option *option,
-                          void *arg)
+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)
+{
+       return opt_inst->option->getter(team, ctx);
+}
+
+static int team_option_set(struct team *team,
+                          struct team_option_inst *opt_inst,
+                          struct team_gsetter_ctx *ctx)
 {
        int err;
 
-       err = option->setter(team, arg);
+       err = opt_inst->option->setter(team, ctx);
        if (err)
                return err;
 
-       option->changed = true;
+       opt_inst->changed = true;
        __team_options_change_check(team);
        return err;
 }
@@ -642,6 +788,13 @@ static int team_port_add(struct team *team, struct net_device *port_dev)
                goto err_handler_register;
        }
 
+       err = team_option_port_add(team, port);
+       if (err) {
+               netdev_err(dev, "Device %s failed to add per-port options\n",
+                          portname);
+               goto err_option_port_add;
+       }
+
        team_port_list_add_port(team, port);
        team_adjust_ops(team);
        __team_compute_features(team);
@@ -651,6 +804,9 @@ static int team_port_add(struct team *team, struct net_device *port_dev)
 
        return 0;
 
+err_option_port_add:
+       netdev_rx_handler_unregister(port_dev);
+
 err_handler_register:
        netdev_set_master(port_dev, NULL);
 
@@ -690,6 +846,7 @@ static int team_port_del(struct team *team, struct net_device *port_dev)
        __team_port_change_check(port, false);
        team_port_list_del_port(team, port);
        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);
@@ -712,19 +869,15 @@ static int team_port_del(struct team *team, struct net_device *port_dev)
 
 static const char team_no_mode_kind[] = "*NOMODE*";
 
-static int team_mode_option_get(struct team *team, void *arg)
+static int team_mode_option_get(struct team *team, struct team_gsetter_ctx *ctx)
 {
-       const char **str = arg;
-
-       *str = team->mode ? team->mode->kind : team_no_mode_kind;
+       ctx->data.str_val = team->mode ? team->mode->kind : team_no_mode_kind;
        return 0;
 }
 
-static int team_mode_option_set(struct team *team, void *arg)
+static int team_mode_option_set(struct team *team, struct team_gsetter_ctx *ctx)
 {
-       const char **str = arg;
-
-       return team_change_mode(team, *str);
+       return team_change_mode(team, ctx->data.str_val);
 }
 
 static const struct team_option team_options[] = {
@@ -756,6 +909,7 @@ static int team_init(struct net_device *dev)
        team_adjust_ops(team);
 
        INIT_LIST_HEAD(&team->option_list);
+       INIT_LIST_HEAD(&team->option_inst_list);
        err = team_options_register(team, team_options, ARRAY_SIZE(team_options));
        if (err)
                goto err_options_register;
@@ -1238,7 +1392,8 @@ static int team_nl_fill_options_get(struct sk_buff *skb,
 {
        struct nlattr *option_list;
        void *hdr;
-       struct team_option *option;
+       struct team_option_inst *opt_inst;
+       int err;
 
        hdr = genlmsg_put(skb, pid, seq, &team_nl_family, flags,
                          TEAM_CMD_OPTIONS_GET);
@@ -1251,50 +1406,61 @@ static int team_nl_fill_options_get(struct sk_buff *skb,
        if (!option_list)
                return -EMSGSIZE;
 
-       list_for_each_entry(option, &team->option_list, list) {
+       list_for_each_entry(opt_inst, &team->option_inst_list, list) {
                struct nlattr *option_item;
-               long arg;
-               struct team_option_binary tbinary;
+               struct team_option *option = opt_inst->option;
+               struct team_gsetter_ctx ctx;
 
                /* Include only changed options if fill all mode is not on */
-               if (!fillall && !option->changed)
+               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 (option->changed) {
+               if (opt_inst->changed) {
                        if (nla_put_flag(skb, TEAM_ATTR_OPTION_CHANGED))
                                goto nla_put_failure;
-                       option->changed = false;
+                       opt_inst->changed = false;
                }
-               if (option->removed &&
+               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;
-                       team_option_get(team, option, &arg);
-                       if (nla_put_u32(skb, TEAM_ATTR_OPTION_DATA, arg))
+                       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;
-                       team_option_get(team, option, &arg);
+                       err = team_option_get(team, opt_inst, &ctx);
+                       if (err)
+                               goto errout;
                        if (nla_put_string(skb, TEAM_ATTR_OPTION_DATA,
-                                          (char *) arg))
+                                          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;
-                       arg = (long) &tbinary;
-                       team_option_get(team, option, &arg);
+                       err = team_option_get(team, opt_inst, &ctx);
+                       if (err)
+                               goto errout;
                        if (nla_put(skb, TEAM_ATTR_OPTION_DATA,
-                                   tbinary.data_len, tbinary.data))
+                                   ctx.data.bin_val.len, ctx.data.bin_val.ptr))
                                goto nla_put_failure;
                        break;
                default:
@@ -1307,8 +1473,10 @@ static int team_nl_fill_options_get(struct sk_buff *skb,
        return genlmsg_end(skb, hdr);
 
 nla_put_failure:
+       err = -EMSGSIZE;
+errout:
        genlmsg_cancel(skb, hdr);
-       return -EMSGSIZE;
+       return err;
 }
 
 static int team_nl_fill_options_get_all(struct sk_buff *skb,
@@ -1354,9 +1522,11 @@ 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 *mode_attrs[TEAM_ATTR_OPTION_MAX + 1];
+               struct nlattr *opt_attrs[TEAM_ATTR_OPTION_MAX + 1];
+               struct nlattr *attr_port_ifindex;
                enum team_option_type opt_type;
-               struct team_option *option;
+               int opt_port_ifindex = 0; /* != 0 for per-port options */
+               struct team_option_inst *opt_inst;
                char *opt_name;
                bool opt_found = false;
 
@@ -1364,17 +1534,17 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info)
                        err = -EINVAL;
                        goto team_put;
                }
-               err = nla_parse_nested(mode_attrs, TEAM_ATTR_OPTION_MAX,
+               err = nla_parse_nested(opt_attrs, TEAM_ATTR_OPTION_MAX,
                                       nl_option, team_nl_option_policy);
                if (err)
                        goto team_put;
-               if (!mode_attrs[TEAM_ATTR_OPTION_NAME] ||
-                   !mode_attrs[TEAM_ATTR_OPTION_TYPE] ||
-                   !mode_attrs[TEAM_ATTR_OPTION_DATA]) {
+               if (!opt_attrs[TEAM_ATTR_OPTION_NAME] ||
+                   !opt_attrs[TEAM_ATTR_OPTION_TYPE] ||
+                   !opt_attrs[TEAM_ATTR_OPTION_DATA]) {
                        err = -EINVAL;
                        goto team_put;
                }
-               switch (nla_get_u8(mode_attrs[TEAM_ATTR_OPTION_TYPE])) {
+               switch (nla_get_u8(opt_attrs[TEAM_ATTR_OPTION_TYPE])) {
                case NLA_U32:
                        opt_type = TEAM_OPTION_TYPE_U32;
                        break;
@@ -1388,39 +1558,47 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info)
                        goto team_put;
                }
 
-               opt_name = nla_data(mode_attrs[TEAM_ATTR_OPTION_NAME]);
-               list_for_each_entry(option, &team->option_list, list) {
-                       long arg;
+               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);
+
+               list_for_each_entry(opt_inst, &team->option_inst_list, list) {
+                       struct team_option *option = opt_inst->option;
                        struct nlattr *opt_data_attr;
-                       struct team_option_binary tbinary;
+                       struct team_gsetter_ctx ctx;
                        int data_len;
+                       int tmp_ifindex;
 
+                       tmp_ifindex = opt_inst->port ?
+                                     opt_inst->port->dev->ifindex : 0;
                        if (option->type != opt_type ||
-                           strcmp(option->name, opt_name))
+                           strcmp(option->name, opt_name) ||
+                           tmp_ifindex != opt_port_ifindex)
                                continue;
                        opt_found = true;
-                       opt_data_attr = mode_attrs[TEAM_ATTR_OPTION_DATA];
+                       opt_data_attr = opt_attrs[TEAM_ATTR_OPTION_DATA];
                        data_len = nla_len(opt_data_attr);
+                       ctx.port = opt_inst->port;
                        switch (opt_type) {
                        case TEAM_OPTION_TYPE_U32:
-                               arg = nla_get_u32(opt_data_attr);
+                               ctx.data.u32_val = nla_get_u32(opt_data_attr);
                                break;
                        case TEAM_OPTION_TYPE_STRING:
                                if (data_len > TEAM_STRING_MAX_LEN) {
                                        err = -EINVAL;
                                        goto team_put;
                                }
-                               arg = (long) nla_data(opt_data_attr);
+                               ctx.data.str_val = nla_data(opt_data_attr);
                                break;
                        case TEAM_OPTION_TYPE_BINARY:
-                               tbinary.data_len = data_len;
-                               tbinary.data = nla_data(opt_data_attr);
-                               arg = (long) &tbinary;
+                               ctx.data.bin_val.len = data_len;
+                               ctx.data.bin_val.ptr = nla_data(opt_data_attr);
                                break;
                        default:
                                BUG();
                        }
-                       err = team_option_set(team, option, &arg);
+                       err = team_option_set(team, opt_inst, &ctx);
                        if (err)
                                goto team_put;
                }
index f4d960e..6cde1ab 100644 (file)
@@ -59,23 +59,21 @@ static void ab_port_leave(struct team *team, struct team_port *port)
                RCU_INIT_POINTER(ab_priv(team)->active_port, NULL);
 }
 
-static int ab_active_port_get(struct team *team, void *arg)
+static int ab_active_port_get(struct team *team, struct team_gsetter_ctx *ctx)
 {
-       u32 *ifindex = arg;
-
-       *ifindex = 0;
        if (ab_priv(team)->active_port)
-               *ifindex = ab_priv(team)->active_port->dev->ifindex;
+               ctx->data.u32_val = ab_priv(team)->active_port->dev->ifindex;
+       else
+               ctx->data.u32_val = 0;
        return 0;
 }
 
-static int ab_active_port_set(struct team *team, void *arg)
+static int ab_active_port_set(struct team *team, struct team_gsetter_ctx *ctx)
 {
-       u32 *ifindex = arg;
        struct team_port *port;
 
        list_for_each_entry_rcu(port, &team->port_list, list) {
-               if (port->dev->ifindex == *ifindex) {
+               if (port->dev->ifindex == ctx->data.u32_val) {
                        rcu_assign_pointer(ab_priv(team)->active_port, port);
                        return 0;
                }
index ed20f39..167cdb4 100644 (file)
@@ -52,22 +52,21 @@ drop:
        return false;
 }
 
-static int lb_bpf_func_get(struct team *team, void *arg)
+static int lb_bpf_func_get(struct team *team, struct team_gsetter_ctx *ctx)
 {
-       struct team_option_binary *tbinary = team_optarg_tbinary(arg);
-
-       memset(tbinary, 0, sizeof(*tbinary));
-       if (!lb_priv(team)->orig_fprog)
+       if (!lb_priv(team)->orig_fprog) {
+               ctx->data.bin_val.len = 0;
+               ctx->data.bin_val.ptr = NULL;
                return 0;
-
-       tbinary->data_len = lb_priv(team)->orig_fprog->len *
-                           sizeof(struct sock_filter);
-       tbinary->data = lb_priv(team)->orig_fprog->filter;
+       }
+       ctx->data.bin_val.len = lb_priv(team)->orig_fprog->len *
+                               sizeof(struct sock_filter);
+       ctx->data.bin_val.ptr = lb_priv(team)->orig_fprog->filter;
        return 0;
 }
 
 static int __fprog_create(struct sock_fprog **pfprog, u32 data_len,
-                         void *data)
+                         const void *data)
 {
        struct sock_fprog *fprog;
        struct sock_filter *filter = (struct sock_filter *) data;
@@ -93,16 +92,15 @@ static void __fprog_destroy(struct sock_fprog *fprog)
        kfree(fprog);
 }
 
-static int lb_bpf_func_set(struct team *team, void *arg)
+static int lb_bpf_func_set(struct team *team, struct team_gsetter_ctx *ctx)
 {
-       struct team_option_binary *tbinary = team_optarg_tbinary(arg);
        struct sk_filter *fp = NULL;
        struct sock_fprog *fprog = NULL;
        int err;
 
-       if (tbinary->data_len) {
-               err = __fprog_create(&fprog, tbinary->data_len,
-                                    tbinary->data);
+       if (ctx->data.bin_val.len) {
+               err = __fprog_create(&fprog, ctx->data.bin_val.len,
+                                    ctx->data.bin_val.ptr);
                if (err)
                        return err;
                err = sk_unattached_filter_create(&fp, fprog);
index 41163ac..6f27c84 100644 (file)
@@ -71,25 +71,27 @@ enum team_option_type {
        TEAM_OPTION_TYPE_BINARY,
 };
 
+struct team_gsetter_ctx {
+       union {
+               u32 u32_val;
+               const char *str_val;
+               struct {
+                       const void *ptr;
+                       u32 len;
+               } bin_val;
+       } data;
+       struct team_port *port;
+};
+
 struct team_option {
        struct list_head list;
        const char *name;
+       bool per_port;
        enum team_option_type type;
-       int (*getter)(struct team *team, void *arg);
-       int (*setter)(struct team *team, void *arg);
-
-       /* Custom gennetlink interface related flags */
-       bool changed;
-       bool removed;
+       int (*getter)(struct team *team, struct team_gsetter_ctx *ctx);
+       int (*setter)(struct team *team, struct team_gsetter_ctx *ctx);
 };
 
-struct team_option_binary {
-       u32 data_len;
-       void *data;
-};
-
-#define team_optarg_tbinary(arg) (*((struct team_option_binary **) arg))
-
 struct team_mode {
        struct list_head list;
        const char *kind;
@@ -118,6 +120,7 @@ struct team {
        struct list_head port_list;
 
        struct list_head option_list;
+       struct list_head option_inst_list; /* list of option instances */
 
        const struct team_mode *mode;
        struct team_mode_ops ops;
@@ -224,6 +227,7 @@ enum {
        TEAM_ATTR_OPTION_TYPE,          /* u8 */
        TEAM_ATTR_OPTION_DATA,          /* dynamic */
        TEAM_ATTR_OPTION_REMOVED,       /* flag */
+       TEAM_ATTR_OPTION_PORT_IFINDEX,  /* u32 */ /* for per-port options */
 
        __TEAM_ATTR_OPTION_MAX,
        TEAM_ATTR_OPTION_MAX = __TEAM_ATTR_OPTION_MAX - 1,