mlxsw: spectrum: Implement FDB add/remove/dump for LAG
authorJiri Pirko <jiri@mellanox.com>
Thu, 3 Dec 2015 11:12:29 +0000 (12:12 +0100)
committerDavid S. Miller <davem@davemloft.net>
Thu, 3 Dec 2015 16:49:29 +0000 (11:49 -0500)
Implement FDB offloading for lagged ports, including learning LAG FDB
entries, adding/removing static FDB entries and dumping existing LAG FDB
entries.

Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c

index f21e23983a1a0ea15323d0cf871524e8e9613d48..406dab2f6b17cee9413f9f6682f585efe9b4eaab 100644 (file)
@@ -490,32 +490,56 @@ static int mlxsw_sp_port_vlans_add(struct mlxsw_sp_port *mlxsw_sp_port,
                                         untagged_flag, pvid_flag);
 }
 
-static int mlxsw_sp_port_fdb_op(struct mlxsw_sp_port *mlxsw_sp_port,
-                               const char *mac, u16 vid, bool adding,
-                               bool dynamic)
+static enum mlxsw_reg_sfd_rec_policy mlxsw_sp_sfd_rec_policy(bool dynamic)
 {
-       enum mlxsw_reg_sfd_rec_policy policy;
-       enum mlxsw_reg_sfd_op op;
+       return dynamic ? MLXSW_REG_SFD_REC_POLICY_DYNAMIC_ENTRY_INGRESS :
+                        MLXSW_REG_SFD_REC_POLICY_STATIC_ENTRY;
+}
+
+static enum mlxsw_reg_sfd_op mlxsw_sp_sfd_op(bool adding)
+{
+       return adding ? MLXSW_REG_SFD_OP_WRITE_EDIT :
+                       MLXSW_REG_SFD_OP_WRITE_REMOVE;
+}
+
+static int mlxsw_sp_port_fdb_uc_op(struct mlxsw_sp_port *mlxsw_sp_port,
+                                  const char *mac, u16 vid, bool adding,
+                                  bool dynamic)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
        char *sfd_pl;
        int err;
 
-       if (!vid)
-               vid = mlxsw_sp_port->pvid;
-
        sfd_pl = kmalloc(MLXSW_REG_SFD_LEN, GFP_KERNEL);
        if (!sfd_pl)
                return -ENOMEM;
 
-       policy = dynamic ? MLXSW_REG_SFD_REC_POLICY_DYNAMIC_ENTRY_INGRESS :
-                          MLXSW_REG_SFD_REC_POLICY_STATIC_ENTRY;
-       op = adding ? MLXSW_REG_SFD_OP_WRITE_EDIT :
-                     MLXSW_REG_SFD_OP_WRITE_REMOVE;
-       mlxsw_reg_sfd_pack(sfd_pl, op, 0);
-       mlxsw_reg_sfd_uc_pack(sfd_pl, 0, policy,
+       mlxsw_reg_sfd_pack(sfd_pl, mlxsw_sp_sfd_op(adding), 0);
+       mlxsw_reg_sfd_uc_pack(sfd_pl, 0, mlxsw_sp_sfd_rec_policy(dynamic),
                              mac, vid, MLXSW_REG_SFD_REC_ACTION_NOP,
                              mlxsw_sp_port->local_port);
-       err = mlxsw_reg_write(mlxsw_sp_port->mlxsw_sp->core, MLXSW_REG(sfd),
-                             sfd_pl);
+       err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfd), sfd_pl);
+       kfree(sfd_pl);
+
+       return err;
+}
+
+static int mlxsw_sp_port_fdb_uc_lag_op(struct mlxsw_sp *mlxsw_sp, u16 lag_id,
+                                      const char *mac, u16 vid, bool adding,
+                                      bool dynamic)
+{
+       char *sfd_pl;
+       int err;
+
+       sfd_pl = kmalloc(MLXSW_REG_SFD_LEN, GFP_KERNEL);
+       if (!sfd_pl)
+               return -ENOMEM;
+
+       mlxsw_reg_sfd_pack(sfd_pl, mlxsw_sp_sfd_op(adding), 0);
+       mlxsw_reg_sfd_uc_lag_pack(sfd_pl, 0, mlxsw_sp_sfd_rec_policy(dynamic),
+                                 mac, vid, MLXSW_REG_SFD_REC_ACTION_NOP,
+                                 lag_id);
+       err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfd), sfd_pl);
        kfree(sfd_pl);
 
        return err;
