OSDN Git Service

net: dsa: felix: support FDB entries on offloaded LAG interfaces
authorVladimir Oltean <vladimir.oltean@nxp.com>
Wed, 23 Feb 2022 14:00:54 +0000 (16:00 +0200)
committerJakub Kicinski <kuba@kernel.org>
Fri, 25 Feb 2022 05:31:44 +0000 (21:31 -0800)
This adds the logic in the Felix DSA driver and Ocelot switch library.
For Ocelot switches, the DEST_IDX that is the output of the MAC table
lookup is a logical port (equal to physical port, if no LAG is used, or
a dynamically allocated number otherwise). The allocation we have in
place for LAG IDs is different from DSA's, so we can't use that:
- DSA allocates a continuous range of LAG IDs starting from 1
- Ocelot appears to require that physical ports and LAG IDs are in the
  same space of [0, num_phys_ports), and additionally, ports that aren't
  in a LAG must have physical port id == logical port id

The implication is that an FDB entry towards a LAG might need to be
deleted and reinstalled when the LAG ID changes.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/dsa/ocelot/felix.c
drivers/net/ethernet/mscc/ocelot.c
include/soc/mscc/ocelot.h

index 6d48388..9959407 100644 (file)
@@ -614,6 +614,22 @@ static int felix_fdb_del(struct dsa_switch *ds, int port,
        return ocelot_fdb_del(ocelot, port, addr, vid);
 }
 
