mlxsw: spectrum_router: Only clear offload indication from valid IPv6 FIB info
[linux-2.6-microblaze.git] / drivers / net / ethernet / mellanox / mlxsw / spectrum_router.c
index ef95d12..645ec70 100644 (file)
@@ -352,6 +352,7 @@ enum mlxsw_sp_fib_entry_type {
        MLXSW_SP_FIB_ENTRY_TYPE_NVE_DECAP,
 };
 
+struct mlxsw_sp_nexthop_group_info;
 struct mlxsw_sp_nexthop_group;
 struct mlxsw_sp_fib_entry;
 
@@ -431,8 +432,8 @@ struct mlxsw_sp_fib_entry {
 
 struct mlxsw_sp_fib4_entry {
        struct mlxsw_sp_fib_entry common;
+       struct fib_info *fi;
        u32 tb_id;
-       u32 prio;
        u8 tos;
        u8 type;
 };
@@ -1352,21 +1353,33 @@ mlxsw_sp_ipip_entry_matches_decap(struct mlxsw_sp *mlxsw_sp,
 
 /* Given decap parameters, find the corresponding IPIP entry. */
 static struct mlxsw_sp_ipip_entry *
-mlxsw_sp_ipip_entry_find_by_decap(struct mlxsw_sp *mlxsw_sp,
-                                 const struct net_device *ul_dev,
+mlxsw_sp_ipip_entry_find_by_decap(struct mlxsw_sp *mlxsw_sp, int ul_dev_ifindex,
                                  enum mlxsw_sp_l3proto ul_proto,
                                  union mlxsw_sp_l3addr ul_dip)
 {
-       struct mlxsw_sp_ipip_entry *ipip_entry;
+       struct mlxsw_sp_ipip_entry *ipip_entry = NULL;
+       struct net_device *ul_dev;
+
+       rcu_read_lock();
+
+       ul_dev = dev_get_by_index_rcu(mlxsw_sp_net(mlxsw_sp), ul_dev_ifindex);
+       if (!ul_dev)
+               goto out_unlock;
 
        list_for_each_entry(ipip_entry, &mlxsw_sp->router->ipip_list,
                            ipip_list_node)
                if (mlxsw_sp_ipip_entry_matches_decap(mlxsw_sp, ul_dev,
                                                      ul_proto, ul_dip,
                                                      ipip_entry))
-                       return ipip_entry;
+                       goto out_unlock;
+
+       rcu_read_unlock();
 
        return NULL;
+
+out_unlock:
+       rcu_read_unlock();
+       return ipip_entry;
 }
 
 static bool mlxsw_sp_netdev_ipip_type(const struct mlxsw_sp *mlxsw_sp,
@@ -1452,11 +1465,7 @@ static bool mlxsw_sp_netdevice_ipip_can_offload(struct mlxsw_sp *mlxsw_sp,
        const struct mlxsw_sp_ipip_ops *ops
                = mlxsw_sp->router->ipip_ops_arr[ipipt];
 
-       /* For deciding whether decap should be offloaded, we don't care about
-        * overlay protocol, so ask whether either one is supported.
-        */
-       return ops->can_offload(mlxsw_sp, ol_dev, MLXSW_SP_L3_PROTO_IPV4) ||
-              ops->can_offload(mlxsw_sp, ol_dev, MLXSW_SP_L3_PROTO_IPV6);
+       return ops->can_offload(mlxsw_sp, ol_dev);
 }
 
 static int mlxsw_sp_netdevice_ipip_ol_reg_event(struct mlxsw_sp *mlxsw_sp,
@@ -2831,10 +2840,11 @@ struct mlxsw_sp_nexthop {
        struct list_head neigh_list_node; /* member of neigh entry list */
        struct list_head rif_list_node;
        struct list_head router_list_node;
-       struct mlxsw_sp_nexthop_group *nh_grp; /* pointer back to the group
-                                               * this belongs to
-                                               */
+       struct mlxsw_sp_nexthop_group_info *nhgi; /* pointer back to the group
+                                                  * this nexthop belongs to
+                                                  */
        struct rhash_head ht_node;
+       struct neigh_table *neigh_tbl;
        struct mlxsw_sp_nexthop_key key;
        unsigned char gw_addr[sizeof(struct in6_addr)];
        int ifindex;
@@ -2860,21 +2870,35 @@ struct mlxsw_sp_nexthop {
        bool counter_valid;
 };
 
-struct mlxsw_sp_nexthop_group {
-       void *priv;
-       struct rhash_head ht_node;
-       struct list_head fib_list; /* list of fib entries that use this group */
-       struct neigh_table *neigh_tbl;
-       u8 adj_index_valid:1,
-          gateway:1; /* routes using the group use a gateway */
+enum mlxsw_sp_nexthop_group_type {
+       MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV4,
+       MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6,
+};
+
+struct mlxsw_sp_nexthop_group_info {
+       struct mlxsw_sp_nexthop_group *nh_grp;
        u32 adj_index;
        u16 ecmp_size;
        u16 count;
        int sum_norm_weight;
+       u8 adj_index_valid:1,
+          gateway:1; /* routes using the group use a gateway */
        struct mlxsw_sp_nexthop nexthops[0];
 #define nh_rif nexthops[0].rif
 };
 
+struct mlxsw_sp_nexthop_group {
+       struct rhash_head ht_node;
+       struct list_head fib_list; /* list of fib entries that use this group */
+       union {
+               struct {
+                       struct fib_info *fi;
+               } ipv4;
+       };
+       struct mlxsw_sp_nexthop_group_info *nhgi;
+       enum mlxsw_sp_nexthop_group_type type;
+};
+
 void mlxsw_sp_nexthop_counter_alloc(struct mlxsw_sp *mlxsw_sp,
                                    struct mlxsw_sp_nexthop *nh)
 {
@@ -2940,18 +2964,18 @@ unsigned char *mlxsw_sp_nexthop_ha(struct mlxsw_sp_nexthop *nh)
 int mlxsw_sp_nexthop_indexes(struct mlxsw_sp_nexthop *nh, u32 *p_adj_index,
                             u32 *p_adj_size, u32 *p_adj_hash_index)
 {
-       struct mlxsw_sp_nexthop_group *nh_grp = nh->nh_grp;
+       struct mlxsw_sp_nexthop_group_info *nhgi = nh->nhgi;
        u32 adj_hash_index = 0;
        int i;
 
-       if (!nh->offloaded || !nh_grp->adj_index_valid)
+       if (!nh->offloaded || !nhgi->adj_index_valid)
                return -EINVAL;
 
-       *p_adj_index = nh_grp->adj_index;
-       *p_adj_size = nh_grp->ecmp_size;
+       *p_adj_index = nhgi->adj_index;
+       *p_adj_size = nhgi->ecmp_size;
 
-       for (i = 0; i < nh_grp->count; i++) {
-               struct mlxsw_sp_nexthop *nh_iter = &nh_grp->nexthops[i];
+       for (i = 0; i < nhgi->count; i++) {
+               struct mlxsw_sp_nexthop *nh_iter = &nhgi->nexthops[i];
 
                if (nh_iter == nh)
                        break;
@@ -2970,11 +2994,11 @@ struct mlxsw_sp_rif *mlxsw_sp_nexthop_rif(struct mlxsw_sp_nexthop *nh)
 
 bool mlxsw_sp_nexthop_group_has_ipip(struct mlxsw_sp_nexthop *nh)
 {
-       struct mlxsw_sp_nexthop_group *nh_grp = nh->nh_grp;
+       struct mlxsw_sp_nexthop_group_info *nhgi = nh->nhgi;
        int i;
 
-       for (i = 0; i < nh_grp->count; i++) {
-               struct mlxsw_sp_nexthop *nh_iter = &nh_grp->nexthops[i];
+       for (i = 0; i < nhgi->count; i++) {
+               struct mlxsw_sp_nexthop *nh_iter = &nhgi->nexthops[i];
 
                if (nh_iter->type == MLXSW_SP_NEXTHOP_TYPE_IPIP)
                        return true;
@@ -2982,14 +3006,8 @@ bool mlxsw_sp_nexthop_group_has_ipip(struct mlxsw_sp_nexthop *nh)
        return false;
 }
 
-static struct fib_info *
-mlxsw_sp_nexthop4_group_fi(const struct mlxsw_sp_nexthop_group *nh_grp)
-{
-       return nh_grp->priv;
-}
-
 struct mlxsw_sp_nexthop_group_cmp_arg {
-       enum mlxsw_sp_l3proto proto;
+       enum mlxsw_sp_nexthop_group_type type;
        union {
                struct fib_info *fi;
                struct mlxsw_sp_fib6_entry *fib6_entry;
@@ -3003,10 +3021,10 @@ mlxsw_sp_nexthop6_group_has_nexthop(const struct mlxsw_sp_nexthop_group *nh_grp,
 {
        int i;
 
-       for (i = 0; i < nh_grp->count; i++) {
+       for (i = 0; i < nh_grp->nhgi->count; i++) {
                const struct mlxsw_sp_nexthop *nh;
 
-               nh = &nh_grp->nexthops[i];
+               nh = &nh_grp->nhgi->nexthops[i];
                if (nh->ifindex == ifindex && nh->nh_weight == weight &&
                    ipv6_addr_equal(gw, (struct in6_addr *) nh->gw_addr))
                        return true;
@@ -3021,7 +3039,7 @@ mlxsw_sp_nexthop6_group_cmp(const struct mlxsw_sp_nexthop_group *nh_grp,
 {
        struct mlxsw_sp_rt6 *mlxsw_sp_rt6;
 
-       if (nh_grp->count != fib6_entry->nrt6)
+       if (nh_grp->nhgi->count != fib6_entry->nrt6)
                return false;
 
        list_for_each_entry(mlxsw_sp_rt6, &fib6_entry->rt6_list, list) {
@@ -3046,10 +3064,13 @@ mlxsw_sp_nexthop_group_cmp(struct rhashtable_compare_arg *arg, const void *ptr)
        const struct mlxsw_sp_nexthop_group_cmp_arg *cmp_arg = arg->key;
        const struct mlxsw_sp_nexthop_group *nh_grp = ptr;
 
-       switch (cmp_arg->proto) {
-       case MLXSW_SP_L3_PROTO_IPV4:
-               return cmp_arg->fi != mlxsw_sp_nexthop4_group_fi(nh_grp);
-       case MLXSW_SP_L3_PROTO_IPV6:
+       if (nh_grp->type != cmp_arg->type)
+               return 1;
+
+       switch (cmp_arg->type) {
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV4:
+               return cmp_arg->fi != nh_grp->ipv4.fi;
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6:
                return !mlxsw_sp_nexthop6_group_cmp(nh_grp,
                                                    cmp_arg->fib6_entry);
        default:
@@ -3058,12 +3079,6 @@ mlxsw_sp_nexthop_group_cmp(struct rhashtable_compare_arg *arg, const void *ptr)
        }
 }
 
-static int
-mlxsw_sp_nexthop_group_type(const struct mlxsw_sp_nexthop_group *nh_grp)
-{
-       return nh_grp->neigh_tbl->family;
-}
-
 static u32 mlxsw_sp_nexthop_group_hash_obj(const void *data, u32 len, u32 seed)
 {
        const struct mlxsw_sp_nexthop_group *nh_grp = data;
@@ -3072,14 +3087,14 @@ static u32 mlxsw_sp_nexthop_group_hash_obj(const void *data, u32 len, u32 seed)
        unsigned int val;
        int i;
 
-       switch (mlxsw_sp_nexthop_group_type(nh_grp)) {
-       case AF_INET:
-               fi = mlxsw_sp_nexthop4_group_fi(nh_grp);
+       switch (nh_grp->type) {
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV4:
+               fi = nh_grp->ipv4.fi;
                return jhash(&fi, sizeof(fi), seed);
-       case AF_INET6:
-               val = nh_grp->count;
-               for (i = 0; i < nh_grp->count; i++) {
-                       nh = &nh_grp->nexthops[i];
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6:
+               val = nh_grp->nhgi->count;
+               for (i = 0; i < nh_grp->nhgi->count; i++) {
+                       nh = &nh_grp->nhgi->nexthops[i];
                        val ^= jhash(&nh->ifindex, sizeof(nh->ifindex), seed);
                        val ^= jhash(&nh->gw_addr, sizeof(nh->gw_addr), seed);
                }
@@ -3113,10 +3128,10 @@ mlxsw_sp_nexthop_group_hash(const void *data, u32 len, u32 seed)
 {
        const struct mlxsw_sp_nexthop_group_cmp_arg *cmp_arg = data;
 
-       switch (cmp_arg->proto) {
-       case MLXSW_SP_L3_PROTO_IPV4:
+       switch (cmp_arg->type) {
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV4:
                return jhash(&cmp_arg->fi, sizeof(cmp_arg->fi), seed);
-       case MLXSW_SP_L3_PROTO_IPV6:
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6:
                return mlxsw_sp_nexthop6_group_hash(cmp_arg->fib6_entry, seed);
        default:
                WARN_ON(1);
@@ -3134,8 +3149,8 @@ static const struct rhashtable_params mlxsw_sp_nexthop_group_ht_params = {
 static int mlxsw_sp_nexthop_group_insert(struct mlxsw_sp *mlxsw_sp,
                                         struct mlxsw_sp_nexthop_group *nh_grp)
 {
-       if (mlxsw_sp_nexthop_group_type(nh_grp) == AF_INET6 &&
-           !nh_grp->gateway)
+       if (nh_grp->type == MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6 &&
+           !nh_grp->nhgi->gateway)
                return 0;
 
        return rhashtable_insert_fast(&mlxsw_sp->router->nexthop_group_ht,
@@ -3146,8 +3161,8 @@ static int mlxsw_sp_nexthop_group_insert(struct mlxsw_sp *mlxsw_sp,
 static void mlxsw_sp_nexthop_group_remove(struct mlxsw_sp *mlxsw_sp,
                                          struct mlxsw_sp_nexthop_group *nh_grp)
 {
-       if (mlxsw_sp_nexthop_group_type(nh_grp) == AF_INET6 &&
-           !nh_grp->gateway)
+       if (nh_grp->type == MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6 &&
+           !nh_grp->nhgi->gateway)
                return;
 
        rhashtable_remove_fast(&mlxsw_sp->router->nexthop_group_ht,
@@ -3161,7 +3176,7 @@ mlxsw_sp_nexthop4_group_lookup(struct mlxsw_sp *mlxsw_sp,
 {
        struct mlxsw_sp_nexthop_group_cmp_arg cmp_arg;
 
-       cmp_arg.proto = MLXSW_SP_L3_PROTO_IPV4;
+       cmp_arg.type = MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV4;
        cmp_arg.fi = fi;
        return rhashtable_lookup_fast(&mlxsw_sp->router->nexthop_group_ht,
                                      &cmp_arg,
@@ -3174,7 +3189,7 @@ mlxsw_sp_nexthop6_group_lookup(struct mlxsw_sp *mlxsw_sp,
 {
        struct mlxsw_sp_nexthop_group_cmp_arg cmp_arg;
 
-       cmp_arg.proto = MLXSW_SP_L3_PROTO_IPV6;
+       cmp_arg.type = MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6;
        cmp_arg.fib6_entry = fib6_entry;
        return rhashtable_lookup_fast(&mlxsw_sp->router->nexthop_group_ht,
                                      &cmp_arg,
@@ -3233,14 +3248,16 @@ static int mlxsw_sp_adj_index_mass_update(struct mlxsw_sp *mlxsw_sp,
        int err;
 
        list_for_each_entry(fib_entry, &nh_grp->fib_list, nexthop_group_node) {
+               struct mlxsw_sp_nexthop_group_info *nhgi = nh_grp->nhgi;
+
                if (fib == fib_entry->fib_node->fib)
                        continue;
                fib = fib_entry->fib_node->fib;
                err = mlxsw_sp_adj_index_mass_update_vr(mlxsw_sp, fib,
                                                        old_adj_index,
                                                        old_ecmp_size,
-                                                       nh_grp->adj_index,
-                                                       nh_grp->ecmp_size);
+                                                       nhgi->adj_index,
+                                                       nhgi->ecmp_size);
                if (err)
                        return err;
        }
@@ -3311,15 +3328,15 @@ static int mlxsw_sp_nexthop_ipip_update(struct mlxsw_sp *mlxsw_sp,
 
 static int
 mlxsw_sp_nexthop_group_update(struct mlxsw_sp *mlxsw_sp,
-                             struct mlxsw_sp_nexthop_group *nh_grp,
+                             struct mlxsw_sp_nexthop_group_info *nhgi,
                              bool reallocate)
 {
-       u32 adj_index = nh_grp->adj_index; /* base */
+       u32 adj_index = nhgi->adj_index; /* base */
        struct mlxsw_sp_nexthop *nh;
        int i;
 
-       for (i = 0; i < nh_grp->count; i++) {
-               nh = &nh_grp->nexthops[i];
+       for (i = 0; i < nhgi->count; i++) {
+               nh = &nhgi->nexthops[i];
 
                if (!nh->should_offload) {
                        nh->offloaded = 0;
@@ -3419,13 +3436,13 @@ static int mlxsw_sp_fix_adj_grp_size(struct mlxsw_sp *mlxsw_sp,
 }
 
 static void
-mlxsw_sp_nexthop_group_normalize(struct mlxsw_sp_nexthop_group *nh_grp)
+mlxsw_sp_nexthop_group_normalize(struct mlxsw_sp_nexthop_group_info *nhgi)
 {
        int i, g = 0, sum_norm_weight = 0;
        struct mlxsw_sp_nexthop *nh;
 
-       for (i = 0; i < nh_grp->count; i++) {
-               nh = &nh_grp->nexthops[i];
+       for (i = 0; i < nhgi->count; i++) {
+               nh = &nhgi->nexthops[i];
 
                if (!nh->should_offload)
                        continue;
@@ -3435,8 +3452,8 @@ mlxsw_sp_nexthop_group_normalize(struct mlxsw_sp_nexthop_group *nh_grp)
                        g = nh->nh_weight;
        }
 
-       for (i = 0; i < nh_grp->count; i++) {
-               nh = &nh_grp->nexthops[i];
+       for (i = 0; i < nhgi->count; i++) {
+               nh = &nhgi->nexthops[i];
 
                if (!nh->should_offload)
                        continue;
@@ -3444,18 +3461,18 @@ mlxsw_sp_nexthop_group_normalize(struct mlxsw_sp_nexthop_group *nh_grp)
                sum_norm_weight += nh->norm_nh_weight;
        }
 
-       nh_grp->sum_norm_weight = sum_norm_weight;
+       nhgi->sum_norm_weight = sum_norm_weight;
 }
 
 static void
-mlxsw_sp_nexthop_group_rebalance(struct mlxsw_sp_nexthop_group *nh_grp)
+mlxsw_sp_nexthop_group_rebalance(struct mlxsw_sp_nexthop_group_info *nhgi)
 {
-       int total = nh_grp->sum_norm_weight;
-       u16 ecmp_size = nh_grp->ecmp_size;
        int i, weight = 0, lower_bound = 0;
+       int total = nhgi->sum_norm_weight;
+       u16 ecmp_size = nhgi->ecmp_size;
 
-       for (i = 0; i < nh_grp->count; i++) {
-               struct mlxsw_sp_nexthop *nh = &nh_grp->nexthops[i];
+       for (i = 0; i < nhgi->count; i++) {
+               struct mlxsw_sp_nexthop *nh = &nhgi->nexthops[i];
                int upper_bound;
 
                if (!nh->should_offload)
@@ -3477,8 +3494,8 @@ mlxsw_sp_nexthop4_group_offload_refresh(struct mlxsw_sp *mlxsw_sp,
 {
        int i;
 
-       for (i = 0; i < nh_grp->count; i++) {
-               struct mlxsw_sp_nexthop *nh = &nh_grp->nexthops[i];
+       for (i = 0; i < nh_grp->nhgi->count; i++) {
+               struct mlxsw_sp_nexthop *nh = &nh_grp->nhgi->nexthops[i];
 
                if (nh->offloaded)
                        nh->key.fib_nh->fib_nh_flags |= RTNH_F_OFFLOAD;
@@ -3524,11 +3541,11 @@ static void
 mlxsw_sp_nexthop_group_offload_refresh(struct mlxsw_sp *mlxsw_sp,
                                       struct mlxsw_sp_nexthop_group *nh_grp)
 {
-       switch (mlxsw_sp_nexthop_group_type(nh_grp)) {
-       case AF_INET:
+       switch (nh_grp->type) {
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV4:
                mlxsw_sp_nexthop4_group_offload_refresh(mlxsw_sp, nh_grp);
                break;
-       case AF_INET6:
+       case MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6:
                mlxsw_sp_nexthop6_group_offload_refresh(mlxsw_sp, nh_grp);
                break;
        }
@@ -3538,6 +3555,7 @@ static void
 mlxsw_sp_nexthop_group_refresh(struct mlxsw_sp *mlxsw_sp,
                               struct mlxsw_sp_nexthop_group *nh_grp)
 {
+       struct mlxsw_sp_nexthop_group_info *nhgi = nh_grp->nhgi;
        u16 ecmp_size, old_ecmp_size;
        struct mlxsw_sp_nexthop *nh;
        bool offload_change = false;
@@ -3547,13 +3565,13 @@ mlxsw_sp_nexthop_group_refresh(struct mlxsw_sp *mlxsw_sp,
        int i;
        int err;
 
-       if (!nh_grp->gateway) {
+       if (!nhgi->gateway) {
                mlxsw_sp_nexthop_fib_entries_update(mlxsw_sp, nh_grp);
                return;
        }
 
-       for (i = 0; i < nh_grp->count; i++) {
-               nh = &nh_grp->nexthops[i];
+       for (i = 0; i < nhgi->count; i++) {
+               nh = &nhgi->nexthops[i];
 
                if (nh->should_offload != nh->offloaded) {
                        offload_change = true;
@@ -3565,21 +3583,21 @@ mlxsw_sp_nexthop_group_refresh(struct mlxsw_sp *mlxsw_sp,
                /* Nothing was added or removed, so no need to reallocate. Just
                 * update MAC on existing adjacency indexes.
                 */
-               err = mlxsw_sp_nexthop_group_update(mlxsw_sp, nh_grp, false);
+               err = mlxsw_sp_nexthop_group_update(mlxsw_sp, nhgi, false);
                if (err) {
                        dev_warn(mlxsw_sp->bus_info->dev, "Failed to update neigh MAC in adjacency table.\n");
                        goto set_trap;
                }
                return;
        }
-       mlxsw_sp_nexthop_group_normalize(nh_grp);
-       if (!nh_grp->sum_norm_weight)
+       mlxsw_sp_nexthop_group_normalize(nhgi);
+       if (!nhgi->sum_norm_weight)
                /* No neigh of this group is connected so we just set
                 * the trap and let everthing flow through kernel.
                 */
                goto set_trap;
 
-       ecmp_size = nh_grp->sum_norm_weight;
+       ecmp_size = nhgi->sum_norm_weight;
        err = mlxsw_sp_fix_adj_grp_size(mlxsw_sp, &ecmp_size);
        if (err)
                /* No valid allocation size available. */
@@ -3594,14 +3612,14 @@ mlxsw_sp_nexthop_group_refresh(struct mlxsw_sp *mlxsw_sp,
                dev_warn(mlxsw_sp->bus_info->dev, "Failed to allocate KVD linear area for nexthop group.\n");
                goto set_trap;
        }
-       old_adj_index_valid = nh_grp->adj_index_valid;
-       old_adj_index = nh_grp->adj_index;
-       old_ecmp_size = nh_grp->ecmp_size;
-       nh_grp->adj_index_valid = 1;
-       nh_grp->adj_index = adj_index;
-       nh_grp->ecmp_size = ecmp_size;
-       mlxsw_sp_nexthop_group_rebalance(nh_grp);
-       err = mlxsw_sp_nexthop_group_update(mlxsw_sp, nh_grp, true);
+       old_adj_index_valid = nhgi->adj_index_valid;
+       old_adj_index = nhgi->adj_index;
+       old_ecmp_size = nhgi->ecmp_size;
+       nhgi->adj_index_valid = 1;
+       nhgi->adj_index = adj_index;
+       nhgi->ecmp_size = ecmp_size;
+       mlxsw_sp_nexthop_group_rebalance(nhgi);
+       err = mlxsw_sp_nexthop_group_update(mlxsw_sp, nhgi, true);
        if (err) {
                dev_warn(mlxsw_sp->bus_info->dev, "Failed to update neigh MAC in adjacency table.\n");
                goto set_trap;
@@ -3633,10 +3651,10 @@ mlxsw_sp_nexthop_group_refresh(struct mlxsw_sp *mlxsw_sp,
        return;
 
 set_trap:
-       old_adj_index_valid = nh_grp->adj_index_valid;
-       nh_grp->adj_index_valid = 0;
-       for (i = 0; i < nh_grp->count; i++) {
-               nh = &nh_grp->nexthops[i];
+       old_adj_index_valid = nhgi->adj_index_valid;
+       nhgi->adj_index_valid = 0;
+       for (i = 0; i < nhgi->count; i++) {
+               nh = &nhgi->nexthops[i];
                nh->offloaded = 0;
        }
        err = mlxsw_sp_nexthop_fib_entries_update(mlxsw_sp, nh_grp);
@@ -3645,7 +3663,7 @@ set_trap:
        mlxsw_sp_nexthop_group_offload_refresh(mlxsw_sp, nh_grp);
        if (old_adj_index_valid)
                mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_ADJ,
-                                  nh_grp->ecmp_size, nh_grp->adj_index);
+                                  nhgi->ecmp_size, nhgi->adj_index);
 }
 
 static void __mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp_nexthop *nh,
@@ -3671,10 +3689,9 @@ mlxsw_sp_nexthop_dead_neigh_replace(struct mlxsw_sp *mlxsw_sp,
        nh = list_first_entry(&neigh_entry->nexthop_list,
                              struct mlxsw_sp_nexthop, neigh_list_node);
 
-       n = neigh_lookup(nh->nh_grp->neigh_tbl, &nh->gw_addr, nh->rif->dev);
+       n = neigh_lookup(nh->neigh_tbl, &nh->gw_addr, nh->rif->dev);
        if (!n) {
-               n = neigh_create(nh->nh_grp->neigh_tbl, &nh->gw_addr,
-                                nh->rif->dev);
+               n = neigh_create(nh->neigh_tbl, &nh->gw_addr, nh->rif->dev);
                if (IS_ERR(n))
                        return PTR_ERR(n);
                neigh_event_send(n, NULL);
@@ -3697,7 +3714,7 @@ mlxsw_sp_nexthop_dead_neigh_replace(struct mlxsw_sp *mlxsw_sp,
                neigh_release(old_n);
                neigh_clone(n);
                __mlxsw_sp_nexthop_neigh_update(nh, !entry_connected);
-               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
+               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nhgi->nh_grp);
        }
 
        neigh_release(n);
@@ -3734,7 +3751,7 @@ mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp *mlxsw_sp,
        list_for_each_entry(nh, &neigh_entry->nexthop_list,
                            neigh_list_node) {
                __mlxsw_sp_nexthop_neigh_update(nh, removing);
-               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
+               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nhgi->nh_grp);
        }
 }
 
@@ -3765,7 +3782,7 @@ static int mlxsw_sp_nexthop_neigh_init(struct mlxsw_sp *mlxsw_sp,
        u8 nud_state, dead;
        int err;
 
-       if (!nh->nh_grp->gateway || nh->neigh_entry)
+       if (!nh->nhgi->gateway || nh->neigh_entry)
                return 0;
 
        /* Take a reference of neigh here ensuring that neigh would
@@ -3773,10 +3790,9 @@ static int mlxsw_sp_nexthop_neigh_init(struct mlxsw_sp *mlxsw_sp,
         * The reference is taken either in neigh_lookup() or
         * in neigh_create() in case n is not found.
         */
-       n = neigh_lookup(nh->nh_grp->neigh_tbl, &nh->gw_addr, nh->rif->dev);
+       n = neigh_lookup(nh->neigh_tbl, &nh->gw_addr, nh->rif->dev);
        if (!n) {
-               n = neigh_create(nh->nh_grp->neigh_tbl, &nh->gw_addr,
-                                nh->rif->dev);
+               n = neigh_create(nh->neigh_tbl, &nh->gw_addr, nh->rif->dev);
                if (IS_ERR(n))
                        return PTR_ERR(n);
                neigh_event_send(n, NULL);
@@ -3857,7 +3873,7 @@ static void mlxsw_sp_nexthop_ipip_init(struct mlxsw_sp *mlxsw_sp,
 {
        bool removing;
 
-       if (!nh->nh_grp->gateway || nh->ipip_entry)
+       if (!nh->nhgi->gateway || nh->ipip_entry)
                return;
 
        nh->ipip_entry = ipip_entry;
@@ -3889,27 +3905,11 @@ static bool mlxsw_sp_nexthop4_ipip_type(const struct mlxsw_sp *mlxsw_sp,
               mlxsw_sp_netdev_ipip_type(mlxsw_sp, dev, p_ipipt);
 }
 
-static void mlxsw_sp_nexthop_type_fini(struct mlxsw_sp *mlxsw_sp,
-                                      struct mlxsw_sp_nexthop *nh)
-{
-       switch (nh->type) {
-       case MLXSW_SP_NEXTHOP_TYPE_ETH:
-               mlxsw_sp_nexthop_neigh_fini(mlxsw_sp, nh);
-               mlxsw_sp_nexthop_rif_fini(nh);
-               break;
-       case MLXSW_SP_NEXTHOP_TYPE_IPIP:
-               mlxsw_sp_nexthop_rif_fini(nh);
-               mlxsw_sp_nexthop_ipip_fini(mlxsw_sp, nh);
-               break;
-       }
-}
-
-static int mlxsw_sp_nexthop4_type_init(struct mlxsw_sp *mlxsw_sp,
-                                      struct mlxsw_sp_nexthop *nh,
-                                      struct fib_nh *fib_nh)
+static int mlxsw_sp_nexthop_type_init(struct mlxsw_sp *mlxsw_sp,
+                                     struct mlxsw_sp_nexthop *nh,
+                                     const struct net_device *dev)
 {
        const struct mlxsw_sp_ipip_ops *ipip_ops;
-       struct net_device *dev = fib_nh->fib_nh_dev;
        struct mlxsw_sp_ipip_entry *ipip_entry;
        struct mlxsw_sp_rif *rif;
        int err;
@@ -3917,8 +3917,7 @@ static int mlxsw_sp_nexthop4_type_init(struct mlxsw_sp *mlxsw_sp,
        ipip_entry = mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, dev);
        if (ipip_entry) {
                ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipip_entry->ipipt];
-               if (ipip_ops->can_offload(mlxsw_sp, dev,
-                                         MLXSW_SP_L3_PROTO_IPV4)) {
+               if (ipip_ops->can_offload(mlxsw_sp, dev)) {
                        nh->type = MLXSW_SP_NEXTHOP_TYPE_IPIP;
                        mlxsw_sp_nexthop_ipip_init(mlxsw_sp, nh, ipip_entry);
                        return 0;
@@ -3942,10 +3941,19 @@ err_neigh_init:
        return err;
 }
 
-static void mlxsw_sp_nexthop4_type_fini(struct mlxsw_sp *mlxsw_sp,
-                                       struct mlxsw_sp_nexthop *nh)
+static void mlxsw_sp_nexthop_type_fini(struct mlxsw_sp *mlxsw_sp,
+                                      struct mlxsw_sp_nexthop *nh)
 {
-       mlxsw_sp_nexthop_type_fini(mlxsw_sp, nh);
+       switch (nh->type) {
+       case MLXSW_SP_NEXTHOP_TYPE_ETH:
+               mlxsw_sp_nexthop_neigh_fini(mlxsw_sp, nh);
+               mlxsw_sp_nexthop_rif_fini(nh);
+               break;
+       case MLXSW_SP_NEXTHOP_TYPE_IPIP:
+               mlxsw_sp_nexthop_rif_fini(nh);
+               mlxsw_sp_nexthop_ipip_fini(mlxsw_sp, nh);
+               break;
+       }
 }
 
 static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,
@@ -3957,7 +3965,7 @@ static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,
        struct in_device *in_dev;
        int err;
 
-       nh->nh_grp = nh_grp;
+       nh->nhgi = nh_grp->nhgi;
        nh->key.fib_nh = fib_nh;
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
        nh->nh_weight = fib_nh->fib_nh_weight;
@@ -3965,6 +3973,7 @@ static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,
        nh->nh_weight = 1;
 #endif
        memcpy(&nh->gw_addr, &fib_nh->fib_nh_gw4, sizeof(fib_nh->fib_nh_gw4));
+       nh->neigh_tbl = &arp_tbl;
        err = mlxsw_sp_nexthop_insert(mlxsw_sp, nh);
        if (err)
                return err;
@@ -3974,6 +3983,7 @@ static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,
 
        if (!dev)
                return 0;
+       nh->ifindex = dev->ifindex;
 
        rcu_read_lock();
        in_dev = __in_dev_get_rcu(dev);
@@ -3984,7 +3994,7 @@ static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,
        }
        rcu_read_unlock();
 
-       err = mlxsw_sp_nexthop4_type_init(mlxsw_sp, nh, fib_nh);
+       err = mlxsw_sp_nexthop_type_init(mlxsw_sp, nh, dev);
        if (err)
                goto err_nexthop_neigh_init;
 
@@ -3998,7 +4008,7 @@ err_nexthop_neigh_init:
 static void mlxsw_sp_nexthop4_fini(struct mlxsw_sp *mlxsw_sp,
                                   struct mlxsw_sp_nexthop *nh)
 {
-       mlxsw_sp_nexthop4_type_fini(mlxsw_sp, nh);
+       mlxsw_sp_nexthop_type_fini(mlxsw_sp, nh);
        list_del(&nh->router_list_node);
        mlxsw_sp_nexthop_counter_free(mlxsw_sp, nh);
        mlxsw_sp_nexthop_remove(mlxsw_sp, nh);
@@ -4020,14 +4030,14 @@ static void mlxsw_sp_nexthop4_event(struct mlxsw_sp *mlxsw_sp,
 
        switch (event) {
        case FIB_EVENT_NH_ADD:
-               mlxsw_sp_nexthop4_type_init(mlxsw_sp, nh, fib_nh);
+               mlxsw_sp_nexthop_type_init(mlxsw_sp, nh, fib_nh->fib_nh_dev);
                break;
        case FIB_EVENT_NH_DEL:
-               mlxsw_sp_nexthop4_type_fini(mlxsw_sp, nh);
+               mlxsw_sp_nexthop_type_fini(mlxsw_sp, nh);
                break;
        }
 
-       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
+       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nhgi->nh_grp);
 }
 
 static void mlxsw_sp_nexthop_rif_update(struct mlxsw_sp *mlxsw_sp,
@@ -4050,7 +4060,7 @@ static void mlxsw_sp_nexthop_rif_update(struct mlxsw_sp *mlxsw_sp,
                }
 
                __mlxsw_sp_nexthop_neigh_update(nh, removing);
-               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
+               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nhgi->nh_grp);
        }
 }
 
@@ -4073,7 +4083,7 @@ static void mlxsw_sp_nexthop_rif_gone_sync(struct mlxsw_sp *mlxsw_sp,
 
        list_for_each_entry_safe(nh, tmp, &rif->nexthop_list, rif_list_node) {
                mlxsw_sp_nexthop_type_fini(mlxsw_sp, nh);
-               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
+               mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nhgi->nh_grp);
        }
 }
 
@@ -4086,45 +4096,88 @@ static bool mlxsw_sp_fi_is_gateway(const struct mlxsw_sp *mlxsw_sp,
               mlxsw_sp_nexthop4_ipip_type(mlxsw_sp, nh, NULL);
 }
 
+static int
+mlxsw_sp_nexthop4_group_info_init(struct mlxsw_sp *mlxsw_sp,
+                                 struct mlxsw_sp_nexthop_group *nh_grp)
+{
+       unsigned int nhs = fib_info_num_path(nh_grp->ipv4.fi);
+       struct mlxsw_sp_nexthop_group_info *nhgi;
+       struct mlxsw_sp_nexthop *nh;
+       int err, i;
+
+       nhgi = kzalloc(struct_size(nhgi, nexthops, nhs), GFP_KERNEL);
+       if (!nhgi)
+               return -ENOMEM;
+       nh_grp->nhgi = nhgi;
+       nhgi->nh_grp = nh_grp;
+       nhgi->gateway = mlxsw_sp_fi_is_gateway(mlxsw_sp, nh_grp->ipv4.fi);
+       nhgi->count = nhs;
+       for (i = 0; i < nhgi->count; i++) {
+               struct fib_nh *fib_nh;
+
+               nh = &nhgi->nexthops[i];
+               fib_nh = fib_info_nh(nh_grp->ipv4.fi, i);
+               err = mlxsw_sp_nexthop4_init(mlxsw_sp, nh_grp, nh, fib_nh);
+               if (err)
+                       goto err_nexthop4_init;
+       }
+       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
+
+       return 0;
+
+err_nexthop4_init:
+       for (i--; i >= 0; i--) {
+               nh = &nhgi->nexthops[i];
+               mlxsw_sp_nexthop4_fini(mlxsw_sp, nh);
+       }
+       kfree(nhgi);
+       return err;
+}
+
+static void
+mlxsw_sp_nexthop4_group_info_fini(struct mlxsw_sp *mlxsw_sp,
+                                 struct mlxsw_sp_nexthop_group *nh_grp)
+{
+       struct mlxsw_sp_nexthop_group_info *nhgi = nh_grp->nhgi;
+       int i;
+
+       for (i = nhgi->count - 1; i >= 0; i--) {
+               struct mlxsw_sp_nexthop *nh = &nhgi->nexthops[i];
+
+               mlxsw_sp_nexthop4_fini(mlxsw_sp, nh);
+       }
+       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
+       WARN_ON_ONCE(nhgi->adj_index_valid);
+       kfree(nhgi);
+}
+
 static struct mlxsw_sp_nexthop_group *
 mlxsw_sp_nexthop4_group_create(struct mlxsw_sp *mlxsw_sp, struct fib_info *fi)
 {
-       unsigned int nhs = fib_info_num_path(fi);
        struct mlxsw_sp_nexthop_group *nh_grp;
-       struct mlxsw_sp_nexthop *nh;
-       struct fib_nh *fib_nh;
-       int i;
        int err;
 
-       nh_grp = kzalloc(struct_size(nh_grp, nexthops, nhs), GFP_KERNEL);
+       nh_grp = kzalloc(sizeof(*nh_grp), GFP_KERNEL);
        if (!nh_grp)
                return ERR_PTR(-ENOMEM);
-       nh_grp->priv = fi;
        INIT_LIST_HEAD(&nh_grp->fib_list);
-       nh_grp->neigh_tbl = &arp_tbl;
-
-       nh_grp->gateway = mlxsw_sp_fi_is_gateway(mlxsw_sp, fi);
-       nh_grp->count = nhs;
+       nh_grp->type = MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV4;
+       nh_grp->ipv4.fi = fi;
        fib_info_hold(fi);
-       for (i = 0; i < nh_grp->count; i++) {
-               nh = &nh_grp->nexthops[i];
-               fib_nh = fib_info_nh(fi, i);
-               err = mlxsw_sp_nexthop4_init(mlxsw_sp, nh_grp, nh, fib_nh);
-               if (err)
-                       goto err_nexthop4_init;
-       }
+
+       err = mlxsw_sp_nexthop4_group_info_init(mlxsw_sp, nh_grp);
+       if (err)
+               goto err_nexthop_group_info_init;
+
        err = mlxsw_sp_nexthop_group_insert(mlxsw_sp, nh_grp);
        if (err)
                goto err_nexthop_group_insert;
-       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
+
        return nh_grp;
 
 err_nexthop_group_insert:
-err_nexthop4_init:
-       for (i--; i >= 0; i--) {
-               nh = &nh_grp->nexthops[i];
-               mlxsw_sp_nexthop4_fini(mlxsw_sp, nh);
-       }
+       mlxsw_sp_nexthop4_group_info_fini(mlxsw_sp, nh_grp);
+err_nexthop_group_info_init:
        fib_info_put(fi);
        kfree(nh_grp);
        return ERR_PTR(err);
@@ -4134,17 +4187,9 @@ static void
 mlxsw_sp_nexthop4_group_destroy(struct mlxsw_sp *mlxsw_sp,
                                struct mlxsw_sp_nexthop_group *nh_grp)
 {
-       struct mlxsw_sp_nexthop *nh;
-       int i;
-
        mlxsw_sp_nexthop_group_remove(mlxsw_sp, nh_grp);
-       for (i = 0; i < nh_grp->count; i++) {
-               nh = &nh_grp->nexthops[i];
-               mlxsw_sp_nexthop4_fini(mlxsw_sp, nh);
-       }
-       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
-       WARN_ON_ONCE(nh_grp->adj_index_valid);
-       fib_info_put(mlxsw_sp_nexthop4_group_fi(nh_grp));
+       mlxsw_sp_nexthop4_group_info_fini(mlxsw_sp, nh_grp);
+       fib_info_put(nh_grp->ipv4.fi);
        kfree(nh_grp);
 }
 
@@ -4202,9 +4247,9 @@ mlxsw_sp_fib_entry_should_offload(const struct mlxsw_sp_fib_entry *fib_entry)
 
        switch (fib_entry->type) {
        case MLXSW_SP_FIB_ENTRY_TYPE_REMOTE:
-               return !!nh_group->adj_index_valid;
+               return !!nh_group->nhgi->adj_index_valid;
        case MLXSW_SP_FIB_ENTRY_TYPE_LOCAL:
-               return !!nh_group->nh_rif;
+               return !!nh_group->nhgi->nh_rif;
        case MLXSW_SP_FIB_ENTRY_TYPE_BLACKHOLE:
        case MLXSW_SP_FIB_ENTRY_TYPE_IPIP_DECAP:
        case MLXSW_SP_FIB_ENTRY_TYPE_NVE_DECAP:
@@ -4220,8 +4265,8 @@ mlxsw_sp_rt6_nexthop(struct mlxsw_sp_nexthop_group *nh_grp,
 {
        int i;
 
-       for (i = 0; i < nh_grp->count; i++) {
-               struct mlxsw_sp_nexthop *nh = &nh_grp->nexthops[i];
+       for (i = 0; i < nh_grp->nhgi->count; i++) {
+               struct mlxsw_sp_nexthop *nh = &nh_grp->nhgi->nexthops[i];
                struct fib6_info *rt = mlxsw_sp_rt6->rt;
 
                if (nh->rif && nh->rif->dev == rt->fib6_nh->fib_nh_dev &&
@@ -4238,7 +4283,6 @@ static void
 mlxsw_sp_fib4_entry_hw_flags_set(struct mlxsw_sp *mlxsw_sp,
                                 struct mlxsw_sp_fib_entry *fib_entry)
 {
-       struct fib_info *fi = mlxsw_sp_nexthop4_group_fi(fib_entry->nh_group);
        u32 *p_dst = (u32 *) fib_entry->fib_node->key.addr;
        int dst_len = fib_entry->fib_node->key.prefix_len;
        struct mlxsw_sp_fib4_entry *fib4_entry;
@@ -4248,7 +4292,7 @@ mlxsw_sp_fib4_entry_hw_flags_set(struct mlxsw_sp *mlxsw_sp,
        should_offload = mlxsw_sp_fib_entry_should_offload(fib_entry);
        fib4_entry = container_of(fib_entry, struct mlxsw_sp_fib4_entry,
                                  common);
-       fri.fi = fi;
+       fri.fi = fib4_entry->fi;
        fri.tb_id = fib4_entry->tb_id;
        fri.dst = cpu_to_be32(*p_dst);
        fri.dst_len = dst_len;
@@ -4263,7 +4307,6 @@ static void
 mlxsw_sp_fib4_entry_hw_flags_clear(struct mlxsw_sp *mlxsw_sp,
                                   struct mlxsw_sp_fib_entry *fib_entry)
 {
-       struct fib_info *fi = mlxsw_sp_nexthop4_group_fi(fib_entry->nh_group);
        u32 *p_dst = (u32 *) fib_entry->fib_node->key.addr;
        int dst_len = fib_entry->fib_node->key.prefix_len;
        struct mlxsw_sp_fib4_entry *fib4_entry;
@@ -4271,7 +4314,7 @@ mlxsw_sp_fib4_entry_hw_flags_clear(struct mlxsw_sp *mlxsw_sp,
 
        fib4_entry = container_of(fib_entry, struct mlxsw_sp_fib4_entry,
                                  common);
-       fri.fi = fi;
+       fri.fi = fib4_entry->fi;
        fri.tb_id = fib4_entry->tb_id;
        fri.dst = cpu_to_be32(*p_dst);
        fri.dst_len = dst_len;
@@ -4350,6 +4393,7 @@ mlxsw_sp_fib_entry_hw_flags_refresh(struct mlxsw_sp *mlxsw_sp,
 {
        switch (op) {
        case MLXSW_SP_FIB_ENTRY_OP_WRITE:
+       case MLXSW_SP_FIB_ENTRY_OP_UPDATE:
                mlxsw_sp_fib_entry_hw_flags_set(mlxsw_sp, fib_entry);
                break;
        case MLXSW_SP_FIB_ENTRY_OP_DELETE:
@@ -4381,6 +4425,7 @@ mlxsw_sp_router_ll_basic_fib_entry_pack(struct mlxsw_sp_fib_entry_op_ctx *op_ctx
 
        switch (op) {
        case MLXSW_SP_FIB_ENTRY_OP_WRITE:
+       case MLXSW_SP_FIB_ENTRY_OP_UPDATE:
                ralue_op = MLXSW_REG_RALUE_OP_WRITE_WRITE;
                break;
        case MLXSW_SP_FIB_ENTRY_OP_DELETE:
@@ -4525,6 +4570,7 @@ static int mlxsw_sp_fib_entry_op_remote(struct mlxsw_sp *mlxsw_sp,
 {
        const struct mlxsw_sp_router_ll_ops *ll_ops = fib_entry->fib_node->fib->ll_ops;
        struct mlxsw_sp_nexthop_group *nh_group = fib_entry->nh_group;
+       struct mlxsw_sp_nexthop_group_info *nhgi = nh_group->nhgi;
        enum mlxsw_reg_ralue_trap_action trap_action;
        u16 trap_id = 0;
        u32 adjacency_index = 0;
@@ -4537,12 +4583,11 @@ static int mlxsw_sp_fib_entry_op_remote(struct mlxsw_sp *mlxsw_sp,
         */
        if (mlxsw_sp_fib_entry_should_offload(fib_entry)) {
                trap_action = MLXSW_REG_RALUE_TRAP_ACTION_NOP;
-               adjacency_index = fib_entry->nh_group->adj_index;
-               ecmp_size = fib_entry->nh_group->ecmp_size;
-       } else if (!nh_group->adj_index_valid && nh_group->count &&
-                  nh_group->nh_rif) {
+               adjacency_index = nhgi->adj_index;
+               ecmp_size = nhgi->ecmp_size;
+       } else if (!nhgi->adj_index_valid && nhgi->count && nhgi->nh_rif) {
                err = mlxsw_sp_adj_discard_write(mlxsw_sp,
-                                                nh_group->nh_rif->rif_index);
+                                                nhgi->nh_rif->rif_index);
                if (err)
                        return err;
                trap_action = MLXSW_REG_RALUE_TRAP_ACTION_NOP;
@@ -4565,7 +4610,7 @@ static int mlxsw_sp_fib_entry_op_local(struct mlxsw_sp *mlxsw_sp,
                                       enum mlxsw_sp_fib_entry_op op)
 {
        const struct mlxsw_sp_router_ll_ops *ll_ops = fib_entry->fib_node->fib->ll_ops;
-       struct mlxsw_sp_rif *rif = fib_entry->nh_group->nh_rif;
+       struct mlxsw_sp_rif *rif = fib_entry->nh_group->nhgi->nh_rif;
        enum mlxsw_reg_ralue_trap_action trap_action;
        u16 trap_id = 0;
        u16 rif_index = 0;
@@ -4699,10 +4744,12 @@ static int mlxsw_sp_fib_entry_op(struct mlxsw_sp *mlxsw_sp,
 
 static int __mlxsw_sp_fib_entry_update(struct mlxsw_sp *mlxsw_sp,
                                       struct mlxsw_sp_fib_entry_op_ctx *op_ctx,
-                                      struct mlxsw_sp_fib_entry *fib_entry)
+                                      struct mlxsw_sp_fib_entry *fib_entry,
+                                      bool is_new)
 {
        return mlxsw_sp_fib_entry_op(mlxsw_sp, op_ctx, fib_entry,
-                                    MLXSW_SP_FIB_ENTRY_OP_WRITE);
+                                    is_new ? MLXSW_SP_FIB_ENTRY_OP_WRITE :
+                                             MLXSW_SP_FIB_ENTRY_OP_UPDATE);
 }
 
 static int mlxsw_sp_fib_entry_update(struct mlxsw_sp *mlxsw_sp,
@@ -4711,7 +4758,7 @@ static int mlxsw_sp_fib_entry_update(struct mlxsw_sp *mlxsw_sp,
        struct mlxsw_sp_fib_entry_op_ctx *op_ctx = mlxsw_sp->router->ll_op_ctx;
 
        mlxsw_sp_fib_entry_op_ctx_clear(op_ctx);
-       return __mlxsw_sp_fib_entry_update(mlxsw_sp, op_ctx, fib_entry);
+       return __mlxsw_sp_fib_entry_update(mlxsw_sp, op_ctx, fib_entry, false);
 }
 
 static int mlxsw_sp_fib_entry_del(struct mlxsw_sp *mlxsw_sp,
@@ -4731,17 +4778,17 @@ mlxsw_sp_fib4_entry_type_set(struct mlxsw_sp *mlxsw_sp,
                             const struct fib_entry_notifier_info *fen_info,
                             struct mlxsw_sp_fib_entry *fib_entry)
 {
-       struct net_device *dev = fib_info_nh(fen_info->fi, 0)->fib_nh_dev;
+       struct mlxsw_sp_nexthop_group_info *nhgi = fib_entry->nh_group->nhgi;
        union mlxsw_sp_l3addr dip = { .addr4 = htonl(fen_info->dst) };
        struct mlxsw_sp_router *router = mlxsw_sp->router;
        u32 tb_id = mlxsw_sp_fix_tb_id(fen_info->tb_id);
+       int ifindex = nhgi->nexthops[0].ifindex;
        struct mlxsw_sp_ipip_entry *ipip_entry;
-       struct fib_info *fi = fen_info->fi;
 
        switch (fen_info->type) {
        case RTN_LOCAL:
-               ipip_entry = mlxsw_sp_ipip_entry_find_by_decap(mlxsw_sp, dev,
-                                                MLXSW_SP_L3_PROTO_IPV4, dip);
+               ipip_entry = mlxsw_sp_ipip_entry_find_by_decap(mlxsw_sp, ifindex,
+                                                              MLXSW_SP_L3_PROTO_IPV4, dip);
                if (ipip_entry && ipip_entry->ol_dev->flags & IFF_UP) {
                        fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_IPIP_DECAP;
                        return mlxsw_sp_fib_entry_decap_init(mlxsw_sp,
@@ -4774,7 +4821,7 @@ mlxsw_sp_fib4_entry_type_set(struct mlxsw_sp *mlxsw_sp,
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_UNREACHABLE;
                return 0;
        case RTN_UNICAST:
-               if (mlxsw_sp_fi_is_gateway(mlxsw_sp, fi))
+               if (nhgi->gateway)
                        fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_REMOTE;
                else
                        fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_LOCAL;
@@ -4817,15 +4864,16 @@ mlxsw_sp_fib4_entry_create(struct mlxsw_sp *mlxsw_sp,
                goto err_fib_entry_priv_create;
        }
 
-       err = mlxsw_sp_fib4_entry_type_set(mlxsw_sp, fen_info, fib_entry);
-       if (err)
-               goto err_fib4_entry_type_set;
-
        err = mlxsw_sp_nexthop4_group_get(mlxsw_sp, fib_entry, fen_info->fi);
        if (err)
                goto err_nexthop4_group_get;
 
-       fib4_entry->prio = fen_info->fi->fib_priority;
+       err = mlxsw_sp_fib4_entry_type_set(mlxsw_sp, fen_info, fib_entry);
+       if (err)
+               goto err_fib4_entry_type_set;
+
+       fib4_entry->fi = fen_info->fi;
+       fib_info_hold(fib4_entry->fi);
        fib4_entry->tb_id = fen_info->tb_id;
        fib4_entry->type = fen_info->type;
        fib4_entry->tos = fen_info->tos;
@@ -4834,9 +4882,9 @@ mlxsw_sp_fib4_entry_create(struct mlxsw_sp *mlxsw_sp,
 
        return fib4_entry;
 
-err_nexthop4_group_get:
-       mlxsw_sp_fib4_entry_type_unset(mlxsw_sp, fib_entry);
 err_fib4_entry_type_set:
+       mlxsw_sp_nexthop4_group_put(mlxsw_sp, &fib4_entry->common);
+err_nexthop4_group_get:
        mlxsw_sp_fib_entry_priv_put(fib_entry->priv);
 err_fib_entry_priv_create:
        kfree(fib4_entry);
@@ -4846,8 +4894,9 @@ err_fib_entry_priv_create:
 static void mlxsw_sp_fib4_entry_destroy(struct mlxsw_sp *mlxsw_sp,
                                        struct mlxsw_sp_fib4_entry *fib4_entry)
 {
-       mlxsw_sp_nexthop4_group_put(mlxsw_sp, &fib4_entry->common);
+       fib_info_put(fib4_entry->fi);
        mlxsw_sp_fib4_entry_type_unset(mlxsw_sp, &fib4_entry->common);
+       mlxsw_sp_nexthop4_group_put(mlxsw_sp, &fib4_entry->common);
        mlxsw_sp_fib_entry_priv_put(fib4_entry->common.priv);
        kfree(fib4_entry);
 }
@@ -4877,8 +4926,7 @@ mlxsw_sp_fib4_entry_lookup(struct mlxsw_sp *mlxsw_sp,
        if (fib4_entry->tb_id == fen_info->tb_id &&
            fib4_entry->tos == fen_info->tos &&
            fib4_entry->type == fen_info->type &&
-           mlxsw_sp_nexthop4_group_fi(fib4_entry->common.nh_group) ==
-           fen_info->fi)
+           fib4_entry->fi == fen_info->fi)
                return fib4_entry;
 
        return NULL;
@@ -5091,11 +5139,12 @@ static int mlxsw_sp_fib_node_entry_link(struct mlxsw_sp *mlxsw_sp,
                                        struct mlxsw_sp_fib_entry *fib_entry)
 {
        struct mlxsw_sp_fib_node *fib_node = fib_entry->fib_node;
+       bool is_new = !fib_node->fib_entry;
        int err;
 
        fib_node->fib_entry = fib_entry;
 
-       err = __mlxsw_sp_fib_entry_update(mlxsw_sp, op_ctx, fib_entry);
+       err = __mlxsw_sp_fib_entry_update(mlxsw_sp, op_ctx, fib_entry, is_new);
        if (err)
                goto err_fib_entry_update;
 
@@ -5275,7 +5324,8 @@ static void mlxsw_sp_rt6_destroy(struct mlxsw_sp_rt6 *mlxsw_sp_rt6)
 {
        struct fib6_nh *fib6_nh = mlxsw_sp_rt6->rt->fib6_nh;
 
-       fib6_nh->fib_nh_flags &= ~RTNH_F_OFFLOAD;
+       if (!mlxsw_sp_rt6->rt->nh)
+               fib6_nh->fib_nh_flags &= ~RTNH_F_OFFLOAD;
        mlxsw_sp_rt6_release(mlxsw_sp_rt6->rt);
        kfree(mlxsw_sp_rt6);
 }
@@ -5309,51 +5359,6 @@ static bool mlxsw_sp_nexthop6_ipip_type(const struct mlxsw_sp *mlxsw_sp,
               mlxsw_sp_netdev_ipip_type(mlxsw_sp, rt->fib6_nh->fib_nh_dev, ret);
 }
 
-static int mlxsw_sp_nexthop6_type_init(struct mlxsw_sp *mlxsw_sp,
-                                      struct mlxsw_sp_nexthop_group *nh_grp,
-                                      struct mlxsw_sp_nexthop *nh,
-                                      const struct fib6_info *rt)
-{
-       const struct mlxsw_sp_ipip_ops *ipip_ops;
-       struct mlxsw_sp_ipip_entry *ipip_entry;
-       struct net_device *dev = rt->fib6_nh->fib_nh_dev;
-       struct mlxsw_sp_rif *rif;
-       int err;
-
-       ipip_entry = mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, dev);
-       if (ipip_entry) {
-               ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipip_entry->ipipt];
-               if (ipip_ops->can_offload(mlxsw_sp, dev,
-                                         MLXSW_SP_L3_PROTO_IPV6)) {
-                       nh->type = MLXSW_SP_NEXTHOP_TYPE_IPIP;
-                       mlxsw_sp_nexthop_ipip_init(mlxsw_sp, nh, ipip_entry);
-                       return 0;
-               }
-       }
-
-       nh->type = MLXSW_SP_NEXTHOP_TYPE_ETH;
-       rif = mlxsw_sp_rif_find_by_dev(mlxsw_sp, dev);
-       if (!rif)
-               return 0;
-       mlxsw_sp_nexthop_rif_init(nh, rif);
-
-       err = mlxsw_sp_nexthop_neigh_init(mlxsw_sp, nh);
-       if (err)
-               goto err_nexthop_neigh_init;
-
-       return 0;
-
-err_nexthop_neigh_init:
-       mlxsw_sp_nexthop_rif_fini(nh);
-       return err;
-}
-
-static void mlxsw_sp_nexthop6_type_fini(struct mlxsw_sp *mlxsw_sp,
-                                       struct mlxsw_sp_nexthop *nh)
-{
-       mlxsw_sp_nexthop_type_fini(mlxsw_sp, nh);
-}
-
 static int mlxsw_sp_nexthop6_init(struct mlxsw_sp *mlxsw_sp,
                                  struct mlxsw_sp_nexthop_group *nh_grp,
                                  struct mlxsw_sp_nexthop *nh,
@@ -5361,9 +5366,12 @@ static int mlxsw_sp_nexthop6_init(struct mlxsw_sp *mlxsw_sp,
 {
        struct net_device *dev = rt->fib6_nh->fib_nh_dev;
 
-       nh->nh_grp = nh_grp;
+       nh->nhgi = nh_grp->nhgi;
        nh->nh_weight = rt->fib6_nh->fib_nh_weight;
        memcpy(&nh->gw_addr, &rt->fib6_nh->fib_nh_gw6, sizeof(nh->gw_addr));
+#if IS_ENABLED(CONFIG_IPV6)
+       nh->neigh_tbl = &nd_tbl;
+#endif
        mlxsw_sp_nexthop_counter_alloc(mlxsw_sp, nh);
 
        list_add_tail(&nh->router_list_node, &mlxsw_sp->router->nexthop_list);
@@ -5372,13 +5380,13 @@ static int mlxsw_sp_nexthop6_init(struct mlxsw_sp *mlxsw_sp,
                return 0;
        nh->ifindex = dev->ifindex;
 
-       return mlxsw_sp_nexthop6_type_init(mlxsw_sp, nh_grp, nh, rt);
+       return mlxsw_sp_nexthop_type_init(mlxsw_sp, nh, dev);
 }
 
 static void mlxsw_sp_nexthop6_fini(struct mlxsw_sp *mlxsw_sp,
                                   struct mlxsw_sp_nexthop *nh)
 {
-       mlxsw_sp_nexthop6_type_fini(mlxsw_sp, nh);
+       mlxsw_sp_nexthop_type_fini(mlxsw_sp, nh);
        list_del(&nh->router_list_node);
        mlxsw_sp_nexthop_counter_free(mlxsw_sp, nh);
 }
@@ -5390,51 +5398,92 @@ static bool mlxsw_sp_rt6_is_gateway(const struct mlxsw_sp *mlxsw_sp,
               mlxsw_sp_nexthop6_ipip_type(mlxsw_sp, rt, NULL);
 }
 
-static struct mlxsw_sp_nexthop_group *
-mlxsw_sp_nexthop6_group_create(struct mlxsw_sp *mlxsw_sp,
-                              struct mlxsw_sp_fib6_entry *fib6_entry)
+static int
+mlxsw_sp_nexthop6_group_info_init(struct mlxsw_sp *mlxsw_sp,
+                                 struct mlxsw_sp_nexthop_group *nh_grp,
+                                 struct mlxsw_sp_fib6_entry *fib6_entry)
 {
-       struct mlxsw_sp_nexthop_group *nh_grp;
+       struct mlxsw_sp_nexthop_group_info *nhgi;
        struct mlxsw_sp_rt6 *mlxsw_sp_rt6;
        struct mlxsw_sp_nexthop *nh;
-       int i = 0;
-       int err;
+       int err, i;
 
-       nh_grp = kzalloc(struct_size(nh_grp, nexthops, fib6_entry->nrt6),
-                        GFP_KERNEL);
-       if (!nh_grp)
-               return ERR_PTR(-ENOMEM);
-       INIT_LIST_HEAD(&nh_grp->fib_list);
-#if IS_ENABLED(CONFIG_IPV6)
-       nh_grp->neigh_tbl = &nd_tbl;
-#endif
+       nhgi = kzalloc(struct_size(nhgi, nexthops, fib6_entry->nrt6),
+                      GFP_KERNEL);
+       if (!nhgi)
+               return -ENOMEM;
+       nh_grp->nhgi = nhgi;
+       nhgi->nh_grp = nh_grp;
        mlxsw_sp_rt6 = list_first_entry(&fib6_entry->rt6_list,
                                        struct mlxsw_sp_rt6, list);
-       nh_grp->gateway = mlxsw_sp_rt6_is_gateway(mlxsw_sp, mlxsw_sp_rt6->rt);
-       nh_grp->count = fib6_entry->nrt6;
-       for (i = 0; i < nh_grp->count; i++) {
+       nhgi->gateway = mlxsw_sp_rt6_is_gateway(mlxsw_sp, mlxsw_sp_rt6->rt);
+       nhgi->count = fib6_entry->nrt6;
+       for (i = 0; i < nhgi->count; i++) {
                struct fib6_info *rt = mlxsw_sp_rt6->rt;
 
-               nh = &nh_grp->nexthops[i];
+               nh = &nhgi->nexthops[i];
                err = mlxsw_sp_nexthop6_init(mlxsw_sp, nh_grp, nh, rt);
                if (err)
                        goto err_nexthop6_init;
                mlxsw_sp_rt6 = list_next_entry(mlxsw_sp_rt6, list);
        }
+       nh_grp->nhgi = nhgi;
+       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
+
+       return 0;
+
+err_nexthop6_init:
+       for (i--; i >= 0; i--) {
+               nh = &nhgi->nexthops[i];
+               mlxsw_sp_nexthop6_fini(mlxsw_sp, nh);
+       }
+       kfree(nhgi);
+       return err;
+}
+
+static void
+mlxsw_sp_nexthop6_group_info_fini(struct mlxsw_sp *mlxsw_sp,
+                                 struct mlxsw_sp_nexthop_group *nh_grp)
+{
+       struct mlxsw_sp_nexthop_group_info *nhgi = nh_grp->nhgi;
+       int i;
+
+       for (i = nhgi->count - 1; i >= 0; i--) {
+               struct mlxsw_sp_nexthop *nh = &nhgi->nexthops[i];
+
+               mlxsw_sp_nexthop6_fini(mlxsw_sp, nh);
+       }
+       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
+       WARN_ON_ONCE(nhgi->adj_index_valid);
+       kfree(nhgi);
+}
+
+static struct mlxsw_sp_nexthop_group *
+mlxsw_sp_nexthop6_group_create(struct mlxsw_sp *mlxsw_sp,
+                              struct mlxsw_sp_fib6_entry *fib6_entry)
+{
+       struct mlxsw_sp_nexthop_group *nh_grp;
+       int err;
+
+       nh_grp = kzalloc(sizeof(*nh_grp), GFP_KERNEL);
+       if (!nh_grp)
+               return ERR_PTR(-ENOMEM);
+       INIT_LIST_HEAD(&nh_grp->fib_list);
+       nh_grp->type = MLXSW_SP_NEXTHOP_GROUP_TYPE_IPV6;
+
+       err = mlxsw_sp_nexthop6_group_info_init(mlxsw_sp, nh_grp, fib6_entry);
+       if (err)
+               goto err_nexthop_group_info_init;
 
        err = mlxsw_sp_nexthop_group_insert(mlxsw_sp, nh_grp);
        if (err)
                goto err_nexthop_group_insert;
 
-       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
        return nh_grp;
 
 err_nexthop_group_insert:
-err_nexthop6_init:
-       for (i--; i >= 0; i--) {
-               nh = &nh_grp->nexthops[i];
-               mlxsw_sp_nexthop6_fini(mlxsw_sp, nh);
-       }
+       mlxsw_sp_nexthop6_group_info_fini(mlxsw_sp, nh_grp);
+err_nexthop_group_info_init:
        kfree(nh_grp);
        return ERR_PTR(err);
 }
@@ -5443,16 +5492,8 @@ static void
 mlxsw_sp_nexthop6_group_destroy(struct mlxsw_sp *mlxsw_sp,
                                struct mlxsw_sp_nexthop_group *nh_grp)
 {
-       struct mlxsw_sp_nexthop *nh;
-       int i = nh_grp->count;
-
        mlxsw_sp_nexthop_group_remove(mlxsw_sp, nh_grp);
-       for (i--; i >= 0; i--) {
-               nh = &nh_grp->nexthops[i];
-               mlxsw_sp_nexthop6_fini(mlxsw_sp, nh);
-       }
-       mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh_grp);
-       WARN_ON(nh_grp->adj_index_valid);
+       mlxsw_sp_nexthop6_group_info_fini(mlxsw_sp, nh_grp);
        kfree(nh_grp);
 }
 
@@ -5468,15 +5509,15 @@ static int mlxsw_sp_nexthop6_group_get(struct mlxsw_sp *mlxsw_sp,
                        return PTR_ERR(nh_grp);
        }
 
-       list_add_tail(&fib6_entry->common.nexthop_group_node,
-                     &nh_grp->fib_list);
-       fib6_entry->common.nh_group = nh_grp;
-
        /* The route and the nexthop are described by the same struct, so we
         * need to the update the nexthop offload indication for the new route.
         */
        __mlxsw_sp_nexthop6_group_offload_refresh(nh_grp, fib6_entry);
 
+       list_add_tail(&fib6_entry->common.nexthop_group_node,
+                     &nh_grp->fib_list);
+       fib6_entry->common.nh_group = nh_grp;
+
        return 0;
 }
 
@@ -5509,7 +5550,8 @@ static int mlxsw_sp_nexthop6_group_update(struct mlxsw_sp *mlxsw_sp,
         * currently associated with it in the device's table is that
         * of the old group. Start using the new one instead.
         */
-       err = __mlxsw_sp_fib_entry_update(mlxsw_sp, op_ctx, &fib6_entry->common);
+       err = __mlxsw_sp_fib_entry_update(mlxsw_sp, op_ctx,
+                                         &fib6_entry->common, false);
        if (err)
                goto err_fib_entry_update;
 
@@ -5593,19 +5635,13 @@ static void mlxsw_sp_fib6_entry_type_set(struct mlxsw_sp *mlxsw_sp,
                                         struct mlxsw_sp_fib_entry *fib_entry,
                                         const struct fib6_info *rt)
 {
-       /* Packets hitting RTF_REJECT routes need to be discarded by the
-        * stack. We can rely on their destination device not having a
-        * RIF (it's the loopback device) and can thus use action type
-        * local, which will cause them to be trapped with a lower
-        * priority than packets that need to be locally received.
-        */
        if (rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST))
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
        else if (rt->fib6_type == RTN_BLACKHOLE)
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_BLACKHOLE;
        else if (rt->fib6_flags & RTF_REJECT)
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_UNREACHABLE;
-       else if (mlxsw_sp_rt6_is_gateway(mlxsw_sp, rt))
+       else if (fib_entry->nh_group->nhgi->gateway)
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_REMOTE;
        else
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_LOCAL;
@@ -5657,12 +5693,12 @@ mlxsw_sp_fib6_entry_create(struct mlxsw_sp *mlxsw_sp,
                fib6_entry->nrt6++;
        }
 
-       mlxsw_sp_fib6_entry_type_set(mlxsw_sp, fib_entry, rt_arr[0]);
-
        err = mlxsw_sp_nexthop6_group_get(mlxsw_sp, fib6_entry);
        if (err)
                goto err_nexthop6_group_get;
 
+       mlxsw_sp_fib6_entry_type_set(mlxsw_sp, fib_entry, rt_arr[0]);
+
        fib_entry->fib_node = fib_node;
 
        return fib6_entry;