@@ -526,11 +550,21 @@ mlxsw_sp_port_fdb_static_add(struct mlxsw_sp_port *mlxsw_sp_port,
                             const struct switchdev_obj_port_fdb *fdb,
                             struct switchdev_trans *trans)
 {
+       u16 vid = fdb->vid;
+
        if (switchdev_trans_ph_prepare(trans))
                return 0;
 
-       return mlxsw_sp_port_fdb_op(mlxsw_sp_port, fdb->addr, fdb->vid,
-                                   true, false);
+       if (!vid)
+               vid = mlxsw_sp_port->pvid;
+
+       if (!mlxsw_sp_port->lagged)
+               return mlxsw_sp_port_fdb_uc_op(mlxsw_sp_port,
+                                              fdb->addr, vid, true, false);
+       else
+               return mlxsw_sp_port_fdb_uc_lag_op(mlxsw_sp_port->mlxsw_sp,
+                                                  mlxsw_sp_port->lag_id,
+                                                  fdb->addr, vid, true, false);
 }
 
 static int mlxsw_sp_port_obj_add(struct net_device *dev,
@@ -645,8 +679,15 @@ static int
 mlxsw_sp_port_fdb_static_del(struct mlxsw_sp_port *mlxsw_sp_port,
                             const struct switchdev_obj_port_fdb *fdb)
 {
-       return mlxsw_sp_port_fdb_op(mlxsw_sp_port, fdb->addr, fdb->vid,
-                                   false, false);
+       if (!mlxsw_sp_port->lagged)
+               return mlxsw_sp_port_fdb_uc_op(mlxsw_sp_port,
+                                              fdb->addr, fdb->vid,
+                                              false, false);
+       else
+               return mlxsw_sp_port_fdb_uc_lag_op(mlxsw_sp_port->mlxsw_sp,
+                                                  mlxsw_sp_port->lag_id,
+                                                  fdb->addr, fdb->vid,
+                                                  false, false);
 }
 
 static int mlxsw_sp_port_obj_del(struct net_device *dev,
@@ -672,14 +713,30 @@ static int mlxsw_sp_port_obj_del(struct net_device *dev,
        return err;
 }
 
+static struct mlxsw_sp_port *mlxsw_sp_lag_rep_port(struct mlxsw_sp *mlxsw_sp,
+                                                  u16 lag_id)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port;
+       int i;
+
+       for (i = 0; i < MLXSW_SP_PORT_PER_LAG_MAX; i++) {
+               mlxsw_sp_port = mlxsw_sp_port_lagged_get(mlxsw_sp, lag_id, i);
+               if (mlxsw_sp_port)
+                       return mlxsw_sp_port;
+       }
+       return NULL;
+}
+
 static int mlxsw_sp_port_fdb_dump(struct mlxsw_sp_port *mlxsw_sp_port,
                                  struct switchdev_obj_port_fdb *fdb,
                                  switchdev_obj_dump_cb_t *cb)
 {
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
        char *sfd_pl;
        char mac[ETH_ALEN];
        u16 vid;
        u8 local_port;
+       u16 lag_id;
        u8 num_rec;
        int stored_err = 0;
        int i;
@@ -692,8 +749,7 @@ static int mlxsw_sp_port_fdb_dump(struct mlxsw_sp_port *mlxsw_sp_port,
        mlxsw_reg_sfd_pack(sfd_pl, MLXSW_REG_SFD_OP_QUERY_DUMP, 0);
        do {
                mlxsw_reg_sfd_num_rec_set(sfd_pl, MLXSW_REG_SFD_REC_MAX_COUNT);
-               err = mlxsw_reg_query(mlxsw_sp_port->mlxsw_sp->core,
-                                     MLXSW_REG(sfd), sfd_pl);
+               err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(sfd), sfd_pl);
                if (err)
                        goto out;
 
@@ -718,6 +774,20 @@ static int mlxsw_sp_port_fdb_dump(struct mlxsw_sp_port *mlxsw_sp_port,
                                        if (err)
                                                stored_err = err;
                                }
+                               break;
+                       case MLXSW_REG_SFD_REC_TYPE_UNICAST_LAG:
+                               mlxsw_reg_sfd_uc_lag_unpack(sfd_pl, i,
+                                                           mac, &vid, &lag_id);
+                               if (mlxsw_sp_port ==
+                                   mlxsw_sp_lag_rep_port(mlxsw_sp, lag_id)) {
+                                       ether_addr_copy(fdb->addr, mac);
+                                       fdb->ndm_state = NUD_REACHABLE;
+                                       fdb->vid = vid;
+                                       err = cb(&fdb->obj);
+                                       if (err)
+                                               stored_err = err;
+                               }
+                               break;
                        }
                }
        } while (num_rec == MLXSW_REG_SFD_REC_MAX_COUNT);
