Commit 80f7c668 authored by Jiri Pirko's avatar Jiri Pirko Committed by David S. Miller

team: add support for per-port options

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: default avatarJiri Pirko <jpirko@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7a5cc242
......@@ -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,7 +101,119 @@ struct team_option *__team_find_option(struct team *team, const char *opt_name)
return NULL;
}
int __team_options_register(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;
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)
{
......@@ -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)
{
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)
{
return option->getter(team, arg);
__team_option_inst_mark_removed_port(team, port);
__team_options_change_check(team);
__team_option_inst_del_port(team, port);
}
static int team_option_set(struct team *team, struct team_option *option,
void *arg)
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;
}
......
......@@ -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;
}
......
......@@ -52,22 +52,21 @@ static bool lb_transmit(struct team *team, struct sk_buff *skb)
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 *
}
ctx->data.bin_val.len = lb_priv(team)->orig_fprog->len *
sizeof(struct sock_filter);
tbinary->data = lb_priv(team)->orig_fprog->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);
......
......@@ -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,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment