Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  spectrum_router.c   Sprache: C

 
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/rhashtable.h>
#include <linux/bitops.h>
#include <linux/in6.h>
#include <linux/notifier.h>
#include <linux/inetdevice.h>
#include <linux/netdevice.h>
#include <linux/if_bridge.h>
#include <linux/socket.h>
#include <linux/route.h>
#include <linux/gcd.h>
#include <linux/if_macvlan.h>
#include <linux/refcount.h>
#include <linux/jhash.h>
#include <linux/net_namespace.h>
#include <linux/mutex.h>
#include <linux/genalloc.h>
#include <linux/xarray.h>
#include <net/netevent.h>
#include <net/neighbour.h>
#include <net/arp.h>
#include <net/inet_dscp.h>
#include <net/ip_fib.h>
#include <net/ip6_fib.h>
#include <net/nexthop.h>
#include <net/fib_rules.h>
#include <net/ip_tunnels.h>
#include <net/l3mdev.h>
#include <net/addrconf.h>
#include <net/ndisc.h>
#include <net/ipv6.h>
#include <net/fib_notifier.h>
#include <net/switchdev.h>

#include "spectrum.h"
#include "core.h"
#include "reg.h"
#include "spectrum_cnt.h"
#include "spectrum_dpipe.h"
#include "spectrum_ipip.h"
#include "spectrum_mr.h"
#include "spectrum_mr_tcam.h"
#include "spectrum_router.h"
#include "spectrum_span.h"

struct mlxsw_sp_fib;
struct mlxsw_sp_vr;
struct mlxsw_sp_lpm_tree;
struct mlxsw_sp_rif_ops;

struct mlxsw_sp_crif_key {
 struct net_device *dev;
};

struct mlxsw_sp_crif {
 struct mlxsw_sp_crif_key key;
 struct rhash_head ht_node;
 bool can_destroy;
 struct list_head nexthop_list;
 struct mlxsw_sp_rif *rif;
};

static const struct rhashtable_params mlxsw_sp_crif_ht_params = {
 .key_offset = offsetof(struct mlxsw_sp_crif, key),
 .key_len = sizeof_field(struct mlxsw_sp_crif, key),
 .head_offset = offsetof(struct mlxsw_sp_crif, ht_node),
};

struct mlxsw_sp_rif {
 struct mlxsw_sp_crif *crif; /* NULL for underlay RIF */
 netdevice_tracker dev_tracker;
 struct list_head neigh_list;
 struct mlxsw_sp_fid *fid;
 unsigned char addr[ETH_ALEN];
 int mtu;
 u16 rif_index;
 u8 mac_profile_id;
 u8 rif_entries;
 u16 vr_id;
 const struct mlxsw_sp_rif_ops *ops;
 struct mlxsw_sp *mlxsw_sp;

 unsigned int counter_ingress;
 bool counter_ingress_valid;
 unsigned int counter_egress;
 bool counter_egress_valid;
};

static struct net_device *mlxsw_sp_rif_dev(const struct mlxsw_sp_rif *rif)
{
 if (!rif->crif)
  return NULL;
 return rif->crif->key.dev;
}

struct mlxsw_sp_rif_params {
 struct net_device *dev;
 union {
  u16 system_port;
  u16 lag_id;
 };
 u16 vid;
 bool lag;
 bool double_entry;
};

struct mlxsw_sp_rif_subport {
 struct mlxsw_sp_rif common;
 refcount_t ref_count;
 union {
  u16 system_port;
  u16 lag_id;
 };
 u16 vid;
 bool lag;
};

struct mlxsw_sp_rif_ipip_lb {
 struct mlxsw_sp_rif common;
 struct mlxsw_sp_rif_ipip_lb_config lb_config;
 u16 ul_vr_id; /* Spectrum-1. */
 u16 ul_rif_id; /* Spectrum-2+. */
};

struct mlxsw_sp_rif_params_ipip_lb {
 struct mlxsw_sp_rif_params common;
 struct mlxsw_sp_rif_ipip_lb_config lb_config;
};

struct mlxsw_sp_rif_ops {
 enum mlxsw_sp_rif_type type;
 size_t rif_size;

 void (*setup)(struct mlxsw_sp_rif *rif,
        const struct mlxsw_sp_rif_params *params);
 int (*configure)(struct mlxsw_sp_rif *rif,
    struct netlink_ext_ack *extack);
 void (*deconfigure)(struct mlxsw_sp_rif *rif);
 struct mlxsw_sp_fid * (*fid_get)(struct mlxsw_sp_rif *rif,
      const struct mlxsw_sp_rif_params *params,
      struct netlink_ext_ack *extack);
 void (*fdb_del)(struct mlxsw_sp_rif *rif, const char *mac);
};

struct mlxsw_sp_rif_mac_profile {
 unsigned char mac_prefix[ETH_ALEN];
 refcount_t ref_count;
 u8 id;
};

struct mlxsw_sp_router_ops {
 int (*init)(struct mlxsw_sp *mlxsw_sp);
 int (*ipips_init)(struct mlxsw_sp *mlxsw_sp);
};

static struct mlxsw_sp_rif *
mlxsw_sp_rif_find_by_dev(const struct mlxsw_sp *mlxsw_sp,
    const struct net_device *dev);
static void mlxsw_sp_rif_destroy(struct mlxsw_sp_rif *rif);
static void mlxsw_sp_lpm_tree_hold(struct mlxsw_sp_lpm_tree *lpm_tree);
static void mlxsw_sp_lpm_tree_put(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_lpm_tree *lpm_tree);
static int mlxsw_sp_vr_lpm_tree_bind(struct mlxsw_sp *mlxsw_sp,
         const struct mlxsw_sp_fib *fib,
         u8 tree_id);
static int mlxsw_sp_vr_lpm_tree_unbind(struct mlxsw_sp *mlxsw_sp,
           const struct mlxsw_sp_fib *fib);

static unsigned int *
mlxsw_sp_rif_p_counter_get(struct mlxsw_sp_rif *rif,
      enum mlxsw_sp_rif_counter_dir dir)
{
 switch (dir) {
 case MLXSW_SP_RIF_COUNTER_EGRESS:
  return &rif->counter_egress;
 case MLXSW_SP_RIF_COUNTER_INGRESS:
  return &rif->counter_ingress;
 }
 return NULL;
}

static bool
mlxsw_sp_rif_counter_valid_get(struct mlxsw_sp_rif *rif,
          enum mlxsw_sp_rif_counter_dir dir)
{
 switch (dir) {
 case MLXSW_SP_RIF_COUNTER_EGRESS:
  return rif->counter_egress_valid;
 case MLXSW_SP_RIF_COUNTER_INGRESS:
  return rif->counter_ingress_valid;
 }
 return false;
}

static void
mlxsw_sp_rif_counter_valid_set(struct mlxsw_sp_rif *rif,
          enum mlxsw_sp_rif_counter_dir dir,
          bool valid)
{
 switch (dir) {
 case MLXSW_SP_RIF_COUNTER_EGRESS:
  rif->counter_egress_valid = valid;
  break;
 case MLXSW_SP_RIF_COUNTER_INGRESS:
  rif->counter_ingress_valid = valid;
  break;
 }
}

static int mlxsw_sp_rif_counter_edit(struct mlxsw_sp *mlxsw_sp, u16 rif_index,
         unsigned int counter_index, bool enable,
         enum mlxsw_sp_rif_counter_dir dir)
{
 char ritr_pl[MLXSW_REG_RITR_LEN];
 bool is_egress = false;
 int err;

 if (dir == MLXSW_SP_RIF_COUNTER_EGRESS)
  is_egress = true;
 mlxsw_reg_ritr_rif_pack(ritr_pl, rif_index);
 err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
 if (err)
  return err;

 mlxsw_reg_ritr_counter_pack(ritr_pl, counter_index, enable,
        is_egress);
 return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
}

int mlxsw_sp_rif_counter_value_get(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_rif *rif,
       enum mlxsw_sp_rif_counter_dir dir, u64 *cnt)
{
 char ricnt_pl[MLXSW_REG_RICNT_LEN];
 unsigned int *p_counter_index;
 bool valid;
 int err;

 valid = mlxsw_sp_rif_counter_valid_get(rif, dir);
 if (!valid)
  return -EINVAL;

 p_counter_index = mlxsw_sp_rif_p_counter_get(rif, dir);
 if (!p_counter_index)
  return -EINVAL;
 mlxsw_reg_ricnt_pack(ricnt_pl, *p_counter_index,
        MLXSW_REG_RICNT_OPCODE_NOP);
 err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(ricnt), ricnt_pl);
 if (err)
  return err;
 *cnt = mlxsw_reg_ricnt_good_unicast_packets_get(ricnt_pl);
 return 0;
}

struct mlxsw_sp_rif_counter_set_basic {
 u64 good_unicast_packets;
 u64 good_multicast_packets;
 u64 good_broadcast_packets;
 u64 good_unicast_bytes;
 u64 good_multicast_bytes;
 u64 good_broadcast_bytes;
 u64 error_packets;
 u64 discard_packets;
 u64 error_bytes;
 u64 discard_bytes;
};

static int
mlxsw_sp_rif_counter_fetch_clear(struct mlxsw_sp_rif *rif,
     enum mlxsw_sp_rif_counter_dir dir,
     struct mlxsw_sp_rif_counter_set_basic *set)
{
 struct mlxsw_sp *mlxsw_sp = rif->mlxsw_sp;
 char ricnt_pl[MLXSW_REG_RICNT_LEN];
 unsigned int *p_counter_index;
 int err;

 if (!mlxsw_sp_rif_counter_valid_get(rif, dir))
  return -EINVAL;

 p_counter_index = mlxsw_sp_rif_p_counter_get(rif, dir);
 if (!p_counter_index)
  return -EINVAL;

 mlxsw_reg_ricnt_pack(ricnt_pl, *p_counter_index,
        MLXSW_REG_RICNT_OPCODE_CLEAR);
 err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(ricnt), ricnt_pl);
 if (err)
  return err;

 if (!set)
  return 0;

#define MLXSW_SP_RIF_COUNTER_EXTRACT(NAME)    \
  (set->NAME = mlxsw_reg_ricnt_ ## NAME ## _get(ricnt_pl))

 MLXSW_SP_RIF_COUNTER_EXTRACT(good_unicast_packets);
 MLXSW_SP_RIF_COUNTER_EXTRACT(good_multicast_packets);
 MLXSW_SP_RIF_COUNTER_EXTRACT(good_broadcast_packets);
 MLXSW_SP_RIF_COUNTER_EXTRACT(good_unicast_bytes);
 MLXSW_SP_RIF_COUNTER_EXTRACT(good_multicast_bytes);
 MLXSW_SP_RIF_COUNTER_EXTRACT(good_broadcast_bytes);
 MLXSW_SP_RIF_COUNTER_EXTRACT(error_packets);
 MLXSW_SP_RIF_COUNTER_EXTRACT(discard_packets);
 MLXSW_SP_RIF_COUNTER_EXTRACT(error_bytes);
 MLXSW_SP_RIF_COUNTER_EXTRACT(discard_bytes);

#undef MLXSW_SP_RIF_COUNTER_EXTRACT

 return 0;
}

static int mlxsw_sp_rif_counter_clear(struct mlxsw_sp *mlxsw_sp,
          unsigned int counter_index)
{
 char ricnt_pl[MLXSW_REG_RICNT_LEN];

 mlxsw_reg_ricnt_pack(ricnt_pl, counter_index,
        MLXSW_REG_RICNT_OPCODE_CLEAR);
 return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ricnt), ricnt_pl);
}

