Instead of allowing any external frame to egress any internal port,
configure the Cross-chip Port VLAN Table (PVT) to forbid that.

When an external source port joins or leaves a bridge crossing this
switch, mask it in the PVT to allow or forbid frames to egress.

Add support for the cross-chip bridge notification to the 6352 family.

Signed-off-by: Vivien Didelot <vivien.dide...@savoirfairelinux.com>
---
 drivers/net/dsa/mv88e6352.c |   1 +
 drivers/net/dsa/mv88e6xxx.c | 137 +++++++++++++++++++++++++++++++++++++++++++-
 drivers/net/dsa/mv88e6xxx.h |   2 +
 3 files changed, 138 insertions(+), 2 deletions(-)

diff --git a/drivers/net/dsa/mv88e6352.c b/drivers/net/dsa/mv88e6352.c
index 4afc24d..03ab309 100644
--- a/drivers/net/dsa/mv88e6352.c
+++ b/drivers/net/dsa/mv88e6352.c
@@ -364,6 +364,7 @@ struct dsa_switch_driver mv88e6352_switch_driver = {
        .port_fdb_add           = mv88e6xxx_port_fdb_add,
        .port_fdb_del           = mv88e6xxx_port_fdb_del,
        .port_fdb_dump          = mv88e6xxx_port_fdb_dump,
+       .cross_chip_bridge      = mv88e6xxx_cross_chip_bridge,
 };
 
 MODULE_ALIAS("platform:mv88e6172");
diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c
index e35bc9f..dccefdb 100644
--- a/drivers/net/dsa/mv88e6xxx.c
+++ b/drivers/net/dsa/mv88e6xxx.c
@@ -481,6 +481,14 @@ static bool mv88e6xxx_has_stu(struct dsa_switch *ds)
        return false;
 }
 
+static bool mv88e6xxx_has_pvt(struct dsa_switch *ds)
+{
+       if (mv88e6xxx_6185_family(ds))
+               return false;
+
+       return true;
+}
+
 /* We expect the switch to perform auto negotiation if there is a real
  * phy. However, in the case of a fixed link phy, we force the port
  * settings from the fixed link settings.
@@ -2228,8 +2236,69 @@ static int _mv88e6xxx_pvt_cmd(struct dsa_switch *ds, int 
src_dev, int src_port,
        return _mv88e6xxx_pvt_wait(ds);
 }
 
+static int _mv88e6xxx_pvt_read(struct dsa_switch *ds, int src_dev, int 
src_port,
+                              u16 *data)
+{
+       int ret;
+
+       ret = _mv88e6xxx_pvt_wait(ds);
+       if (ret < 0)
+               return ret;
+
+       ret = _mv88e6xxx_pvt_cmd(ds, src_dev, src_port,
+                               GLOBAL2_PVT_ADDR_OP_READ);
+       if (ret < 0)
+               return ret;
+
+       ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL2, GLOBAL2_PVT_DATA);
+       if (ret < 0)
+               return ret;
+
+       *data = ret;
+
+       return 0;
+}
+
+static int _mv88e6xxx_pvt_write(struct dsa_switch *ds, int src_dev,
+                               int src_port, u16 data)
+{
+       int err;
+
+       err = _mv88e6xxx_pvt_wait(ds);
+       if (err)
+               return err;
+
+       err = _mv88e6xxx_reg_write(ds, REG_GLOBAL2, GLOBAL2_PVT_DATA, data);
+       if (err)
+               return err;
+
+        return _mv88e6xxx_pvt_cmd(ds, src_dev, src_port,
+                               GLOBAL2_PVT_ADDR_OP_WRITE_PVLAN);
+}
+
+static int _mv88e6xxx_pvt_map(struct dsa_switch *ds, int src_dev, int src_port,
+                             struct net_device *bridge)
+{
+       struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
+       u16 pvlan = 0;
+       int port;
+
+       for (port = 0; port < ps->info->num_ports; ++port) {
+               /* Frames from external ports can egress DSA and CPU ports */
+               if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
+                       pvlan |= BIT(port);
+
+               /* Frames can egress bridge group members */
+               if (bridge && ps->ports[port].bridge_dev == bridge)
+                       pvlan |= BIT(port);
+       }
+
+       return _mv88e6xxx_pvt_write(ds, src_dev, src_port, pvlan);
+}
+
 static int _mv88e6xxx_pvt_init(struct dsa_switch *ds)
 {
+       int src_dev, src_port;
        int err;
 
        /* Clear 5 Bit Port for usage with Marvell Link Street devices:
@@ -2240,8 +2309,21 @@ static int _mv88e6xxx_pvt_init(struct dsa_switch *ds)
        if (err)
                return err;
 
-       /* Allow any external frame to egress any internal port */
-       return _mv88e6xxx_pvt_cmd(ds, 0, 0, GLOBAL2_PVT_ADDR_OP_INIT_ONES);
+       /* Forbid every port of potential neighbor switches to egress frames on
+        * the normal ports of this switch.
+        */
+       for (src_dev = 0; src_dev < 32; ++src_dev) {
+               if (src_dev == ds->index)
+                       continue;
+
+               for (src_port = 0; src_port < 16; ++src_port) {
+                       err = _mv88e6xxx_pvt_map(ds, src_dev, src_port, NULL);
+                       if (err)
+                               return err;
+               }
+       }
+
+       return 0;
 }
 
 int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
