Commit 47f8328b authored by Scott Feldman's avatar Scott Feldman Committed by David S. Miller

switchdev: add new switchdev bridge setlink

Add new switchdev_port_bridge_setlink that can be used by drivers
implementing .ndo_bridge_setlink to set switchdev bridge attributes.
Basically turn the raw rtnl_bridge_setlink netlink into switchdev attr
sets.  Proper netlink attr policy checking is done on the protinfo part of
the netlink msg.

Currently, for protinfo, only bridge port attrs BR_LEARNING and
BR_LEARNING_SYNC are parsed and passed to port driver.

For afspec, VLAN objs are passed so switchdev driver can set VLANs assigned
to SELF.  To illustrate with iproute2 cmd, we have:

	bridge vlan add vid 10 dev sw1p1 self master

To add VLAN 10 to port sw1p1 for both the bridge (master) and the device
(self).
Signed-off-by: default avatarScott Feldman <sfeldma@gmail.com>
Acked-by: default avatarJiri Pirko <jiri@resnulli.us>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 6004c867
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/if_bridge.h>
#include <net/ip_fib.h> #include <net/ip_fib.h>
#include <net/switchdev.h> #include <net/switchdev.h>
...@@ -357,28 +358,156 @@ int call_switchdev_notifiers(unsigned long val, struct net_device *dev, ...@@ -357,28 +358,156 @@ int call_switchdev_notifiers(unsigned long val, struct net_device *dev,
} }
EXPORT_SYMBOL_GPL(call_switchdev_notifiers); EXPORT_SYMBOL_GPL(call_switchdev_notifiers);
static int switchdev_port_br_setflag(struct net_device *dev,
struct nlattr *nlattr,
unsigned long brport_flag)
{
struct switchdev_attr attr = {
.id = SWITCHDEV_ATTR_PORT_BRIDGE_FLAGS,
};
u8 flag = nla_get_u8(nlattr);
int err;
err = switchdev_port_attr_get(dev, &attr);
if (err)
return err;
if (flag)
attr.brport_flags |= brport_flag;
else
attr.brport_flags &= ~brport_flag;
return switchdev_port_attr_set(dev, &attr);
}
static const struct nla_policy
switchdev_port_bridge_policy[IFLA_BRPORT_MAX + 1] = {
[IFLA_BRPORT_STATE] = { .type = NLA_U8 },
[IFLA_BRPORT_COST] = { .type = NLA_U32 },
[IFLA_BRPORT_PRIORITY] = { .type = NLA_U16 },
[IFLA_BRPORT_MODE] = { .type = NLA_U8 },
[IFLA_BRPORT_GUARD] = { .type = NLA_U8 },
[IFLA_BRPORT_PROTECT] = { .type = NLA_U8 },
[IFLA_BRPORT_FAST_LEAVE] = { .type = NLA_U8 },
[IFLA_BRPORT_LEARNING] = { .type = NLA_U8 },
[IFLA_BRPORT_LEARNING_SYNC] = { .type = NLA_U8 },
[IFLA_BRPORT_UNICAST_FLOOD] = { .type = NLA_U8 },
};
static int switchdev_port_br_setlink_protinfo(struct net_device *dev,
struct nlattr *protinfo)
{
struct nlattr *attr;
int rem;
int err;
err = nla_validate_nested(protinfo, IFLA_BRPORT_MAX,
switchdev_port_bridge_policy);
if (err)
return err;
nla_for_each_nested(attr, protinfo, rem) {
switch (nla_type(attr)) {
case IFLA_BRPORT_LEARNING:
err = switchdev_port_br_setflag(dev, attr,
BR_LEARNING);
break;
case IFLA_BRPORT_LEARNING_SYNC:
err = switchdev_port_br_setflag(dev, attr,
BR_LEARNING_SYNC);
break;
default:
err = -EOPNOTSUPP;
break;
}
if (err)
return err;
}
return 0;
}
static int switchdev_port_br_afspec(struct net_device *dev,
struct nlattr *afspec,
int (*f)(struct net_device *dev,
struct switchdev_obj *obj))
{
struct nlattr *attr;
struct bridge_vlan_info *vinfo;
struct switchdev_obj obj = {
.id = SWITCHDEV_OBJ_PORT_VLAN,
};
int rem;
int err;
nla_for_each_nested(attr, afspec, rem) {
if (nla_type(attr) != IFLA_BRIDGE_VLAN_INFO)
continue;
if (nla_len(attr) != sizeof(struct bridge_vlan_info))
return -EINVAL;
vinfo = nla_data(attr);
obj.vlan.flags = vinfo->flags;
if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
if (obj.vlan.vid_start)
return -EINVAL;
obj.vlan.vid_start = vinfo->vid;
} else if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END) {
if (!obj.vlan.vid_start)
return -EINVAL;
obj.vlan.vid_end = vinfo->vid;
if (obj.vlan.vid_end <= obj.vlan.vid_start)
return -EINVAL;
err = f(dev, &obj);
if (err)
return err;
memset(&obj.vlan, 0, sizeof(obj.vlan));
} else {
if (obj.vlan.vid_start)
return -EINVAL;
obj.vlan.vid_start = vinfo->vid;
obj.vlan.vid_end = vinfo->vid;
err = f(dev, &obj);
if (err)
return err;
memset(&obj.vlan, 0, sizeof(obj.vlan));
}
}
return 0;
}
/** /**
* switchdev_port_bridge_setlink - Notify switch device port of bridge * switchdev_port_bridge_setlink - Set bridge port attributes
* port attributes
* *
* @dev: port device * @dev: port device
* @nlh: netlink msg with bridge port attributes * @nlh: netlink header
* @flags: bridge setlink flags * @flags: netlink flags
* *
* Notify switch device port of bridge port attributes * Called for SELF on rtnl_bridge_setlink to set bridge port
* attributes.
*/ */
int switchdev_port_bridge_setlink(struct net_device *dev, int switchdev_port_bridge_setlink(struct net_device *dev,
struct nlmsghdr *nlh, u16 flags) struct nlmsghdr *nlh, u16 flags)
{ {
const struct net_device_ops *ops = dev->netdev_ops; struct nlattr *protinfo;
struct nlattr *afspec;
int err = 0;
if (!(dev->features & NETIF_F_HW_SWITCH_OFFLOAD)) protinfo = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg),
return 0; IFLA_PROTINFO);
if (protinfo) {
err = switchdev_port_br_setlink_protinfo(dev, protinfo);
if (err)
return err;
}
if (!ops->ndo_bridge_setlink) afspec = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg),
return -EOPNOTSUPP; IFLA_AF_SPEC);
if (afspec)
err = switchdev_port_br_afspec(dev, afspec,
switchdev_port_obj_add);
return ops->ndo_bridge_setlink(dev, nlh, flags); return err;
} }
EXPORT_SYMBOL_GPL(switchdev_port_bridge_setlink); EXPORT_SYMBOL_GPL(switchdev_port_bridge_setlink);
......
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