int mlxsw_sp_rif_counter_alloc(struct mlxsw_sp_rif *rif,
          enum mlxsw_sp_rif_counter_dir dir)
{
 struct mlxsw_sp *mlxsw_sp = rif->mlxsw_sp;
 unsigned int *p_counter_index;
 int err;

 if (mlxsw_sp_rif_counter_valid_get(rif, dir))
  return 0;

 p_counter_index = mlxsw_sp_rif_p_counter_get(rif, dir);
 if (!p_counter_index)
  return -EINVAL;

 err = mlxsw_sp_counter_alloc(mlxsw_sp, MLXSW_SP_COUNTER_SUB_POOL_RIF,
         p_counter_index);
 if (err)
  return err;

 err = mlxsw_sp_rif_counter_clear(mlxsw_sp, *p_counter_index);
 if (err)
  goto err_counter_clear;

 err = mlxsw_sp_rif_counter_edit(mlxsw_sp, rif->rif_index,
     *p_counter_index, true, dir);
 if (err)
  goto err_counter_edit;
 mlxsw_sp_rif_counter_valid_set(rif, dir, true);
 return 0;

err_counter_edit:
err_counter_clear:
 mlxsw_sp_counter_free(mlxsw_sp, MLXSW_SP_COUNTER_SUB_POOL_RIF,
         *p_counter_index);
 return err;
}

void mlxsw_sp_rif_counter_free(struct mlxsw_sp_rif *rif,
          enum mlxsw_sp_rif_counter_dir dir)
{
 struct mlxsw_sp *mlxsw_sp = rif->mlxsw_sp;
 unsigned int *p_counter_index;

 if (!mlxsw_sp_rif_counter_valid_get(rif, dir))
  return;

 p_counter_index = mlxsw_sp_rif_p_counter_get(rif, dir);
 if (WARN_ON(!p_counter_index))
  return;
 mlxsw_sp_rif_counter_edit(mlxsw_sp, rif->rif_index,
      *p_counter_index, false, dir);
 mlxsw_sp_counter_free(mlxsw_sp, MLXSW_SP_COUNTER_SUB_POOL_RIF,
         *p_counter_index);
 mlxsw_sp_rif_counter_valid_set(rif, dir, false);
}

static void mlxsw_sp_rif_counters_alloc(struct mlxsw_sp_rif *rif)
{
 struct mlxsw_sp *mlxsw_sp = rif->mlxsw_sp;
 struct devlink *devlink;

 devlink = priv_to_devlink(mlxsw_sp->core);
 if (!devlink_dpipe_table_counter_enabled(devlink,
       MLXSW_SP_DPIPE_TABLE_NAME_ERIF))
  return;
 mlxsw_sp_rif_counter_alloc(rif, MLXSW_SP_RIF_COUNTER_EGRESS);
}

static void mlxsw_sp_rif_counters_free(struct mlxsw_sp_rif *rif)
{
 mlxsw_sp_rif_counter_free(rif, MLXSW_SP_RIF_COUNTER_EGRESS);
}

#define MLXSW_SP_PREFIX_COUNT (sizeof(struct in6_addr) * BITS_PER_BYTE + 1)

struct mlxsw_sp_prefix_usage {
 DECLARE_BITMAP(b, MLXSW_SP_PREFIX_COUNT);
};

#define mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage) \
 for_each_set_bit(prefix, (prefix_usage)->b, MLXSW_SP_PREFIX_COUNT)

static bool
mlxsw_sp_prefix_usage_eq(struct mlxsw_sp_prefix_usage *prefix_usage1,
    struct mlxsw_sp_prefix_usage *prefix_usage2)
{
 return !memcmp(prefix_usage1, prefix_usage2, sizeof(*prefix_usage1));
}

static void
mlxsw_sp_prefix_usage_cpy(struct mlxsw_sp_prefix_usage *prefix_usage1,
     struct mlxsw_sp_prefix_usage *prefix_usage2)
{
 memcpy(prefix_usage1, prefix_usage2, sizeof(*prefix_usage1));
}

static void
mlxsw_sp_prefix_usage_set(struct mlxsw_sp_prefix_usage *prefix_usage,
     unsigned char prefix_len)
{
 set_bit(prefix_len, prefix_usage->b);
}

static void
mlxsw_sp_prefix_usage_clear(struct mlxsw_sp_prefix_usage *prefix_usage,
       unsigned char prefix_len)
{
 clear_bit(prefix_len, prefix_usage->b);
}

struct mlxsw_sp_fib_key {
 unsigned char addr[sizeof(struct in6_addr)];
 unsigned char prefix_len;
};

enum mlxsw_sp_fib_entry_type {
 MLXSW_SP_FIB_ENTRY_TYPE_REMOTE,
 MLXSW_SP_FIB_ENTRY_TYPE_LOCAL,
 MLXSW_SP_FIB_ENTRY_TYPE_TRAP,
 MLXSW_SP_FIB_ENTRY_TYPE_BLACKHOLE,
 MLXSW_SP_FIB_ENTRY_TYPE_UNREACHABLE,

 /* This is a special case of local delivery, where a packet should be
 * decapsulated on reception. Note that there is no corresponding ENCAP,
 * because that's a type of next hop, not of FIB entry. (There can be
 * several next hops in a REMOTE entry, and some of them may be
 * encapsulating entries.)
 */

 MLXSW_SP_FIB_ENTRY_TYPE_IPIP_DECAP,
 MLXSW_SP_FIB_ENTRY_TYPE_NVE_DECAP,
};

struct mlxsw_sp_nexthop_group_info;
struct mlxsw_sp_nexthop_group;
struct mlxsw_sp_fib_entry;

struct mlxsw_sp_fib_node {
 struct mlxsw_sp_fib_entry *fib_entry;
 struct list_head list;
 struct rhash_head ht_node;
 struct mlxsw_sp_fib *fib;
 struct mlxsw_sp_fib_key key;
};

struct mlxsw_sp_fib_entry_decap {
 struct mlxsw_sp_ipip_entry *ipip_entry;
 u32 tunnel_index;
};

struct mlxsw_sp_fib_entry {
 struct mlxsw_sp_fib_node *fib_node;
 enum mlxsw_sp_fib_entry_type type;
 struct list_head nexthop_group_node;
 struct mlxsw_sp_nexthop_group *nh_group;
 struct mlxsw_sp_fib_entry_decap decap; /* Valid for decap entries. */
};

struct mlxsw_sp_fib4_entry {
 struct mlxsw_sp_fib_entry common;
 struct fib_info *fi;
 u32 tb_id;
 dscp_t dscp;
 u8 type;
};

struct mlxsw_sp_fib6_entry {
 struct mlxsw_sp_fib_entry common;
 struct list_head rt6_list;
 unsigned int nrt6;
};

struct mlxsw_sp_rt6 {
 struct list_head list;
 struct fib6_info *rt;
};

struct mlxsw_sp_lpm_tree {
 u8 id; /* tree ID */
 refcount_t ref_count;
 enum mlxsw_sp_l3proto proto;
 unsigned long prefix_ref_count[MLXSW_SP_PREFIX_COUNT];
 struct mlxsw_sp_prefix_usage prefix_usage;
};

struct mlxsw_sp_fib {
 struct rhashtable ht;
 struct list_head node_list;
 struct mlxsw_sp_vr *vr;
 struct mlxsw_sp_lpm_tree *lpm_tree;
 enum mlxsw_sp_l3proto proto;
};

struct mlxsw_sp_vr {
 u16 id; /* virtual router ID */
 u32 tb_id; /* kernel fib table id */
 unsigned int rif_count;
 struct mlxsw_sp_fib *fib4;
 struct mlxsw_sp_fib *fib6;
 struct mlxsw_sp_mr_table *mr_table[MLXSW_SP_L3_PROTO_MAX];
 struct mlxsw_sp_rif *ul_rif;
 refcount_t ul_rif_refcnt;
};

static const struct rhashtable_params mlxsw_sp_fib_ht_params;

static struct mlxsw_sp_fib *mlxsw_sp_fib_create(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_vr *vr,
      enum mlxsw_sp_l3proto proto)
{
 struct mlxsw_sp_lpm_tree *lpm_tree;
 struct mlxsw_sp_fib *fib;
 int err;

 lpm_tree = mlxsw_sp->router->lpm.proto_trees[proto];
 fib = kzalloc(sizeof(*fib), GFP_KERNEL);
 if (!fib)
  return ERR_PTR(-ENOMEM);
 err = rhashtable_init(&fib->ht, &mlxsw_sp_fib_ht_params);
 if (err)
  goto err_rhashtable_init;
 INIT_LIST_HEAD(&fib->node_list);
 fib->proto = proto;
 fib->vr = vr;
 fib->lpm_tree = lpm_tree;
 mlxsw_sp_lpm_tree_hold(lpm_tree);
 err = mlxsw_sp_vr_lpm_tree_bind(mlxsw_sp, fib, lpm_tree->id);
 if (err)
  goto err_lpm_tree_bind;
 return fib;

err_lpm_tree_bind:
 mlxsw_sp_lpm_tree_put(mlxsw_sp, lpm_tree);
err_rhashtable_init:
 kfree(fib);
 return ERR_PTR(err);
}

static void mlxsw_sp_fib_destroy(struct mlxsw_sp *mlxsw_sp,
     struct mlxsw_sp_fib *fib)
{
 mlxsw_sp_vr_lpm_tree_unbind(mlxsw_sp, fib);
 mlxsw_sp_lpm_tree_put(mlxsw_sp, fib->lpm_tree);
 WARN_ON(!list_empty(&fib->node_list));
 rhashtable_destroy(&fib->ht);
 kfree(fib);
}

static struct mlxsw_sp_lpm_tree *
mlxsw_sp_lpm_tree_find_unused(struct mlxsw_sp *mlxsw_sp)
{
 static struct mlxsw_sp_lpm_tree *lpm_tree;
 int i;

 for (i = 0; i < mlxsw_sp->router->lpm.tree_count; i++) {
  lpm_tree = &mlxsw_sp->router->lpm.trees[i];
  if (refcount_read(&lpm_tree->ref_count) == 0)
   return lpm_tree;
 }
 return NULL;
}

static int mlxsw_sp_lpm_tree_alloc(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_lpm_tree *lpm_tree)
{
 char ralta_pl[MLXSW_REG_RALTA_LEN];

 mlxsw_reg_ralta_pack(ralta_pl, true,
        (enum mlxsw_reg_ralxx_protocol) lpm_tree->proto,
        lpm_tree->id);
 return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralta), ralta_pl);
}

static void mlxsw_sp_lpm_tree_free(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_lpm_tree *lpm_tree)
{
 char ralta_pl[MLXSW_REG_RALTA_LEN];

 mlxsw_reg_ralta_pack(ralta_pl, false,
        (enum mlxsw_reg_ralxx_protocol) lpm_tree->proto,
        lpm_tree->id);
 mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralta), ralta_pl);
}

static int
mlxsw_sp_lpm_tree_left_struct_set(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_prefix_usage *prefix_usage,
      struct mlxsw_sp_lpm_tree *lpm_tree)
{
 char ralst_pl[MLXSW_REG_RALST_LEN];
 u8 root_bin = 0;
 u8 prefix;
 u8 last_prefix = MLXSW_REG_RALST_BIN_NO_CHILD;

 mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage)
  root_bin = prefix;

 mlxsw_reg_ralst_pack(ralst_pl, root_bin, lpm_tree->id);
 mlxsw_sp_prefix_usage_for_each(prefix, prefix_usage) {
  if (prefix == 0)
   continue;
  mlxsw_reg_ralst_bin_pack(ralst_pl, prefix, last_prefix,
      MLXSW_REG_RALST_BIN_NO_CHILD);
  last_prefix = prefix;
 }
 return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ralst), ralst_pl);
}

static struct mlxsw_sp_lpm_tree *
mlxsw_sp_lpm_tree_create(struct mlxsw_sp *mlxsw_sp,
    struct mlxsw_sp_prefix_usage *prefix_usage,
    enum mlxsw_sp_l3proto proto)
{
 struct mlxsw_sp_lpm_tree *lpm_tree;
 int err;

 lpm_tree = mlxsw_sp_lpm_tree_find_unused(mlxsw_sp);
 if (!lpm_tree)
  return ERR_PTR(-EBUSY);
 lpm_tree->proto = proto;
 err = mlxsw_sp_lpm_tree_alloc(mlxsw_sp, lpm_tree);
 if (err)
  return ERR_PTR(err);

 err = mlxsw_sp_lpm_tree_left_struct_set(mlxsw_sp, prefix_usage,
      lpm_tree);
 if (err)
  goto err_left_struct_set;
 memcpy(&lpm_tree->prefix_usage, prefix_usage,
        sizeof(lpm_tree->prefix_usage));
 memset(&lpm_tree->prefix_ref_count, 0,
        sizeof(lpm_tree->prefix_ref_count));
 refcount_set(&lpm_tree->ref_count, 1);
 return lpm_tree;

err_left_struct_set:
 mlxsw_sp_lpm_tree_free(mlxsw_sp, lpm_tree);
 return ERR_PTR(err);
}

static void mlxsw_sp_lpm_tree_destroy(struct mlxsw_sp *mlxsw_sp,
          struct mlxsw_sp_lpm_tree *lpm_tree)
{
 mlxsw_sp_lpm_tree_free(mlxsw_sp, lpm_tree);
}

static struct mlxsw_sp_lpm_tree *
mlxsw_sp_lpm_tree_get(struct mlxsw_sp *mlxsw_sp,
        struct mlxsw_sp_prefix_usage *prefix_usage,
        enum mlxsw_sp_l3proto proto)
{
 struct mlxsw_sp_lpm_tree *lpm_tree;
 int i;

 for (i = 0; i < mlxsw_sp->router->lpm.tree_count; i++) {
  lpm_tree = &mlxsw_sp->router->lpm.trees[i];
  if (refcount_read(&lpm_tree->ref_count) &&
      lpm_tree->proto == proto &&
      mlxsw_sp_prefix_usage_eq(&lpm_tree->prefix_usage,
          prefix_usage)) {
   mlxsw_sp_lpm_tree_hold(lpm_tree);
   return lpm_tree;
  }
 }
 return mlxsw_sp_lpm_tree_create(mlxsw_sp, prefix_usage, proto);
}

static void mlxsw_sp_lpm_tree_hold(struct mlxsw_sp_lpm_tree *lpm_tree)
{
 refcount_inc(&lpm_tree->ref_count);
}

static void mlxsw_sp_lpm_tree_put(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_lpm_tree *lpm_tree)
{
 if (!refcount_dec_and_test(&lpm_tree->ref_count))
  return;
 mlxsw_sp_lpm_tree_destroy(mlxsw_sp, lpm_tree);
}

#define MLXSW_SP_LPM_TREE_MIN 1 /* tree 0 is reserved */

static int mlxsw_sp_lpm_init(struct mlxsw_sp *mlxsw_sp)
{
 struct mlxsw_sp_prefix_usage req_prefix_usage = {{ 0 } };
 struct mlxsw_sp_lpm_tree *lpm_tree;
 u64 max_trees;
 int err, i;

 if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, MAX_LPM_TREES))
  return -EIO;

 max_trees = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_LPM_TREES);
 mlxsw_sp->router->lpm.tree_count = max_trees - MLXSW_SP_LPM_TREE_MIN;
 mlxsw_sp->router->lpm.trees = kcalloc(mlxsw_sp->router->lpm.tree_count,
          sizeof(struct mlxsw_sp_lpm_tree),
          GFP_KERNEL);
 if (!mlxsw_sp->router->lpm.trees)
  return -ENOMEM;

 for (i = 0; i < mlxsw_sp->router->lpm.tree_count; i++) {
  lpm_tree = &mlxsw_sp->router->lpm.trees[i];
  lpm_tree->id = i + MLXSW_SP_LPM_TREE_MIN;
 }

 lpm_tree = mlxsw_sp_lpm_tree_get(mlxsw_sp, &req_prefix_usage,
      MLXSW_SP_L3_PROTO_IPV4);
 if (IS_ERR(lpm_tree)) {
  err = PTR_ERR(lpm_tree);
  goto err_ipv4_tree_get;
 }
 mlxsw_sp->router->lpm.proto_trees[MLXSW_SP_L3_PROTO_IPV4] = lpm_tree;

 lpm_tree = mlxsw_sp_lpm_tree_get(mlxsw_sp, &req_prefix_usage,
      MLXSW_SP_L3_PROTO_IPV6);
 if (IS_ERR(lpm_tree)) {
  err = PTR_ERR(lpm_tree);
  goto err_ipv6_tree_get;
 }
 mlxsw_sp->router->lpm.proto_trees[MLXSW_SP_L3_PROTO_IPV6] = lpm_tree;

 return 0;

err_ipv6_tree_get:
 lpm_tree = mlxsw_sp->router->lpm.proto_trees[MLXSW_SP_L3_PROTO_IPV4];
 mlxsw_sp_lpm_tree_put(mlxsw_sp, lpm_tree);
err_ipv4_tree_get:
 kfree(mlxsw_sp->router->lpm.trees);
 return err;
}

static void mlxsw_sp_lpm_fini(struct mlxsw_sp *mlxsw_sp)
{
 struct mlxsw_sp_lpm_tree *lpm_tree;

 lpm_tree = mlxsw_sp->router->lpm.proto_trees[MLXSW_SP_L3_PROTO_IPV6];
 mlxsw_sp_lpm_tree_put(mlxsw_sp, lpm_tree);

 lpm_tree = mlxsw_sp->router->lpm.proto_trees[MLXSW_SP_L3_PROTO_IPV4];
 mlxsw_sp_lpm_tree_put(mlxsw_sp, lpm_tree);

 kfree(mlxsw_sp->router->lpm.trees);
}

static bool mlxsw_sp_vr_is_used(const struct mlxsw_sp_vr *vr)
{
 return !!vr->fib4 || !!vr->fib6 ||
        !!vr->mr_table[MLXSW_SP_L3_PROTO_IPV4] ||
        !!vr->mr_table[MLXSW_SP_L3_PROTO_IPV6];
}

static struct mlxsw_sp_vr *mlxsw_sp_vr_find_unused(struct mlxsw_sp *mlxsw_sp)
{
 int max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
 struct mlxsw_sp_vr *vr;
 int i;

 for (i = 0; i < max_vrs; i++) {
  vr = &mlxsw_sp->router->vrs[i];
  if (!mlxsw_sp_vr_is_used(vr))
   return vr;
 }
 return NULL;
}

static int mlxsw_sp_vr_lpm_tree_bind(struct mlxsw_sp *mlxsw_sp,
         const struct mlxsw_sp_fib *fib, u8 tree_id)
{
 char raltb_pl[MLXSW_REG_RALTB_LEN];

 mlxsw_reg_raltb_pack(raltb_pl, fib->vr->id,
        (enum mlxsw_reg_ralxx_protocol) fib->proto,
        tree_id);
 return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(raltb), raltb_pl);
}

static int mlxsw_sp_vr_lpm_tree_unbind(struct mlxsw_sp *mlxsw_sp,
           const struct mlxsw_sp_fib *fib)
{
 char raltb_pl[MLXSW_REG_RALTB_LEN];

 /* Bind to tree 0 which is default */
 mlxsw_reg_raltb_pack(raltb_pl, fib->vr->id,
        (enum mlxsw_reg_ralxx_protocol) fib->proto, 0);
 return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(raltb), raltb_pl);
}

static u32 mlxsw_sp_fix_tb_id(u32 tb_id)
{
 /* For our purpose, squash main, default and local tables into one */
 if (tb_id == RT_TABLE_LOCAL || tb_id == RT_TABLE_DEFAULT)
  tb_id = RT_TABLE_MAIN;
 return tb_id;
}

static struct mlxsw_sp_vr *mlxsw_sp_vr_find(struct mlxsw_sp *mlxsw_sp,
         u32 tb_id)
{
 int max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
 struct mlxsw_sp_vr *vr;
 int i;

 tb_id = mlxsw_sp_fix_tb_id(tb_id);

 for (i = 0; i < max_vrs; i++) {
  vr = &mlxsw_sp->router->vrs[i];
  if (mlxsw_sp_vr_is_used(vr) && vr->tb_id == tb_id)
   return vr;
 }
 return NULL;
}

int mlxsw_sp_router_tb_id_vr_id(struct mlxsw_sp *mlxsw_sp, u32 tb_id,
    u16 *vr_id)
{
 struct mlxsw_sp_vr *vr;
 int err = 0;

 mutex_lock(&mlxsw_sp->router->lock);
 vr = mlxsw_sp_vr_find(mlxsw_sp, tb_id);
 if (!vr) {
  err = -ESRCH;
  goto out;
 }
 *vr_id = vr->id;
out:
 mutex_unlock(&mlxsw_sp->router->lock);
 return err;
}

static struct mlxsw_sp_fib *mlxsw_sp_vr_fib(const struct mlxsw_sp_vr *vr,
         enum mlxsw_sp_l3proto proto)
{
 switch (proto) {
 case MLXSW_SP_L3_PROTO_IPV4:
  return vr->fib4;
 case MLXSW_SP_L3_PROTO_IPV6:
  return vr->fib6;
 }
 return NULL;
}

static struct mlxsw_sp_vr *mlxsw_sp_vr_create(struct mlxsw_sp *mlxsw_sp,
           u32 tb_id,
           struct netlink_ext_ack *extack)
{
 struct mlxsw_sp_mr_table *mr4_table, *mr6_table;
 struct mlxsw_sp_fib *fib4;
 struct mlxsw_sp_fib *fib6;
 struct mlxsw_sp_vr *vr;
 int err;

 vr = mlxsw_sp_vr_find_unused(mlxsw_sp);
 if (!vr) {
  NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported virtual routers");
  return ERR_PTR(-EBUSY);
 }
 fib4 = mlxsw_sp_fib_create(mlxsw_sp, vr, MLXSW_SP_L3_PROTO_IPV4);
 if (IS_ERR(fib4))
  return ERR_CAST(fib4);
 fib6 = mlxsw_sp_fib_create(mlxsw_sp, vr, MLXSW_SP_L3_PROTO_IPV6);
 if (IS_ERR(fib6)) {
  err = PTR_ERR(fib6);
  goto err_fib6_create;
 }
 mr4_table = mlxsw_sp_mr_table_create(mlxsw_sp, vr->id,
          MLXSW_SP_L3_PROTO_IPV4);
 if (IS_ERR(mr4_table)) {
  err = PTR_ERR(mr4_table);
  goto err_mr4_table_create;
 }
 mr6_table = mlxsw_sp_mr_table_create(mlxsw_sp, vr->id,
          MLXSW_SP_L3_PROTO_IPV6);
 if (IS_ERR(mr6_table)) {
  err = PTR_ERR(mr6_table);
  goto err_mr6_table_create;
 }

