mlxsw: spectrum: Configure FIDs based on bridge events
authorIdo Schimmel <idosch@mellanox.com>
Mon, 4 Jul 2016 06:23:16 +0000 (08:23 +0200)
committerDavid S. Miller <davem@davemloft.net>
Tue, 5 Jul 2016 01:25:16 +0000 (18:25 -0700)
Before introducing support for L3 interfaces on top of the VLAN-aware
bridge we need to add some missing infrastructure.

Such an interface can either be the bridge device itself or a VLAN
device on top of it. In the first case the router interface (RIF) is
associated with FID 1, which is created whenever the first port netdev
joins the bridge. We currently assume the default PVID is 1 and that
it's already created, as it seems reasonable. This can be extended in
the future.

However, in the second case it's entirely possible we've yet to create a
matching FID. This can happen if the VLAN device was configured before
making any bridge port member in the VLAN.

Prevent such ordering problems by using the VLAN device's CHANGEUPPER
event to configure the FID. Make the VLAN device hold a reference to the
FID and prevent it from being destroyed even if none of the port netdevs
is using it.

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

index e987a8afab4802cd7868900d0d25a14dac9cd796..e49f80baeabe491f508767f67c8e3a74f98e8a1d 100644 (file)
@@ -2887,6 +2887,17 @@ int mlxsw_sp_port_fdb_flush(struct mlxsw_sp_port *mlxsw_sp_port, u16 fid)
                return mlxsw_sp_port_fdb_flush_by_port_fid(mlxsw_sp_port, fid);
 }
 