@@ -2286,6 +2368,35 @@ unlock:
        return err;
 }
 
+static int _mv88e6xxx_pvt_unmap_local(struct dsa_switch *ds, int port)
+{
+       u16 pvlan;
+       int src_dev, src_port, err;
+
+       for (src_dev = 0; src_dev < 32; ++src_dev) {
+               if (src_dev == ds->index)
+                       continue;
+
+               for (src_port = 0; src_port < 16; ++src_port) {
+                       err = _mv88e6xxx_pvt_read(ds, src_dev, src_port,
+                                                 &pvlan);
+                       if (err)
+                               return err;
+
+                       /* Forbid external normal frames to egress this port */
+                       if (pvlan & BIT(port)) {
+                               err = _mv88e6xxx_pvt_write(ds, src_dev,
+                                                          src_port,
+                                                          pvlan & ~BIT(port));
+                               if (err)
+                                       return err;
+                       }
+               }
+       }
+
+       return 0;
+}
+
 void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port)
 {
        struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
@@ -2308,6 +2419,28 @@ void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, 
int port)
                        if (_mv88e6xxx_port_based_vlan_map(ds, i))
                                netdev_warn(ds->ports[i], "failed to remap\n");
 
+       if (mv88e6xxx_has_pvt(ds) && _mv88e6xxx_pvt_unmap_local(ds, port))
+               netdev_err(ds->ports[port], "failed to unmap\n");
+
+       mutex_unlock(&ps->smi_mutex);
+}
+
+void mv88e6xxx_cross_chip_bridge(struct dsa_switch *ds, int sw_index,
+                                int sw_port, struct net_device *bridge)
+{
+       struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
+
+       if (!mv88e6xxx_has_pvt(ds))
+               return;
+
+       /* Update the Cross-chip Port VLAN Table (PVT) entry for this external
+        * source port to map which internal ports frames are allowed to egress.
+        */
+
+       mutex_lock(&ps->smi_mutex);
+       if (_mv88e6xxx_pvt_map(ds, sw_index, sw_port, bridge))
+               dev_err(ds->master_dev, "failed to access PVT for sw%dp%d\n",
+                       sw_index, sw_port);
        mutex_unlock(&ps->smi_mutex);
 }
 
diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h
index dd63377..ea214f2 100644
--- a/drivers/net/dsa/mv88e6xxx.h
+++ b/drivers/net/dsa/mv88e6xxx.h
@@ -523,6 +523,8 @@ int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port,
 int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port,
                            struct switchdev_obj_port_fdb *fdb,
                            int (*cb)(struct switchdev_obj *obj));
+void mv88e6xxx_cross_chip_bridge(struct dsa_switch *ds, int sw_index,
+                                int sw_port, struct net_device *bridge);
 int mv88e6xxx_phy_page_read(struct dsa_switch *ds, int port, int page, int 
reg);
 int mv88e6xxx_phy_page_write(struct dsa_switch *ds, int port, int page,
                             int reg, int val);
-- 
2.8.0

Reply via email to