 vr->fib4 = fib4;
 vr->fib6 = fib6;
 vr->mr_table[MLXSW_SP_L3_PROTO_IPV4] = mr4_table;
 vr->mr_table[MLXSW_SP_L3_PROTO_IPV6] = mr6_table;
 vr->tb_id = tb_id;
 return vr;

err_mr6_table_create:
 mlxsw_sp_mr_table_destroy(mr4_table);
err_mr4_table_create:
 mlxsw_sp_fib_destroy(mlxsw_sp, fib6);
err_fib6_create:
 mlxsw_sp_fib_destroy(mlxsw_sp, fib4);
 return ERR_PTR(err);
}

static void mlxsw_sp_vr_destroy(struct mlxsw_sp *mlxsw_sp,
    struct mlxsw_sp_vr *vr)
{
 mlxsw_sp_mr_table_destroy(vr->mr_table[MLXSW_SP_L3_PROTO_IPV6]);
 vr->mr_table[MLXSW_SP_L3_PROTO_IPV6] = NULL;
 mlxsw_sp_mr_table_destroy(vr->mr_table[MLXSW_SP_L3_PROTO_IPV4]);
 vr->mr_table[MLXSW_SP_L3_PROTO_IPV4] = NULL;
 mlxsw_sp_fib_destroy(mlxsw_sp, vr->fib6);
 vr->fib6 = NULL;
 mlxsw_sp_fib_destroy(mlxsw_sp, vr->fib4);
 vr->fib4 = NULL;
}

static struct mlxsw_sp_vr *mlxsw_sp_vr_get(struct mlxsw_sp *mlxsw_sp, u32 tb_id,
        struct netlink_ext_ack *extack)
{
 struct mlxsw_sp_vr *vr;

 tb_id = mlxsw_sp_fix_tb_id(tb_id);
 vr = mlxsw_sp_vr_find(mlxsw_sp, tb_id);
 if (!vr)
  vr = mlxsw_sp_vr_create(mlxsw_sp, tb_id, extack);
 return vr;
}

static void mlxsw_sp_vr_put(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_vr *vr)
{
 if (!vr->rif_count && list_empty(&vr->fib4->node_list) &&
     list_empty(&vr->fib6->node_list) &&
     mlxsw_sp_mr_table_empty(vr->mr_table[MLXSW_SP_L3_PROTO_IPV4]) &&
     mlxsw_sp_mr_table_empty(vr->mr_table[MLXSW_SP_L3_PROTO_IPV6]))
  mlxsw_sp_vr_destroy(mlxsw_sp, vr);
}

static bool
mlxsw_sp_vr_lpm_tree_should_replace(struct mlxsw_sp_vr *vr,
        enum mlxsw_sp_l3proto proto, u8 tree_id)
{
 struct mlxsw_sp_fib *fib = mlxsw_sp_vr_fib(vr, proto);

 if (!mlxsw_sp_vr_is_used(vr))
  return false;
 if (fib->lpm_tree->id == tree_id)
  return true;
 return false;
}

static int mlxsw_sp_vr_lpm_tree_replace(struct mlxsw_sp *mlxsw_sp,
     struct mlxsw_sp_fib *fib,
     struct mlxsw_sp_lpm_tree *new_tree)
{
 struct mlxsw_sp_lpm_tree *old_tree = fib->lpm_tree;
 int err;

 fib->lpm_tree = new_tree;
 mlxsw_sp_lpm_tree_hold(new_tree);
 err = mlxsw_sp_vr_lpm_tree_bind(mlxsw_sp, fib, new_tree->id);
 if (err)
  goto err_tree_bind;
 mlxsw_sp_lpm_tree_put(mlxsw_sp, old_tree);
 return 0;

err_tree_bind:
 mlxsw_sp_lpm_tree_put(mlxsw_sp, new_tree);
 fib->lpm_tree = old_tree;
 return err;
}

static int mlxsw_sp_vrs_lpm_tree_replace(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_fib *fib,
      struct mlxsw_sp_lpm_tree *new_tree)
{
 int max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
 enum mlxsw_sp_l3proto proto = fib->proto;
 struct mlxsw_sp_lpm_tree *old_tree;
 u8 old_id, new_id = new_tree->id;
 struct mlxsw_sp_vr *vr;
 int i, err;

 old_tree = mlxsw_sp->router->lpm.proto_trees[proto];
 old_id = old_tree->id;

 for (i = 0; i < max_vrs; i++) {
  vr = &mlxsw_sp->router->vrs[i];
  if (!mlxsw_sp_vr_lpm_tree_should_replace(vr, proto, old_id))
   continue;
  err = mlxsw_sp_vr_lpm_tree_replace(mlxsw_sp,
         mlxsw_sp_vr_fib(vr, proto),
         new_tree);
  if (err)
   goto err_tree_replace;
 }

 memcpy(new_tree->prefix_ref_count, old_tree->prefix_ref_count,
        sizeof(new_tree->prefix_ref_count));
 mlxsw_sp->router->lpm.proto_trees[proto] = new_tree;
 mlxsw_sp_lpm_tree_put(mlxsw_sp, old_tree);

 return 0;

err_tree_replace:
 for (i--; i >= 0; i--) {
  if (!mlxsw_sp_vr_lpm_tree_should_replace(vr, proto, new_id))
   continue;
  mlxsw_sp_vr_lpm_tree_replace(mlxsw_sp,
          mlxsw_sp_vr_fib(vr, proto),
          old_tree);
 }
 return err;
}

static int mlxsw_sp_vrs_init(struct mlxsw_sp *mlxsw_sp)
{
 struct mlxsw_sp_vr *vr;
 u64 max_vrs;
 int i;

 if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, MAX_VRS))
  return -EIO;

 max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
 mlxsw_sp->router->vrs = kcalloc(max_vrs, sizeof(struct mlxsw_sp_vr),
     GFP_KERNEL);
 if (!mlxsw_sp->router->vrs)
  return -ENOMEM;

 for (i = 0; i < max_vrs; i++) {
  vr = &mlxsw_sp->router->vrs[i];
  vr->id = i;
 }

 return 0;
}

static void mlxsw_sp_router_fib_flush(struct mlxsw_sp *mlxsw_sp);

static void mlxsw_sp_vrs_fini(struct mlxsw_sp *mlxsw_sp)
{
 /* At this stage we're guaranteed not to have new incoming
 * FIB notifications and the work queue is free from FIBs
 * sitting on top of mlxsw netdevs. However, we can still
 * have other FIBs queued. Flush the queue before flushing
 * the device's tables. No need for locks, as we're the only
 * writer.
 */

 mlxsw_core_flush_owq();
 mlxsw_sp_router_fib_flush(mlxsw_sp);
 kfree(mlxsw_sp->router->vrs);
}

u32 mlxsw_sp_ipip_dev_ul_tb_id(const struct net_device *ol_dev)
{
 struct net_device *d;
 u32 tb_id;

 rcu_read_lock();
 d = mlxsw_sp_ipip_netdev_ul_dev_get(ol_dev);
 if (d)
  tb_id = l3mdev_fib_table(d) ? : RT_TABLE_MAIN;
 else
  tb_id = RT_TABLE_MAIN;
 rcu_read_unlock();

 return tb_id;
}

static void
mlxsw_sp_crif_init(struct mlxsw_sp_crif *crif, struct net_device *dev)
{
 crif->key.dev = dev;
 INIT_LIST_HEAD(&crif->nexthop_list);
}

static struct mlxsw_sp_crif *
mlxsw_sp_crif_alloc(struct net_device *dev)
{
 struct mlxsw_sp_crif *crif;

 crif = kzalloc(sizeof(*crif), GFP_KERNEL);
 if (!crif)
  return NULL;

 mlxsw_sp_crif_init(crif, dev);
 return crif;
}

static void mlxsw_sp_crif_free(struct mlxsw_sp_crif *crif)
{
 if (WARN_ON(crif->rif))
  return;

 WARN_ON(!list_empty(&crif->nexthop_list));
 kfree(crif);
}

static int mlxsw_sp_crif_insert(struct mlxsw_sp_router *router,
    struct mlxsw_sp_crif *crif)
{
 return rhashtable_insert_fast(&router->crif_ht, &crif->ht_node,
          mlxsw_sp_crif_ht_params);
}

static void mlxsw_sp_crif_remove(struct mlxsw_sp_router *router,
     struct mlxsw_sp_crif *crif)
{
 rhashtable_remove_fast(&router->crif_ht, &crif->ht_node,
          mlxsw_sp_crif_ht_params);
}

static struct mlxsw_sp_crif *
mlxsw_sp_crif_lookup(struct mlxsw_sp_router *router,
       const struct net_device *dev)
{
 struct mlxsw_sp_crif_key key = {
  .dev = (struct net_device *)dev,
 };

 return rhashtable_lookup_fast(&router->crif_ht, &key,
          mlxsw_sp_crif_ht_params);
}

static struct mlxsw_sp_rif *
mlxsw_sp_rif_create(struct mlxsw_sp *mlxsw_sp,
      const struct mlxsw_sp_rif_params *params,
      struct netlink_ext_ack *extack);

static struct mlxsw_sp_rif_ipip_lb *
mlxsw_sp_ipip_ol_ipip_lb_create(struct mlxsw_sp *mlxsw_sp,
    enum mlxsw_sp_ipip_type ipipt,
    struct net_device *ol_dev,
    struct netlink_ext_ack *extack)
{
 struct mlxsw_sp_rif_params_ipip_lb lb_params;
 const struct mlxsw_sp_ipip_ops *ipip_ops;
 struct mlxsw_sp_rif *rif;

 ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipipt];
 lb_params = (struct mlxsw_sp_rif_params_ipip_lb) {
  .common.dev = ol_dev,
  .common.lag = false,
  .common.double_entry = ipip_ops->double_rif_entry,
  .lb_config = ipip_ops->ol_loopback_config(mlxsw_sp, ol_dev),
 };

 rif = mlxsw_sp_rif_create(mlxsw_sp, &lb_params.common, extack);
 if (IS_ERR(rif))
  return ERR_CAST(rif);
 return container_of(rif, struct mlxsw_sp_rif_ipip_lb, common);
}

static struct mlxsw_sp_ipip_entry *
mlxsw_sp_ipip_entry_alloc(struct mlxsw_sp *mlxsw_sp,
     enum mlxsw_sp_ipip_type ipipt,
     struct net_device *ol_dev)
{
 const struct mlxsw_sp_ipip_ops *ipip_ops;
 struct mlxsw_sp_ipip_entry *ipip_entry;
 struct mlxsw_sp_ipip_entry *ret = NULL;
 int err;

 ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipipt];
 ipip_entry = kzalloc(sizeof(*ipip_entry), GFP_KERNEL);
 if (!ipip_entry)
  return ERR_PTR(-ENOMEM);

 ipip_entry->ol_lb = mlxsw_sp_ipip_ol_ipip_lb_create(mlxsw_sp, ipipt,
           ol_dev, NULL);
 if (IS_ERR(ipip_entry->ol_lb)) {
  ret = ERR_CAST(ipip_entry->ol_lb);
  goto err_ol_ipip_lb_create;
 }

 ipip_entry->ipipt = ipipt;
 ipip_entry->ol_dev = ol_dev;
 ipip_entry->parms = ipip_ops->parms_init(ol_dev);

 err = ipip_ops->rem_ip_addr_set(mlxsw_sp, ipip_entry);
 if (err) {
  ret = ERR_PTR(err);
  goto err_rem_ip_addr_set;
 }

 return ipip_entry;