@@ -779,6 +849,21 @@ static const struct switchdev_ops mlxsw_sp_port_switchdev_ops = {
        .switchdev_port_obj_dump        = mlxsw_sp_port_obj_dump,
 };
 
+static void mlxsw_sp_fdb_call_notifiers(bool learning, bool learning_sync,
+                                       bool adding, char *mac, u16 vid,
+                                       struct net_device *dev)
+{
+       struct switchdev_notifier_fdb_info info;
+       unsigned long notifier_type;
+
+       if (learning && learning_sync) {
+               info.addr = mac;
+               info.vid = vid;
+               notifier_type = adding ? SWITCHDEV_FDB_ADD : SWITCHDEV_FDB_DEL;
+               call_switchdev_notifiers(notifier_type, dev, &info.info);
+       }
+}
+
 static void mlxsw_sp_fdb_notify_mac_process(struct mlxsw_sp *mlxsw_sp,
                                            char *sfn_pl, int rec_index,
                                            bool adding)
@@ -796,24 +881,49 @@ static void mlxsw_sp_fdb_notify_mac_process(struct mlxsw_sp *mlxsw_sp,
                return;
        }
 
-       err = mlxsw_sp_port_fdb_op(mlxsw_sp_port, mac, vid,
-                                  adding && mlxsw_sp_port->learning, true);
+       err = mlxsw_sp_port_fdb_uc_op(mlxsw_sp_port, mac, vid,
+                                     adding && mlxsw_sp_port->learning, true);
        if (err) {
                if (net_ratelimit())
                        netdev_err(mlxsw_sp_port->dev, "Failed to set FDB entry\n");
                return;
        }
 
-       if (mlxsw_sp_port->learning && mlxsw_sp_port->learning_sync) {
-               struct switchdev_notifier_fdb_info info;
-               unsigned long notifier_type;
+       mlxsw_sp_fdb_call_notifiers(mlxsw_sp_port->learning,
+                                   mlxsw_sp_port->learning_sync,
+                                   adding, mac, vid, mlxsw_sp_port->dev);
+}
 
-               info.addr = mac;
-               info.vid = vid;
-               notifier_type = adding ? SWITCHDEV_FDB_ADD : SWITCHDEV_FDB_DEL;
-               call_switchdev_notifiers(notifier_type, mlxsw_sp_port->dev,
-                                        &info.info);
+static void mlxsw_sp_fdb_notify_mac_lag_process(struct mlxsw_sp *mlxsw_sp,
+                                               char *sfn_pl, int rec_index,
+                                               bool adding)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port;
+       char mac[ETH_ALEN];
+       u16 lag_id;
+       u16 vid;
+       int err;
+
+       mlxsw_reg_sfn_mac_lag_unpack(sfn_pl, rec_index, mac, &vid, &lag_id);
+       mlxsw_sp_port = mlxsw_sp_lag_rep_port(mlxsw_sp, lag_id);
+       if (!mlxsw_sp_port) {
+               dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Cannot find port representor for LAG\n");
+               return;
        }
+
+       err = mlxsw_sp_port_fdb_uc_lag_op(mlxsw_sp, lag_id, mac, vid,
+                                         adding && mlxsw_sp_port->learning,
+                                         true);
+       if (err) {
+               if (net_ratelimit())
+                       netdev_err(mlxsw_sp_port->dev, "Failed to set FDB entry\n");
+               return;
+       }
+
+       mlxsw_sp_fdb_call_notifiers(mlxsw_sp_port->learning,
+                                   mlxsw_sp_port->learning_sync,
+                                   adding, mac, vid,
+                                   mlxsw_sp_lag_get(mlxsw_sp, lag_id)->dev);
 }
 
 static void mlxsw_sp_fdb_notify_rec_process(struct mlxsw_sp *mlxsw_sp,
@@ -828,6 +938,14 @@ static void mlxsw_sp_fdb_notify_rec_process(struct mlxsw_sp *mlxsw_sp,
                mlxsw_sp_fdb_notify_mac_process(mlxsw_sp, sfn_pl,
                                                rec_index, false);
                break;
+       case MLXSW_REG_SFN_REC_TYPE_LEARNED_MAC_LAG:
+               mlxsw_sp_fdb_notify_mac_lag_process(mlxsw_sp, sfn_pl,
+                                                   rec_index, true);
+               break;
+       case MLXSW_REG_SFN_REC_TYPE_AGED_OUT_MAC_LAG:
+               mlxsw_sp_fdb_notify_mac_lag_process(mlxsw_sp, sfn_pl,
+                                                   rec_index, false);
+               break;
        }
 }