net: dsa: mv88e6xxx: isolate unbridged ports
authorVivien Didelot <vivien.didelot@savoirfairelinux.com>
Wed, 4 Nov 2015 22:23:40 +0000 (17:23 -0500)
committerDavid S. Miller <davem@davemloft.net>
Thu, 5 Nov 2015 18:37:23 +0000 (13:37 -0500)
The DSA documentation specifies that each port must be capable of
forwarding frames to the CPU port. The last changes on bridging support
for the mv88e6xxx driver broke this requirement for non-bridged ports.

So as for the bridged ports, reserve a few VLANs (4000+) in the switch
to isolate ports that have not been bridged yet.

By default, a port will be isolated with the CPU and DSA ports. When the
port joins a bridge, it will leave its reserved port. When it is removed
from a bridge, it will join its reserved VLAN again.

Fixes: 5fe7f68016ff ("net: dsa: mv88e6xxx: fix hardware bridging")
Reported-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/mv88e6171.c
drivers/net/dsa/mv88e6352.c
drivers/net/dsa/mv88e6xxx.c
drivers/net/dsa/mv88e6xxx.h

index 54aa00012dd0ce5ca36427c0cfb41f773ea47138..6e18213b9c04434a1feaae0c87da255859632732 100644 (file)
@@ -103,6 +103,8 @@ struct dsa_switch_driver mv88e6171_switch_driver = {
 #endif
        .get_regs_len           = mv88e6xxx_get_regs_len,
        .get_regs               = mv88e6xxx_get_regs,
+       .port_join_bridge       = mv88e6xxx_port_bridge_join,
+       .port_leave_bridge      = mv88e6xxx_port_bridge_leave,
        .port_stp_update        = mv88e6xxx_port_stp_update,
        .port_pvid_get          = mv88e6xxx_port_pvid_get,
        .port_vlan_prepare      = mv88e6xxx_port_vlan_prepare,
index ff846d0cfcebeff1b332b8f8ae1ca48d106c667d..cc6c5455341839eb91ba55dfd23e96e954c41fb7 100644 (file)
@@ -323,6 +323,8 @@ struct dsa_switch_driver mv88e6352_switch_driver = {
        .set_eeprom             = mv88e6352_set_eeprom,
        .get_regs_len           = mv88e6xxx_get_regs_len,
        .get_regs               = mv88e6xxx_get_regs,
+       .port_join_bridge       = mv88e6xxx_port_bridge_join,
+       .port_leave_bridge      = mv88e6xxx_port_bridge_leave,
        .port_stp_update        = mv88e6xxx_port_stp_update,
        .port_pvid_get          = mv88e6xxx_port_pvid_get,
        .port_vlan_prepare      = mv88e6xxx_port_vlan_prepare,
index 04cff58d771bd5a86f3b2a0bf2a047de1ee4b6f4..b06dba05594aaeb79988487bc431412b3dfae2c4 100644 (file)
@@ -1462,6 +1462,10 @@ int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port,
                                const struct switchdev_obj_port_vlan *vlan,
                                struct switchdev_trans *trans)
 {
+       /* We reserve a few VLANs to isolate unbridged ports */
+       if (vlan->vid_end >= 4000)
+               return -EOPNOTSUPP;
+
        /* We don't need any dynamic resource from the kernel (yet),
         * so skip the prepare phase.
         */
@@ -1870,6 +1874,36 @@ unlock:
        return err;
 }
 
+int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, u32 members)
+{
+       struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
+       const u16 pvid = 4000 + ds->index * DSA_MAX_PORTS + port;
+       int err;
+
+       /* The port joined a bridge, so leave its reserved VLAN */
+       mutex_lock(&ps->smi_mutex);
+       err = _mv88e6xxx_port_vlan_del(ds, port, pvid);
+       if (!err)
+               err = _mv88e6xxx_port_pvid_set(ds, port, 0);
+       mutex_unlock(&ps->smi_mutex);
+       return err;
+}
+
+int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port, u32 members)
+{
+       struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
+       const u16 pvid = 4000 + ds->index * DSA_MAX_PORTS + port;
+       int err;
+
+       /* The port left the bridge, so join its reserved VLAN */
+       mutex_lock(&ps->smi_mutex);
+       err = _mv88e6xxx_port_vlan_add(ds, port, pvid, true);
+       if (!err)
+               err = _mv88e6xxx_port_pvid_set(ds, port, pvid);
+       mutex_unlock(&ps->smi_mutex);
+       return err;
+}
+
 static void mv88e6xxx_bridge_work(struct work_struct *work)
 {
        struct mv88e6xxx_priv_state *ps;
@@ -2140,6 +2174,14 @@ int mv88e6xxx_setup_ports(struct dsa_switch *ds)
                ret = mv88e6xxx_setup_port(ds, i);
                if (ret < 0)
                        return ret;
+
+               if (dsa_is_cpu_port(ds, i) || dsa_is_dsa_port(ds, i))
+                       continue;
+
+               /* setup the unbridged state */
+               ret = mv88e6xxx_port_bridge_leave(ds, i, 0);
+               if (ret < 0)
+                       return ret;
        }
        return 0;
 }
index fb9a8739712f3cfc8a4f1736d594d221dedeeb89..21c8daa03f788e369caa9eb2fe12c764607acd62 100644 (file)
@@ -468,6 +468,8 @@ int mv88e6xxx_phy_write_indirect(struct dsa_switch *ds, int addr, int regnum,
 int mv88e6xxx_get_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
 int mv88e6xxx_set_eee(struct dsa_switch *ds, int port,
                      struct phy_device *phydev, struct ethtool_eee *e);
+int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, u32 members);
+int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port, u32 members);
 int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state);
 int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port,
                                const struct switchdev_obj_port_vlan *vlan,