err_rem_ip_addr_set:
 mlxsw_sp_rif_destroy(&ipip_entry->ol_lb->common);
err_ol_ipip_lb_create:
 kfree(ipip_entry);
 return ret;
}

static void mlxsw_sp_ipip_entry_dealloc(struct mlxsw_sp *mlxsw_sp,
     struct mlxsw_sp_ipip_entry *ipip_entry)
{
 const struct mlxsw_sp_ipip_ops *ipip_ops =
  mlxsw_sp->router->ipip_ops_arr[ipip_entry->ipipt];

 ipip_ops->rem_ip_addr_unset(mlxsw_sp, ipip_entry);
 mlxsw_sp_rif_destroy(&ipip_entry->ol_lb->common);
 kfree(ipip_entry);
}

static bool
mlxsw_sp_ipip_entry_saddr_matches(struct mlxsw_sp *mlxsw_sp,
      const enum mlxsw_sp_l3proto ul_proto,
      union mlxsw_sp_l3addr saddr,
      u32 ul_tb_id,
      struct mlxsw_sp_ipip_entry *ipip_entry)
{
 u32 tun_ul_tb_id = mlxsw_sp_ipip_dev_ul_tb_id(ipip_entry->ol_dev);
 enum mlxsw_sp_ipip_type ipipt = ipip_entry->ipipt;
 union mlxsw_sp_l3addr tun_saddr;

 if (mlxsw_sp->router->ipip_ops_arr[ipipt]->ul_proto != ul_proto)
  return false;

 tun_saddr = mlxsw_sp_ipip_netdev_saddr(ul_proto, ipip_entry->ol_dev);
 return tun_ul_tb_id == ul_tb_id &&
        mlxsw_sp_l3addr_eq(&tun_saddr, &saddr);
}

static int mlxsw_sp_ipip_decap_parsing_depth_inc(struct mlxsw_sp *mlxsw_sp,
       enum mlxsw_sp_ipip_type ipipt)
{
 const struct mlxsw_sp_ipip_ops *ipip_ops;

 ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipipt];

 /* Not all tunnels require to increase the default pasing depth
 * (96 bytes).
 */

 if (ipip_ops->inc_parsing_depth)
  return mlxsw_sp_parsing_depth_inc(mlxsw_sp);

 return 0;
}

static void mlxsw_sp_ipip_decap_parsing_depth_dec(struct mlxsw_sp *mlxsw_sp,
        enum mlxsw_sp_ipip_type ipipt)
{
 const struct mlxsw_sp_ipip_ops *ipip_ops =
  mlxsw_sp->router->ipip_ops_arr[ipipt];

 if (ipip_ops->inc_parsing_depth)
  mlxsw_sp_parsing_depth_dec(mlxsw_sp);
}

static int
mlxsw_sp_fib_entry_decap_init(struct mlxsw_sp *mlxsw_sp,
         struct mlxsw_sp_fib_entry *fib_entry,
         struct mlxsw_sp_ipip_entry *ipip_entry)
{
 u32 tunnel_index;
 int err;

 err = mlxsw_sp_kvdl_alloc(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_ADJ,
      1, &tunnel_index);
 if (err)
  return err;

 err = mlxsw_sp_ipip_decap_parsing_depth_inc(mlxsw_sp,
          ipip_entry->ipipt);
 if (err)
  goto err_parsing_depth_inc;

 ipip_entry->decap_fib_entry = fib_entry;
 fib_entry->decap.ipip_entry = ipip_entry;
 fib_entry->decap.tunnel_index = tunnel_index;

 return 0;

err_parsing_depth_inc:
 mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_ADJ, 1,
      fib_entry->decap.tunnel_index);
 return err;
}

static void mlxsw_sp_fib_entry_decap_fini(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_fib_entry *fib_entry)
{
 enum mlxsw_sp_ipip_type ipipt = fib_entry->decap.ipip_entry->ipipt;

 /* Unlink this node from the IPIP entry that it's the decap entry of. */
 fib_entry->decap.ipip_entry->decap_fib_entry = NULL;
 fib_entry->decap.ipip_entry = NULL;
 mlxsw_sp_ipip_decap_parsing_depth_dec(mlxsw_sp, ipipt);
 mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_ADJ,
      1, fib_entry->decap.tunnel_index);
}

static struct mlxsw_sp_fib_node *
mlxsw_sp_fib_node_lookup(struct mlxsw_sp_fib *fib, const void *addr,
    size_t addr_len, unsigned char prefix_len);
static int mlxsw_sp_fib_entry_update(struct mlxsw_sp *mlxsw_sp,
         struct mlxsw_sp_fib_entry *fib_entry);

static void
mlxsw_sp_ipip_entry_demote_decap(struct mlxsw_sp *mlxsw_sp,
     struct mlxsw_sp_ipip_entry *ipip_entry)
{
 struct mlxsw_sp_fib_entry *fib_entry = ipip_entry->decap_fib_entry;

 mlxsw_sp_fib_entry_decap_fini(mlxsw_sp, fib_entry);
 fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;

 mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
}

static void
mlxsw_sp_ipip_entry_promote_decap(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_ipip_entry *ipip_entry,
      struct mlxsw_sp_fib_entry *decap_fib_entry)
{
 if (mlxsw_sp_fib_entry_decap_init(mlxsw_sp, decap_fib_entry,
       ipip_entry))
  return;
 decap_fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_IPIP_DECAP;

 if (mlxsw_sp_fib_entry_update(mlxsw_sp, decap_fib_entry))
  mlxsw_sp_ipip_entry_demote_decap(mlxsw_sp, ipip_entry);
}

static struct mlxsw_sp_fib_entry *
mlxsw_sp_router_ip2me_fib_entry_find(struct mlxsw_sp *mlxsw_sp, u32 tb_id,
         enum mlxsw_sp_l3proto proto,
         const union mlxsw_sp_l3addr *addr,
         enum mlxsw_sp_fib_entry_type type)
{
 struct mlxsw_sp_fib_node *fib_node;
 unsigned char addr_prefix_len;
 struct mlxsw_sp_fib *fib;
 struct mlxsw_sp_vr *vr;
 const void *addrp;
 size_t addr_len;
 u32 addr4;

 vr = mlxsw_sp_vr_find(mlxsw_sp, tb_id);
 if (!vr)
  return NULL;
 fib = mlxsw_sp_vr_fib(vr, proto);

 switch (proto) {
 case MLXSW_SP_L3_PROTO_IPV4:
  addr4 = be32_to_cpu(addr->addr4);
  addrp = &addr4;
  addr_len = 4;
  addr_prefix_len = 32;
  break;
 case MLXSW_SP_L3_PROTO_IPV6:
  addrp = &addr->addr6;
  addr_len = 16;
  addr_prefix_len = 128;
  break;
 default:
  WARN_ON(1);
  return NULL;
 }

 fib_node = mlxsw_sp_fib_node_lookup(fib, addrp, addr_len,
         addr_prefix_len);
 if (!fib_node || fib_node->fib_entry->type != type)
  return NULL;

 return fib_node->fib_entry;
}

/* Given an IPIP entry, find the corresponding decap route. */
static struct mlxsw_sp_fib_entry *
mlxsw_sp_ipip_entry_find_decap(struct mlxsw_sp *mlxsw_sp,
          struct mlxsw_sp_ipip_entry *ipip_entry)
{
 static struct mlxsw_sp_fib_node *fib_node;
 const struct mlxsw_sp_ipip_ops *ipip_ops;
 unsigned char saddr_prefix_len;
 union mlxsw_sp_l3addr saddr;
 struct mlxsw_sp_fib *ul_fib;
 struct mlxsw_sp_vr *ul_vr;
 const void *saddrp;
 size_t saddr_len;
 u32 ul_tb_id;
 u32 saddr4;

 ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipip_entry->ipipt];

 ul_tb_id = mlxsw_sp_ipip_dev_ul_tb_id(ipip_entry->ol_dev);
 ul_vr = mlxsw_sp_vr_find(mlxsw_sp, ul_tb_id);
 if (!ul_vr)
  return NULL;

 ul_fib = mlxsw_sp_vr_fib(ul_vr, ipip_ops->ul_proto);
 saddr = mlxsw_sp_ipip_netdev_saddr(ipip_ops->ul_proto,
        ipip_entry->ol_dev);

 switch (ipip_ops->ul_proto) {
 case MLXSW_SP_L3_PROTO_IPV4:
  saddr4 = be32_to_cpu(saddr.addr4);
  saddrp = &saddr4;
  saddr_len = 4;
  saddr_prefix_len = 32;
  break;
 case MLXSW_SP_L3_PROTO_IPV6:
  saddrp = &saddr.addr6;
  saddr_len = 16;
  saddr_prefix_len = 128;
  break;
 default:
  WARN_ON(1);
  return NULL;
 }

 fib_node = mlxsw_sp_fib_node_lookup(ul_fib, saddrp, saddr_len,
         saddr_prefix_len);
 if (!fib_node ||
     fib_node->fib_entry->type != MLXSW_SP_FIB_ENTRY_TYPE_TRAP)
  return NULL;

 return fib_node->fib_entry;
}

static struct mlxsw_sp_ipip_entry *
mlxsw_sp_ipip_entry_create(struct mlxsw_sp *mlxsw_sp,
      enum mlxsw_sp_ipip_type ipipt,
      struct net_device *ol_dev)
{
 struct mlxsw_sp_ipip_entry *ipip_entry;

 ipip_entry = mlxsw_sp_ipip_entry_alloc(mlxsw_sp, ipipt, ol_dev);
 if (IS_ERR(ipip_entry))
  return ipip_entry;

 list_add_tail(&ipip_entry->ipip_list_node,
        &mlxsw_sp->router->ipip_list);

 return ipip_entry;
}

static void
mlxsw_sp_ipip_entry_destroy(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_ipip_entry *ipip_entry)
{
 list_del(&ipip_entry->ipip_list_node);
 mlxsw_sp_ipip_entry_dealloc(mlxsw_sp, ipip_entry);
}

static bool
mlxsw_sp_ipip_entry_matches_decap(struct mlxsw_sp *mlxsw_sp,
      const struct net_device *ul_dev,
      enum mlxsw_sp_l3proto ul_proto,
      union mlxsw_sp_l3addr ul_dip,
      struct mlxsw_sp_ipip_entry *ipip_entry)
{
 u32 ul_tb_id = l3mdev_fib_table(ul_dev) ? : RT_TABLE_MAIN;
 enum mlxsw_sp_ipip_type ipipt = ipip_entry->ipipt;

 if (mlxsw_sp->router->ipip_ops_arr[ipipt]->ul_proto != ul_proto)
  return false;

 return mlxsw_sp_ipip_entry_saddr_matches(mlxsw_sp, ul_proto, ul_dip,
       ul_tb_id, ipip_entry);
}

