Merge branch 'Replay-and-offload-host-VLAN-entries-in-DSA'
Vladimir Oltean says: ==================== Replay and offload host VLAN entries in DSA v2->v3: - make the bridge stop notifying switchdev for !BRENTRY VLANs - create precommit and commit wrappers around __vlan_add_flags(). - special-case the BRENTRY transition from false to true, instead of treating it as a change of flags and letting drivers figure out that it really isn't. - avoid setting *changed unless we know that functions will not error out later. - drop "old_flags" from struct switchdev_obj_port_vlan, nobody needs it now, in v2 only DSA needed it to filter out BRENTRY transitions, that is now solved cleaner. - no BRIDGE_VLAN_INFO_BRENTRY flag checks and manipulations in DSA whatsoever, use the "bool changed" bit as-is after changing what it means. - merge dsa_slave_host_vlan_{add,del}() with dsa_slave_foreign_vlan_{add,del}(), since now they do the same thing, because the host_vlan functions no longer need to mangle the vlan BRENTRY flags and bool changed. v1->v2: - prune switchdev VLAN additions with no actual change differently - no longer need to revert struct net_bridge_vlan changes on error from switchdev - no longer need to first delete a changed VLAN before readding it - pass 'bool changed' and 'u16 old_flags' through switchdev_obj_port_vlan so that DSA can do some additional post-processing with the BRIDGE_VLAN_INFO_BRENTRY flag - support VLANs on foreign interfaces - fix the same -EOPNOTSUPP error in mv88e6xxx, this time on removal, due to VLAN deletion getting replayed earlier than FDB deletion The motivation behind these patches is that Rafael reported the following error with mv88e6xxx when the first switch port joins a bridge: mv88e6085 0x0000000008b96000:00: port 0 failed to add a6:ef:77:c8:5f:3d vid 1 to fdb: -95 (-EOPNOTSUPP) The FDB entry that's added is the MAC address of the bridge, in VID 1 (the default_pvid), being replayed as part of br_add_if() -> ... -> nbp_switchdev_sync_objs(). -EOPNOTSUPP is the mv88e6xxx driver's way of saying that VID 1 doesn't exist in the VTU, so it can't program the ATU with a FID, something which it needs. It appears to be a race, but it isn't, since we only end up installing VID 1 in the VTU by coincidence. DSA's approximation of programming VLANs on the CPU port together with the user ports breaks down with host FDB entries on mv88e6xxx, since that strictly requires the VTU to contain the VID. But the user may freely add VLANs pointing just towards the bridge, and FDB entries in those VLANs, and DSA will not be aware of them, because it only listens for VLANs on user ports. To create a solution that scales properly to cross-chip setups and doesn't leak entries behind, some changes in the bridge driver are required. I believe that these are for the better overall, but I may be wrong. Namely, the same refcounting procedure that DSA has in place for host FDB and MDB entries can be replicated for VLANs, except that it's garbage in, garbage out: the VLAN addition and removal notifications from switchdev aren't balanced. So the first 2 patches attempt to deal with that. This patch set has been superficially tested on a board with 3 mv88e6xxx switches in a daisy chain and appears to produce the primary desired effect - the driver no longer returns -EOPNOTSUPP when the first port joins a bridge, and is successful in performing local termination under a VLAN-aware bridge. As an additional side effect, it silences the annoying "p%d: already a member of VLAN %d\n" warning messages that the mv88e6xxx driver produces when coupled with systemd-networkd, and a few VLANs are configured. Furthermore, it advances Florian's idea from a few years back, which never got merged: https://lore.kernel.org/lkml/20180624153339.13572-1-f.fainelli@gmail.com/ v2 has also been tested on the NXP LS1028A felix switch. Some testing: root@debian:~# bridge vlan add dev br0 vid 101 pvid self [ 100.709220] mv88e6085 d0032004.mdio-mii:10: mv88e6xxx_port_vlan_add: port 9 vlan 101 [ 100.873426] mv88e6085 d0032004.mdio-mii:10: mv88e6xxx_port_vlan_add: port 10 vlan 101 [ 100.892314] mv88e6085 d0032004.mdio-mii:11: mv88e6xxx_port_vlan_add: port 9 vlan 101 [ 101.053392] mv88e6085 d0032004.mdio-mii:11: mv88e6xxx_port_vlan_add: port 10 vlan 101 [ 101.076994] mv88e6085 d0032004.mdio-mii:12: mv88e6xxx_port_vlan_add: port 9 vlan 101 root@debian:~# bridge vlan add dev br0 vid 101 pvid self root@debian:~# bridge vlan add dev br0 vid 101 pvid self root@debian:~# bridge vlan port vlan-id eth0 1 PVID Egress Untagged lan9 1 PVID Egress Untagged lan10 1 PVID Egress Untagged lan11 1 PVID Egress Untagged lan12 1 PVID Egress Untagged lan13 1 PVID Egress Untagged lan14 1 PVID Egress Untagged lan15 1 PVID Egress Untagged lan16 1 PVID Egress Untagged lan17 1 PVID Egress Untagged lan18 1 PVID Egress Untagged lan19 1 PVID Egress Untagged lan20 1 PVID Egress Untagged lan21 1 PVID Egress Untagged lan22 1 PVID Egress Untagged lan23 1 PVID Egress Untagged lan24 1 PVID Egress Untagged sfp 1 PVID Egress Untagged lan1 1 PVID Egress Untagged lan2 1 PVID Egress Untagged lan3 1 PVID Egress Untagged lan4 1 PVID Egress Untagged lan5 1 PVID Egress Untagged lan6 1 PVID Egress Untagged lan7 1 PVID Egress Untagged lan8 1 PVID Egress Untagged br0 1 Egress Untagged 101 PVID root@debian:~# bridge vlan del dev br0 vid 101 pvid self [ 108.340487] mv88e6085 d0032004.mdio-mii:11: mv88e6xxx_port_vlan_del: port 9 vlan 101 [ 108.379167] mv88e6085 d0032004.mdio-mii:11: mv88e6xxx_port_vlan_del: port 10 vlan 101 [ 108.402319] mv88e6085 d0032004.mdio-mii:12: mv88e6xxx_port_vlan_del: port 9 vlan 101 [ 108.425866] mv88e6085 d0032004.mdio-mii:10: mv88e6xxx_port_vlan_del: port 9 vlan 101 [ 108.452280] mv88e6085 d0032004.mdio-mii:10: mv88e6xxx_port_vlan_del: port 10 vlan 101 root@debian:~# bridge vlan del dev br0 vid 101 pvid self root@debian:~# bridge vlan del dev br0 vid 101 pvid self root@debian:~# bridge vlan port vlan-id eth0 1 PVID Egress Untagged lan9 1 PVID Egress Untagged lan10 1 PVID Egress Untagged lan11 1 PVID Egress Untagged lan12 1 PVID Egress Untagged lan13 1 PVID Egress Untagged lan14 1 PVID Egress Untagged lan15 1 PVID Egress Untagged lan16 1 PVID Egress Untagged lan17 1 PVID Egress Untagged lan18 1 PVID Egress Untagged lan19 1 PVID Egress Untagged lan20 1 PVID Egress Untagged lan21 1 PVID Egress Untagged lan22 1 PVID Egress Untagged lan23 1 PVID Egress Untagged lan24 1 PVID Egress Untagged sfp 1 PVID Egress Untagged lan1 1 PVID Egress Untagged lan2 1 PVID Egress Untagged lan3 1 PVID Egress Untagged lan4 1 PVID Egress Untagged lan5 1 PVID Egress Untagged lan6 1 PVID Egress Untagged lan7 1 PVID Egress Untagged lan8 1 PVID Egress Untagged br0 1 Egress Untagged root@debian:~# bridge vlan del dev br0 vid 101 pvid self ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
f0ead99e62
|
@ -312,6 +312,10 @@ struct dsa_port {
|
|||
struct mutex addr_lists_lock;
|
||||
struct list_head fdbs;
|
||||
struct list_head mdbs;
|
||||
|
||||
/* List of VLANs that CPU and DSA ports are members of. */
|
||||
struct mutex vlans_lock;
|
||||
struct list_head vlans;
|
||||
};
|
||||
|
||||
/* TODO: ideally DSA ports would have a single dp->link_dp member,
|
||||
|
@ -332,6 +336,12 @@ struct dsa_mac_addr {
|
|||
struct list_head list;
|
||||
};
|
||||
|
||||
struct dsa_vlan {
|
||||
u16 vid;
|
||||
refcount_t refcount;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct dsa_switch {
|
||||
struct device *dev;
|
||||
|
||||
|
|
|
@ -81,6 +81,13 @@ struct switchdev_obj_port_vlan {
|
|||
struct switchdev_obj obj;
|
||||
u16 flags;
|
||||
u16 vid;
|
||||
/* If set, the notifier signifies a change of one of the following
|
||||
* flags for a VLAN that already exists:
|
||||
* - BRIDGE_VLAN_INFO_PVID
|
||||
* - BRIDGE_VLAN_INFO_UNTAGGED
|
||||
* Entries with BRIDGE_VLAN_INFO_BRENTRY unset are not notified at all.
|
||||
*/
|
||||
bool changed;
|
||||
};
|
||||
|
||||
#define SWITCHDEV_OBJ_PORT_VLAN(OBJ) \
|
||||
|
@ -317,11 +324,26 @@ int switchdev_handle_port_obj_add(struct net_device *dev,
|
|||
int (*add_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj,
|
||||
struct netlink_ext_ack *extack));
|
||||
int switchdev_handle_port_obj_add_foreign(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*add_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj,
|
||||
struct netlink_ext_ack *extack));
|
||||
int switchdev_handle_port_obj_del(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
int (*del_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj));
|
||||
int switchdev_handle_port_obj_del_foreign(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*del_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj));
|
||||
|
||||
int switchdev_handle_port_attr_set(struct net_device *dev,
|
||||
struct switchdev_notifier_port_attr_info *port_attr_info,
|
||||
|
@ -440,6 +462,18 @@ switchdev_handle_port_obj_add(struct net_device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline int switchdev_handle_port_obj_add_foreign(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*add_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj,
|
||||
struct netlink_ext_ack *extack))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
switchdev_handle_port_obj_del(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
|
@ -450,6 +484,18 @@ switchdev_handle_port_obj_del(struct net_device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
switchdev_handle_port_obj_del_foreign(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*del_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
switchdev_handle_port_attr_set(struct net_device *dev,
|
||||
struct switchdev_notifier_port_attr_info *port_attr_info,
|
||||
|
|
|
@ -1985,7 +1985,7 @@ void br_switchdev_mdb_notify(struct net_device *dev,
|
|||
struct net_bridge_port_group *pg,
|
||||
int type);
|
||||
int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags,
|
||||
struct netlink_ext_ack *extack);
|
||||
bool changed, struct netlink_ext_ack *extack);
|
||||
int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid);
|
||||
void br_switchdev_init(struct net_bridge *br);
|
||||
|
||||
|
@ -2052,8 +2052,8 @@ static inline int br_switchdev_set_port_flag(struct net_bridge_port *p,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline int br_switchdev_port_vlan_add(struct net_device *dev,
|
||||
u16 vid, u16 flags,
|
||||
static inline int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid,
|
||||
u16 flags, bool changed,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
|
|
|
@ -160,13 +160,14 @@ br_switchdev_fdb_notify(struct net_bridge *br,
|
|||
}
|
||||
|
||||
int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags,
|
||||
struct netlink_ext_ack *extack)
|
||||
bool changed, struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct switchdev_obj_port_vlan v = {
|
||||
.obj.orig_dev = dev,
|
||||
.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
|
||||
.flags = flags,
|
||||
.vid = vid,
|
||||
.changed = changed,
|
||||
};
|
||||
|
||||
return switchdev_port_obj_add(dev, &v.obj, extack);
|
||||
|
@ -351,51 +352,19 @@ br_switchdev_vlan_replay_one(struct notifier_block *nb,
|
|||
return notifier_to_errno(err);
|
||||
}
|
||||
|
||||
static int br_switchdev_vlan_replay(struct net_device *br_dev,
|
||||
static int br_switchdev_vlan_replay_group(struct notifier_block *nb,
|
||||
struct net_device *dev,
|
||||
const void *ctx, bool adding,
|
||||
struct notifier_block *nb,
|
||||
struct net_bridge_vlan_group *vg,
|
||||
const void *ctx, unsigned long action,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct net_bridge_vlan_group *vg;
|
||||
struct net_bridge_vlan *v;
|
||||
struct net_bridge_port *p;
|
||||
struct net_bridge *br;
|
||||
unsigned long action;
|
||||
int err = 0;
|
||||
u16 pvid;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
if (!nb)
|
||||
return 0;
|
||||
|
||||
if (!netif_is_bridge_master(br_dev))
|
||||
return -EINVAL;
|
||||
|
||||
if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
|
||||
return -EINVAL;
|
||||
|
||||
if (netif_is_bridge_master(dev)) {
|
||||
br = netdev_priv(dev);
|
||||
vg = br_vlan_group(br);
|
||||
p = NULL;
|
||||
} else {
|
||||
p = br_port_get_rtnl(dev);
|
||||
if (WARN_ON(!p))
|
||||
return -EINVAL;
|
||||
vg = nbp_vlan_group(p);
|
||||
br = p->br;
|
||||
}
|
||||
|
||||
if (!vg)
|
||||
return 0;
|
||||
|
||||
if (adding)
|
||||
action = SWITCHDEV_PORT_OBJ_ADD;
|
||||
else
|
||||
action = SWITCHDEV_PORT_OBJ_DEL;
|
||||
|
||||
pvid = br_get_pvid(vg);
|
||||
|
||||
list_for_each_entry(v, &vg->vlan_list, vlist) {
|
||||
|
@ -415,7 +384,48 @@ static int br_switchdev_vlan_replay(struct net_device *br_dev,
|
|||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int br_switchdev_vlan_replay(struct net_device *br_dev,
|
||||
const void *ctx, bool adding,
|
||||
struct notifier_block *nb,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct net_bridge *br = netdev_priv(br_dev);
|
||||
struct net_bridge_port *p;
|
||||
unsigned long action;
|
||||
int err;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
if (!nb)
|
||||
return 0;
|
||||
|
||||
if (!netif_is_bridge_master(br_dev))
|
||||
return -EINVAL;
|
||||
|
||||
if (adding)
|
||||
action = SWITCHDEV_PORT_OBJ_ADD;
|
||||
else
|
||||
action = SWITCHDEV_PORT_OBJ_DEL;
|
||||
|
||||
err = br_switchdev_vlan_replay_group(nb, br_dev, br_vlan_group(br),
|
||||
ctx, action, extack);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
list_for_each_entry(p, &br->port_list, list) {
|
||||
struct net_device *dev = p->dev;
|
||||
|
||||
err = br_switchdev_vlan_replay_group(nb, dev,
|
||||
nbp_vlan_group(p),
|
||||
ctx, action, extack);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
|
||||
|
@ -681,8 +691,7 @@ static int nbp_switchdev_sync_objs(struct net_bridge_port *p, const void *ctx,
|
|||
struct net_device *dev = p->dev;
|
||||
int err;
|
||||
|
||||
err = br_switchdev_vlan_replay(br_dev, dev, ctx, true, blocking_nb,
|
||||
extack);
|
||||
err = br_switchdev_vlan_replay(br_dev, ctx, true, blocking_nb, extack);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
return err;
|
||||
|
||||
|
@ -706,11 +715,11 @@ static void nbp_switchdev_unsync_objs(struct net_bridge_port *p,
|
|||
struct net_device *br_dev = p->br->dev;
|
||||
struct net_device *dev = p->dev;
|
||||
|
||||
br_switchdev_vlan_replay(br_dev, dev, ctx, false, blocking_nb, NULL);
|
||||
br_switchdev_fdb_replay(br_dev, ctx, false, atomic_nb);
|
||||
|
||||
br_switchdev_mdb_replay(br_dev, dev, ctx, false, blocking_nb, NULL);
|
||||
|
||||
br_switchdev_fdb_replay(br_dev, ctx, false, atomic_nb);
|
||||
br_switchdev_vlan_replay(br_dev, ctx, false, blocking_nb, NULL);
|
||||
}
|
||||
|
||||
/* Let the bridge know that this port is offloaded, so that it can assign a
|
||||
|
|
|
@ -34,53 +34,70 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
|
|||
return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
|
||||
}
|
||||
|
||||
static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg,
|
||||
static void __vlan_add_pvid(struct net_bridge_vlan_group *vg,
|
||||
const struct net_bridge_vlan *v)
|
||||
{
|
||||
if (vg->pvid == v->vid)
|
||||
return false;
|
||||
return;
|
||||
|
||||
smp_wmb();
|
||||
br_vlan_set_pvid_state(vg, v->state);
|
||||
vg->pvid = v->vid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
|
||||
static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
|
||||
{
|
||||
if (vg->pvid != vid)
|
||||
return false;
|
||||
return;
|
||||
|
||||
smp_wmb();
|
||||
vg->pvid = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* return true if anything changed, false otherwise */
|
||||
static bool __vlan_add_flags(struct net_bridge_vlan *v, u16 flags)
|
||||
/* Update the BRIDGE_VLAN_INFO_PVID and BRIDGE_VLAN_INFO_UNTAGGED flags of @v.
|
||||
* If @commit is false, return just whether the BRIDGE_VLAN_INFO_PVID and
|
||||
* BRIDGE_VLAN_INFO_UNTAGGED bits of @flags would produce any change onto @v.
|
||||
*/
|
||||
static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
|
||||
bool commit)
|
||||
{
|
||||
struct net_bridge_vlan_group *vg;
|
||||
u16 old_flags = v->flags;
|
||||
bool ret;
|
||||
bool change;
|
||||
|
||||
if (br_vlan_is_master(v))
|
||||
vg = br_vlan_group(v->br);
|
||||
else
|
||||
vg = nbp_vlan_group(v->port);
|
||||
|
||||
/* check if anything would be changed on commit */
|
||||
change = !!(flags & BRIDGE_VLAN_INFO_PVID) == !!(vg->pvid != v->vid) ||
|
||||
((flags ^ v->flags) & BRIDGE_VLAN_INFO_UNTAGGED);
|
||||
|
||||
if (!commit)
|
||||
goto out;
|
||||
|
||||
if (flags & BRIDGE_VLAN_INFO_PVID)
|
||||
ret = __vlan_add_pvid(vg, v);
|
||||
__vlan_add_pvid(vg, v);
|
||||
else
|
||||
ret = __vlan_delete_pvid(vg, v->vid);
|
||||
__vlan_delete_pvid(vg, v->vid);
|
||||
|
||||
if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
|
||||
v->flags |= BRIDGE_VLAN_INFO_UNTAGGED;
|
||||
else
|
||||
v->flags &= ~BRIDGE_VLAN_INFO_UNTAGGED;
|
||||
|
||||
return ret || !!(old_flags ^ v->flags);
|
||||
out:
|
||||
return change;
|
||||
}
|
||||
|
||||
static bool __vlan_flags_would_change(struct net_bridge_vlan *v, u16 flags)
|
||||
{
|
||||
return __vlan_flags_update(v, flags, false);
|
||||
}
|
||||
|
||||
static void __vlan_flags_commit(struct net_bridge_vlan *v, u16 flags)
|
||||
{
|
||||
__vlan_flags_update(v, flags, true);
|
||||
}
|
||||
|
||||
static int __vlan_vid_add(struct net_device *dev, struct net_bridge *br,
|
||||
|
@ -92,7 +109,7 @@ static int __vlan_vid_add(struct net_device *dev, struct net_bridge *br,
|
|||
/* Try switchdev op first. In case it is not supported, fallback to
|
||||
* 8021q add.
|
||||
*/
|
||||
err = br_switchdev_port_vlan_add(dev, v->vid, flags, extack);
|
||||
err = br_switchdev_port_vlan_add(dev, v->vid, flags, false, extack);
|
||||
if (err == -EOPNOTSUPP)
|
||||
return vlan_vid_add(dev, br->vlan_proto, v->vid);
|
||||
v->priv_flags |= BR_VLFLAG_ADDED_BY_SWITCHDEV;
|
||||
|
@ -284,9 +301,12 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
|
|||
}
|
||||
br_multicast_port_ctx_init(p, v, &v->port_mcast_ctx);
|
||||
} else {
|
||||
err = br_switchdev_port_vlan_add(dev, v->vid, flags, extack);
|
||||
if (br_vlan_should_use(v)) {
|
||||
err = br_switchdev_port_vlan_add(dev, v->vid, flags,
|
||||
false, extack);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
goto out;
|
||||
}
|
||||
br_multicast_ctx_init(br, v, &v->br_mcast_ctx);
|
||||
v->priv_flags |= BR_VLFLAG_GLOBAL_MCAST_ENABLED;
|
||||
}
|
||||
|
@ -310,7 +330,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
|
|||
goto out_fdb_insert;
|
||||
|
||||
__vlan_add_list(v);
|
||||
__vlan_add_flags(v, flags);
|
||||
__vlan_flags_commit(v, flags);
|
||||
br_multicast_toggle_one_vlan(v, true);
|
||||
|
||||
if (p)
|
||||
|
@ -677,18 +697,29 @@ static int br_vlan_add_existing(struct net_bridge *br,
|
|||
u16 flags, bool *changed,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
bool would_change = __vlan_flags_would_change(vlan, flags);
|
||||
bool becomes_brentry = false;
|
||||
int err;
|
||||
|
||||
err = br_switchdev_port_vlan_add(br->dev, vlan->vid, flags, extack);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
return err;
|
||||
|
||||
if (!br_vlan_is_brentry(vlan)) {
|
||||
/* Trying to change flags of non-existent bridge vlan */
|
||||
if (!(flags & BRIDGE_VLAN_INFO_BRENTRY)) {
|
||||
err = -EINVAL;
|
||||
goto err_flags;
|
||||
if (!(flags & BRIDGE_VLAN_INFO_BRENTRY))
|
||||
return -EINVAL;
|
||||
|
||||
becomes_brentry = true;
|
||||
}
|
||||
|
||||
/* Master VLANs that aren't brentries weren't notified before,
|
||||
* time to notify them now.
|
||||
*/
|
||||
if (becomes_brentry || would_change) {
|
||||
err = br_switchdev_port_vlan_add(br->dev, vlan->vid, flags,
|
||||
would_change, extack);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (becomes_brentry) {
|
||||
/* It was only kept for port vlans, now make it real */
|
||||
err = br_fdb_add_local(br, NULL, br->dev->dev_addr, vlan->vid);
|
||||
if (err) {
|
||||
|
@ -703,13 +734,13 @@ static int br_vlan_add_existing(struct net_bridge *br,
|
|||
br_multicast_toggle_one_vlan(vlan, true);
|
||||
}
|
||||
|
||||
if (__vlan_add_flags(vlan, flags))
|
||||
__vlan_flags_commit(vlan, flags);
|
||||
if (would_change)
|
||||
*changed = true;
|
||||
|
||||
return 0;
|
||||
|
||||
err_fdb_insert:
|
||||
err_flags:
|
||||
br_switchdev_port_vlan_del(br->dev, vlan->vid);
|
||||
return err;
|
||||
}
|
||||
|
@ -1254,11 +1285,18 @@ int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags,
|
|||
*changed = false;
|
||||
vlan = br_vlan_find(nbp_vlan_group(port), vid);
|
||||
if (vlan) {
|
||||
bool would_change = __vlan_flags_would_change(vlan, flags);
|
||||
|
||||
if (would_change) {
|
||||
/* Pass the flags to the hardware bridge */
|
||||
ret = br_switchdev_port_vlan_add(port->dev, vid, flags, extack);
|
||||
ret = br_switchdev_port_vlan_add(port->dev, vid, flags,
|
||||
true, extack);
|
||||
if (ret && ret != -EOPNOTSUPP)
|
||||
return ret;
|
||||
*changed = __vlan_add_flags(vlan, flags);
|
||||
}
|
||||
|
||||
__vlan_flags_commit(vlan, flags);
|
||||
*changed = would_change;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -453,8 +453,10 @@ static int dsa_port_setup(struct dsa_port *dp)
|
|||
return 0;
|
||||
|
||||
mutex_init(&dp->addr_lists_lock);
|
||||
mutex_init(&dp->vlans_lock);
|
||||
INIT_LIST_HEAD(&dp->fdbs);
|
||||
INIT_LIST_HEAD(&dp->mdbs);
|
||||
INIT_LIST_HEAD(&dp->vlans);
|
||||
|
||||
if (ds->ops->port_setup) {
|
||||
err = ds->ops->port_setup(ds, dp->index);
|
||||
|
@ -563,6 +565,7 @@ static void dsa_port_teardown(struct dsa_port *dp)
|
|||
struct dsa_switch *ds = dp->ds;
|
||||
struct dsa_mac_addr *a, *tmp;
|
||||
struct net_device *slave;
|
||||
struct dsa_vlan *v, *n;
|
||||
|
||||
if (!dp->setup)
|
||||
return;
|
||||
|
@ -603,6 +606,11 @@ static void dsa_port_teardown(struct dsa_port *dp)
|
|||
kfree(a);
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(v, n, &dp->vlans, list) {
|
||||
list_del(&v->list);
|
||||
kfree(v);
|
||||
}
|
||||
|
||||
dp->setup = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ enum {
|
|||
DSA_NOTIFIER_HOST_MDB_DEL,
|
||||
DSA_NOTIFIER_VLAN_ADD,
|
||||
DSA_NOTIFIER_VLAN_DEL,
|
||||
DSA_NOTIFIER_HOST_VLAN_ADD,
|
||||
DSA_NOTIFIER_HOST_VLAN_DEL,
|
||||
DSA_NOTIFIER_MTU,
|
||||
DSA_NOTIFIER_TAG_PROTO,
|
||||
DSA_NOTIFIER_TAG_PROTO_CONNECT,
|
||||
|
@ -234,6 +236,11 @@ int dsa_port_vlan_add(struct dsa_port *dp,
|
|||
struct netlink_ext_ack *extack);
|
||||
int dsa_port_vlan_del(struct dsa_port *dp,
|
||||
const struct switchdev_obj_port_vlan *vlan);
|
||||
int dsa_port_host_vlan_add(struct dsa_port *dp,
|
||||
const struct switchdev_obj_port_vlan *vlan,
|
||||
struct netlink_ext_ack *extack);
|
||||
int dsa_port_host_vlan_del(struct dsa_port *dp,
|
||||
const struct switchdev_obj_port_vlan *vlan);
|
||||
int dsa_port_mrp_add(const struct dsa_port *dp,
|
||||
const struct switchdev_obj_mrp *mrp);
|
||||
int dsa_port_mrp_del(const struct dsa_port *dp,
|
||||
|
|
|
@ -904,6 +904,48 @@ int dsa_port_vlan_del(struct dsa_port *dp,
|
|||
return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_DEL, &info);
|
||||
}
|
||||
|
||||
int dsa_port_host_vlan_add(struct dsa_port *dp,
|
||||
const struct switchdev_obj_port_vlan *vlan,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct dsa_notifier_vlan_info info = {
|
||||
.sw_index = dp->ds->index,
|
||||
.port = dp->index,
|
||||
.vlan = vlan,
|
||||
.extack = extack,
|
||||
};
|
||||
struct dsa_port *cpu_dp = dp->cpu_dp;
|
||||
int err;
|
||||
|
||||
err = dsa_port_notify(dp, DSA_NOTIFIER_HOST_VLAN_ADD, &info);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
return err;
|
||||
|
||||
vlan_vid_add(cpu_dp->master, htons(ETH_P_8021Q), vlan->vid);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int dsa_port_host_vlan_del(struct dsa_port *dp,
|
||||
const struct switchdev_obj_port_vlan *vlan)
|
||||
{
|
||||
struct dsa_notifier_vlan_info info = {
|
||||
.sw_index = dp->ds->index,
|
||||
.port = dp->index,
|
||||
.vlan = vlan,
|
||||
};
|
||||
struct dsa_port *cpu_dp = dp->cpu_dp;
|
||||
int err;
|
||||
|
||||
err = dsa_port_notify(dp, DSA_NOTIFIER_HOST_VLAN_DEL, &info);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
return err;
|
||||
|
||||
vlan_vid_del(cpu_dp->master, htons(ETH_P_8021Q), vlan->vid);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int dsa_port_mrp_add(const struct dsa_port *dp,
|
||||
const struct switchdev_obj_mrp *mrp)
|
||||
{
|
||||
|
|
|
@ -348,9 +348,8 @@ static int dsa_slave_vlan_add(struct net_device *dev,
|
|||
const struct switchdev_obj *obj,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct net_device *master = dsa_slave_to_master(dev);
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan vlan;
|
||||
struct switchdev_obj_port_vlan *vlan;
|
||||
int err;
|
||||
|
||||
if (dsa_port_skip_vlan_configuration(dp)) {
|
||||
|
@ -358,14 +357,14 @@ static int dsa_slave_vlan_add(struct net_device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
vlan = *SWITCHDEV_OBJ_PORT_VLAN(obj);
|
||||
vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
|
||||
|
||||
/* Deny adding a bridge VLAN when there is already an 802.1Q upper with
|
||||
* the same VID.
|
||||
*/
|
||||
if (br_vlan_enabled(dsa_port_bridge_dev_get(dp))) {
|
||||
rcu_read_lock();
|
||||
err = dsa_slave_vlan_check_for_8021q_uppers(dev, &vlan);
|
||||
err = dsa_slave_vlan_check_for_8021q_uppers(dev, vlan);
|
||||
rcu_read_unlock();
|
||||
if (err) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
|
@ -374,21 +373,36 @@ static int dsa_slave_vlan_add(struct net_device *dev,
|
|||
}
|
||||
}
|
||||
|
||||
err = dsa_port_vlan_add(dp, &vlan, extack);
|
||||
if (err)
|
||||
return err;
|
||||
return dsa_port_vlan_add(dp, vlan, extack);
|
||||
}
|
||||
|
||||
/* We need the dedicated CPU port to be a member of the VLAN as well.
|
||||
* Even though drivers often handle CPU membership in special ways,
|
||||
/* Offload a VLAN installed on the bridge or on a foreign interface by
|
||||
* installing it as a VLAN towards the CPU port.
|
||||
*/
|
||||
static int dsa_slave_host_vlan_add(struct net_device *dev,
|
||||
const struct switchdev_obj *obj,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan vlan;
|
||||
|
||||
/* Do nothing if this is a software bridge */
|
||||
if (!dp->bridge)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (dsa_port_skip_vlan_configuration(dp)) {
|
||||
NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
|
||||
return 0;
|
||||
}
|
||||
|
||||
vlan = *SWITCHDEV_OBJ_PORT_VLAN(obj);
|
||||
|
||||
/* Even though drivers often handle CPU membership in special ways,
|
||||
* it doesn't make sense to program a PVID, so clear this flag.
|
||||
*/
|
||||
vlan.flags &= ~BRIDGE_VLAN_INFO_PVID;
|
||||
|
||||
err = dsa_port_vlan_add(dp->cpu_dp, &vlan, extack);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return vlan_vid_add(master, htons(ETH_P_8021Q), vlan.vid);
|
||||
return dsa_port_host_vlan_add(dp, &vlan, extack);
|
||||
}
|
||||
|
||||
static int dsa_slave_port_obj_add(struct net_device *dev, const void *ctx,
|
||||
|
@ -415,10 +429,10 @@ static int dsa_slave_port_obj_add(struct net_device *dev, const void *ctx,
|
|||
err = dsa_port_host_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
||||
if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (dsa_port_offloads_bridge_port(dp, obj->orig_dev))
|
||||
err = dsa_slave_vlan_add(dev, obj, extack);
|
||||
else
|
||||
err = dsa_slave_host_vlan_add(dev, obj, extack);
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_MRP:
|
||||
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
|
||||
|
@ -444,26 +458,33 @@ static int dsa_slave_port_obj_add(struct net_device *dev, const void *ctx,
|
|||
static int dsa_slave_vlan_del(struct net_device *dev,
|
||||
const struct switchdev_obj *obj)
|
||||
{
|
||||
struct net_device *master = dsa_slave_to_master(dev);
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan *vlan;
|
||||
int err;
|
||||
|
||||
if (dsa_port_skip_vlan_configuration(dp))
|
||||
return 0;
|
||||
|
||||
vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
|
||||
|
||||
/* Do not deprogram the CPU port as it may be shared with other user
|
||||
* ports which can be members of this VLAN as well.
|
||||
*/
|
||||
err = dsa_port_vlan_del(dp, vlan);
|
||||
if (err)
|
||||
return err;
|
||||
return dsa_port_vlan_del(dp, vlan);
|
||||
}
|
||||
|
||||
vlan_vid_del(master, htons(ETH_P_8021Q), vlan->vid);
|
||||
static int dsa_slave_host_vlan_del(struct net_device *dev,
|
||||
const struct switchdev_obj *obj)
|
||||
{
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan *vlan;
|
||||
|
||||
/* Do nothing if this is a software bridge */
|
||||
if (!dp->bridge)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (dsa_port_skip_vlan_configuration(dp))
|
||||
return 0;
|
||||
|
||||
vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
|
||||
|
||||
return dsa_port_host_vlan_del(dp, vlan);
|
||||
}
|
||||
|
||||
static int dsa_slave_port_obj_del(struct net_device *dev, const void *ctx,
|
||||
|
@ -489,10 +510,10 @@ static int dsa_slave_port_obj_del(struct net_device *dev, const void *ctx,
|
|||
err = dsa_port_host_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
||||
if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (dsa_port_offloads_bridge_port(dp, obj->orig_dev))
|
||||
err = dsa_slave_vlan_del(dev, obj);
|
||||
else
|
||||
err = dsa_slave_host_vlan_del(dev, obj);
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_MRP:
|
||||
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
|
||||
|
@ -1347,7 +1368,6 @@ static int dsa_slave_get_ts_info(struct net_device *dev,
|
|||
static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
|
||||
u16 vid)
|
||||
{
|
||||
struct net_device *master = dsa_slave_to_master(dev);
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan vlan = {
|
||||
.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
|
||||
|
@ -1367,7 +1387,7 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
|
|||
}
|
||||
|
||||
/* And CPU port... */
|
||||
ret = dsa_port_vlan_add(dp->cpu_dp, &vlan, &extack);
|
||||
ret = dsa_port_host_vlan_add(dp, &vlan, &extack);
|
||||
if (ret) {
|
||||
if (extack._msg)
|
||||
netdev_err(dev, "CPU port %d: %s\n", dp->cpu_dp->index,
|
||||
|
@ -1375,13 +1395,12 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
|
|||
return ret;
|
||||
}
|
||||
|
||||
return vlan_vid_add(master, proto, vid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
|
||||
u16 vid)
|
||||
{
|
||||
struct net_device *master = dsa_slave_to_master(dev);
|
||||
struct dsa_port *dp = dsa_slave_to_port(dev);
|
||||
struct switchdev_obj_port_vlan vlan = {
|
||||
.vid = vid,
|
||||
|
@ -1390,16 +1409,11 @@ static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
|
|||
};
|
||||
int err;
|
||||
|
||||
/* Do not deprogram the CPU port as it may be shared with other user
|
||||
* ports which can be members of this VLAN as well.
|
||||
*/
|
||||
err = dsa_port_vlan_del(dp, &vlan);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
vlan_vid_del(master, proto, vid);
|
||||
|
||||
return 0;
|
||||
return dsa_port_host_vlan_del(dp, &vlan);
|
||||
}
|
||||
|
||||
static int dsa_slave_restore_vlan(struct net_device *vdev, int vid, void *arg)
|
||||
|
@ -2530,13 +2544,15 @@ static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
|
|||
|
||||
switch (event) {
|
||||
case SWITCHDEV_PORT_OBJ_ADD:
|
||||
err = switchdev_handle_port_obj_add(dev, ptr,
|
||||
err = switchdev_handle_port_obj_add_foreign(dev, ptr,
|
||||
dsa_slave_dev_check,
|
||||
dsa_foreign_dev_check,
|
||||
dsa_slave_port_obj_add);
|
||||
return notifier_from_errno(err);
|
||||
case SWITCHDEV_PORT_OBJ_DEL:
|
||||
err = switchdev_handle_port_obj_del(dev, ptr,
|
||||
err = switchdev_handle_port_obj_del_foreign(dev, ptr,
|
||||
dsa_slave_dev_check,
|
||||
dsa_foreign_dev_check,
|
||||
dsa_slave_port_obj_del);
|
||||
return notifier_from_errno(err);
|
||||
case SWITCHDEV_PORT_ATTR_SET:
|
||||
|
|
185
net/dsa/switch.c
185
net/dsa/switch.c
|
@ -558,6 +558,7 @@ static int dsa_switch_host_mdb_del(struct dsa_switch *ds,
|
|||
return err;
|
||||
}
|
||||
|
||||
/* Port VLANs match on the targeted port and on all DSA ports */
|
||||
static bool dsa_port_vlan_match(struct dsa_port *dp,
|
||||
struct dsa_notifier_vlan_info *info)
|
||||
{
|
||||
|
@ -570,6 +571,126 @@ static bool dsa_port_vlan_match(struct dsa_port *dp,
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Host VLANs match on the targeted port's CPU port, and on all DSA ports
|
||||
* (upstream and downstream) of that switch and its upstream switches.
|
||||
*/
|
||||
static bool dsa_port_host_vlan_match(struct dsa_port *dp,
|
||||
struct dsa_notifier_vlan_info *info)
|
||||
{
|
||||
struct dsa_port *targeted_dp, *cpu_dp;
|
||||
struct dsa_switch *targeted_ds;
|
||||
|
||||
targeted_ds = dsa_switch_find(dp->ds->dst->index, info->sw_index);
|
||||
targeted_dp = dsa_to_port(targeted_ds, info->port);
|
||||
cpu_dp = targeted_dp->cpu_dp;
|
||||
|
||||
if (dsa_switch_is_upstream_of(dp->ds, targeted_ds))
|
||||
return dsa_port_is_dsa(dp) || dp == cpu_dp;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct dsa_vlan *dsa_vlan_find(struct list_head *vlan_list,
|
||||
const struct switchdev_obj_port_vlan *vlan)
|
||||
{
|
||||
struct dsa_vlan *v;
|
||||
|
||||
list_for_each_entry(v, vlan_list, list)
|
||||
if (v->vid == vlan->vid)
|
||||
return v;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int dsa_port_do_vlan_add(struct dsa_port *dp,
|
||||
const struct switchdev_obj_port_vlan *vlan,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
int port = dp->index;
|
||||
struct dsa_vlan *v;
|
||||
int err = 0;
|
||||
|
||||
/* No need to bother with refcounting for user ports. */
|
||||
if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp)))
|
||||
return ds->ops->port_vlan_add(ds, port, vlan, extack);
|
||||
|
||||
/* No need to propagate on shared ports the existing VLANs that were
|
||||
* re-notified after just the flags have changed. This would cause a
|
||||
* refcount bump which we need to avoid, since it unbalances the
|
||||
* additions with the deletions.
|
||||
*/
|
||||
if (vlan->changed)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&dp->vlans_lock);
|
||||
|
||||
v = dsa_vlan_find(&dp->vlans, vlan);
|
||||
if (v) {
|
||||
refcount_inc(&v->refcount);
|
||||
goto out;
|
||||
}
|
||||
|
||||
v = kzalloc(sizeof(*v), GFP_KERNEL);
|
||||
if (!v) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = ds->ops->port_vlan_add(ds, port, vlan, extack);
|
||||
if (err) {
|
||||
kfree(v);
|
||||
goto out;
|
||||
}
|
||||
|
||||
v->vid = vlan->vid;
|
||||
refcount_set(&v->refcount, 1);
|
||||
list_add_tail(&v->list, &dp->vlans);
|
||||
|
||||
out:
|
||||
mutex_unlock(&dp->vlans_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int dsa_port_do_vlan_del(struct dsa_port *dp,
|
||||
const struct switchdev_obj_port_vlan *vlan)
|
||||
{
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
int port = dp->index;
|
||||
struct dsa_vlan *v;
|
||||
int err = 0;
|
||||
|
||||
/* No need to bother with refcounting for user ports */
|
||||
if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp)))
|
||||
return ds->ops->port_vlan_del(ds, port, vlan);
|
||||
|
||||
mutex_lock(&dp->vlans_lock);
|
||||
|
||||
v = dsa_vlan_find(&dp->vlans, vlan);
|
||||
if (!v) {
|
||||
err = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!refcount_dec_and_test(&v->refcount))
|
||||
goto out;
|
||||
|
||||
err = ds->ops->port_vlan_del(ds, port, vlan);
|
||||
if (err) {
|
||||
refcount_set(&v->refcount, 1);
|
||||
goto out;
|
||||
}
|
||||
|
||||
list_del(&v->list);
|
||||
kfree(v);
|
||||
|
||||
out:
|
||||
mutex_unlock(&dp->vlans_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int dsa_switch_vlan_add(struct dsa_switch *ds,
|
||||
struct dsa_notifier_vlan_info *info)
|
||||
{
|
||||
|
@ -581,7 +702,7 @@ static int dsa_switch_vlan_add(struct dsa_switch *ds,
|
|||
|
||||
dsa_switch_for_each_port(dp, ds) {
|
||||
if (dsa_port_vlan_match(dp, info)) {
|
||||
err = ds->ops->port_vlan_add(ds, dp->index, info->vlan,
|
||||
err = dsa_port_do_vlan_add(dp, info->vlan,
|
||||
info->extack);
|
||||
if (err)
|
||||
return err;
|
||||
|
@ -594,15 +715,61 @@ static int dsa_switch_vlan_add(struct dsa_switch *ds,
|
|||
static int dsa_switch_vlan_del(struct dsa_switch *ds,
|
||||
struct dsa_notifier_vlan_info *info)
|
||||
{
|
||||
struct dsa_port *dp;
|
||||
int err;
|
||||
|
||||
if (!ds->ops->port_vlan_del)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (ds->index == info->sw_index)
|
||||
return ds->ops->port_vlan_del(ds, info->port, info->vlan);
|
||||
dsa_switch_for_each_port(dp, ds) {
|
||||
if (dsa_port_vlan_match(dp, info)) {
|
||||
err = dsa_port_do_vlan_del(dp, info->vlan);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsa_switch_host_vlan_add(struct dsa_switch *ds,
|
||||
struct dsa_notifier_vlan_info *info)
|
||||
{
|
||||
struct dsa_port *dp;
|
||||
int err;
|
||||
|
||||
if (!ds->ops->port_vlan_add)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
dsa_switch_for_each_port(dp, ds) {
|
||||
if (dsa_port_host_vlan_match(dp, info)) {
|
||||
err = dsa_port_do_vlan_add(dp, info->vlan,
|
||||
info->extack);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsa_switch_host_vlan_del(struct dsa_switch *ds,
|
||||
struct dsa_notifier_vlan_info *info)
|
||||
{
|
||||
struct dsa_port *dp;
|
||||
int err;
|
||||
|
||||
if (!ds->ops->port_vlan_del)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
dsa_switch_for_each_port(dp, ds) {
|
||||
if (dsa_port_host_vlan_match(dp, info)) {
|
||||
err = dsa_port_do_vlan_del(dp, info->vlan);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Do not deprogram the DSA links as they may be used as conduit
|
||||
* for other VLAN members in the fabric.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -764,6 +931,12 @@ static int dsa_switch_event(struct notifier_block *nb,
|
|||
case DSA_NOTIFIER_VLAN_DEL:
|
||||
err = dsa_switch_vlan_del(ds, info);
|
||||
break;
|
||||
case DSA_NOTIFIER_HOST_VLAN_ADD:
|
||||
err = dsa_switch_host_vlan_add(ds, info);
|
||||
break;
|
||||
case DSA_NOTIFIER_HOST_VLAN_DEL:
|
||||
err = dsa_switch_host_vlan_del(ds, info);
|
||||
break;
|
||||
case DSA_NOTIFIER_MTU:
|
||||
err = dsa_switch_mtu(ds, info);
|
||||
break;
|
||||
|
|
|
@ -409,7 +409,7 @@ static int switchdev_lower_dev_walk(struct net_device *lower_dev,
|
|||
}
|
||||
|
||||
static struct net_device *
|
||||
switchdev_lower_dev_find(struct net_device *dev,
|
||||
switchdev_lower_dev_find_rcu(struct net_device *dev,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev))
|
||||
|
@ -429,6 +429,27 @@ switchdev_lower_dev_find(struct net_device *dev,
|
|||
return switchdev_priv.lower_dev;
|
||||
}
|
||||
|
||||
static struct net_device *
|
||||
switchdev_lower_dev_find(struct net_device *dev,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev))
|
||||
{
|
||||
struct switchdev_nested_priv switchdev_priv = {
|
||||
.check_cb = check_cb,
|
||||
.foreign_dev_check_cb = foreign_dev_check_cb,
|
||||
.dev = dev,
|
||||
.lower_dev = NULL,
|
||||
};
|
||||
struct netdev_nested_priv priv = {
|
||||
.data = &switchdev_priv,
|
||||
};
|
||||
|
||||
netdev_walk_all_lower_dev(dev, switchdev_lower_dev_walk, &priv);
|
||||
|
||||
return switchdev_priv.lower_dev;
|
||||
}
|
||||
|
||||
static int __switchdev_handle_fdb_event_to_device(struct net_device *dev,
|
||||
struct net_device *orig_dev, unsigned long event,
|
||||
const struct switchdev_notifier_fdb_info *fdb_info,
|
||||
|
@ -451,7 +472,7 @@ static int __switchdev_handle_fdb_event_to_device(struct net_device *dev,
|
|||
return mod_cb(dev, orig_dev, event, info->ctx, fdb_info);
|
||||
|
||||
if (netif_is_lag_master(dev)) {
|
||||
if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb))
|
||||
if (!switchdev_lower_dev_find_rcu(dev, check_cb, foreign_dev_check_cb))
|
||||
goto maybe_bridged_with_us;
|
||||
|
||||
/* This is a LAG interface that we offload */
|
||||
|
@ -465,7 +486,7 @@ static int __switchdev_handle_fdb_event_to_device(struct net_device *dev,
|
|||
* towards a bridge device.
|
||||
*/
|
||||
if (netif_is_bridge_master(dev)) {
|
||||
if (!switchdev_lower_dev_find(dev, check_cb, foreign_dev_check_cb))
|
||||
if (!switchdev_lower_dev_find_rcu(dev, check_cb, foreign_dev_check_cb))
|
||||
return 0;
|
||||
|
||||
/* This is a bridge interface that we offload */
|
||||
|
@ -478,7 +499,7 @@ static int __switchdev_handle_fdb_event_to_device(struct net_device *dev,
|
|||
* that we offload.
|
||||
*/
|
||||
if (!check_cb(lower_dev) &&
|
||||
!switchdev_lower_dev_find(lower_dev, check_cb,
|
||||
!switchdev_lower_dev_find_rcu(lower_dev, check_cb,
|
||||
foreign_dev_check_cb))
|
||||
continue;
|
||||
|
||||
|
@ -501,7 +522,7 @@ maybe_bridged_with_us:
|
|||
if (!br || !netif_is_bridge_master(br))
|
||||
return 0;
|
||||
|
||||
if (!switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb))
|
||||
if (!switchdev_lower_dev_find_rcu(br, check_cb, foreign_dev_check_cb))
|
||||
return 0;
|
||||
|
||||
return __switchdev_handle_fdb_event_to_device(br, orig_dev, event, fdb_info,
|
||||
|
@ -536,13 +557,15 @@ EXPORT_SYMBOL_GPL(switchdev_handle_fdb_event_to_device);
|
|||
static int __switchdev_handle_port_obj_add(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*add_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj,
|
||||
struct netlink_ext_ack *extack))
|
||||
{
|
||||
struct switchdev_notifier_info *info = &port_obj_info->info;
|
||||
struct net_device *br, *lower_dev;
|
||||
struct netlink_ext_ack *extack;
|
||||
struct net_device *lower_dev;
|
||||
struct list_head *iter;
|
||||
int err = -EOPNOTSUPP;
|
||||
|
||||
|
@ -566,15 +589,42 @@ static int __switchdev_handle_port_obj_add(struct net_device *dev,
|
|||
if (netif_is_bridge_master(lower_dev))
|
||||
continue;
|
||||
|
||||
/* When searching for switchdev interfaces that are neighbors
|
||||
* of foreign ones, and @dev is a bridge, do not recurse on the
|
||||
* foreign interface again, it was already visited.
|
||||
*/
|
||||
if (foreign_dev_check_cb && !check_cb(lower_dev) &&
|
||||
!switchdev_lower_dev_find(lower_dev, check_cb, foreign_dev_check_cb))
|
||||
continue;
|
||||
|
||||
err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info,
|
||||
check_cb, add_cb);
|
||||
check_cb, foreign_dev_check_cb,
|
||||
add_cb);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Event is neither on a bridge nor a LAG. Check whether it is on an
|
||||
* interface that is in a bridge with us.
|
||||
*/
|
||||
if (!foreign_dev_check_cb)
|
||||
return err;
|
||||
|
||||
br = netdev_master_upper_dev_get(dev);
|
||||
if (!br || !netif_is_bridge_master(br))
|
||||
return err;
|
||||
|
||||
if (!switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb))
|
||||
return err;
|
||||
|
||||
return __switchdev_handle_port_obj_add(br, port_obj_info, check_cb,
|
||||
foreign_dev_check_cb, add_cb);
|
||||
}
|
||||
|
||||
/* Pass through a port object addition, if @dev passes @check_cb, or replicate
|
||||
* it towards all lower interfaces of @dev that pass @check_cb, if @dev is a
|
||||
* bridge or a LAG.
|
||||
*/
|
||||
int switchdev_handle_port_obj_add(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
|
@ -585,21 +635,46 @@ int switchdev_handle_port_obj_add(struct net_device *dev,
|
|||
int err;
|
||||
|
||||
err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,
|
||||
add_cb);
|
||||
NULL, add_cb);
|
||||
if (err == -EOPNOTSUPP)
|
||||
err = 0;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add);
|
||||
|
||||
/* Same as switchdev_handle_port_obj_add(), except if object is notified on a
|
||||
* @dev that passes @foreign_dev_check_cb, it is replicated towards all devices
|
||||
* that pass @check_cb and are in the same bridge as @dev.
|
||||
*/
|
||||
int switchdev_handle_port_obj_add_foreign(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*add_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj,
|
||||
struct netlink_ext_ack *extack))
|
||||
{
|
||||
int err;
|
||||
|
||||
err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,
|
||||
foreign_dev_check_cb, add_cb);
|
||||
if (err == -EOPNOTSUPP)
|
||||
err = 0;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add_foreign);
|
||||
|
||||
static int __switchdev_handle_port_obj_del(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*del_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj))
|
||||
{
|
||||
struct switchdev_notifier_info *info = &port_obj_info->info;
|
||||
struct net_device *lower_dev;
|
||||
struct net_device *br, *lower_dev;
|
||||
struct list_head *iter;
|
||||
int err = -EOPNOTSUPP;
|
||||
|
||||
|
@ -621,15 +696,42 @@ static int __switchdev_handle_port_obj_del(struct net_device *dev,
|
|||
if (netif_is_bridge_master(lower_dev))
|
||||
continue;
|
||||
|
||||
/* When searching for switchdev interfaces that are neighbors
|
||||
* of foreign ones, and @dev is a bridge, do not recurse on the
|
||||
* foreign interface again, it was already visited.
|
||||
*/
|
||||
if (foreign_dev_check_cb && !check_cb(lower_dev) &&
|
||||
!switchdev_lower_dev_find(lower_dev, check_cb, foreign_dev_check_cb))
|
||||
continue;
|
||||
|
||||
err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info,
|
||||
check_cb, del_cb);
|
||||
check_cb, foreign_dev_check_cb,
|
||||
del_cb);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Event is neither on a bridge nor a LAG. Check whether it is on an
|
||||
* interface that is in a bridge with us.
|
||||
*/
|
||||
if (!foreign_dev_check_cb)
|
||||
return err;
|
||||
|
||||
br = netdev_master_upper_dev_get(dev);
|
||||
if (!br || !netif_is_bridge_master(br))
|
||||
return err;
|
||||
|
||||
if (!switchdev_lower_dev_find(br, check_cb, foreign_dev_check_cb))
|
||||
return err;
|
||||
|
||||
return __switchdev_handle_port_obj_del(br, port_obj_info, check_cb,
|
||||
foreign_dev_check_cb, del_cb);
|
||||
}
|
||||
|
||||
/* Pass through a port object deletion, if @dev passes @check_cb, or replicate
|
||||
* it towards all lower interfaces of @dev that pass @check_cb, if @dev is a
|
||||
* bridge or a LAG.
|
||||
*/
|
||||
int switchdev_handle_port_obj_del(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
|
@ -639,13 +741,35 @@ int switchdev_handle_port_obj_del(struct net_device *dev,
|
|||
int err;
|
||||
|
||||
err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,
|
||||
del_cb);
|
||||
NULL, del_cb);
|
||||
if (err == -EOPNOTSUPP)
|
||||
err = 0;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del);
|
||||
|
||||
/* Same as switchdev_handle_port_obj_del(), except if object is notified on a
|
||||
* @dev that passes @foreign_dev_check_cb, it is replicated towards all devices
|
||||
* that pass @check_cb and are in the same bridge as @dev.
|
||||
*/
|
||||
int switchdev_handle_port_obj_del_foreign(struct net_device *dev,
|
||||
struct switchdev_notifier_port_obj_info *port_obj_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
bool (*foreign_dev_check_cb)(const struct net_device *dev,
|
||||
const struct net_device *foreign_dev),
|
||||
int (*del_cb)(struct net_device *dev, const void *ctx,
|
||||
const struct switchdev_obj *obj))
|
||||
{
|
||||
int err;
|
||||
|
||||
err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,
|
||||
foreign_dev_check_cb, del_cb);
|
||||
if (err == -EOPNOTSUPP)
|
||||
err = 0;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del_foreign);
|
||||
|
||||
static int __switchdev_handle_port_attr_set(struct net_device *dev,
|
||||
struct switchdev_notifier_port_attr_info *port_attr_info,
|
||||
bool (*check_cb)(const struct net_device *dev),
|
||||
|
|
Loading…
Reference in New Issue