+static int felix_lag_fdb_add(struct dsa_switch *ds, struct dsa_lag lag,
+                            const unsigned char *addr, u16 vid)
+{
+       struct ocelot *ocelot = ds->priv;
+
+       return ocelot_lag_fdb_add(ocelot, lag.dev, addr, vid);
+}
+
+static int felix_lag_fdb_del(struct dsa_switch *ds, struct dsa_lag lag,
+                            const unsigned char *addr, u16 vid)
+{
+       struct ocelot *ocelot = ds->priv;
+
+       return ocelot_lag_fdb_del(ocelot, lag.dev, addr, vid);
+}
+
 static int felix_mdb_add(struct dsa_switch *ds, int port,
                         const struct switchdev_obj_port_mdb *mdb)
 {
@@ -1579,6 +1595,8 @@ const struct dsa_switch_ops felix_switch_ops = {
        .port_fdb_dump                  = felix_fdb_dump,
        .port_fdb_add                   = felix_fdb_add,
        .port_fdb_del                   = felix_fdb_del,
+       .lag_fdb_add                    = felix_lag_fdb_add,
+       .lag_fdb_del                    = felix_lag_fdb_del,
        .port_mdb_add                   = felix_mdb_add,
        .port_mdb_del                   = felix_mdb_del,
        .port_pre_bridge_flags          = felix_pre_bridge_flags,
index 2fb713e..0e8fa0a 100644 (file)
@@ -1907,6 +1907,8 @@ static u32 ocelot_get_bond_mask(struct ocelot *ocelot, struct net_device *bond)
        u32 mask = 0;
        int port;
 
+       lockdep_assert_held(&ocelot->fwd_domain_lock);
+
        for (port = 0; port < ocelot->num_phys_ports; port++) {
                struct ocelot_port *ocelot_port = ocelot->ports[port];
 
@@ -1920,6 +1922,19 @@ static u32 ocelot_get_bond_mask(struct ocelot *ocelot, struct net_device *bond)
        return mask;
 }
 
+/* The logical port number of a LAG is equal to the lowest numbered physical
+ * port ID present in that LAG. It may change if that port ever leaves the LAG.
+ */
+static int ocelot_bond_get_id(struct ocelot *ocelot, struct net_device *bond)
+{
+       int bond_mask = ocelot_get_bond_mask(ocelot, bond);
+
+       if (!bond_mask)
+               return -ENOENT;
+
+       return __ffs(bond_mask);
+}
+
 u32 ocelot_get_bridge_fwd_mask(struct ocelot *ocelot, int src_port)
 {
        struct ocelot_port *ocelot_port = ocelot->ports[src_port];
@@ -2413,7 +2428,7 @@ static void ocelot_setup_logical_port_ids(struct ocelot *ocelot)
 
                bond = ocelot_port->bond;
                if (bond) {
-                       int lag = __ffs(ocelot_get_bond_mask(ocelot, bond));
+                       int lag = ocelot_bond_get_id(ocelot, bond);
 
                        ocelot_rmw_gix(ocelot,
                                       ANA_PORT_PORT_CFG_PORTID_VAL(lag),
@@ -2428,6 +2443,46 @@ static void ocelot_setup_logical_port_ids(struct ocelot *ocelot)
        }
 }
 
+/* Documentation for PORTID_VAL says:
+ *     Logical port number for front port. If port is not a member of a LLAG,
+ *     then PORTID must be set to the physical port number.
+ *     If port is a member of a LLAG, then PORTID must be set to the common
+ *     PORTID_VAL used for all member ports of the LLAG.
+ *     The value must not exceed the number of physical ports on the device.
+ *
+ * This means we have little choice but to migrate FDB entries pointing towards
+ * a logical port when that changes.
+ */
+static void ocelot_migrate_lag_fdbs(struct ocelot *ocelot,
+                                   struct net_device *bond,
+                                   int lag)
+{
+       struct ocelot_lag_fdb *fdb;
+       int err;
+
+       lockdep_assert_held(&ocelot->fwd_domain_lock);
+
+       list_for_each_entry(fdb, &ocelot->lag_fdbs, list) {
+               if (fdb->bond != bond)
+                       continue;
+
+               err = ocelot_mact_forget(ocelot, fdb->addr, fdb->vid);
+               if (err) {
+                       dev_err(ocelot->dev,
+                               "failed to delete LAG %s FDB %pM vid %d: %pe\n",
+                               bond->name, fdb->addr, fdb->vid, ERR_PTR(err));
+               }
+
+               err = ocelot_mact_learn(ocelot, lag, fdb->addr, fdb->vid,
+                                       ENTRYTYPE_LOCKED);
+               if (err) {
+                       dev_err(ocelot->dev,
+                               "failed to migrate LAG %s FDB %pM vid %d: %pe\n",
+                               bond->name, fdb->addr, fdb->vid, ERR_PTR(err));
+               }
+       }
+}
+
 int ocelot_port_lag_join(struct ocelot *ocelot, int port,
                         struct net_device *bond,
                         struct netdev_lag_upper_info *info)
@@ -2452,14 +2507,23 @@ EXPORT_SYMBOL(ocelot_port_lag_join);
 void ocelot_port_lag_leave(struct ocelot *ocelot, int port,
                           struct net_device *bond)
 {
+       int old_lag_id, new_lag_id;
+
        mutex_lock(&ocelot->fwd_domain_lock);
 
+       old_lag_id = ocelot_bond_get_id(ocelot, bond);
+
        ocelot->ports[port]->bond = NULL;
 
        ocelot_setup_logical_port_ids(ocelot);
        ocelot_apply_bridge_fwd_mask(ocelot, false);
        ocelot_set_aggr_pgids(ocelot);
 
+       new_lag_id = ocelot_bond_get_id(ocelot, bond);
+
+       if (new_lag_id >= 0 && old_lag_id != new_lag_id)
+               ocelot_migrate_lag_fdbs(ocelot, bond, new_lag_id);
+
        mutex_unlock(&ocelot->fwd_domain_lock);
 }
 EXPORT_SYMBOL(ocelot_port_lag_leave);
@@ -2468,13 +2532,74 @@ void ocelot_port_lag_change(struct ocelot *ocelot, int port, bool lag_tx_active)
 {
        struct ocelot_port *ocelot_port = ocelot->ports[port];
 
+       mutex_lock(&ocelot->fwd_domain_lock);
+
        ocelot_port->lag_tx_active = lag_tx_active;
 
        /* Rebalance the LAGs */
        ocelot_set_aggr_pgids(ocelot);
+
+       mutex_unlock(&ocelot->fwd_domain_lock);
 }
 EXPORT_SYMBOL(ocelot_port_lag_change);
 
+int ocelot_lag_fdb_add(struct ocelot *ocelot, struct net_device *bond,
+                      const unsigned char *addr, u16 vid)
+{
+       struct ocelot_lag_fdb *fdb;
+       int lag, err;
+
+       fdb = kzalloc(sizeof(*fdb), GFP_KERNEL);
+       if (!fdb)
+               return -ENOMEM;
+
+       ether_addr_copy(fdb->addr, addr);
+       fdb->vid = vid;
+       fdb->bond = bond;
+
+       mutex_lock(&ocelot->fwd_domain_lock);
+       lag = ocelot_bond_get_id(ocelot, bond);
+
+       err = ocelot_mact_learn(ocelot, lag, addr, vid, ENTRYTYPE_LOCKED);
+       if (err) {
+               mutex_unlock(&ocelot->fwd_domain_lock);
+               kfree(fdb);
+               return err;
+       }
+
+       list_add_tail(&fdb->list, &ocelot->lag_fdbs);
+       mutex_unlock(&ocelot->fwd_domain_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(ocelot_lag_fdb_add);
+
+int ocelot_lag_fdb_del(struct ocelot *ocelot, struct net_device *bond,
+                      const unsigned char *addr, u16 vid)
+{
+       struct ocelot_lag_fdb *fdb, *tmp;
+
+       mutex_lock(&ocelot->fwd_domain_lock);
+
+       list_for_each_entry_safe(fdb, tmp, &ocelot->lag_fdbs, list) {
+               if (!ether_addr_equal(fdb->addr, addr) || fdb->vid != vid ||
+                   fdb->bond != bond)
+                       continue;
+
+               ocelot_mact_forget(ocelot, addr, vid);
+               list_del(&fdb->list);
+               mutex_unlock(&ocelot->fwd_domain_lock);
+               kfree(fdb);
+
+               return 0;
+       }
+
+       mutex_unlock(&ocelot->fwd_domain_lock);
+
+       return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(ocelot_lag_fdb_del);
+
 /* Configure the maximum SDU (L2 payload) on RX to the value specified in @sdu.
  * The length of VLAN tags is accounted for automatically via DEV_MAC_TAGS_CFG.
  * In the special case that it's the NPI port that we're configuring, the
@@ -2769,6 +2894,7 @@ int ocelot_init(struct ocelot *ocelot)
        INIT_LIST_HEAD(&ocelot->multicast);
        INIT_LIST_HEAD(&ocelot->pgids);
        INIT_LIST_HEAD(&ocelot->vlans);
+       INIT_LIST_HEAD(&ocelot->lag_fdbs);
        ocelot_detect_features(ocelot);
        ocelot_mact_init(ocelot);
        ocelot_vlan_init(ocelot);
index 78f5650..dd4fc34 100644 (file)
@@ -635,6 +635,13 @@ enum macaccess_entry_type {
 #define OCELOT_QUIRK_PCS_PERFORMS_RATE_ADAPTATION      BIT(0)
 #define OCELOT_QUIRK_QSGMII_PORTS_MUST_BE_UP           BIT(1)
 
+struct ocelot_lag_fdb {
+       unsigned char addr[ETH_ALEN];
+       u16 vid;
+       struct net_device *bond;
+       struct list_head list;
+};
+
 struct ocelot_port {
        struct ocelot                   *ocelot;
 
@@ -690,6 +697,7 @@ struct ocelot {
 
        struct list_head                vlans;
        struct list_head                traps;
+       struct list_head                lag_fdbs;
 
        /* Switches like VSC9959 have flooding per traffic class */
        int                             num_flooding_pgids;
@@ -866,6 +874,10 @@ int ocelot_fdb_add(struct ocelot *ocelot, int port,
                   const unsigned char *addr, u16 vid);
 int ocelot_fdb_del(struct ocelot *ocelot, int port,
                   const unsigned char *addr, u16 vid);
+int ocelot_lag_fdb_add(struct ocelot *ocelot, struct net_device *bond,
+                      const unsigned char *addr, u16 vid);
+int ocelot_lag_fdb_del(struct ocelot *ocelot, struct net_device *bond,
+                      const unsigned char *addr, u16 vid);
 int ocelot_vlan_prepare(struct ocelot *ocelot, int port, u16 vid, bool pvid,
                        bool untagged, struct netlink_ext_ack *extack);
 int ocelot_vlan_add(struct ocelot *ocelot, int port, u16 vid, bool pvid,