/* 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, int ul_dev_ifindex,
      enum mlxsw_sp_l3proto ul_proto,
      union mlxsw_sp_l3addr ul_dip)
{
 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))
   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,
          const struct net_device *dev,
          enum mlxsw_sp_ipip_type *p_type)
{
 struct mlxsw_sp_router *router = mlxsw_sp->router;
 const struct mlxsw_sp_ipip_ops *ipip_ops;
 enum mlxsw_sp_ipip_type ipipt;

 for (ipipt = 0; ipipt < MLXSW_SP_IPIP_TYPE_MAX; ++ipipt) {
  ipip_ops = router->ipip_ops_arr[ipipt];
  if (dev->type == ipip_ops->dev_type) {
   if (p_type)
    *p_type = ipipt;
   return true;
  }
 }
 return false;
}

static bool mlxsw_sp_netdev_is_ipip_ol(const struct mlxsw_sp *mlxsw_sp,
           const struct net_device *dev)
{
 return mlxsw_sp_netdev_ipip_type(mlxsw_sp, dev, NULL);
}

static struct mlxsw_sp_ipip_entry *
mlxsw_sp_ipip_entry_find_by_ol_dev(struct mlxsw_sp *mlxsw_sp,
       const struct net_device *ol_dev)
{
 struct mlxsw_sp_ipip_entry *ipip_entry;

 list_for_each_entry(ipip_entry, &mlxsw_sp->router->ipip_list,
       ipip_list_node)
  if (ipip_entry->ol_dev == ol_dev)
   return ipip_entry;

 return NULL;
}

static struct mlxsw_sp_ipip_entry *
mlxsw_sp_ipip_entry_find_by_ul_dev(const struct mlxsw_sp *mlxsw_sp,
       const struct net_device *ul_dev,
       struct mlxsw_sp_ipip_entry *start)
{
 struct mlxsw_sp_ipip_entry *ipip_entry;

 ipip_entry = list_prepare_entry(start, &mlxsw_sp->router->ipip_list,
     ipip_list_node);
 list_for_each_entry_continue(ipip_entry, &mlxsw_sp->router->ipip_list,
         ipip_list_node) {
  struct net_device *ol_dev = ipip_entry->ol_dev;
  struct net_device *ipip_ul_dev;

  rcu_read_lock();
  ipip_ul_dev = mlxsw_sp_ipip_netdev_ul_dev_get(ol_dev);
  rcu_read_unlock();

  if (ipip_ul_dev == ul_dev)
   return ipip_entry;
 }

 return NULL;
}

static bool mlxsw_sp_netdev_is_ipip_ul(struct mlxsw_sp *mlxsw_sp,
           const struct net_device *dev)
{
 return mlxsw_sp_ipip_entry_find_by_ul_dev(mlxsw_sp, dev, NULL);
}

static bool mlxsw_sp_netdevice_ipip_can_offload(struct mlxsw_sp *mlxsw_sp,
      const struct net_device *ol_dev,
      enum mlxsw_sp_ipip_type ipipt)
{
 const struct mlxsw_sp_ipip_ops *ops
  = mlxsw_sp->router->ipip_ops_arr[ipipt];

 return ops->can_offload(mlxsw_sp, ol_dev);
}

static int mlxsw_sp_netdevice_ipip_ol_reg_event(struct mlxsw_sp *mlxsw_sp,
      struct net_device *ol_dev)
{
 enum mlxsw_sp_ipip_type ipipt = MLXSW_SP_IPIP_TYPE_MAX;
 struct mlxsw_sp_ipip_entry *ipip_entry;
 enum mlxsw_sp_l3proto ul_proto;
 union mlxsw_sp_l3addr saddr;
 u32 ul_tb_id;

 mlxsw_sp_netdev_ipip_type(mlxsw_sp, ol_dev, &ipipt);
 if (mlxsw_sp_netdevice_ipip_can_offload(mlxsw_sp, ol_dev, ipipt)) {
  ul_tb_id = mlxsw_sp_ipip_dev_ul_tb_id(ol_dev);
  ul_proto = mlxsw_sp->router->ipip_ops_arr[ipipt]->ul_proto;
  saddr = mlxsw_sp_ipip_netdev_saddr(ul_proto, ol_dev);
  if (!mlxsw_sp_ipip_demote_tunnel_by_saddr(mlxsw_sp, ul_proto,
         saddr, ul_tb_id,
         NULL)) {
   ipip_entry = mlxsw_sp_ipip_entry_create(mlxsw_sp, ipipt,
        ol_dev);
   if (IS_ERR(ipip_entry))
    return PTR_ERR(ipip_entry);
  }
 }

 return 0;
}

static void mlxsw_sp_netdevice_ipip_ol_unreg_event(struct mlxsw_sp *mlxsw_sp,
         struct net_device *ol_dev)
{
 struct mlxsw_sp_ipip_entry *ipip_entry;

 ipip_entry = mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, ol_dev);
 if (ipip_entry)
  mlxsw_sp_ipip_entry_destroy(mlxsw_sp, ipip_entry);
}

static void
mlxsw_sp_ipip_entry_ol_up_event(struct mlxsw_sp *mlxsw_sp,
    struct mlxsw_sp_ipip_entry *ipip_entry)
{
 struct mlxsw_sp_fib_entry *decap_fib_entry;

 decap_fib_entry = mlxsw_sp_ipip_entry_find_decap(mlxsw_sp, ipip_entry);
 if (decap_fib_entry)
  mlxsw_sp_ipip_entry_promote_decap(mlxsw_sp, ipip_entry,
        decap_fib_entry);
}

static int
mlxsw_sp_rif_ipip_lb_op(struct mlxsw_sp_rif_ipip_lb *lb_rif, u16 ul_vr_id,
   u16 ul_rif_id, bool enable)
{
 struct mlxsw_sp_rif_ipip_lb_config lb_cf = lb_rif->lb_config;
 struct net_device *dev = mlxsw_sp_rif_dev(&lb_rif->common);
 enum mlxsw_reg_ritr_loopback_ipip_options ipip_options;
 struct mlxsw_sp_rif *rif = &lb_rif->common;
 struct mlxsw_sp *mlxsw_sp = rif->mlxsw_sp;
 char ritr_pl[MLXSW_REG_RITR_LEN];
 struct in6_addr *saddr6;
 u32 saddr4;

 ipip_options = MLXSW_REG_RITR_LOOPBACK_IPIP_OPTIONS_GRE_KEY_PRESET;
 switch (lb_cf.ul_protocol) {
 case MLXSW_SP_L3_PROTO_IPV4:
  saddr4 = be32_to_cpu(lb_cf.saddr.addr4);
  mlxsw_reg_ritr_pack(ritr_pl, enable, MLXSW_REG_RITR_LOOPBACK_IF,
        rif->rif_index, rif->vr_id, dev->mtu);
  mlxsw_reg_ritr_loopback_ipip4_pack(ritr_pl, lb_cf.lb_ipipt,
         ipip_options, ul_vr_id,
         ul_rif_id, saddr4,
         lb_cf.okey);
  break;

 case MLXSW_SP_L3_PROTO_IPV6:
  saddr6 = &lb_cf.saddr.addr6;
  mlxsw_reg_ritr_pack(ritr_pl, enable, MLXSW_REG_RITR_LOOPBACK_IF,
        rif->rif_index, rif->vr_id, dev->mtu);
  mlxsw_reg_ritr_loopback_ipip6_pack(ritr_pl, lb_cf.lb_ipipt,
         ipip_options, ul_vr_id,
         ul_rif_id, saddr6,
         lb_cf.okey);
  break;
 }

 return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
}

static int mlxsw_sp_netdevice_ipip_ol_update_mtu(struct mlxsw_sp *mlxsw_sp,
       struct net_device *ol_dev)
{
 struct mlxsw_sp_ipip_entry *ipip_entry;
 struct mlxsw_sp_rif_ipip_lb *lb_rif;
 int err = 0;

 ipip_entry = mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, ol_dev);
 if (ipip_entry) {
  lb_rif = ipip_entry->ol_lb;
  err = mlxsw_sp_rif_ipip_lb_op(lb_rif, lb_rif->ul_vr_id,
           lb_rif->ul_rif_id, true);
  if (err)
   goto out;
  lb_rif->common.mtu = ol_dev->mtu;
 }

out:
 return err;
}

static void mlxsw_sp_netdevice_ipip_ol_up_event(struct mlxsw_sp *mlxsw_sp,
      struct net_device *ol_dev)
{
 struct mlxsw_sp_ipip_entry *ipip_entry;

 ipip_entry = mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, ol_dev);
 if (ipip_entry)
  mlxsw_sp_ipip_entry_ol_up_event(mlxsw_sp, ipip_entry);
}

static void
mlxsw_sp_ipip_entry_ol_down_event(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_ipip_entry *ipip_entry)
{
 if (ipip_entry->decap_fib_entry)
  mlxsw_sp_ipip_entry_demote_decap(mlxsw_sp, ipip_entry);
}

static void mlxsw_sp_netdevice_ipip_ol_down_event(struct mlxsw_sp *mlxsw_sp,
        struct net_device *ol_dev)
{
 struct mlxsw_sp_ipip_entry *ipip_entry;

 ipip_entry = mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, ol_dev);
 if (ipip_entry)
  mlxsw_sp_ipip_entry_ol_down_event(mlxsw_sp, ipip_entry);
}

static void mlxsw_sp_nexthop_rif_update(struct mlxsw_sp *mlxsw_sp,
     struct mlxsw_sp_rif *rif);

static void mlxsw_sp_rif_migrate_destroy(struct mlxsw_sp *mlxsw_sp,
      struct mlxsw_sp_rif *old_rif,
      struct mlxsw_sp_rif *new_rif,
      bool migrate_nhs)
{
 struct mlxsw_sp_crif *crif = old_rif->crif;
 struct mlxsw_sp_crif mock_crif = {};

 if (migrate_nhs)
  mlxsw_sp_nexthop_rif_update(mlxsw_sp, new_rif);

 /* Plant a mock CRIF so that destroying the old RIF doesn't unoffload
 * our nexthops and IPIP tunnels, and doesn't sever the crif->rif link.
 */

 mlxsw_sp_crif_init(&mock_crif, crif->key.dev);
 old_rif->crif = &mock_crif;
 mock_crif.rif = old_rif;
 mlxsw_sp_rif_destroy(old_rif);
}

static int
mlxsw_sp_ipip_entry_ol_lb_update(struct mlxsw_sp *mlxsw_sp,
     struct mlxsw_sp_ipip_entry *ipip_entry,
     bool keep_encap,
     struct netlink_ext_ack *extack)
{
 struct mlxsw_sp_rif_ipip_lb *old_lb_rif = ipip_entry->ol_lb;
 struct mlxsw_sp_rif_ipip_lb *new_lb_rif;

 new_lb_rif = mlxsw_sp_ipip_ol_ipip_lb_create(mlxsw_sp,
           ipip_entry->ipipt,
           ipip_entry->ol_dev,
           extack);
 if (IS_ERR(new_lb_rif))
  return PTR_ERR(new_lb_rif);
 ipip_entry->ol_lb = new_lb_rif;

 mlxsw_sp_rif_migrate_destroy(mlxsw_sp, &old_lb_rif->common,
         &new_lb_rif->common, keep_encap);
 return 0;
}

/**
 * __mlxsw_sp_ipip_entry_update_tunnel - Update offload related to IPIP entry.
 * @mlxsw_sp: mlxsw_sp.
 * @ipip_entry: IPIP entry.
 * @recreate_loopback: Recreates the associated loopback RIF.
 * @keep_encap: Updates next hops that use the tunnel netdevice. This is only
 *              relevant when recreate_loopback is true.
 * @update_nexthops: Updates next hops, keeping the current loopback RIF. This
 *                   is only relevant when recreate_loopback is false.
 * @extack: extack.
 *
 * Return: Non-zero value on failure.
 */

