mlxsw: spectrum_router: Reflect IPv6 neighbours to the device
[sfrench/cifs-2.6.git] / drivers / net / ethernet / mellanox / mlxsw / spectrum_router.c
index 192cb93e7669be9587d850bc3c5a661d9816af24..a0de7365d57e5dc7b2bdbb1048090a7e25ba0eb9 100644 (file)
@@ -49,6 +49,9 @@
 #include <net/ip_fib.h>
 #include <net/fib_rules.h>
 #include <net/l3mdev.h>
+#include <net/addrconf.h>
+#include <net/ndisc.h>
+#include <net/ipv6.h>
 
 #include "spectrum.h"
 #include "core.h"
@@ -1146,6 +1149,32 @@ mlxsw_sp_router_neigh_entry_op4(struct mlxsw_sp *mlxsw_sp,
        mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rauht), rauht_pl);
 }
 
+static void
+mlxsw_sp_router_neigh_entry_op6(struct mlxsw_sp *mlxsw_sp,
+                               struct mlxsw_sp_neigh_entry *neigh_entry,
+                               enum mlxsw_reg_rauht_op op)
+{
+       struct neighbour *n = neigh_entry->key.n;
+       char rauht_pl[MLXSW_REG_RAUHT_LEN];
+       const char *dip = n->primary_key;
+
+       mlxsw_reg_rauht_pack6(rauht_pl, op, neigh_entry->rif, neigh_entry->ha,
+                             dip);
+       mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rauht), rauht_pl);
+}
+
+static bool mlxsw_sp_neigh_ipv6_ignore(struct neighbour *n)
+{
+       /* Packets with a link-local destination address are trapped
+        * after LPM lookup and never reach the neighbour table, so
+        * there is no need to program such neighbours to the device.
+        */
+       if (ipv6_addr_type((struct in6_addr *) &n->primary_key) &
+           IPV6_ADDR_LINKLOCAL)
+               return true;
+       return false;
+}
+
 static void
 mlxsw_sp_neigh_entry_update(struct mlxsw_sp *mlxsw_sp,
                            struct mlxsw_sp_neigh_entry *neigh_entry,
@@ -1154,11 +1183,17 @@ mlxsw_sp_neigh_entry_update(struct mlxsw_sp *mlxsw_sp,
        if (!adding && !neigh_entry->connected)
                return;
        neigh_entry->connected = adding;
-       if (neigh_entry->key.n->tbl == &arp_tbl)
+       if (neigh_entry->key.n->tbl == &arp_tbl) {
                mlxsw_sp_router_neigh_entry_op4(mlxsw_sp, neigh_entry,
                                                mlxsw_sp_rauht_op(adding));
-       else
+       } else if (neigh_entry->key.n->tbl == &nd_tbl) {
+               if (mlxsw_sp_neigh_ipv6_ignore(neigh_entry->key.n))
+                       return;
+               mlxsw_sp_router_neigh_entry_op6(mlxsw_sp, neigh_entry,
+                                               mlxsw_sp_rauht_op(adding));
+       } else {
                WARN_ON_ONCE(1);
+       }
 }
 
 struct mlxsw_sp_neigh_event_work {
@@ -1246,7 +1281,7 @@ int mlxsw_sp_router_netevent_event(struct notifier_block *unused,
        case NETEVENT_NEIGH_UPDATE:
                n = ptr;
 
-               if (n->tbl != &arp_tbl)
+               if (n->tbl != &arp_tbl && n->tbl != &nd_tbl)
                        return NOTIFY_DONE;
 
                mlxsw_sp_port = mlxsw_sp_port_lower_dev_hold(n->dev);
@@ -1790,6 +1825,7 @@ static int mlxsw_sp_nexthop_init(struct mlxsw_sp *mlxsw_sp,
        return 0;
 
 err_nexthop_neigh_init:
+       mlxsw_sp_nexthop_rif_fini(nh);
        mlxsw_sp_nexthop_remove(mlxsw_sp, nh);
        return err;
 }
@@ -1866,6 +1902,7 @@ mlxsw_sp_nexthop_group_create(struct mlxsw_sp *mlxsw_sp, struct fib_info *fi)
        nh_grp->gateway = fi->fib_nh->nh_scope == RT_SCOPE_LINK;
        nh_grp->count = fi->fib_nhs;
        nh_grp->key.fi = fi;
+       fib_info_hold(fi);
        for (i = 0; i < nh_grp->count; i++) {
                nh = &nh_grp->nexthops[i];
                fib_nh = &fi->fib_nh[i];
@@ -1885,6 +1922,7 @@ err_nexthop_init:
                nh = &nh_grp->nexthops[i];
                mlxsw_sp_nexthop_fini(mlxsw_sp, nh);
        }
+       fib_info_put(nh_grp->key.fi);
        kfree(nh_grp);
        return ERR_PTR(err);
 }
@@ -1903,6 +1941,7 @@ mlxsw_sp_nexthop_group_destroy(struct mlxsw_sp *mlxsw_sp,
        }
        mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
        WARN_ON_ONCE(nh_grp->adj_index_valid);
+       fib_info_put(nh_grp->key.fi);
        kfree(nh_grp);
 }
 
@@ -2937,17 +2976,30 @@ static void mlxsw_sp_router_rif_gone_sync(struct mlxsw_sp *mlxsw_sp,
        mlxsw_sp_neigh_rif_gone_sync(mlxsw_sp, rif);
 }
 
-static bool mlxsw_sp_rif_should_config(struct mlxsw_sp_rif *rif,
-                                      const struct in_device *in_dev,
-                                      unsigned long event)
+static bool
+mlxsw_sp_rif_should_config(struct mlxsw_sp_rif *rif, struct net_device *dev,
+                          unsigned long event)
 {
+       struct inet6_dev *inet6_dev;
+       bool addr_list_empty = true;
+       struct in_device *idev;
+
        switch (event) {
        case NETDEV_UP:
                if (!rif)
                        return true;
                return false;
        case NETDEV_DOWN:
-               if (rif && !in_dev->ifa_list &&
+               idev = __in_dev_get_rtnl(dev);
+               if (idev && idev->ifa_list)
+                       addr_list_empty = false;
+
+               inet6_dev = __in6_dev_get(dev);
+               if (addr_list_empty && inet6_dev &&
+                   !list_empty(&inet6_dev->addr_list))
+                       addr_list_empty = false;
+
+               if (rif && addr_list_empty &&
                    !netif_is_l3_slave(rif->dev))
                        return true;
                /* It is possible we already removed the RIF ourselves
@@ -3345,7 +3397,7 @@ int mlxsw_sp_inetaddr_event(struct notifier_block *unused,
                goto out;
 
        rif = mlxsw_sp_rif_find_by_dev(mlxsw_sp, dev);
-       if (!mlxsw_sp_rif_should_config(rif, ifa->ifa_dev, event))
+       if (!mlxsw_sp_rif_should_config(rif, dev, event))
                goto out;
 
        err = __mlxsw_sp_inetaddr_event(dev, event);
@@ -3353,6 +3405,61 @@ out:
        return notifier_from_errno(err);
 }
 
+struct mlxsw_sp_inet6addr_event_work {
+       struct work_struct work;
+       struct net_device *dev;
+       unsigned long event;
+};
+
+static void mlxsw_sp_inet6addr_event_work(struct work_struct *work)
+{
+       struct mlxsw_sp_inet6addr_event_work *inet6addr_work =
+               container_of(work, struct mlxsw_sp_inet6addr_event_work, work);
+       struct net_device *dev = inet6addr_work->dev;
+       unsigned long event = inet6addr_work->event;
+       struct mlxsw_sp *mlxsw_sp;
+       struct mlxsw_sp_rif *rif;
+
+       rtnl_lock();
+       mlxsw_sp = mlxsw_sp_lower_get(dev);
+       if (!mlxsw_sp)
+               goto out;
+
+       rif = mlxsw_sp_rif_find_by_dev(mlxsw_sp, dev);
+       if (!mlxsw_sp_rif_should_config(rif, dev, event))
+               goto out;
+
+       __mlxsw_sp_inetaddr_event(dev, event);
+out:
+       rtnl_unlock();
+       dev_put(dev);
+       kfree(inet6addr_work);
+}
+
+/* Called with rcu_read_lock() */
+int mlxsw_sp_inet6addr_event(struct notifier_block *unused,
+                            unsigned long event, void *ptr)
+{
+       struct inet6_ifaddr *if6 = (struct inet6_ifaddr *) ptr;
+       struct mlxsw_sp_inet6addr_event_work *inet6addr_work;
+       struct net_device *dev = if6->idev->dev;
+
+       if (!mlxsw_sp_port_dev_lower_find_rcu(dev))
+               return NOTIFY_DONE;
+
+       inet6addr_work = kzalloc(sizeof(*inet6addr_work), GFP_ATOMIC);
+       if (!inet6addr_work)
+               return NOTIFY_BAD;
+
+       INIT_WORK(&inet6addr_work->work, mlxsw_sp_inet6addr_event_work);
+       inet6addr_work->dev = dev;
+       inet6addr_work->event = event;
+       dev_hold(dev);
+       mlxsw_core_schedule_work(&inet6addr_work->work);
+
+       return NOTIFY_DONE;
+}
+
 static int mlxsw_sp_rif_edit(struct mlxsw_sp *mlxsw_sp, u16 rif_index,
                             const char *mac, int mtu)
 {
@@ -3554,6 +3661,11 @@ static int mlxsw_sp_rif_vlan_configure(struct mlxsw_sp_rif *rif)
        if (err)
                return err;
 
+       err = mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_MC,
+                                    mlxsw_sp_router_port(mlxsw_sp), true);
+       if (err)
+               goto err_fid_mc_flood_set;
+
        err = mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_BC,
                                     mlxsw_sp_router_port(mlxsw_sp), true);
        if (err)
@@ -3562,6 +3674,9 @@ static int mlxsw_sp_rif_vlan_configure(struct mlxsw_sp_rif *rif)
        return 0;
 
 err_fid_bc_flood_set:
+       mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_MC,
+                              mlxsw_sp_router_port(mlxsw_sp), false);
+err_fid_mc_flood_set:
        mlxsw_sp_rif_vlan_fid_op(rif, MLXSW_REG_RITR_VLAN_IF, vid, false);
        return err;
 }
@@ -3573,6 +3688,8 @@ static void mlxsw_sp_rif_vlan_deconfigure(struct mlxsw_sp_rif *rif)
 
        mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_BC,
                               mlxsw_sp_router_port(mlxsw_sp), false);
