mlxsw: spectrum: Enable L3 interfaces on top of bridge devices
authorIdo Schimmel <idosch@mellanox.com>
Mon, 4 Jul 2016 06:23:17 +0000 (08:23 +0200)
committerDavid S. Miller <davem@davemloft.net>
Tue, 5 Jul 2016 01:25:16 +0000 (18:25 -0700)
As with the previously introduced L3 interfaces, listen to 'inetaddr'
notifications sent for bridges devices configured on top of the port
netdevs and create / destroy router interfaces (RIFs) accordingly.
This also includes VLAN devices configured on top of the VLAN-aware
bridge.

The RIFs will be destroyed either when the last IP address is removed or
when the underlying FID is is destroyed.

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 e49f80b..7b2b741 100644 (file)
@@ -2706,10 +2706,135 @@ static int mlxsw_sp_inetaddr_lag_event(struct net_device *lag_dev,
        return __mlxsw_sp_inetaddr_lag_event(lag_dev, lag_dev, event, 1);
 }
 
+static struct mlxsw_sp_fid *mlxsw_sp_bridge_fid_get(struct mlxsw_sp *mlxsw_sp,
+                                                   struct net_device *l3_dev)
+{
+       u16 fid;
+
+       if (is_vlan_dev(l3_dev))
+               fid = vlan_dev_vlan_id(l3_dev);
+       else if (mlxsw_sp->master_bridge.dev == l3_dev)
+               fid = 1;
+       else
+               return mlxsw_sp_vfid_find(mlxsw_sp, l3_dev);
+
+       return mlxsw_sp_fid_find(mlxsw_sp, fid);
+}
+
+static enum mlxsw_reg_ritr_if_type mlxsw_sp_rif_type_get(u16 fid)
+{
+       if (mlxsw_sp_fid_is_vfid(fid))
+               return MLXSW_REG_RITR_FID_IF;
+       else
+               return MLXSW_REG_RITR_VLAN_IF;
+}
+
+static int mlxsw_sp_rif_bridge_op(struct mlxsw_sp *mlxsw_sp,
+                                 struct net_device *l3_dev,
+                                 u16 fid, u16 rif,
+                                 bool create)
+{
+       enum mlxsw_reg_ritr_if_type rif_type;
+       char ritr_pl[MLXSW_REG_RITR_LEN];
+
+       rif_type = mlxsw_sp_rif_type_get(fid);
+       mlxsw_reg_ritr_pack(ritr_pl, create, rif_type, rif, l3_dev->mtu,
+                           l3_dev->dev_addr);
+       mlxsw_reg_ritr_fid_set(ritr_pl, rif_type, fid);
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
+}
+
+static int mlxsw_sp_rif_bridge_create(struct mlxsw_sp *mlxsw_sp,
+                                     struct net_device *l3_dev,
+                                     struct mlxsw_sp_fid *f)
+{
+       struct mlxsw_sp_rif *r;
+       u16 rif;
+       int err;
+
+       rif = mlxsw_sp_avail_rif_get(mlxsw_sp);
+       if (rif == MLXSW_SP_RIF_MAX)
+               return -ERANGE;
+
+       err = mlxsw_sp_rif_bridge_op(mlxsw_sp, l3_dev, f->fid, rif, true);
+       if (err)
+               return err;
+
+       err = mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, f->fid, true);
+       if (err)
+               goto err_rif_fdb_op;
+
+       r = mlxsw_sp_rif_alloc(rif, l3_dev, f);
+       if (!r) {
+               err = -ENOMEM;
+               goto err_rif_alloc;
+       }
+
+       f->r = r;
+       mlxsw_sp->rifs[rif] = r;
+
+       netdev_dbg(l3_dev, "RIF=%d created\n", rif);
+
+       return 0;
+
+err_rif_alloc:
+       mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, f->fid, false);
+err_rif_fdb_op:
+       mlxsw_sp_rif_bridge_op(mlxsw_sp, l3_dev, f->fid, rif, false);
+       return err;
+}
+
+void mlxsw_sp_rif_bridge_destroy(struct mlxsw_sp *mlxsw_sp,
+                                struct mlxsw_sp_rif *r)
+{
+       struct net_device *l3_dev = r->dev;
+       struct mlxsw_sp_fid *f = r->f;
+       u16 rif = r->rif;
+
+       mlxsw_sp->rifs[rif] = NULL;
+       f->r = NULL;
+
+       kfree(r);
+
+       mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, f->fid, false);
+
+       mlxsw_sp_rif_bridge_op(mlxsw_sp, l3_dev, f->fid, rif, false);
+
+       netdev_dbg(l3_dev, "RIF=%d destroyed\n", rif);
+}
+
+static int mlxsw_sp_inetaddr_bridge_event(struct net_device *l3_dev,
+                                         struct net_device *br_dev,
+                                         unsigned long event)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(l3_dev);
+       struct mlxsw_sp_fid *f;
+
+       /* FID can either be an actual FID if the L3 device is the
+        * VLAN-aware bridge or a VLAN device on top. Otherwise, the
+        * L3 device is a VLAN-unaware bridge and we get a vFID.
+        */
+       f = mlxsw_sp_bridge_fid_get(mlxsw_sp, l3_dev);
+       if (WARN_ON(!f))
+               return -EINVAL;
+
+       switch (event) {
+       case NETDEV_UP:
+               return mlxsw_sp_rif_bridge_create(mlxsw_sp, l3_dev, f);
+       case NETDEV_DOWN:
+               mlxsw_sp_rif_bridge_destroy(mlxsw_sp, f->r);
+               break;
+       }
+
+       return 0;
+}
+
 static int mlxsw_sp_inetaddr_vlan_event(struct net_device *vlan_dev,
                                        unsigned long event)
 {
        struct net_device *real_dev = vlan_dev_real_dev(vlan_dev);
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(vlan_dev);
        u16 vid = vlan_dev_vlan_id(vlan_dev);
 
        if (mlxsw_sp_port_dev_check(real_dev))
@@ -2718,6 +2843,10 @@ static int mlxsw_sp_inetaddr_vlan_event(struct net_device *vlan_dev,
        else if (netif_is_lag_master(real_dev))
                return __mlxsw_sp_inetaddr_lag_event(vlan_dev, real_dev, event,
                                                     vid);
+       else if (netif_is_bridge_master(real_dev) &&
+                mlxsw_sp->master_bridge.dev == real_dev)
+               return mlxsw_sp_inetaddr_bridge_event(vlan_dev, real_dev,
+                                                     event);
 
        return 0;
 }
@@ -2743,6 +2872,8 @@ static int mlxsw_sp_inetaddr_event(struct notifier_block *unused,
                err = mlxsw_sp_inetaddr_port_event(dev, event);
        else if (netif_is_lag_master(dev))
                err = mlxsw_sp_inetaddr_lag_event(dev, event);
+       else if (netif_is_bridge_master(dev))
+               err = mlxsw_sp_inetaddr_bridge_event(dev, dev, event);
        else if (is_vlan_dev(dev))
                err = mlxsw_sp_inetaddr_vlan_event(dev, event);
 
@@ -3416,6 +3547,8 @@ static void mlxsw_sp_master_bridge_vlan_unlink(struct mlxsw_sp *mlxsw_sp,
        struct mlxsw_sp_fid *f;
 
        f = mlxsw_sp_fid_find(mlxsw_sp, fid);
+       if (f && f->r)
+               mlxsw_sp_rif_bridge_destroy(mlxsw_sp, f->r);
        if (f && --f->ref_count == 0)
                mlxsw_sp_fid_destroy(mlxsw_sp, f);
 }
@@ -3514,13 +3647,17 @@ static void mlxsw_sp_vfid_destroy(struct mlxsw_sp *mlxsw_sp,
                                  struct mlxsw_sp_fid *f)
 {
        u16 vfid = mlxsw_sp_fid_to_vfid(f->fid);
+       u16 fid = f->fid;
 
        clear_bit(vfid, mlxsw_sp->vfids.mapped);
        list_del(&f->list);
 
-       mlxsw_sp_vfid_op(mlxsw_sp, f->fid, false);
+       if (f->r)
+               mlxsw_sp_rif_bridge_destroy(mlxsw_sp, f->r);
 
        kfree(f);
+
+       mlxsw_sp_vfid_op(mlxsw_sp, fid, false);
 }
 
 static int mlxsw_sp_vport_fid_map(struct mlxsw_sp_port *mlxsw_sp_vport, u16 fid,
index 17c5d3b..958e821 100644 (file)
@@ -486,6 +486,8 @@ 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);
+void mlxsw_sp_rif_bridge_destroy(struct mlxsw_sp *mlxsw_sp,
+                                struct mlxsw_sp_rif *r);
 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 e446640..a1ad5e6 100644 (file)
@@ -445,6 +445,9 @@ void mlxsw_sp_fid_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *f)
 
        list_del(&f->list);
 
+       if (f->r)
+               mlxsw_sp_rif_bridge_destroy(mlxsw_sp, f->r);
+
        kfree(f);
 
        mlxsw_sp_fid_op(mlxsw_sp, fid, false);