int __mlxsw_sp_ipip_entry_update_tunnel(struct mlxsw_sp *mlxsw_sp,
     struct mlxsw_sp_ipip_entry *ipip_entry,
     bool recreate_loopback,
     bool keep_encap,
     bool update_nexthops,
     struct netlink_ext_ack *extack)
{
 int err;

 /* RIFs can't be edited, so to update loopback, we need to destroy and
 * recreate it. That creates a window of opportunity where RALUE and
 * RATR registers end up referencing a RIF that's already gone. RATRs
 * are handled in mlxsw_sp_ipip_entry_ol_lb_update(), and to take care
 * of RALUE, demote the decap route back.
 */

 if (ipip_entry->decap_fib_entry)
  mlxsw_sp_ipip_entry_demote_decap(mlxsw_sp, ipip_entry);

 if (recreate_loopback) {
  err = mlxsw_sp_ipip_entry_ol_lb_update(mlxsw_sp, ipip_entry,
             keep_encap, extack);
  if (err)
   return err;
 } else if (update_nexthops) {
  mlxsw_sp_nexthop_rif_update(mlxsw_sp,
         &ipip_entry->ol_lb->common);
 }

 if (ipip_entry->ol_dev->flags & IFF_UP)
  mlxsw_sp_ipip_entry_ol_up_event(mlxsw_sp, ipip_entry);

 return 0;
}

static int mlxsw_sp_netdevice_ipip_ol_vrf_event(struct mlxsw_sp *mlxsw_sp,
      struct net_device *ol_dev,
      struct netlink_ext_ack *extack)
{
 struct mlxsw_sp_ipip_entry *ipip_entry =
  mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, ol_dev);

 if (!ipip_entry)
  return 0;

 return __mlxsw_sp_ipip_entry_update_tunnel(mlxsw_sp, ipip_entry,
         truefalsefalse, extack);
}

static int
mlxsw_sp_netdevice_ipip_ul_vrf_event(struct mlxsw_sp *mlxsw_sp,
         struct mlxsw_sp_ipip_entry *ipip_entry,
         struct net_device *ul_dev,
         bool *demote_this,
         struct netlink_ext_ack *extack)
{
 u32 ul_tb_id = l3mdev_fib_table(ul_dev) ? : RT_TABLE_MAIN;
 enum mlxsw_sp_l3proto ul_proto;
 union mlxsw_sp_l3addr saddr;

 /* Moving underlay to a different VRF might cause local address
 * conflict, and the conflicting tunnels need to be demoted.
 */

 ul_proto = mlxsw_sp->router->ipip_ops_arr[ipip_entry->ipipt]->ul_proto;
 saddr = mlxsw_sp_ipip_netdev_saddr(ul_proto, ipip_entry->ol_dev);
 if (mlxsw_sp_ipip_demote_tunnel_by_saddr(mlxsw_sp, ul_proto,
       saddr, ul_tb_id,
       ipip_entry)) {
  *demote_this = true;
  return 0;
 }

 return __mlxsw_sp_ipip_entry_update_tunnel(mlxsw_sp, ipip_entry,
         truetruefalse, extack);
}

static int
mlxsw_sp_netdevice_ipip_ul_up_event(struct mlxsw_sp *mlxsw_sp,
        struct mlxsw_sp_ipip_entry *ipip_entry,
        struct net_device *ul_dev)
{
 return __mlxsw_sp_ipip_entry_update_tunnel(mlxsw_sp, ipip_entry,
         falsefalsetrue, NULL);
}

static int
mlxsw_sp_netdevice_ipip_ul_down_event(struct mlxsw_sp *mlxsw_sp,
          struct mlxsw_sp_ipip_entry *ipip_entry,
          struct net_device *ul_dev)
{
 /* A down underlay device causes encapsulated packets to not be
 * forwarded, but decap still works. So refresh next hops without
 * touching anything else.
 */

 return __mlxsw_sp_ipip_entry_update_tunnel(mlxsw_sp, ipip_entry,
         falsefalsetrue, NULL);
}

static int
mlxsw_sp_netdevice_ipip_ol_change_event(struct mlxsw_sp *mlxsw_sp,
     struct net_device *ol_dev,
     struct netlink_ext_ack *extack)
{
 const struct mlxsw_sp_ipip_ops *ipip_ops;
 struct mlxsw_sp_ipip_entry *ipip_entry;
 int err;

 ipip_entry = mlxsw_sp_ipip_entry_find_by_ol_dev(mlxsw_sp, ol_dev);
 if (!ipip_entry)
  /* A change might make a tunnel eligible for offloading, but
 * that is currently not implemented. What falls to slow path
 * stays there.
 */

  return 0;

 /* A change might make a tunnel not eligible for offloading. */
 if (!mlxsw_sp_netdevice_ipip_can_offload(mlxsw_sp, ol_dev,
       ipip_entry->ipipt)) {
  mlxsw_sp_ipip_entry_demote_tunnel(mlxsw_sp, ipip_entry);
  return 0;
 }

 ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipip_entry->ipipt];
 err = ipip_ops->ol_netdev_change(mlxsw_sp, ipip_entry, extack);
 return err;
}

void mlxsw_sp_ipip_entry_demote_tunnel(struct mlxsw_sp *mlxsw_sp,
           struct mlxsw_sp_ipip_entry *ipip_entry)
{
 struct net_device *ol_dev = ipip_entry->ol_dev;

 if (ol_dev->flags & IFF_UP)
  mlxsw_sp_ipip_entry_ol_down_event(mlxsw_sp, ipip_entry);
 mlxsw_sp_ipip_entry_destroy(mlxsw_sp, ipip_entry);
}

/* The configuration where several tunnels have the same local address in the
 * same underlay table needs special treatment in the HW. That is currently not
 * implemented in the driver. This function finds and demotes the first tunnel
 * with a given source address, except the one passed in the argument
 * `except'.
 */

bool
mlxsw_sp_ipip_demote_tunnel_by_saddr(struct mlxsw_sp *mlxsw_sp,
         enum mlxsw_sp_l3proto ul_proto,
         union mlxsw_sp_l3addr saddr,
         u32 ul_tb_id,
         const struct mlxsw_sp_ipip_entry *except)
{
 struct mlxsw_sp_ipip_entry *ipip_entry, *tmp;

 list_for_each_entry_safe(ipip_entry, tmp, &mlxsw_sp->router->ipip_list,
     ipip_list_node) {
  if (ipip_entry != except &&
      mlxsw_sp_ipip_entry_saddr_matches(mlxsw_sp, ul_proto, saddr,
            ul_tb_id, ipip_entry)) {
   mlxsw_sp_ipip_entry_demote_tunnel(mlxsw_sp, ipip_entry);
   return true;
  }
 }

 return false;
}

static void mlxsw_sp_ipip_demote_tunnel_by_ul_netdev(struct mlxsw_sp *mlxsw_sp,
           struct net_device *ul_dev)
{
 struct mlxsw_sp_ipip_entry *ipip_entry, *tmp;

 list_for_each_entry_safe(ipip_entry, tmp, &mlxsw_sp->router->ipip_list,
     ipip_list_node) {
  struct net_device *ol_dev = ipip_entry->ol_dev;
  struct net_device *ipip_ul_dev;

  rcu_read_lock();
  ipip_ul_dev = mlxsw_sp_ipip_netdev_ul_dev_get(ol_dev);
  rcu_read_unlock();
  if (ipip_ul_dev == ul_dev)
   mlxsw_sp_ipip_entry_demote_tunnel(mlxsw_sp, ipip_entry);
 }
}

static int mlxsw_sp_netdevice_ipip_ol_event(struct mlxsw_sp *mlxsw_sp,
         struct net_device *ol_dev,
         unsigned long event,
         struct netdev_notifier_info *info)
{
 struct netdev_notifier_changeupper_info *chup;
 struct netlink_ext_ack *extack;
 int err = 0;

 switch (event) {
 case NETDEV_REGISTER:
  err = mlxsw_sp_netdevice_ipip_ol_reg_event(mlxsw_sp, ol_dev);
  break;
 case NETDEV_UNREGISTER:
  mlxsw_sp_netdevice_ipip_ol_unreg_event(mlxsw_sp, ol_dev);
  break;
 case NETDEV_UP:
  mlxsw_sp_netdevice_ipip_ol_up_event(mlxsw_sp, ol_dev);
  break;
 case NETDEV_DOWN:
  mlxsw_sp_netdevice_ipip_ol_down_event(mlxsw_sp, ol_dev);
  break;
 case NETDEV_CHANGEUPPER:
  chup = container_of(info, typeof(*chup), info);
  extack = info->extack;
  if (netif_is_l3_master(chup->upper_dev))
   err = mlxsw_sp_netdevice_ipip_ol_vrf_event(mlxsw_sp,
           ol_dev,
           extack);
  break;
 case NETDEV_CHANGE:
  extack = info->extack;
  err = mlxsw_sp_netdevice_ipip_ol_change_event(mlxsw_sp,
             ol_dev, extack);
  break;
 case NETDEV_CHANGEMTU:
  err = mlxsw_sp_netdevice_ipip_ol_update_mtu(mlxsw_sp, ol_dev);
  break;
 }
 return err;
}

static int
__mlxsw_sp_netdevice_ipip_ul_event(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_ipip_entry *ipip_entry,
       struct net_device *ul_dev,
       bool *demote_this,
       unsigned long event,
       struct netdev_notifier_info *info)
{
 struct netdev_notifier_changeupper_info *chup;
 struct netlink_ext_ack *extack;

 switch (event) {
 case NETDEV_CHANGEUPPER:
  chup = container_of(info, typeof(*chup), info);
  extack = info->extack;
  if (netif_is_l3_master(chup->upper_dev))
   return mlxsw_sp_netdevice_ipip_ul_vrf_event(mlxsw_sp,
            ipip_entry,
            ul_dev,
            demote_this,
            extack);
  break;

 case NETDEV_UP:
  return mlxsw_sp_netdevice_ipip_ul_up_event(mlxsw_sp, ipip_entry,
          ul_dev);
 case NETDEV_DOWN:
  return mlxsw_sp_netdevice_ipip_ul_down_event(mlxsw_sp,
            ipip_entry,
            ul_dev);
 }
 return 0;
}

static int
mlxsw_sp_netdevice_ipip_ul_event(struct mlxsw_sp *mlxsw_sp,
     struct net_device *ul_dev,
     unsigned long event,
     struct netdev_notifier_info *info)
{
 struct mlxsw_sp_ipip_entry *ipip_entry = NULL;
 int err;

 while ((ipip_entry = mlxsw_sp_ipip_entry_find_by_ul_dev(mlxsw_sp,
        ul_dev,
        ipip_entry))) {
  struct mlxsw_sp_ipip_entry *prev;
  bool demote_this = false;

  err = __mlxsw_sp_netdevice_ipip_ul_event(mlxsw_sp, ipip_entry,
        ul_dev, &demote_this,
        event, info);
  if (err) {
   mlxsw_sp_ipip_demote_tunnel_by_ul_netdev(mlxsw_sp,
         ul_dev);
   return err;
  }

  if (demote_this) {
   if (list_is_first(&ipip_entry->ipip_list_node,
       &mlxsw_sp->router->ipip_list))
    prev = NULL;
   else
    /* This can't be cached from previous iteration,
 * because that entry could be gone now.
 */

    prev = list_prev_entry(ipip_entry,
             ipip_list_node);
   mlxsw_sp_ipip_entry_demote_tunnel(mlxsw_sp, ipip_entry);
   ipip_entry = prev;
  }
 }

