diff mbox

[net-next,1/5] team: add support for per-port options

Message ID 1334070946-7704-2-git-send-email-jpirko@redhat.com
State Accepted, archived
Delegated to: David Miller
Headers show

Commit Message

Jiri Pirko April 10, 2012, 3:15 p.m. UTC
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>
---
 drivers/net/team/team.c                   |  306 +++++++++++++++++++++++------
 drivers/net/team/team_mode_activebackup.c |   14 +-
 drivers/net/team/team_mode_loadbalance.c  |   28 ++--
 include/linux/if_team.h                   |   30 ++--
 4 files changed, 278 insertions(+), 100 deletions(-)

Comments

David Miller April 10, 2012, 6:33 p.m. UTC | #1
From: Jiri Pirko <jpirko@redhat.com>
Date: Tue, 10 Apr 2012 17:15:42 +0200

> @@ -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;
> +};
> +

All this indirection... just simply embed struct team_option into
struct team_option_inst instead of using a pointer, and allocate a
full team_option_inst where you currently memdup in the options.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jiri Pirko April 11, 2012, 5:43 a.m. UTC | #2
Tue, Apr 10, 2012 at 08:33:56PM CEST, davem@davemloft.net wrote:
>From: Jiri Pirko <jpirko@redhat.com>
>Date: Tue, 10 Apr 2012 17:15:42 +0200
>
>> @@ -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;
>> +};
>> +
>
>All this indirection... just simply embed struct team_option into
>struct team_option_inst instead of using a pointer, and allocate a
>full team_option_inst where you currently memdup in the options.

Well the list of options is needed alone. When port is added/removed, this list
gets iterated over and instances are created/deleted. Therefore I put
pointer to option to option instance struct to save memory (and also to
be nicer)
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller April 11, 2012, 1:58 p.m. UTC | #3
From: Jiri Pirko <jpirko@redhat.com>
Date: Wed, 11 Apr 2012 07:43:51 +0200

> Tue, Apr 10, 2012 at 08:33:56PM CEST, davem@davemloft.net wrote:
>>From: Jiri Pirko <jpirko@redhat.com>
>>Date: Tue, 10 Apr 2012 17:15:42 +0200
>>
>>> @@ -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;
>>> +};
>>> +
>>
>>All this indirection... just simply embed struct team_option into
>>struct team_option_inst instead of using a pointer, and allocate a
>>full team_option_inst where you currently memdup in the options.
> 
> Well the list of options is needed alone. When port is added/removed, this list
> gets iterated over and instances are created/deleted. Therefore I put
> pointer to option to option instance struct to save memory (and also to
> be nicer)

Fair enough, I'll apply this series, thanks.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c
index ea96f82..eaf8441 100644
--- a/drivers/net/team/team.c
+++ b/drivers/net/team/team.c
@@ -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;
 		}
diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c
index f4d960e..6cde1ab 100644
--- a/drivers/net/team/team_mode_activebackup.c
+++ b/drivers/net/team/team_mode_activebackup.c
@@ -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;
 		}
diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c
index ed20f39..167cdb4 100644
--- a/drivers/net/team/team_mode_loadbalance.c
+++ b/drivers/net/team/team_mode_loadbalance.c
@@ -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);
diff --git a/include/linux/if_team.h b/include/linux/if_team.h
index 41163ac..6f27c84 100644
--- a/include/linux/if_team.h
+++ b/include/linux/if_team.h
@@ -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,