+       mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_MC,
+                              mlxsw_sp_router_port(mlxsw_sp), false);
        mlxsw_sp_rif_vlan_fid_op(rif, MLXSW_REG_RITR_VLAN_IF, vid, false);
 }
 
@@ -3603,6 +3720,11 @@ static int mlxsw_sp_rif_fid_configure(struct mlxsw_sp_rif *rif)
        if (err)
                return err;
 
+       err = mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_MC,
+                                    mlxsw_sp_router_port(mlxsw_sp), true);
+       if (err)
+               goto err_fid_mc_flood_set;
+
        err = mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_BC,
                                     mlxsw_sp_router_port(mlxsw_sp), true);
        if (err)
@@ -3611,6 +3733,9 @@ static int mlxsw_sp_rif_fid_configure(struct mlxsw_sp_rif *rif)
        return 0;
 
 err_fid_bc_flood_set:
+       mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_MC,
+                              mlxsw_sp_router_port(mlxsw_sp), false);
+err_fid_mc_flood_set:
        mlxsw_sp_rif_vlan_fid_op(rif, MLXSW_REG_RITR_FID_IF, fid_index, false);
        return err;
 }
@@ -3622,6 +3747,8 @@ static void mlxsw_sp_rif_fid_deconfigure(struct mlxsw_sp_rif *rif)
 
        mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_BC,
                               mlxsw_sp_router_port(mlxsw_sp), false);
+       mlxsw_sp_fid_flood_set(rif->fid, MLXSW_SP_FLOOD_TYPE_MC,
+                              mlxsw_sp_router_port(mlxsw_sp), false);
        mlxsw_sp_rif_vlan_fid_op(rif, MLXSW_REG_RITR_FID_IF, fid_index, false);
 }
 
@@ -3693,7 +3820,7 @@ static int __mlxsw_sp_router_init(struct mlxsw_sp *mlxsw_sp)
                return -EIO;
        max_rifs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS);
 
-       mlxsw_reg_rgcr_pack(rgcr_pl, true);
+       mlxsw_reg_rgcr_pack(rgcr_pl, true, true);
        mlxsw_reg_rgcr_max_router_interfaces_set(rgcr_pl, max_rifs);
        err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rgcr), rgcr_pl);
        if (err)
@@ -3705,7 +3832,7 @@ static void __mlxsw_sp_router_fini(struct mlxsw_sp *mlxsw_sp)
 {
        char rgcr_pl[MLXSW_REG_RGCR_LEN];
 
-       mlxsw_reg_rgcr_pack(rgcr_pl, false);
+       mlxsw_reg_rgcr_pack(rgcr_pl, false, false);
        mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rgcr), rgcr_pl);
 }