 return 0;
}

int mlxsw_sp_router_nve_promote_decap(struct mlxsw_sp *mlxsw_sp, u32 ul_tb_id,
          enum mlxsw_sp_l3proto ul_proto,
          const union mlxsw_sp_l3addr *ul_sip,
          u32 tunnel_index)
{
 enum mlxsw_sp_fib_entry_type type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
 struct mlxsw_sp_router *router = mlxsw_sp->router;
 struct mlxsw_sp_fib_entry *fib_entry;
 int err = 0;

 mutex_lock(&mlxsw_sp->router->lock);

 if (WARN_ON_ONCE(router->nve_decap_config.valid)) {
  err = -EINVAL;
  goto out;
 }

 router->nve_decap_config.ul_tb_id = ul_tb_id;
 router->nve_decap_config.tunnel_index = tunnel_index;
 router->nve_decap_config.ul_proto = ul_proto;
 router->nve_decap_config.ul_sip = *ul_sip;
 router->nve_decap_config.valid = true;

 /* It is valid to create a tunnel with a local IP and only later
 * assign this IP address to a local interface
 */

 fib_entry = mlxsw_sp_router_ip2me_fib_entry_find(mlxsw_sp, ul_tb_id,
        ul_proto, ul_sip,
        type);
 if (!fib_entry)
  goto out;

 fib_entry->decap.tunnel_index = tunnel_index;
 fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_NVE_DECAP;

 err = mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
 if (err)
  goto err_fib_entry_update;

 goto out;

err_fib_entry_update:
 fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
 mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
out:
 mutex_unlock(&mlxsw_sp->router->lock);
 return err;
}

void mlxsw_sp_router_nve_demote_decap(struct mlxsw_sp *mlxsw_sp, u32 ul_tb_id,
          enum mlxsw_sp_l3proto ul_proto,
          const union mlxsw_sp_l3addr *ul_sip)
{
 enum mlxsw_sp_fib_entry_type type = MLXSW_SP_FIB_ENTRY_TYPE_NVE_DECAP;
 struct mlxsw_sp_router *router = mlxsw_sp->router;
 struct mlxsw_sp_fib_entry *fib_entry;

 mutex_lock(&mlxsw_sp->router->lock);

 if (WARN_ON_ONCE(!router->nve_decap_config.valid))
  goto out;

 router->nve_decap_config.valid = false;

 fib_entry = mlxsw_sp_router_ip2me_fib_entry_find(mlxsw_sp, ul_tb_id,
        ul_proto, ul_sip,
        type);
 if (!fib_entry)
  goto out;

 fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
 mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
out:
 mutex_unlock(&mlxsw_sp->router->lock);
}

static bool mlxsw_sp_router_nve_is_decap(struct mlxsw_sp *mlxsw_sp,
      u32 ul_tb_id,
      enum mlxsw_sp_l3proto ul_proto,
      const union mlxsw_sp_l3addr *ul_sip)
{
 struct mlxsw_sp_router *router = mlxsw_sp->router;

 return router->nve_decap_config.valid &&
        router->nve_decap_config.ul_tb_id == ul_tb_id &&
        router->nve_decap_config.ul_proto == ul_proto &&
        !memcmp(&router->nve_decap_config.ul_sip, ul_sip,
         sizeof(*ul_sip));
}

struct mlxsw_sp_neigh_key {
 struct neighbour *n;
};

struct mlxsw_sp_neigh_entry {
 struct list_head rif_list_node;
 struct rhash_head ht_node;
 struct mlxsw_sp_neigh_key key;
 u16 rif;
 bool connected;
 unsigned char ha[ETH_ALEN];
 struct list_head nexthop_list; /* list of nexthops using
* this neigh entry
*/

 struct list_head nexthop_neighs_list_node;
 unsigned int counter_index;
 bool counter_valid;
};

static const struct rhashtable_params mlxsw_sp_neigh_ht_params = {
 .key_offset = offsetof(struct mlxsw_sp_neigh_entry, key),
 .head_offset = offsetof(struct mlxsw_sp_neigh_entry, ht_node),
 .key_len = sizeof(struct mlxsw_sp_neigh_key),
};

struct mlxsw_sp_neigh_entry *
mlxsw_sp_rif_neigh_next(struct mlxsw_sp_rif *rif,
   struct mlxsw_sp_neigh_entry *neigh_entry)
{
 if (!neigh_entry) {
  if (list_empty(&rif->neigh_list))
   return NULL;
  else
   return list_first_entry(&rif->neigh_list,
      typeof(*neigh_entry),
      rif_list_node);
 }
 if (list_is_last(&neigh_entry->rif_list_node, &rif->neigh_list))
  return NULL;
 return list_next_entry(neigh_entry, rif_list_node);
}

int mlxsw_sp_neigh_entry_type(struct mlxsw_sp_neigh_entry *neigh_entry)
{
 return neigh_entry->key.n->tbl->family;
}

unsigned char *
mlxsw_sp_neigh_entry_ha(struct mlxsw_sp_neigh_entry *neigh_entry)
{
 return neigh_entry->ha;
}

u32 mlxsw_sp_neigh4_entry_dip(struct mlxsw_sp_neigh_entry *neigh_entry)
{
 struct neighbour *n;

 n = neigh_entry->key.n;
 return ntohl(*((__be32 *) n->primary_key));
}

struct in6_addr *
mlxsw_sp_neigh6_entry_dip(struct mlxsw_sp_neigh_entry *neigh_entry)
{
 struct neighbour *n;

 n = neigh_entry->key.n;
 return (struct in6_addr *) &n->primary_key;
}

int mlxsw_sp_neigh_counter_get(struct mlxsw_sp *mlxsw_sp,
          struct mlxsw_sp_neigh_entry *neigh_entry,
          u64 *p_counter)
{
 if (!neigh_entry->counter_valid)
  return -EINVAL;

 return mlxsw_sp_flow_counter_get(mlxsw_sp, neigh_entry->counter_index,
      false, p_counter, NULL);
}

static struct mlxsw_sp_neigh_entry *
mlxsw_sp_neigh_entry_alloc(struct mlxsw_sp *mlxsw_sp, struct neighbour *n,
      u16 rif)
{
 struct mlxsw_sp_neigh_entry *neigh_entry;

 neigh_entry = kzalloc(sizeof(*neigh_entry), GFP_KERNEL);
 if (!neigh_entry)
  return NULL;

 neigh_entry->key.n = n;
 neigh_entry->rif = rif;
 INIT_LIST_HEAD(&neigh_entry->nexthop_list);

 return neigh_entry;
}

static void mlxsw_sp_neigh_entry_free(struct mlxsw_sp_neigh_entry *neigh_entry)
{
 kfree(neigh_entry);
}

static int
mlxsw_sp_neigh_entry_insert(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_neigh_entry *neigh_entry)
{
 return rhashtable_insert_fast(&mlxsw_sp->router->neigh_ht,
          &neigh_entry->ht_node,
          mlxsw_sp_neigh_ht_params);
}

static void
mlxsw_sp_neigh_entry_remove(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_neigh_entry *neigh_entry)
{
 rhashtable_remove_fast(&mlxsw_sp->router->neigh_ht,
          &neigh_entry->ht_node,
          mlxsw_sp_neigh_ht_params);
}

static bool
mlxsw_sp_neigh_counter_should_alloc(struct mlxsw_sp *mlxsw_sp,
        struct mlxsw_sp_neigh_entry *neigh_entry)
{
 struct devlink *devlink;
 const char *table_name;

 switch (mlxsw_sp_neigh_entry_type(neigh_entry)) {
 case AF_INET:
  table_name = MLXSW_SP_DPIPE_TABLE_NAME_HOST4;
  break;
 case AF_INET6:
  table_name = MLXSW_SP_DPIPE_TABLE_NAME_HOST6;
  break;
 default:
  WARN_ON(1);
  return false;
 }

 devlink = priv_to_devlink(mlxsw_sp->core);
 return devlink_dpipe_table_counter_enabled(devlink, table_name);
}

static void
mlxsw_sp_neigh_counter_alloc(struct mlxsw_sp *mlxsw_sp,
        struct mlxsw_sp_neigh_entry *neigh_entry)
{
 if (!mlxsw_sp_neigh_counter_should_alloc(mlxsw_sp, neigh_entry))
  return;

 if (mlxsw_sp_flow_counter_alloc(mlxsw_sp, &neigh_entry->counter_index))
  return;

 neigh_entry->counter_valid = true;
}

static void
mlxsw_sp_neigh_counter_free(struct mlxsw_sp *mlxsw_sp,
       struct mlxsw_sp_neigh_entry *neigh_entry)
{
 if (!neigh_entry->counter_valid)
  return;
 mlxsw_sp_flow_counter_free(mlxsw_sp,
       neigh_entry->counter_index);
 neigh_entry->counter_valid = false;
}

static struct mlxsw_sp_neigh_entry *
mlxsw_sp_neigh_entry_create(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
{
 struct mlxsw_sp_neigh_entry *neigh_entry;
 struct mlxsw_sp_rif *rif;
 int err;

 rif = mlxsw_sp_rif_find_by_dev(mlxsw_sp, n->dev);
 if (!rif)
  return ERR_PTR(-EINVAL);

 neigh_entry = mlxsw_sp_neigh_entry_alloc(mlxsw_sp, n, rif->rif_index);
 if (!neigh_entry)
  return ERR_PTR(-ENOMEM);

 err = mlxsw_sp_neigh_entry_insert(mlxsw_sp, neigh_entry);
 if (err)
  goto err_neigh_entry_insert;

 mlxsw_sp_neigh_counter_alloc(mlxsw_sp, neigh_entry);
 atomic_inc(&mlxsw_sp->router->neighs_update.neigh_count);
 list_add(&neigh_entry->rif_list_node, &rif->neigh_list);

 return neigh_entry;

err_neigh_entry_insert:
 mlxsw_sp_neigh_entry_free(neigh_entry);
 return ERR_PTR(err);
}

static void
mlxsw_sp_neigh_entry_destroy(struct mlxsw_sp *mlxsw_sp,
        struct mlxsw_sp_neigh_entry *neigh_entry)
{
 list_del(&neigh_entry->rif_list_node);
 atomic_dec(&mlxsw_sp->router->neighs_update.neigh_count);
 mlxsw_sp_neigh_counter_free(mlxsw_sp, neigh_entry);
 mlxsw_sp_neigh_entry_remove(mlxsw_sp, neigh_entry);
 mlxsw_sp_neigh_entry_free(neigh_entry);
}

static struct mlxsw_sp_neigh_entry *
mlxsw_sp_neigh_entry_lookup(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
{
 struct mlxsw_sp_neigh_key key;

 key.n = n;
 return rhashtable_lookup_fast(&mlxsw_sp->router->neigh_ht,
          &key, mlxsw_sp_neigh_ht_params);
}

static void
mlxsw_sp_router_neighs_update_interval_init(struct mlxsw_sp *mlxsw_sp)
{
 unsigned long interval;

#if IS_ENABLED(CONFIG_IPV6)
 interval = min_t(unsigned long,
    NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME),
    NEIGH_VAR(&nd_tbl.parms, DELAY_PROBE_TIME));
#else
 interval = NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME);
#endif
 mlxsw_sp->router->neighs_update.interval = jiffies_to_msecs(interval);
}

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=99 H=96 G=97

¤ Dauer der Verarbeitung: 0.27 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge