net: dsa: Be aware of switches where VLAN filtering is a global setting
authorVladimir Oltean <olteanv@gmail.com>
Sun, 28 Apr 2019 18:45:44 +0000 (21:45 +0300)
committerDavid S. Miller <davem@davemloft.net>
Wed, 1 May 2019 03:05:28 +0000 (23:05 -0400)
On some switches, the action of whether to parse VLAN frame headers and use
that information for ingress admission is configurable, but not per
port. Such is the case for the Broadcom BCM53xx and the NXP SJA1105
families, for example. In that case, DSA can prevent the bridge core
from trying to apply different VLAN filtering settings on net devices
that belong to the same switch.

Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
Suggested-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/dsa.h
net/dsa/port.c

index 79a87913126c99832e758c79c84a36d9383805ba..aab3c2029edd50fb769e04a29df133a1ccaed7f4 100644 (file)
@@ -228,6 +228,11 @@ struct dsa_switch {
        /* Number of switch port queues */
        unsigned int            num_tx_queues;
 
+       /* Disallow bridge core from requesting different VLAN awareness
+        * settings on ports if not hardware-supported
+        */
+       bool                    vlan_filtering_is_global;
+
        unsigned long           *bitmap;
        unsigned long           _bitmap;
 
index a86fe3be126106abc6d83f47aae6b0a1c3723838..9a6ed138878c356fc9c088545f0d848ff02cbc57 100644 (file)
@@ -154,6 +154,39 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
        dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
 }
 
+static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp,
+                                             bool vlan_filtering)
+{
+       struct dsa_switch *ds = dp->ds;
+       int i;
+
+       if (!ds->vlan_filtering_is_global)
+               return true;
+
+       /* For cases where enabling/disabling VLAN awareness is global to the
+        * switch, we need to handle the case where multiple bridges span
+        * different ports of the same switch device and one of them has a
+        * different setting than what is being requested.
+        */
+       for (i = 0; i < ds->num_ports; i++) {
+               struct net_device *other_bridge;
+
+               other_bridge = dsa_to_port(ds, i)->bridge_dev;
+               if (!other_bridge)
+                       continue;
+               /* If it's the same bridge, it also has same
+                * vlan_filtering setting => no need to check
+                */
+               if (other_bridge == dp->bridge_dev)
+                       continue;
+               if (br_vlan_enabled(other_bridge) != vlan_filtering) {
+                       dev_err(ds->dev, "VLAN filtering is a global setting\n");
+                       return false;
+               }
+       }
+       return true;
+}
+
 int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
                            struct switchdev_trans *trans)
 {
@@ -164,13 +197,18 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
        if (switchdev_trans_ph_prepare(trans))
                return 0;
 
-       if (ds->ops->port_vlan_filtering) {
-               err = ds->ops->port_vlan_filtering(ds, dp->index,
-                                                  vlan_filtering);
-               if (err)
-                       return err;
-               dp->vlan_filtering = vlan_filtering;
-       }
+       if (!ds->ops->port_vlan_filtering)
+               return 0;
+
+       if (!dsa_port_can_apply_vlan_filtering(dp, vlan_filtering))
+               return -EINVAL;
+
+       err = ds->ops->port_vlan_filtering(ds, dp->index,
+                                          vlan_filtering);
+       if (err)
+               return err;
+
+       dp->vlan_filtering = vlan_filtering;
        return 0;
 }