diff mbox series

[v2,net,1/3] net: dsa: only unset VLAN filtering when last port leaves last VLAN-aware bridge

Message ID 20210320225928.2481575-2-olteanv@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series Clear rx-vlan-filter feature in DSA when necessary | expand

Checks

Context Check Description
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for net
netdev/subject_prefix success Link
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/verify_fixes success Link
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 29 lines checked
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/header_inline success Link

Commit Message

Vladimir Oltean March 20, 2021, 10:59 p.m. UTC
From: Vladimir Oltean <vladimir.oltean@nxp.com>

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: d371b7c92d19 ("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>
---
 net/dsa/switch.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

Comments

Tobias Waldekranz March 22, 2021, 5:52 p.m. UTC | #1
On Sun, Mar 21, 2021 at 00:59, Vladimir Oltean <olteanv@gmail.com> wrote:
> From: Vladimir Oltean <vladimir.oltean@nxp.com>
>
> 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: d371b7c92d19 ("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>
> ---
>  net/dsa/switch.c | 15 +++++++++------
>  1 file changed, 9 insertions(+), 6 deletions(-)
>
> diff --git a/net/dsa/switch.c b/net/dsa/switch.c
> index 4b5da89dc27a..32963276452f 100644
> --- a/net/dsa/switch.c
> +++ b/net/dsa/switch.c
> @@ -107,7 +107,7 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
>  	bool unset_vlan_filtering = br_vlan_enabled(info->br);
>  	struct dsa_switch_tree *dst = ds->dst;
>  	struct netlink_ext_ack extack = {0};
> -	int err, i;
> +	int err, port;
>  
>  	if (dst->index == info->tree_index && ds->index == info->sw_index &&
>  	    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
>  	 * handle the case where the switch's vlan_filtering setting is global
>  	 * (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) {
> -		for (i = 0; i < ds->num_ports; i++) {
> -			if (i == info->port)
> -				continue;
> -			if (dsa_to_port(ds, i)->bridge_dev == info->br) {
> +		for (port = 0; port < ds->num_ports; port++) {
> +			struct net_device *bridge_dev;
> +
> +			bridge_dev = dsa_to_port(ds, port)->bridge_dev;
> +
> +			if (bridge_dev && br_vlan_enabled(bridge_dev)) {
>  				unset_vlan_filtering = false;
>  				break;
>  			}
> -- 
> 2.25.1

Is it the case that all devices in which VLAN filtering is a global
setting are also single-chip? To my _D_SA eyes, it feels like we should
have to iterate over all ports in the tree, not just the switch.
Vladimir Oltean March 22, 2021, 5:56 p.m. UTC | #2
On Mon, Mar 22, 2021 at 06:52:58PM +0100, Tobias Waldekranz wrote:
> On Sun, Mar 21, 2021 at 00:59, Vladimir Oltean <olteanv@gmail.com> wrote:
> > From: Vladimir Oltean <vladimir.oltean@nxp.com>
> >
> > 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: d371b7c92d19 ("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>
> > ---
> >  net/dsa/switch.c | 15 +++++++++------
> >  1 file changed, 9 insertions(+), 6 deletions(-)
> >
> > diff --git a/net/dsa/switch.c b/net/dsa/switch.c
> > index 4b5da89dc27a..32963276452f 100644
> > --- a/net/dsa/switch.c
> > +++ b/net/dsa/switch.c
> > @@ -107,7 +107,7 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
> >  	bool unset_vlan_filtering = br_vlan_enabled(info->br);
> >  	struct dsa_switch_tree *dst = ds->dst;
> >  	struct netlink_ext_ack extack = {0};
> > -	int err, i;
> > +	int err, port;
> >  
> >  	if (dst->index == info->tree_index && ds->index == info->sw_index &&
> >  	    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
> >  	 * handle the case where the switch's vlan_filtering setting is global
> >  	 * (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) {
> > -		for (i = 0; i < ds->num_ports; i++) {
> > -			if (i == info->port)
> > -				continue;
> > -			if (dsa_to_port(ds, i)->bridge_dev == info->br) {
> > +		for (port = 0; port < ds->num_ports; port++) {
> > +			struct net_device *bridge_dev;
> > +
> > +			bridge_dev = dsa_to_port(ds, port)->bridge_dev;
> > +
> > +			if (bridge_dev && br_vlan_enabled(bridge_dev)) {
> >  				unset_vlan_filtering = false;
> >  				break;
> >  			}
> > -- 
> > 2.25.1
> 
> Is it the case that all devices in which VLAN filtering is a global
> setting are also single-chip? To my _D_SA eyes, it feels like we should
> have to iterate over all ports in the tree, not just the switch.

Correct, I might revisit this if I ever get my hands on a board with
sja1105 switches in a real multi-switch tree, and not in disjoint trees
as I have had access to so far.
diff mbox series

Patch

diff --git a/net/dsa/switch.c b/net/dsa/switch.c
index 4b5da89dc27a..32963276452f 100644
--- a/net/dsa/switch.c
+++ b/net/dsa/switch.c
@@ -107,7 +107,7 @@  static int dsa_switch_bridge_leave(struct dsa_switch *ds,
 	bool unset_vlan_filtering = br_vlan_enabled(info->br);
 	struct dsa_switch_tree *dst = ds->dst;
 	struct netlink_ext_ack extack = {0};
-	int err, i;
+	int err, port;
 
 	if (dst->index == info->tree_index && ds->index == info->sw_index &&
 	    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
 	 * handle the case where the switch's vlan_filtering setting is global
 	 * (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) {
-		for (i = 0; i < ds->num_ports; i++) {
-			if (i == info->port)
-				continue;
-			if (dsa_to_port(ds, i)->bridge_dev == info->br) {
+		for (port = 0; port < ds->num_ports; port++) {
+			struct net_device *bridge_dev;
+
+			bridge_dev = dsa_to_port(ds, port)->bridge_dev;
+
+			if (bridge_dev && br_vlan_enabled(bridge_dev)) {
 				unset_vlan_filtering = false;
 				break;
 			}