net: dsa: only unset VLAN filtering when last port leaves last VLAN-aware bridge

DSA is aware of switches with global VLAN filtering since the blamed
commit, but it makes a bad decision when multiple bridges are spanning
the same switch:

ip link add br0 type bridge vlan_filtering 1
ip link add br1 type bridge vlan_filtering 1
ip link set swp2 master br0
ip link set swp3 master br0
ip link set swp4 master br1
ip link set swp5 master br1
ip link set swp5 nomaster
ip link set swp4 nomaster
[138665.939930] sja1105 spi0.1: port 3: dsa_core: VLAN filtering is a global setting
[138665.947514] DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE

When all ports leave br1, DSA blindly attempts to disable VLAN filtering
on the switch, ignoring the fact that br0 still exists and is VLAN-aware
too. It fails while doing that.

This patch checks whether any port exists at all and is under a
VLAN-aware bridge.

Fixes: d371b7c92d ("net: dsa: Unset vlan_filtering when ports leave the bridge")
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Tested-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Vladimir Oltean 2021-03-24 11:56:39 +02:00 committed by David S. Miller
parent 002322402d
commit 479dc497db

View File

@ -107,7 +107,7 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
bool unset_vlan_filtering = br_vlan_enabled(info->br); bool unset_vlan_filtering = br_vlan_enabled(info->br);
struct dsa_switch_tree *dst = ds->dst; struct dsa_switch_tree *dst = ds->dst;
struct netlink_ext_ack extack = {0}; struct netlink_ext_ack extack = {0};
int err, i; int err, port;
if (dst->index == info->tree_index && ds->index == info->sw_index && if (dst->index == info->tree_index && ds->index == info->sw_index &&
ds->ops->port_bridge_join) ds->ops->port_bridge_join)
@ -124,13 +124,16 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
* it. That is a good thing, because that lets us handle it and also * it. That is a good thing, because that lets us handle it and also
* handle the case where the switch's vlan_filtering setting is global * handle the case where the switch's vlan_filtering setting is global
* (not per port). When that happens, the correct moment to trigger the * (not per port). When that happens, the correct moment to trigger the
* vlan_filtering callback is only when the last port left this bridge. * vlan_filtering callback is only when the last port leaves the last
* VLAN-aware bridge.
*/ */
if (unset_vlan_filtering && ds->vlan_filtering_is_global) { if (unset_vlan_filtering && ds->vlan_filtering_is_global) {
for (i = 0; i < ds->num_ports; i++) { for (port = 0; port < ds->num_ports; port++) {
if (i == info->port) struct net_device *bridge_dev;
continue;
if (dsa_to_port(ds, i)->bridge_dev == info->br) { bridge_dev = dsa_to_port(ds, port)->bridge_dev;
if (bridge_dev && br_vlan_enabled(bridge_dev)) {
unset_vlan_filtering = false; unset_vlan_filtering = false;
break; break;
} }