+static void mlxsw_sp_master_bridge_gone_sync(struct mlxsw_sp *mlxsw_sp)
+{
+       struct mlxsw_sp_fid *f, *tmp;
+
+       list_for_each_entry_safe(f, tmp, &mlxsw_sp->fids, list)
+               if (--f->ref_count == 0)
+                       mlxsw_sp_fid_destroy(mlxsw_sp, f);
+               else
+                       WARN_ON_ONCE(1);
+}
+
 static bool mlxsw_sp_master_bridge_check(struct mlxsw_sp *mlxsw_sp,
                                         struct net_device *br_dev)
 {
@@ -2903,8 +2914,15 @@ static void mlxsw_sp_master_bridge_inc(struct mlxsw_sp *mlxsw_sp,
 
 static void mlxsw_sp_master_bridge_dec(struct mlxsw_sp *mlxsw_sp)
 {
-       if (--mlxsw_sp->master_bridge.ref_count == 0)
+       if (--mlxsw_sp->master_bridge.ref_count == 0) {
                mlxsw_sp->master_bridge.dev = NULL;
+               /* It's possible upper VLAN devices are still holding
+                * references to underlying FIDs. Drop the reference
+                * and release the resources if it was the last one.
+                * If it wasn't, then something bad happened.
+                */
+               mlxsw_sp_master_bridge_gone_sync(mlxsw_sp);
+       }
 }
 
 static int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
@@ -3373,18 +3391,68 @@ static int mlxsw_sp_netdevice_lag_event(struct net_device *lag_dev,
        return 0;
 }
 
-static struct mlxsw_sp_fid *
-mlxsw_sp_vfid_find(const struct mlxsw_sp *mlxsw_sp,
-                  const struct net_device *br_dev)
+static int mlxsw_sp_master_bridge_vlan_link(struct mlxsw_sp *mlxsw_sp,
+                                           struct net_device *vlan_dev)
 {
+       u16 fid = vlan_dev_vlan_id(vlan_dev);
        struct mlxsw_sp_fid *f;
 
-       list_for_each_entry(f, &mlxsw_sp->vfids.list, list) {
-               if (f->dev == br_dev)
-                       return f;
+       f = mlxsw_sp_fid_find(mlxsw_sp, fid);
+       if (!f) {
+               f = mlxsw_sp_fid_create(mlxsw_sp, fid);
+               if (IS_ERR(f))
+                       return PTR_ERR(f);
        }
 
-       return NULL;
+       f->ref_count++;
+
+       return 0;
+}
+
+static void mlxsw_sp_master_bridge_vlan_unlink(struct mlxsw_sp *mlxsw_sp,
+                                              struct net_device *vlan_dev)
+{
+       u16 fid = vlan_dev_vlan_id(vlan_dev);
+       struct mlxsw_sp_fid *f;
+
+       f = mlxsw_sp_fid_find(mlxsw_sp, fid);
+       if (f && --f->ref_count == 0)
+               mlxsw_sp_fid_destroy(mlxsw_sp, f);
+}
+
+static int mlxsw_sp_netdevice_bridge_event(struct net_device *br_dev,
+                                          unsigned long event, void *ptr)
+{
+       struct netdev_notifier_changeupper_info *info;
+       struct net_device *upper_dev;
+       struct mlxsw_sp *mlxsw_sp;
+       int err;
+
+       mlxsw_sp = mlxsw_sp_lower_get(br_dev);
+       if (!mlxsw_sp)
+               return 0;
+       if (br_dev != mlxsw_sp->master_bridge.dev)
+               return 0;
+
+       info = ptr;
+
+       switch (event) {
+       case NETDEV_CHANGEUPPER:
+               upper_dev = info->upper_dev;
+               if (!is_vlan_dev(upper_dev))
+                       break;
+               if (info->linking) {
+                       err = mlxsw_sp_master_bridge_vlan_link(mlxsw_sp,
+                                                              upper_dev);
+                       if (err)
+                               return err;
+               } else {
+                       mlxsw_sp_master_bridge_vlan_unlink(mlxsw_sp, upper_dev);
+               }
+               break;
+       }
+
+       return 0;
 }
 
 static u16 mlxsw_sp_avail_vfid_get(const struct mlxsw_sp *mlxsw_sp)
@@ -3675,6 +3743,8 @@ static int mlxsw_sp_netdevice_event(struct notifier_block *unused,
                err = mlxsw_sp_netdevice_port_event(dev, event, ptr);
        else if (netif_is_lag_master(dev))
                err = mlxsw_sp_netdevice_lag_event(dev, event, ptr);
+       else if (netif_is_bridge_master(dev))
+               err = mlxsw_sp_netdevice_bridge_event(dev, event, ptr);
        else if (is_vlan_dev(dev))
                err = mlxsw_sp_netdevice_vlan_event(dev, event, ptr);
 
index b15b47b9161f187381f0ee8ddf0e6b65ec4d8def..17c5d3b68f264b2bc84e1ed5eb6b0a9fb7f7d108 100644 (file)
@@ -387,6 +387,31 @@ mlxsw_sp_port_vport_find_by_fid(const struct mlxsw_sp_port *mlxsw_sp_port,
        return NULL;
 }
 
+static inline struct mlxsw_sp_fid *mlxsw_sp_fid_find(struct mlxsw_sp *mlxsw_sp,
+                                                    u16 fid)
+{
+       struct mlxsw_sp_fid *f;
+
+       list_for_each_entry(f, &mlxsw_sp->fids, list)
+               if (f->fid == fid)
+                       return f;
+
+       return NULL;
+}
+
+static inline struct mlxsw_sp_fid *
+mlxsw_sp_vfid_find(const struct mlxsw_sp *mlxsw_sp,
+                  const struct net_device *br_dev)
+{
+       struct mlxsw_sp_fid *f;
+
+       list_for_each_entry(f, &mlxsw_sp->vfids.list, list)
+               if (f->dev == br_dev)
+                       return f;
+
+       return NULL;
+}
+
 static inline struct mlxsw_sp_rif *
 mlxsw_sp_rif_find_by_dev(const struct mlxsw_sp *mlxsw_sp,
                         const struct net_device *dev)
@@ -459,6 +484,8 @@ int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid);
 int mlxsw_sp_port_fdb_flush(struct mlxsw_sp_port *mlxsw_sp_port, u16 fid);
 int mlxsw_sp_rif_fdb_op(struct mlxsw_sp *mlxsw_sp, const char *mac, u16 fid,
                        bool adding);
+struct mlxsw_sp_fid *mlxsw_sp_fid_create(struct mlxsw_sp *mlxsw_sp, u16 fid);
+void mlxsw_sp_fid_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *f);
 int mlxsw_sp_port_ets_set(struct mlxsw_sp_port *mlxsw_sp_port,
                          enum mlxsw_reg_qeec_hr hr, u8 index, u8 next_index,
                          bool dwrr, u8 dwrr_weight);
index 941acd7869377880b7baa7307ba9a2be833bba77..e446640e659b0768bd1acccd749b87804c4db774 100644 (file)
@@ -374,18 +374,6 @@ static int mlxsw_sp_port_attr_set(struct net_device *dev,
        return err;
 }
 
-static struct mlxsw_sp_fid *mlxsw_sp_fid_find(struct mlxsw_sp *mlxsw_sp,
-                                             u16 fid)
-{
-       struct mlxsw_sp_fid *f;
-
-       list_for_each_entry(f, &mlxsw_sp->fids, list)
-               if (f->fid == fid)
-                       return f;
-
-       return NULL;
-}
-
 static int mlxsw_sp_fid_op(struct mlxsw_sp *mlxsw_sp, u16 fid, bool create)
 {
        char sfmr_pl[MLXSW_REG_SFMR_LEN];
@@ -416,8 +404,7 @@ static struct mlxsw_sp_fid *mlxsw_sp_fid_alloc(u16 fid)
        return f;
 }
 
-static struct mlxsw_sp_fid *mlxsw_sp_fid_create(struct mlxsw_sp *mlxsw_sp,
-                                               u16 fid)
+struct mlxsw_sp_fid *mlxsw_sp_fid_create(struct mlxsw_sp *mlxsw_sp, u16 fid)
 {
        struct mlxsw_sp_fid *f;
        int err;
@@ -452,8 +439,7 @@ err_fid_map:
        return ERR_PTR(err);
 }
 
-static void mlxsw_sp_fid_destroy(struct mlxsw_sp *mlxsw_sp,
-                                struct mlxsw_sp_fid *f)
+void mlxsw_sp_fid_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *f)
 {
        u16 fid = f->fid;