mlxsw: spectrum: Don't return upon error in removal path
[cascardo/linux.git] / drivers / net / ethernet / mellanox / mlxsw / spectrum.c
index 3740800..76b53ed 100644 (file)
 #include <linux/jiffies.h>
 #include <linux/bitops.h>
 #include <linux/list.h>
+#include <linux/notifier.h>
 #include <linux/dcbnl.h>
+#include <linux/inetdevice.h>
 #include <net/switchdev.h>
 #include <generated/utsrelease.h>
+#include <net/pkt_cls.h>
+#include <net/tc_act/tc_mirred.h>
 
 #include "spectrum.h"
 #include "core.h"
@@ -131,6 +135,8 @@ MLXSW_ITEM32(tx, hdr, fid, 0x08, 0, 16);
  */
 MLXSW_ITEM32(tx, hdr, type, 0x0C, 0, 4);
 
+static bool mlxsw_sp_port_dev_check(const struct net_device *dev);
+
 static void mlxsw_sp_txhdr_construct(struct sk_buff *skb,
                                     const struct mlxsw_tx_info *tx_info)
 {
@@ -159,6 +165,303 @@ static int mlxsw_sp_base_mac_get(struct mlxsw_sp *mlxsw_sp)
        return 0;
 }
 
+static int mlxsw_sp_span_init(struct mlxsw_sp *mlxsw_sp)
+{
+       struct mlxsw_resources *resources;
+       int i;
+
+       resources = mlxsw_core_resources_get(mlxsw_sp->core);
+       if (!resources->max_span_valid)
+               return -EIO;
+
+       mlxsw_sp->span.entries_count = resources->max_span;
+       mlxsw_sp->span.entries = kcalloc(mlxsw_sp->span.entries_count,
+                                        sizeof(struct mlxsw_sp_span_entry),
+                                        GFP_KERNEL);
+       if (!mlxsw_sp->span.entries)
+               return -ENOMEM;
+
+       for (i = 0; i < mlxsw_sp->span.entries_count; i++)
+               INIT_LIST_HEAD(&mlxsw_sp->span.entries[i].bound_ports_list);
+
+       return 0;
+}
+
+static void mlxsw_sp_span_fini(struct mlxsw_sp *mlxsw_sp)
+{
+       int i;
+
+       for (i = 0; i < mlxsw_sp->span.entries_count; i++) {
+               struct mlxsw_sp_span_entry *curr = &mlxsw_sp->span.entries[i];
+
+               WARN_ON_ONCE(!list_empty(&curr->bound_ports_list));
+       }
+       kfree(mlxsw_sp->span.entries);
+}
+
+static struct mlxsw_sp_span_entry *
+mlxsw_sp_span_entry_create(struct mlxsw_sp_port *port)
+{
+       struct mlxsw_sp *mlxsw_sp = port->mlxsw_sp;
+       struct mlxsw_sp_span_entry *span_entry;
+       char mpat_pl[MLXSW_REG_MPAT_LEN];
+       u8 local_port = port->local_port;
+       int index;
+       int i;
+       int err;
+
+       /* find a free entry to use */
+       index = -1;
+       for (i = 0; i < mlxsw_sp->span.entries_count; i++) {
+               if (!mlxsw_sp->span.entries[i].used) {
+                       index = i;
+                       span_entry = &mlxsw_sp->span.entries[i];
+                       break;
+               }
+       }
+       if (index < 0)
+               return NULL;
+
+       /* create a new port analayzer entry for local_port */
+       mlxsw_reg_mpat_pack(mpat_pl, index, local_port, true);
+       err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mpat), mpat_pl);
+       if (err)
+               return NULL;
+
+       span_entry->used = true;
+       span_entry->id = index;
+       span_entry->ref_count = 0;
+       span_entry->local_port = local_port;
+       return span_entry;
+}
+
+static void mlxsw_sp_span_entry_destroy(struct mlxsw_sp *mlxsw_sp,
+                                       struct mlxsw_sp_span_entry *span_entry)
+{
+       u8 local_port = span_entry->local_port;
+       char mpat_pl[MLXSW_REG_MPAT_LEN];
+       int pa_id = span_entry->id;
+
+       mlxsw_reg_mpat_pack(mpat_pl, pa_id, local_port, false);
+       mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mpat), mpat_pl);
+       span_entry->used = false;
+}
+
+struct mlxsw_sp_span_entry *mlxsw_sp_span_entry_find(struct mlxsw_sp_port *port)
+{
+       struct mlxsw_sp *mlxsw_sp = port->mlxsw_sp;
+       int i;
+
+       for (i = 0; i < mlxsw_sp->span.entries_count; i++) {
+               struct mlxsw_sp_span_entry *curr = &mlxsw_sp->span.entries[i];
+
+               if (curr->used && curr->local_port == port->local_port)
+                       return curr;
+       }
+       return NULL;
+}
+
+struct mlxsw_sp_span_entry *mlxsw_sp_span_entry_get(struct mlxsw_sp_port *port)
+{
+       struct mlxsw_sp_span_entry *span_entry;
+
+       span_entry = mlxsw_sp_span_entry_find(port);
+       if (span_entry) {
+               span_entry->ref_count++;
+               return span_entry;
+       }
+
+       return mlxsw_sp_span_entry_create(port);
+}
+
+static int mlxsw_sp_span_entry_put(struct mlxsw_sp *mlxsw_sp,
+                                  struct mlxsw_sp_span_entry *span_entry)
+{
+       if (--span_entry->ref_count == 0)
+               mlxsw_sp_span_entry_destroy(mlxsw_sp, span_entry);
+       return 0;
+}
+
+static bool mlxsw_sp_span_is_egress_mirror(struct mlxsw_sp_port *port)
+{
+       struct mlxsw_sp *mlxsw_sp = port->mlxsw_sp;
+       struct mlxsw_sp_span_inspected_port *p;
+       int i;
+
+       for (i = 0; i < mlxsw_sp->span.entries_count; i++) {
+               struct mlxsw_sp_span_entry *curr = &mlxsw_sp->span.entries[i];
+
+               list_for_each_entry(p, &curr->bound_ports_list, list)
+                       if (p->local_port == port->local_port &&
+                           p->type == MLXSW_SP_SPAN_EGRESS)
+                               return true;
+       }
+
+       return false;
+}
+
+static int mlxsw_sp_span_mtu_to_buffsize(int mtu)
+{
+       return MLXSW_SP_BYTES_TO_CELLS(mtu * 5 / 2) + 1;
+}
+
+static int mlxsw_sp_span_port_mtu_update(struct mlxsw_sp_port *port, u16 mtu)
+{
+       struct mlxsw_sp *mlxsw_sp = port->mlxsw_sp;
+       char sbib_pl[MLXSW_REG_SBIB_LEN];
+       int err;
+
+       /* If port is egress mirrored, the shared buffer size should be
+        * updated according to the mtu value
+        */
+       if (mlxsw_sp_span_is_egress_mirror(port)) {
+               mlxsw_reg_sbib_pack(sbib_pl, port->local_port,
+                                   mlxsw_sp_span_mtu_to_buffsize(mtu));
+               err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sbib), sbib_pl);
+               if (err) {
+                       netdev_err(port->dev, "Could not update shared buffer for mirroring\n");
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
+static struct mlxsw_sp_span_inspected_port *
+mlxsw_sp_span_entry_bound_port_find(struct mlxsw_sp_port *port,
+                                   struct mlxsw_sp_span_entry *span_entry)
+{
+       struct mlxsw_sp_span_inspected_port *p;
+
+       list_for_each_entry(p, &span_entry->bound_ports_list, list)
+               if (port->local_port == p->local_port)
+                       return p;
+       return NULL;
+}
+
+static int
+mlxsw_sp_span_inspected_port_bind(struct mlxsw_sp_port *port,
+                                 struct mlxsw_sp_span_entry *span_entry,
+                                 enum mlxsw_sp_span_type type)
+{
+       struct mlxsw_sp_span_inspected_port *inspected_port;
+       struct mlxsw_sp *mlxsw_sp = port->mlxsw_sp;
+       char mpar_pl[MLXSW_REG_MPAR_LEN];
+       char sbib_pl[MLXSW_REG_SBIB_LEN];
+       int pa_id = span_entry->id;
+       int err;
+
+       /* if it is an egress SPAN, bind a shared buffer to it */
+       if (type == MLXSW_SP_SPAN_EGRESS) {
+               mlxsw_reg_sbib_pack(sbib_pl, port->local_port,
+                                   mlxsw_sp_span_mtu_to_buffsize(port->dev->mtu));
+               err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sbib), sbib_pl);
+               if (err) {
+                       netdev_err(port->dev, "Could not create shared buffer for mirroring\n");
+                       return err;
+               }
+       }
+
+       /* bind the port to the SPAN entry */
+       mlxsw_reg_mpar_pack(mpar_pl, port->local_port, type, true, pa_id);
+       err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mpar), mpar_pl);
+       if (err)
+               goto err_mpar_reg_write;
+
+       inspected_port = kzalloc(sizeof(*inspected_port), GFP_KERNEL);
+       if (!inspected_port) {
+               err = -ENOMEM;
+               goto err_inspected_port_alloc;
+       }
+       inspected_port->local_port = port->local_port;
+       inspected_port->type = type;
+       list_add_tail(&inspected_port->list, &span_entry->bound_ports_list);
+
+       return 0;
+
+err_mpar_reg_write:
+err_inspected_port_alloc:
+       if (type == MLXSW_SP_SPAN_EGRESS) {
+               mlxsw_reg_sbib_pack(sbib_pl, port->local_port, 0);
+               mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sbib), sbib_pl);
+       }
+       return err;
+}
+
+static void
+mlxsw_sp_span_inspected_port_unbind(struct mlxsw_sp_port *port,
+                                   struct mlxsw_sp_span_entry *span_entry,
+                                   enum mlxsw_sp_span_type type)
+{
+       struct mlxsw_sp_span_inspected_port *inspected_port;
+       struct mlxsw_sp *mlxsw_sp = port->mlxsw_sp;
+       char mpar_pl[MLXSW_REG_MPAR_LEN];
+       char sbib_pl[MLXSW_REG_SBIB_LEN];
+       int pa_id = span_entry->id;
+
+       inspected_port = mlxsw_sp_span_entry_bound_port_find(port, span_entry);
+       if (!inspected_port)
+               return;
+
+       /* remove the inspected port */
+       mlxsw_reg_mpar_pack(mpar_pl, port->local_port, type, false, pa_id);
+       mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mpar), mpar_pl);
+
+       /* remove the SBIB buffer if it was egress SPAN */
+       if (type == MLXSW_SP_SPAN_EGRESS) {
+               mlxsw_reg_sbib_pack(sbib_pl, port->local_port, 0);
+               mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sbib), sbib_pl);
+       }
+
+       mlxsw_sp_span_entry_put(mlxsw_sp, span_entry);
+
+       list_del(&inspected_port->list);
+       kfree(inspected_port);
+}
+
+static int mlxsw_sp_span_mirror_add(struct mlxsw_sp_port *from,
+                                   struct mlxsw_sp_port *to,
+                                   enum mlxsw_sp_span_type type)
+{
+       struct mlxsw_sp *mlxsw_sp = from->mlxsw_sp;
+       struct mlxsw_sp_span_entry *span_entry;
+       int err;
+
+       span_entry = mlxsw_sp_span_entry_get(to);
+       if (!span_entry)
+               return -ENOENT;
+
+       netdev_dbg(from->dev, "Adding inspected port to SPAN entry %d\n",
+                  span_entry->id);
+
+       err = mlxsw_sp_span_inspected_port_bind(from, span_entry, type);
+       if (err)
+               goto err_port_bind;
+
+       return 0;
+
+err_port_bind:
+       mlxsw_sp_span_entry_put(mlxsw_sp, span_entry);
+       return err;
+}
+
+static void mlxsw_sp_span_mirror_remove(struct mlxsw_sp_port *from,
+                                       struct mlxsw_sp_port *to,
+                                       enum mlxsw_sp_span_type type)
+{
+       struct mlxsw_sp_span_entry *span_entry;
+
+       span_entry = mlxsw_sp_span_entry_find(to);
+       if (!span_entry) {
+               netdev_err(from->dev, "no span entry found\n");
+               return;
+       }
+
+       netdev_dbg(from->dev, "removing inspected port from SPAN entry %d\n",
+                  span_entry->id);
+       mlxsw_sp_span_inspected_port_unbind(from, span_entry, type);
+}
+
 static int mlxsw_sp_port_admin_status_set(struct mlxsw_sp_port *mlxsw_sp_port,
                                          bool is_up)
 {
@@ -192,23 +495,6 @@ static int mlxsw_sp_port_dev_addr_init(struct mlxsw_sp_port *mlxsw_sp_port)
        return mlxsw_sp_port_dev_addr_set(mlxsw_sp_port, addr);
 }
 
-static int mlxsw_sp_port_stp_state_set(struct mlxsw_sp_port *mlxsw_sp_port,
-                                      u16 vid, enum mlxsw_reg_spms_state state)
-{
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
-       char *spms_pl;
-       int err;
-
-       spms_pl = kmalloc(MLXSW_REG_SPMS_LEN, GFP_KERNEL);
-       if (!spms_pl)
-               return -ENOMEM;
-       mlxsw_reg_spms_pack(spms_pl, mlxsw_sp_port->local_port);
-       mlxsw_reg_spms_vid_pack(spms_pl, vid, state);
-       err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(spms), spms_pl);
-       kfree(spms_pl);
-       return err;
-}
-
 static int mlxsw_sp_port_mtu_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 mtu)
 {
        struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
@@ -508,6 +794,9 @@ static int mlxsw_sp_port_change_mtu(struct net_device *dev, int mtu)
        err = mlxsw_sp_port_headroom_set(mlxsw_sp_port, mtu, pause_en);
        if (err)
                return err;
+       err = mlxsw_sp_span_port_mtu_update(mlxsw_sp_port, mtu);
+       if (err)
+               goto err_span_port_mtu_update;
        err = mlxsw_sp_port_mtu_set(mlxsw_sp_port, mtu);
        if (err)
                goto err_port_mtu_set;
@@ -515,6 +804,8 @@ static int mlxsw_sp_port_change_mtu(struct net_device *dev, int mtu)
        return 0;
 
 err_port_mtu_set:
+       mlxsw_sp_span_port_mtu_update(mlxsw_sp_port, dev->mtu);
+err_span_port_mtu_update:
        mlxsw_sp_port_headroom_set(mlxsw_sp_port, dev->mtu, pause_en);
        return err;
 }
@@ -619,94 +910,8 @@ static int mlxsw_sp_port_vlan_mode_trans(struct mlxsw_sp_port *mlxsw_sp_port)
        return 0;
 }
 
-static struct mlxsw_sp_vfid *
-mlxsw_sp_vfid_find(const struct mlxsw_sp *mlxsw_sp, u16 vid)
-{
-       struct mlxsw_sp_vfid *vfid;
-
-       list_for_each_entry(vfid, &mlxsw_sp->port_vfids.list, list) {
-               if (vfid->vid == vid)
-                       return vfid;
-       }
-
-       return NULL;
-}
-
-static u16 mlxsw_sp_avail_vfid_get(const struct mlxsw_sp *mlxsw_sp)
-{
-       return find_first_zero_bit(mlxsw_sp->port_vfids.mapped,
-                                  MLXSW_SP_VFID_PORT_MAX);
-}
-
-static int __mlxsw_sp_vfid_create(struct mlxsw_sp *mlxsw_sp, u16 vfid)
-{
-       u16 fid = mlxsw_sp_vfid_to_fid(vfid);
-       char sfmr_pl[MLXSW_REG_SFMR_LEN];
-
-       mlxsw_reg_sfmr_pack(sfmr_pl, MLXSW_REG_SFMR_OP_CREATE_FID, fid, 0);
-       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfmr), sfmr_pl);
-}
-
-static void __mlxsw_sp_vfid_destroy(struct mlxsw_sp *mlxsw_sp, u16 vfid)
-{
-       u16 fid = mlxsw_sp_vfid_to_fid(vfid);
-       char sfmr_pl[MLXSW_REG_SFMR_LEN];
-
-       mlxsw_reg_sfmr_pack(sfmr_pl, MLXSW_REG_SFMR_OP_DESTROY_FID, fid, 0);
-       mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfmr), sfmr_pl);
-}
-
-static struct mlxsw_sp_vfid *mlxsw_sp_vfid_create(struct mlxsw_sp *mlxsw_sp,
-                                                 u16 vid)
-{
-       struct device *dev = mlxsw_sp->bus_info->dev;
-       struct mlxsw_sp_vfid *vfid;
-       u16 n_vfid;
-       int err;
-
-       n_vfid = mlxsw_sp_avail_vfid_get(mlxsw_sp);
-       if (n_vfid == MLXSW_SP_VFID_PORT_MAX) {
-               dev_err(dev, "No available vFIDs\n");
-               return ERR_PTR(-ERANGE);
-       }
-
-       err = __mlxsw_sp_vfid_create(mlxsw_sp, n_vfid);
-       if (err) {
-               dev_err(dev, "Failed to create vFID=%d\n", n_vfid);
-               return ERR_PTR(err);
-       }
-
-       vfid = kzalloc(sizeof(*vfid), GFP_KERNEL);
-       if (!vfid)
-               goto err_allocate_vfid;
-
-       vfid->vfid = n_vfid;
-       vfid->vid = vid;
-
-       list_add(&vfid->list, &mlxsw_sp->port_vfids.list);
-       set_bit(n_vfid, mlxsw_sp->port_vfids.mapped);
-
-       return vfid;
-
-err_allocate_vfid:
-       __mlxsw_sp_vfid_destroy(mlxsw_sp, n_vfid);
-       return ERR_PTR(-ENOMEM);
-}
-
-static void mlxsw_sp_vfid_destroy(struct mlxsw_sp *mlxsw_sp,
-                                 struct mlxsw_sp_vfid *vfid)
-{
-       clear_bit(vfid->vfid, mlxsw_sp->port_vfids.mapped);
-       list_del(&vfid->list);
-
-       __mlxsw_sp_vfid_destroy(mlxsw_sp, vfid->vfid);
-
-       kfree(vfid);
-}
-
 static struct mlxsw_sp_port *
-mlxsw_sp_port_vport_create(struct mlxsw_sp_port *mlxsw_sp_port,
-                          struct mlxsw_sp_vfid *vfid)
+mlxsw_sp_port_vport_create(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid)
 {
        struct mlxsw_sp_port *mlxsw_sp_vport;
 
@@ -724,8 +929,7 @@ mlxsw_sp_port_vport_create(struct mlxsw_sp_port *mlxsw_sp_port,
        mlxsw_sp_vport->stp_state = BR_STATE_FORWARDING;
        mlxsw_sp_vport->lagged = mlxsw_sp_port->lagged;
        mlxsw_sp_vport->lag_id = mlxsw_sp_port->lag_id;
-       mlxsw_sp_vport->vport.vfid = vfid;
-       mlxsw_sp_vport->vport.vid = vfid->vid;
+       mlxsw_sp_vport->vport.vid = vid;
 
        list_add(&mlxsw_sp_vport->vport.list, &mlxsw_sp_port->vports_list);
 
@@ -742,9 +946,8 @@ int mlxsw_sp_port_add_vid(struct net_device *dev, __be16 __always_unused proto,
                          u16 vid)
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
        struct mlxsw_sp_port *mlxsw_sp_vport;
-       struct mlxsw_sp_vfid *vfid;
+       bool untagged = vid == 1;
        int err;
 
        /* VLAN 0 is added to HW filter when device goes up, but it is
@@ -758,31 +961,10 @@ int mlxsw_sp_port_add_vid(struct net_device *dev, __be16 __always_unused proto,
                return 0;
        }
 
-       vfid = mlxsw_sp_vfid_find(mlxsw_sp, vid);
-       if (!vfid) {
-               vfid = mlxsw_sp_vfid_create(mlxsw_sp, vid);
-               if (IS_ERR(vfid)) {
-                       netdev_err(dev, "Failed to create vFID for VID=%d\n",
-                                  vid);
-                       return PTR_ERR(vfid);
-               }
-       }
-
-       mlxsw_sp_vport = mlxsw_sp_port_vport_create(mlxsw_sp_port, vfid);
+       mlxsw_sp_vport = mlxsw_sp_port_vport_create(mlxsw_sp_port, vid);
        if (!mlxsw_sp_vport) {
                netdev_err(dev, "Failed to create vPort for VID=%d\n", vid);
-               err = -ENOMEM;
-               goto err_port_vport_create;
-       }
-
-       if (!vfid->nr_vports) {
-               err = mlxsw_sp_vport_flood_set(mlxsw_sp_vport, vfid->vfid,
-                                              true, false);
-               if (err) {
-                       netdev_err(dev, "Failed to setup flooding for vFID=%d\n",
-                                  vfid->vfid);
-                       goto err_vport_flood_set;
-               }
+               return -ENOMEM;
        }
 
        /* When adding the first VLAN interface on a bridged port we need to
@@ -797,71 +979,37 @@ int mlxsw_sp_port_add_vid(struct net_device *dev, __be16 __always_unused proto,
                }
        }
 
-       err = mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                          MLXSW_REG_SVFA_MT_PORT_VID_TO_FID,
-                                          true,
-                                          mlxsw_sp_vfid_to_fid(vfid->vfid),
-                                          vid);
-       if (err) {
-               netdev_err(dev, "Failed to map {Port, VID=%d} to vFID=%d\n",
-                          vid, vfid->vfid);
-               goto err_port_vid_to_fid_set;
-       }
-
        err = mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, false);
        if (err) {
                netdev_err(dev, "Failed to disable learning for VID=%d\n", vid);
                goto err_port_vid_learning_set;
        }
 
-       err = mlxsw_sp_port_vlan_set(mlxsw_sp_vport, vid, vid, true, false);
+       err = mlxsw_sp_port_vlan_set(mlxsw_sp_vport, vid, vid, true, untagged);
        if (err) {
                netdev_err(dev, "Failed to set VLAN membership for VID=%d\n",
                           vid);
                goto err_port_add_vid;
        }
 
-       err = mlxsw_sp_port_stp_state_set(mlxsw_sp_vport, vid,
-                                         MLXSW_REG_SPMS_STATE_FORWARDING);
-       if (err) {
-               netdev_err(dev, "Failed to set STP state for VID=%d\n", vid);
-               goto err_port_stp_state_set;
-       }
-
-       vfid->nr_vports++;
-
        return 0;
 
-err_port_stp_state_set:
-       mlxsw_sp_port_vlan_set(mlxsw_sp_vport, vid, vid, false, false);
 err_port_add_vid:
        mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, true);
 err_port_vid_learning_set:
-       mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                    MLXSW_REG_SVFA_MT_PORT_VID_TO_FID, false,
-                                    mlxsw_sp_vfid_to_fid(vfid->vfid), vid);
-err_port_vid_to_fid_set:
        if (list_is_singular(&mlxsw_sp_port->vports_list))
                mlxsw_sp_port_vlan_mode_trans(mlxsw_sp_port);
 err_port_vp_mode_trans:
-       if (!vfid->nr_vports)
-               mlxsw_sp_vport_flood_set(mlxsw_sp_vport, vfid->vfid, false,
-                                        false);
-err_vport_flood_set:
        mlxsw_sp_port_vport_destroy(mlxsw_sp_vport);
-err_port_vport_create:
-       if (!vfid->nr_vports)
-               mlxsw_sp_vfid_destroy(mlxsw_sp, vfid);
        return err;
 }
 
-int mlxsw_sp_port_kill_vid(struct net_device *dev,
-                          __be16 __always_unused proto, u16 vid)
+static int mlxsw_sp_port_kill_vid(struct net_device *dev,
+                                 __be16 __always_unused proto, u16 vid)
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
        struct mlxsw_sp_port *mlxsw_sp_vport;
-       struct mlxsw_sp_vfid *vfid;
-       int err;
+       struct mlxsw_sp_fid *f;
 
        /* VLAN 0 is removed from HW filter when device goes down, but
         * it is reserved in our case, so simply return.
@@ -870,63 +1018,29 @@ int mlxsw_sp_port_kill_vid(struct net_device *dev,
                return 0;
 
        mlxsw_sp_vport = mlxsw_sp_port_vport_find(mlxsw_sp_port, vid);
-       if (!mlxsw_sp_vport) {
-               netdev_warn(dev, "VID=%d does not exist\n", vid);
+       if (WARN_ON(!mlxsw_sp_vport))
                return 0;
-       }
-
-       vfid = mlxsw_sp_vport->vport.vfid;
-
-       err = mlxsw_sp_port_stp_state_set(mlxsw_sp_vport, vid,
-                                         MLXSW_REG_SPMS_STATE_DISCARDING);
-       if (err) {
-               netdev_err(dev, "Failed to set STP state for VID=%d\n", vid);
-               return err;
-       }
 
-       err = mlxsw_sp_port_vlan_set(mlxsw_sp_vport, vid, vid, false, false);
-       if (err) {
-               netdev_err(dev, "Failed to set VLAN membership for VID=%d\n",
-                          vid);
-               return err;
-       }
+       mlxsw_sp_port_vlan_set(mlxsw_sp_vport, vid, vid, false, false);
 
-       err = mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, true);
-       if (err) {
-               netdev_err(dev, "Failed to enable learning for VID=%d\n", vid);
-               return err;
-       }
+       mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, true);
 
-       err = mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                          MLXSW_REG_SVFA_MT_PORT_VID_TO_FID,
-                                          false,
-                                          mlxsw_sp_vfid_to_fid(vfid->vfid),
-                                          vid);
-       if (err) {
-               netdev_err(dev, "Failed to invalidate {Port, VID=%d} to vFID=%d mapping\n",
-                          vid, vfid->vfid);
-               return err;
-       }
+       /* Drop FID reference. If this was the last reference the
+        * resources will be freed.
+        */
+       f = mlxsw_sp_vport_fid_get(mlxsw_sp_vport);
+       if (f && !WARN_ON(!f->leave))
+               f->leave(mlxsw_sp_vport);
 
        /* When removing the last VLAN interface on a bridged port we need to
         * transition all active 802.1Q bridge VLANs to use VID to FID
         * mappings and set port's mode to VLAN mode.
         */
-       if (list_is_singular(&mlxsw_sp_port->vports_list)) {
-               err = mlxsw_sp_port_vlan_mode_trans(mlxsw_sp_port);
-               if (err) {
-                       netdev_err(dev, "Failed to set to VLAN mode\n");
-                       return err;
-               }
-       }
+       if (list_is_singular(&mlxsw_sp_port->vports_list))
+               mlxsw_sp_port_vlan_mode_trans(mlxsw_sp_port);
 
-       vfid->nr_vports--;
        mlxsw_sp_port_vport_destroy(mlxsw_sp_vport);
 
-       /* Destroy the vFID if no vPorts are assigned to it anymore. */
-       if (!vfid->nr_vports)
-               mlxsw_sp_vfid_destroy(mlxsw_sp_port->mlxsw_sp, vfid);
-
        return 0;
 }
 
@@ -951,22 +1065,168 @@ static int mlxsw_sp_port_get_phys_port_name(struct net_device *dev, char *name,
        return 0;
 }
 
-static const struct net_device_ops mlxsw_sp_port_netdev_ops = {
-       .ndo_open               = mlxsw_sp_port_open,
-       .ndo_stop               = mlxsw_sp_port_stop,
-       .ndo_start_xmit         = mlxsw_sp_port_xmit,
-       .ndo_set_rx_mode        = mlxsw_sp_set_rx_mode,
-       .ndo_set_mac_address    = mlxsw_sp_port_set_mac_address,
-       .ndo_change_mtu         = mlxsw_sp_port_change_mtu,
-       .ndo_get_stats64        = mlxsw_sp_port_get_stats64,
-       .ndo_vlan_rx_add_vid    = mlxsw_sp_port_add_vid,
-       .ndo_vlan_rx_kill_vid   = mlxsw_sp_port_kill_vid,
-       .ndo_fdb_add            = switchdev_port_fdb_add,
-       .ndo_fdb_del            = switchdev_port_fdb_del,
-       .ndo_fdb_dump           = switchdev_port_fdb_dump,
-       .ndo_bridge_setlink     = switchdev_port_bridge_setlink,
-       .ndo_bridge_getlink     = switchdev_port_bridge_getlink,
-       .ndo_bridge_dellink     = switchdev_port_bridge_dellink,
+static struct mlxsw_sp_port_mall_tc_entry *
+mlxsw_sp_port_mirror_entry_find(struct mlxsw_sp_port *port,
+                               unsigned long cookie) {
+       struct mlxsw_sp_port_mall_tc_entry *mall_tc_entry;
+
+       list_for_each_entry(mall_tc_entry, &port->mall_tc_list, list)
+               if (mall_tc_entry->cookie == cookie)
+                       return mall_tc_entry;
+
+       return NULL;
+}
+
+static int
+mlxsw_sp_port_add_cls_matchall_mirror(struct mlxsw_sp_port *mlxsw_sp_port,
+                                     struct tc_cls_matchall_offload *cls,
+                                     const struct tc_action *a,
+                                     bool ingress)
+{
+       struct mlxsw_sp_port_mall_tc_entry *mall_tc_entry;
+       struct net *net = dev_net(mlxsw_sp_port->dev);
+       enum mlxsw_sp_span_type span_type;
+       struct mlxsw_sp_port *to_port;
+       struct net_device *to_dev;
+       int ifindex;
+       int err;
+
+       ifindex = tcf_mirred_ifindex(a);
+       to_dev = __dev_get_by_index(net, ifindex);
+       if (!to_dev) {
+               netdev_err(mlxsw_sp_port->dev, "Could not find requested device\n");
+               return -EINVAL;
+       }
+
+       if (!mlxsw_sp_port_dev_check(to_dev)) {
+               netdev_err(mlxsw_sp_port->dev, "Cannot mirror to a non-spectrum port");
+               return -ENOTSUPP;
+       }
+       to_port = netdev_priv(to_dev);
+
+       mall_tc_entry = kzalloc(sizeof(*mall_tc_entry), GFP_KERNEL);
+       if (!mall_tc_entry)
+               return -ENOMEM;
+
+       mall_tc_entry->cookie = cls->cookie;
+       mall_tc_entry->type = MLXSW_SP_PORT_MALL_MIRROR;
+       mall_tc_entry->mirror.to_local_port = to_port->local_port;
+       mall_tc_entry->mirror.ingress = ingress;
+       list_add_tail(&mall_tc_entry->list, &mlxsw_sp_port->mall_tc_list);
+
+       span_type = ingress ? MLXSW_SP_SPAN_INGRESS : MLXSW_SP_SPAN_EGRESS;
+       err = mlxsw_sp_span_mirror_add(mlxsw_sp_port, to_port, span_type);
+       if (err)
+               goto err_mirror_add;
+       return 0;
+
+err_mirror_add:
+       list_del(&mall_tc_entry->list);
+       kfree(mall_tc_entry);
+       return err;
+}
+
+static int mlxsw_sp_port_add_cls_matchall(struct mlxsw_sp_port *mlxsw_sp_port,
+                                         __be16 protocol,
+                                         struct tc_cls_matchall_offload *cls,
+                                         bool ingress)
+{
+       const struct tc_action *a;
+       int err;
+
+       if (!tc_single_action(cls->exts)) {
+               netdev_err(mlxsw_sp_port->dev, "only singular actions are supported\n");
+               return -ENOTSUPP;
+       }
+
+       tc_for_each_action(a, cls->exts) {
+               if (!is_tcf_mirred_mirror(a) || protocol != htons(ETH_P_ALL))
+                       return -ENOTSUPP;
+
+               err = mlxsw_sp_port_add_cls_matchall_mirror(mlxsw_sp_port, cls,
+                                                           a, ingress);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+static void mlxsw_sp_port_del_cls_matchall(struct mlxsw_sp_port *mlxsw_sp_port,
+                                          struct tc_cls_matchall_offload *cls)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       struct mlxsw_sp_port_mall_tc_entry *mall_tc_entry;
+       enum mlxsw_sp_span_type span_type;
+       struct mlxsw_sp_port *to_port;
+
+       mall_tc_entry = mlxsw_sp_port_mirror_entry_find(mlxsw_sp_port,
+                                                       cls->cookie);
+       if (!mall_tc_entry) {
+               netdev_dbg(mlxsw_sp_port->dev, "tc entry not found on port\n");
+               return;
+       }
+
+       switch (mall_tc_entry->type) {
+       case MLXSW_SP_PORT_MALL_MIRROR:
+               to_port = mlxsw_sp->ports[mall_tc_entry->mirror.to_local_port];
+               span_type = mall_tc_entry->mirror.ingress ?
+                               MLXSW_SP_SPAN_INGRESS : MLXSW_SP_SPAN_EGRESS;
+
+               mlxsw_sp_span_mirror_remove(mlxsw_sp_port, to_port, span_type);
+               break;
+       default:
+               WARN_ON(1);
+       }
+
+       list_del(&mall_tc_entry->list);
+       kfree(mall_tc_entry);
+}
+
+static int mlxsw_sp_setup_tc(struct net_device *dev, u32 handle,
+                            __be16 proto, struct tc_to_netdev *tc)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
+       bool ingress = TC_H_MAJ(handle) == TC_H_MAJ(TC_H_INGRESS);
+
+       if (tc->type == TC_SETUP_MATCHALL) {
+               switch (tc->cls_mall->command) {
+               case TC_CLSMATCHALL_REPLACE:
+                       return mlxsw_sp_port_add_cls_matchall(mlxsw_sp_port,
+                                                             proto,
+                                                             tc->cls_mall,
+                                                             ingress);
+               case TC_CLSMATCHALL_DESTROY:
+                       mlxsw_sp_port_del_cls_matchall(mlxsw_sp_port,
+                                                      tc->cls_mall);
+                       return 0;
+               default:
+                       return -EINVAL;
+               }
+       }
+
+       return -ENOTSUPP;
+}
+
+static const struct net_device_ops mlxsw_sp_port_netdev_ops = {
+       .ndo_open               = mlxsw_sp_port_open,
+       .ndo_stop               = mlxsw_sp_port_stop,
+       .ndo_start_xmit         = mlxsw_sp_port_xmit,
+       .ndo_setup_tc           = mlxsw_sp_setup_tc,
+       .ndo_set_rx_mode        = mlxsw_sp_set_rx_mode,
+       .ndo_set_mac_address    = mlxsw_sp_port_set_mac_address,
+       .ndo_change_mtu         = mlxsw_sp_port_change_mtu,
+       .ndo_get_stats64        = mlxsw_sp_port_get_stats64,
+       .ndo_vlan_rx_add_vid    = mlxsw_sp_port_add_vid,
+       .ndo_vlan_rx_kill_vid   = mlxsw_sp_port_kill_vid,
+       .ndo_neigh_construct    = mlxsw_sp_router_neigh_construct,
+       .ndo_neigh_destroy      = mlxsw_sp_router_neigh_destroy,
+       .ndo_fdb_add            = switchdev_port_fdb_add,
+       .ndo_fdb_del            = switchdev_port_fdb_del,
+       .ndo_fdb_dump           = switchdev_port_fdb_dump,
+       .ndo_bridge_setlink     = switchdev_port_bridge_setlink,
+       .ndo_bridge_getlink     = switchdev_port_bridge_getlink,
+       .ndo_bridge_dellink     = switchdev_port_bridge_dellink,
        .ndo_get_phys_port_name = mlxsw_sp_port_get_phys_port_name,
 };
 
@@ -1055,7 +1315,7 @@ struct mlxsw_sp_port_hw_stats {
        u64 (*getter)(char *payload);
 };
 
-static const struct mlxsw_sp_port_hw_stats mlxsw_sp_port_hw_stats[] = {
+static struct mlxsw_sp_port_hw_stats mlxsw_sp_port_hw_stats[] = {
        {
                .str = "a_frames_transmitted_ok",
                .getter = mlxsw_reg_ppcnt_a_frames_transmitted_ok_get,
@@ -1136,6 +1396,90 @@ static const struct mlxsw_sp_port_hw_stats mlxsw_sp_port_hw_stats[] = {
 
 #define MLXSW_SP_PORT_HW_STATS_LEN ARRAY_SIZE(mlxsw_sp_port_hw_stats)
 
+static struct mlxsw_sp_port_hw_stats mlxsw_sp_port_hw_prio_stats[] = {
+       {
+               .str = "rx_octets_prio",
+               .getter = mlxsw_reg_ppcnt_rx_octets_get,
+       },
+       {
+               .str = "rx_frames_prio",
+               .getter = mlxsw_reg_ppcnt_rx_frames_get,
+       },
+       {
+               .str = "tx_octets_prio",
+               .getter = mlxsw_reg_ppcnt_tx_octets_get,
+       },
+       {
+               .str = "tx_frames_prio",
+               .getter = mlxsw_reg_ppcnt_tx_frames_get,
+       },
+       {
+               .str = "rx_pause_prio",
+               .getter = mlxsw_reg_ppcnt_rx_pause_get,
+       },
+       {
+               .str = "rx_pause_duration_prio",
+               .getter = mlxsw_reg_ppcnt_rx_pause_duration_get,
+       },
+       {
+               .str = "tx_pause_prio",
+               .getter = mlxsw_reg_ppcnt_tx_pause_get,
+       },
+       {
+               .str = "tx_pause_duration_prio",
+               .getter = mlxsw_reg_ppcnt_tx_pause_duration_get,
+       },
+};
+
+#define MLXSW_SP_PORT_HW_PRIO_STATS_LEN ARRAY_SIZE(mlxsw_sp_port_hw_prio_stats)
+
+static u64 mlxsw_reg_ppcnt_tc_transmit_queue_bytes_get(char *ppcnt_pl)
+{
+       u64 transmit_queue = mlxsw_reg_ppcnt_tc_transmit_queue_get(ppcnt_pl);
+
+       return MLXSW_SP_CELLS_TO_BYTES(transmit_queue);
+}
+
+static struct mlxsw_sp_port_hw_stats mlxsw_sp_port_hw_tc_stats[] = {
+       {
+               .str = "tc_transmit_queue_tc",
+               .getter = mlxsw_reg_ppcnt_tc_transmit_queue_bytes_get,
+       },
+       {
+               .str = "tc_no_buffer_discard_uc_tc",
+               .getter = mlxsw_reg_ppcnt_tc_no_buffer_discard_uc_get,
+       },
+};
+
+#define MLXSW_SP_PORT_HW_TC_STATS_LEN ARRAY_SIZE(mlxsw_sp_port_hw_tc_stats)
+
+#define MLXSW_SP_PORT_ETHTOOL_STATS_LEN (MLXSW_SP_PORT_HW_STATS_LEN + \
+                                        (MLXSW_SP_PORT_HW_PRIO_STATS_LEN + \
+                                         MLXSW_SP_PORT_HW_TC_STATS_LEN) * \
+                                        IEEE_8021QAZ_MAX_TCS)
+
+static void mlxsw_sp_port_get_prio_strings(u8 **p, int prio)
+{
+       int i;
+
+       for (i = 0; i < MLXSW_SP_PORT_HW_PRIO_STATS_LEN; i++) {
+               snprintf(*p, ETH_GSTRING_LEN, "%s_%d",
+                        mlxsw_sp_port_hw_prio_stats[i].str, prio);
+               *p += ETH_GSTRING_LEN;
+       }
+}
+
+static void mlxsw_sp_port_get_tc_strings(u8 **p, int tc)
+{
+       int i;
+
+       for (i = 0; i < MLXSW_SP_PORT_HW_TC_STATS_LEN; i++) {
+               snprintf(*p, ETH_GSTRING_LEN, "%s_%d",
+                        mlxsw_sp_port_hw_tc_stats[i].str, tc);
+               *p += ETH_GSTRING_LEN;
+       }
+}
+
 static void mlxsw_sp_port_get_strings(struct net_device *dev,
                                      u32 stringset, u8 *data)
 {
@@ -1149,6 +1493,13 @@ static void mlxsw_sp_port_get_strings(struct net_device *dev,
                               ETH_GSTRING_LEN);
                        p += ETH_GSTRING_LEN;
                }
+
+               for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++)
+                       mlxsw_sp_port_get_prio_strings(&p, i);
+
+               for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++)
+                       mlxsw_sp_port_get_tc_strings(&p, i);
+
                break;
        }
 }
@@ -1176,27 +1527,80 @@ static int mlxsw_sp_port_set_phys_id(struct net_device *dev,
        return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mlcr), mlcr_pl);
 }
 
-static void mlxsw_sp_port_get_stats(struct net_device *dev,
-                                   struct ethtool_stats *stats, u64 *data)
+static int
+mlxsw_sp_get_hw_stats_by_group(struct mlxsw_sp_port_hw_stats **p_hw_stats,
+                              int *p_len, enum mlxsw_reg_ppcnt_grp grp)
+{
+       switch (grp) {
+       case  MLXSW_REG_PPCNT_IEEE_8023_CNT:
+               *p_hw_stats = mlxsw_sp_port_hw_stats;
+               *p_len = MLXSW_SP_PORT_HW_STATS_LEN;
+               break;
+       case MLXSW_REG_PPCNT_PRIO_CNT:
+               *p_hw_stats = mlxsw_sp_port_hw_prio_stats;
+               *p_len = MLXSW_SP_PORT_HW_PRIO_STATS_LEN;
+               break;
+       case MLXSW_REG_PPCNT_TC_CNT:
+               *p_hw_stats = mlxsw_sp_port_hw_tc_stats;
+               *p_len = MLXSW_SP_PORT_HW_TC_STATS_LEN;
+               break;
+       default:
+               WARN_ON(1);
+               return -ENOTSUPP;
+       }
+       return 0;
+}
+
+static void __mlxsw_sp_port_get_stats(struct net_device *dev,
+                                     enum mlxsw_reg_ppcnt_grp grp, int prio,
+                                     u64 *data, int data_index)
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
        struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       struct mlxsw_sp_port_hw_stats *hw_stats;
        char ppcnt_pl[MLXSW_REG_PPCNT_LEN];
-       int i;
+       int i, len;
        int err;
 
-       mlxsw_reg_ppcnt_pack(ppcnt_pl, mlxsw_sp_port->local_port,
-                            MLXSW_REG_PPCNT_IEEE_8023_CNT, 0);
+       err = mlxsw_sp_get_hw_stats_by_group(&hw_stats, &len, grp);
+       if (err)
+               return;
+       mlxsw_reg_ppcnt_pack(ppcnt_pl, mlxsw_sp_port->local_port, grp, prio);
        err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(ppcnt), ppcnt_pl);
-       for (i = 0; i < MLXSW_SP_PORT_HW_STATS_LEN; i++)
-               data[i] = !err ? mlxsw_sp_port_hw_stats[i].getter(ppcnt_pl) : 0;
+       for (i = 0; i < len; i++)
+               data[data_index + i] = !err ? hw_stats[i].getter(ppcnt_pl) : 0;
+}
+
+static void mlxsw_sp_port_get_stats(struct net_device *dev,
+                                   struct ethtool_stats *stats, u64 *data)
+{
+       int i, data_index = 0;
+
+       /* IEEE 802.3 Counters */
+       __mlxsw_sp_port_get_stats(dev, MLXSW_REG_PPCNT_IEEE_8023_CNT, 0,
+                                 data, data_index);
+       data_index = MLXSW_SP_PORT_HW_STATS_LEN;
+
+       /* Per-Priority Counters */
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               __mlxsw_sp_port_get_stats(dev, MLXSW_REG_PPCNT_PRIO_CNT, i,
+                                         data, data_index);
+               data_index += MLXSW_SP_PORT_HW_PRIO_STATS_LEN;
+       }
+
+       /* Per-TC Counters */
+       for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+               __mlxsw_sp_port_get_stats(dev, MLXSW_REG_PPCNT_TC_CNT, i,
+                                         data, data_index);
+               data_index += MLXSW_SP_PORT_HW_TC_STATS_LEN;
+       }
 }
 
 static int mlxsw_sp_port_get_sset_count(struct net_device *dev, int sset)
 {
        switch (sset) {
        case ETH_SS_STATS:
-               return MLXSW_SP_PORT_HW_STATS_LEN;
+               return MLXSW_SP_PORT_ETHTOOL_STATS_LEN;
        default:
                return -EOPNOTSUPP;
        }
@@ -1686,6 +2090,7 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
                goto err_port_untagged_vlans_alloc;
        }
        INIT_LIST_HEAD(&mlxsw_sp_port->vports_list);
+       INIT_LIST_HEAD(&mlxsw_sp_port->mall_tc_list);
 
        mlxsw_sp_port->pcpu_stats =
                netdev_alloc_pcpu_stats(struct mlxsw_sp_port_pcpu_stats);
@@ -1707,7 +2112,8 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
        netif_carrier_off(dev);
 
        dev->features |= NETIF_F_NETNS_LOCAL | NETIF_F_LLTX | NETIF_F_SG |
-                        NETIF_F_HW_VLAN_CTAG_FILTER;
+                        NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_TC;
+       dev->hw_features |= NETIF_F_HW_TC;
 
        /* Each packet needs to have a Tx header (metadata) on top all other
         * headers.
@@ -1797,6 +2203,7 @@ err_port_vlan_init:
 err_core_port_init:
        unregister_netdev(dev);
 err_register_netdev:
+       mlxsw_sp_port_dcb_fini(mlxsw_sp_port);
 err_port_dcb_init:
 err_port_ets_init:
 err_port_buffers_init:
@@ -1816,23 +2223,6 @@ err_port_active_vlans_alloc:
        return err;
 }
 
-static void mlxsw_sp_port_vports_fini(struct mlxsw_sp_port *mlxsw_sp_port)
-{
-       struct net_device *dev = mlxsw_sp_port->dev;
-       struct mlxsw_sp_port *mlxsw_sp_vport, *tmp;
-
-       list_for_each_entry_safe(mlxsw_sp_vport, tmp,
-                                &mlxsw_sp_port->vports_list, vport.list) {
-               u16 vid = mlxsw_sp_vport_vid_get(mlxsw_sp_vport);
-
-               /* vPorts created for VLAN devices should already be gone
-                * by now, since we unregistered the port netdev.
-                */
-               WARN_ON(is_vlan_dev(mlxsw_sp_vport->dev));
-               mlxsw_sp_port_kill_vid(dev, 0, vid);
-       }
-}
-
 static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port)
 {
        struct mlxsw_sp_port *mlxsw_sp_port = mlxsw_sp->ports[local_port];
@@ -1843,13 +2233,14 @@ static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port)
        mlxsw_core_port_fini(&mlxsw_sp_port->core_port);
        unregister_netdev(mlxsw_sp_port->dev); /* This calls ndo_stop */
        mlxsw_sp_port_dcb_fini(mlxsw_sp_port);
-       mlxsw_sp_port_vports_fini(mlxsw_sp_port);
+       mlxsw_sp_port_kill_vid(mlxsw_sp_port->dev, 0, 1);
        mlxsw_sp_port_switchdev_fini(mlxsw_sp_port);
        mlxsw_sp_port_swid_set(mlxsw_sp_port, MLXSW_PORT_SWID_DISABLED_PORT);
        mlxsw_sp_port_module_unmap(mlxsw_sp, mlxsw_sp_port->local_port);
        free_percpu(mlxsw_sp_port->pcpu_stats);
        kfree(mlxsw_sp_port->untagged_vlans);
        kfree(mlxsw_sp_port->active_vlans);
+       WARN_ON_ONCE(!list_empty(&mlxsw_sp_port->vports_list));
        free_netdev(mlxsw_sp_port->dev);
 }
 
@@ -2086,11 +2477,8 @@ static void mlxsw_sp_pude_event_func(const struct mlxsw_reg_info *reg,
 
        local_port = mlxsw_reg_pude_local_port_get(pude_pl);
        mlxsw_sp_port = mlxsw_sp->ports[local_port];
-       if (!mlxsw_sp_port) {
-               dev_warn(mlxsw_sp->bus_info->dev, "Port %d: Link event received for non-existent port\n",
-                        local_port);
+       if (!mlxsw_sp_port)
                return;
-       }
 
        status = mlxsw_reg_pude_oper_status_get(pude_pl);
        if (status == MLXSW_PORT_OPER_STATUS_UP) {
@@ -2245,6 +2633,31 @@ static const struct mlxsw_rx_listener mlxsw_sp_rx_listener[] = {
                .local_port = MLXSW_PORT_DONT_CARE,
                .trap_id = MLXSW_TRAP_ID_IGMP_V3_REPORT,
        },
+       {
+               .func = mlxsw_sp_rx_listener_func,
+               .local_port = MLXSW_PORT_DONT_CARE,
+               .trap_id = MLXSW_TRAP_ID_ARPBC,
+       },
+       {
+               .func = mlxsw_sp_rx_listener_func,
+               .local_port = MLXSW_PORT_DONT_CARE,
+               .trap_id = MLXSW_TRAP_ID_ARPUC,
+       },
+       {
+               .func = mlxsw_sp_rx_listener_func,
+               .local_port = MLXSW_PORT_DONT_CARE,
+               .trap_id = MLXSW_TRAP_ID_IP2ME,
+       },
+       {
+               .func = mlxsw_sp_rx_listener_func,
+               .local_port = MLXSW_PORT_DONT_CARE,
+               .trap_id = MLXSW_TRAP_ID_RTR_INGRESS0,
+       },
+       {
+               .func = mlxsw_sp_rx_listener_func,
+               .local_port = MLXSW_PORT_DONT_CARE,
+               .trap_id = MLXSW_TRAP_ID_HOST_MISS_IPV4,
+       },
 };
 
 static int mlxsw_sp_traps_init(struct mlxsw_sp *mlxsw_sp)
@@ -2285,7 +2698,7 @@ err_rx_trap_set:
                                          mlxsw_sp);
 err_rx_listener_register:
        for (i--; i >= 0; i--) {
-               mlxsw_reg_hpkt_pack(hpkt_pl, MLXSW_REG_HPKT_ACTION_FORWARD,
+               mlxsw_reg_hpkt_pack(hpkt_pl, MLXSW_REG_HPKT_ACTION_DISCARD,
                                    mlxsw_sp_rx_listener[i].trap_id);
                mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(hpkt), hpkt_pl);
 
@@ -2302,7 +2715,7 @@ static void mlxsw_sp_traps_fini(struct mlxsw_sp *mlxsw_sp)
        int i;
 
        for (i = 0; i < ARRAY_SIZE(mlxsw_sp_rx_listener); i++) {
-               mlxsw_reg_hpkt_pack(hpkt_pl, MLXSW_REG_HPKT_ACTION_FORWARD,
+               mlxsw_reg_hpkt_pack(hpkt_pl, MLXSW_REG_HPKT_ACTION_DISCARD,
                                    mlxsw_sp_rx_listener[i].trap_id);
                mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(hpkt), hpkt_pl);
 
@@ -2381,8 +2794,8 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
 
        mlxsw_sp->core = mlxsw_core;
        mlxsw_sp->bus_info = mlxsw_bus_info;
-       INIT_LIST_HEAD(&mlxsw_sp->port_vfids.list);
-       INIT_LIST_HEAD(&mlxsw_sp->br_vfids.list);
+       INIT_LIST_HEAD(&mlxsw_sp->fids);
+       INIT_LIST_HEAD(&mlxsw_sp->vfids.list);
        INIT_LIST_HEAD(&mlxsw_sp->br_mids.list);
 
        err = mlxsw_sp_base_mac_get(mlxsw_sp);
@@ -2391,16 +2804,10 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
                return err;
        }
 
-       err = mlxsw_sp_ports_create(mlxsw_sp);
-       if (err) {
-               dev_err(mlxsw_sp->bus_info->dev, "Failed to create ports\n");
-               return err;
-       }
-
        err = mlxsw_sp_event_register(mlxsw_sp, MLXSW_TRAP_ID_PUDE);
        if (err) {
                dev_err(mlxsw_sp->bus_info->dev, "Failed to register for PUDE events\n");
-               goto err_event_register;
+               return err;
        }
 
        err = mlxsw_sp_traps_init(mlxsw_sp);
@@ -2433,8 +2840,32 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
                goto err_switchdev_init;
        }
 
+       err = mlxsw_sp_router_init(mlxsw_sp);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Failed to initialize router\n");
+               goto err_router_init;
+       }
+
+       err = mlxsw_sp_span_init(mlxsw_sp);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Failed to init span system\n");
+               goto err_span_init;
+       }
+
+       err = mlxsw_sp_ports_create(mlxsw_sp);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Failed to create ports\n");
+               goto err_ports_create;
+       }
+
        return 0;
 
+err_ports_create:
+       mlxsw_sp_span_fini(mlxsw_sp);
+err_span_init:
+       mlxsw_sp_router_fini(mlxsw_sp);
+err_router_init:
+       mlxsw_sp_switchdev_fini(mlxsw_sp);
 err_switchdev_init:
 err_lag_init:
        mlxsw_sp_buffers_fini(mlxsw_sp);
@@ -2443,20 +2874,25 @@ err_flood_init:
        mlxsw_sp_traps_fini(mlxsw_sp);
 err_rx_listener_register:
        mlxsw_sp_event_unregister(mlxsw_sp, MLXSW_TRAP_ID_PUDE);
-err_event_register:
-       mlxsw_sp_ports_remove(mlxsw_sp);
        return err;
 }
 
 static void mlxsw_sp_fini(struct mlxsw_core *mlxsw_core)
 {
        struct mlxsw_sp *mlxsw_sp = mlxsw_core_driver_priv(mlxsw_core);
+       int i;
 
+       mlxsw_sp_ports_remove(mlxsw_sp);
+       mlxsw_sp_span_fini(mlxsw_sp);
+       mlxsw_sp_router_fini(mlxsw_sp);
        mlxsw_sp_switchdev_fini(mlxsw_sp);
        mlxsw_sp_buffers_fini(mlxsw_sp);
        mlxsw_sp_traps_fini(mlxsw_sp);
        mlxsw_sp_event_unregister(mlxsw_sp, MLXSW_TRAP_ID_PUDE);
-       mlxsw_sp_ports_remove(mlxsw_sp);
+       WARN_ON(!list_empty(&mlxsw_sp->vfids.list));
+       WARN_ON(!list_empty(&mlxsw_sp->fids));
+       for (i = 0; i < MLXSW_SP_RIF_MAX; i++)
+               WARN_ON_ONCE(mlxsw_sp->rifs[i]);
 }
 
 static struct mlxsw_config_profile mlxsw_sp_config_profile = {
@@ -2487,12 +2923,17 @@ static struct mlxsw_config_profile mlxsw_sp_config_profile = {
        .max_ib_mc                      = 0,
        .used_max_pkey                  = 1,
        .max_pkey                       = 0,
+       .used_kvd_sizes                 = 1,
+       .kvd_linear_size                = MLXSW_SP_KVD_LINEAR_SIZE,
+       .kvd_hash_single_size           = MLXSW_SP_KVD_HASH_SINGLE_SIZE,
+       .kvd_hash_double_size           = MLXSW_SP_KVD_HASH_DOUBLE_SIZE,
        .swid_config                    = {
                {
                        .used_type      = 1,
                        .type           = MLXSW_PORT_SWID_TYPE_ETH,
                }
        },
+       .resource_query_enable          = 1,
 };
 
 static struct mlxsw_driver mlxsw_sp_driver = {
@@ -2518,121 +2959,679 @@ static struct mlxsw_driver mlxsw_sp_driver = {
        .profile                        = &mlxsw_sp_config_profile,
 };
 
-static int
-mlxsw_sp_port_fdb_flush_by_port(const struct mlxsw_sp_port *mlxsw_sp_port)
+static bool mlxsw_sp_port_dev_check(const struct net_device *dev)
 {
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
-       char sfdf_pl[MLXSW_REG_SFDF_LEN];
-
-       mlxsw_reg_sfdf_pack(sfdf_pl, MLXSW_REG_SFDF_FLUSH_PER_PORT);
-       mlxsw_reg_sfdf_system_port_set(sfdf_pl, mlxsw_sp_port->local_port);
-
-       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfdf), sfdf_pl);
+       return dev->netdev_ops == &mlxsw_sp_port_netdev_ops;
 }
 
-static int
-mlxsw_sp_port_fdb_flush_by_port_fid(const struct mlxsw_sp_port *mlxsw_sp_port,
-                                   u16 fid)
+static struct mlxsw_sp_port *mlxsw_sp_port_dev_lower_find(struct net_device *dev)
 {
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
-       char sfdf_pl[MLXSW_REG_SFDF_LEN];
+       struct net_device *lower_dev;
+       struct list_head *iter;
 
-       mlxsw_reg_sfdf_pack(sfdf_pl, MLXSW_REG_SFDF_FLUSH_PER_PORT_AND_FID);
-       mlxsw_reg_sfdf_fid_set(sfdf_pl, fid);
-       mlxsw_reg_sfdf_port_fid_system_port_set(sfdf_pl,
-                                               mlxsw_sp_port->local_port);
+       if (mlxsw_sp_port_dev_check(dev))
+               return netdev_priv(dev);
 
-       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfdf), sfdf_pl);
+       netdev_for_each_all_lower_dev(dev, lower_dev, iter) {
+               if (mlxsw_sp_port_dev_check(lower_dev))
+                       return netdev_priv(lower_dev);
+       }
+       return NULL;
 }
 
-static int
-mlxsw_sp_port_fdb_flush_by_lag_id(const struct mlxsw_sp_port *mlxsw_sp_port)
+static struct mlxsw_sp *mlxsw_sp_lower_get(struct net_device *dev)
 {
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
-       char sfdf_pl[MLXSW_REG_SFDF_LEN];
-
-       mlxsw_reg_sfdf_pack(sfdf_pl, MLXSW_REG_SFDF_FLUSH_PER_LAG);
-       mlxsw_reg_sfdf_lag_id_set(sfdf_pl, mlxsw_sp_port->lag_id);
+       struct mlxsw_sp_port *mlxsw_sp_port;
 
-       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfdf), sfdf_pl);
+       mlxsw_sp_port = mlxsw_sp_port_dev_lower_find(dev);
+       return mlxsw_sp_port ? mlxsw_sp_port->mlxsw_sp : NULL;
 }
 
-static int
-mlxsw_sp_port_fdb_flush_by_lag_id_fid(const struct mlxsw_sp_port *mlxsw_sp_port,
-                                     u16 fid)
+static struct mlxsw_sp_port *mlxsw_sp_port_dev_lower_find_rcu(struct net_device *dev)
 {
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
-       char sfdf_pl[MLXSW_REG_SFDF_LEN];
+       struct net_device *lower_dev;
+       struct list_head *iter;
 
-       mlxsw_reg_sfdf_pack(sfdf_pl, MLXSW_REG_SFDF_FLUSH_PER_LAG_AND_FID);
-       mlxsw_reg_sfdf_fid_set(sfdf_pl, fid);
-       mlxsw_reg_sfdf_lag_fid_lag_id_set(sfdf_pl, mlxsw_sp_port->lag_id);
+       if (mlxsw_sp_port_dev_check(dev))
+               return netdev_priv(dev);
 
-       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfdf), sfdf_pl);
+       netdev_for_each_all_lower_dev_rcu(dev, lower_dev, iter) {
+               if (mlxsw_sp_port_dev_check(lower_dev))
+                       return netdev_priv(lower_dev);
+       }
+       return NULL;
 }
 
-static int
-__mlxsw_sp_port_fdb_flush(const struct mlxsw_sp_port *mlxsw_sp_port)
+struct mlxsw_sp_port *mlxsw_sp_port_lower_dev_hold(struct net_device *dev)
 {
-       int err, last_err = 0;
-       u16 vid;
-
-       for (vid = 1; vid < VLAN_N_VID - 1; vid++) {
-               err = mlxsw_sp_port_fdb_flush_by_port_fid(mlxsw_sp_port, vid);
-               if (err)
-                       last_err = err;
-       }
+       struct mlxsw_sp_port *mlxsw_sp_port;
 
-       return last_err;
+       rcu_read_lock();
+       mlxsw_sp_port = mlxsw_sp_port_dev_lower_find_rcu(dev);
+       if (mlxsw_sp_port)
+               dev_hold(mlxsw_sp_port->dev);
+       rcu_read_unlock();
+       return mlxsw_sp_port;
 }
 
-static int
-__mlxsw_sp_port_fdb_flush_lagged(const struct mlxsw_sp_port *mlxsw_sp_port)
+void mlxsw_sp_port_dev_put(struct mlxsw_sp_port *mlxsw_sp_port)
 {
-       int err, last_err = 0;
-       u16 vid;
+       dev_put(mlxsw_sp_port->dev);
+}
 
-       for (vid = 1; vid < VLAN_N_VID - 1; vid++) {
-               err = mlxsw_sp_port_fdb_flush_by_lag_id_fid(mlxsw_sp_port, vid);
-               if (err)
-                       last_err = err;
+static bool mlxsw_sp_rif_should_config(struct mlxsw_sp_rif *r,
+                                      unsigned long event)
+{
+       switch (event) {
+       case NETDEV_UP:
+               if (!r)
+                       return true;
+               r->ref_count++;
+               return false;
+       case NETDEV_DOWN:
+               if (r && --r->ref_count == 0)
+                       return true;
+               /* It is possible we already removed the RIF ourselves
+                * if it was assigned to a netdev that is now a bridge
+                * or LAG slave.
+                */
+               return false;
        }
 
-       return last_err;
+       return false;
 }
 
-static int mlxsw_sp_port_fdb_flush(struct mlxsw_sp_port *mlxsw_sp_port)
+static int mlxsw_sp_avail_rif_get(struct mlxsw_sp *mlxsw_sp)
 {
-       if (!list_empty(&mlxsw_sp_port->vports_list))
-               if (mlxsw_sp_port->lagged)
-                       return __mlxsw_sp_port_fdb_flush_lagged(mlxsw_sp_port);
-               else
-                       return __mlxsw_sp_port_fdb_flush(mlxsw_sp_port);
+       int i;
+
+       for (i = 0; i < MLXSW_SP_RIF_MAX; i++)
+               if (!mlxsw_sp->rifs[i])
+                       return i;
+
+       return MLXSW_SP_RIF_MAX;
+}
+
+static void mlxsw_sp_vport_rif_sp_attr_get(struct mlxsw_sp_port *mlxsw_sp_vport,
+                                          bool *p_lagged, u16 *p_system_port)
+{
+       u8 local_port = mlxsw_sp_vport->local_port;
+
+       *p_lagged = mlxsw_sp_vport->lagged;
+       *p_system_port = *p_lagged ? mlxsw_sp_vport->lag_id : local_port;
+}
+
+static int mlxsw_sp_vport_rif_sp_op(struct mlxsw_sp_port *mlxsw_sp_vport,
+                                   struct net_device *l3_dev, u16 rif,
+                                   bool create)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_vport->mlxsw_sp;
+       bool lagged = mlxsw_sp_vport->lagged;
+       char ritr_pl[MLXSW_REG_RITR_LEN];
+       u16 system_port;
+
+       mlxsw_reg_ritr_pack(ritr_pl, create, MLXSW_REG_RITR_SP_IF, rif,
+                           l3_dev->mtu, l3_dev->dev_addr);
+
+       mlxsw_sp_vport_rif_sp_attr_get(mlxsw_sp_vport, &lagged, &system_port);
+       mlxsw_reg_ritr_sp_if_pack(ritr_pl, lagged, system_port,
+                                 mlxsw_sp_vport_vid_get(mlxsw_sp_vport));
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
+}
+
+static void mlxsw_sp_vport_rif_sp_leave(struct mlxsw_sp_port *mlxsw_sp_vport);
+
+static struct mlxsw_sp_fid *
+mlxsw_sp_rfid_alloc(u16 fid, struct net_device *l3_dev)
+{
+       struct mlxsw_sp_fid *f;
+
+       f = kzalloc(sizeof(*f), GFP_KERNEL);
+       if (!f)
+               return NULL;
+
+       f->leave = mlxsw_sp_vport_rif_sp_leave;
+       f->ref_count = 0;
+       f->dev = l3_dev;
+       f->fid = fid;
+
+       return f;
+}
+
+static struct mlxsw_sp_rif *
+mlxsw_sp_rif_alloc(u16 rif, struct net_device *l3_dev, struct mlxsw_sp_fid *f)
+{
+       struct mlxsw_sp_rif *r;
+
+       r = kzalloc(sizeof(*r), GFP_KERNEL);
+       if (!r)
+               return NULL;
+
+       ether_addr_copy(r->addr, l3_dev->dev_addr);
+       r->mtu = l3_dev->mtu;
+       r->ref_count = 1;
+       r->dev = l3_dev;
+       r->rif = rif;
+       r->f = f;
+
+       return r;
+}
+
+static struct mlxsw_sp_rif *
+mlxsw_sp_vport_rif_sp_create(struct mlxsw_sp_port *mlxsw_sp_vport,
+                            struct net_device *l3_dev)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_vport->mlxsw_sp;
+       struct mlxsw_sp_fid *f;
+       struct mlxsw_sp_rif *r;
+       u16 fid, rif;
+       int err;
+
+       rif = mlxsw_sp_avail_rif_get(mlxsw_sp);
+       if (rif == MLXSW_SP_RIF_MAX)
+               return ERR_PTR(-ERANGE);
+
+       err = mlxsw_sp_vport_rif_sp_op(mlxsw_sp_vport, l3_dev, rif, true);
+       if (err)
+               return ERR_PTR(err);
+
+       fid = mlxsw_sp_rif_sp_to_fid(rif);
+       err = mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, fid, true);
+       if (err)
+               goto err_rif_fdb_op;
+
+       f = mlxsw_sp_rfid_alloc(fid, l3_dev);
+       if (!f) {
+               err = -ENOMEM;
+               goto err_rfid_alloc;
+       }
+
+       r = mlxsw_sp_rif_alloc(rif, l3_dev, f);
+       if (!r) {
+               err = -ENOMEM;
+               goto err_rif_alloc;
+       }
+
+       f->r = r;
+       mlxsw_sp->rifs[rif] = r;
+
+       return r;
+
+err_rif_alloc:
+       kfree(f);
+err_rfid_alloc:
+       mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, fid, false);
+err_rif_fdb_op:
+       mlxsw_sp_vport_rif_sp_op(mlxsw_sp_vport, l3_dev, rif, false);
+       return ERR_PTR(err);
+}
+
+static void mlxsw_sp_vport_rif_sp_destroy(struct mlxsw_sp_port *mlxsw_sp_vport,
+                                         struct mlxsw_sp_rif *r)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_vport->mlxsw_sp;
+       struct net_device *l3_dev = r->dev;
+       struct mlxsw_sp_fid *f = r->f;
+       u16 fid = f->fid;
+       u16 rif = r->rif;
+
+       mlxsw_sp->rifs[rif] = NULL;
+       f->r = NULL;
+
+       kfree(r);
+
+       kfree(f);
+
+       mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, fid, false);
+
+       mlxsw_sp_vport_rif_sp_op(mlxsw_sp_vport, l3_dev, rif, false);
+}
+
+static int mlxsw_sp_vport_rif_sp_join(struct mlxsw_sp_port *mlxsw_sp_vport,
+                                     struct net_device *l3_dev)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_vport->mlxsw_sp;
+       struct mlxsw_sp_rif *r;
+
+       r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, l3_dev);
+       if (!r) {
+               r = mlxsw_sp_vport_rif_sp_create(mlxsw_sp_vport, l3_dev);
+               if (IS_ERR(r))
+                       return PTR_ERR(r);
+       }
+
+       mlxsw_sp_vport_fid_set(mlxsw_sp_vport, r->f);
+       r->f->ref_count++;
+
+       netdev_dbg(mlxsw_sp_vport->dev, "Joined FID=%d\n", r->f->fid);
+
+       return 0;
+}
+
+static void mlxsw_sp_vport_rif_sp_leave(struct mlxsw_sp_port *mlxsw_sp_vport)
+{
+       struct mlxsw_sp_fid *f = mlxsw_sp_vport_fid_get(mlxsw_sp_vport);
+
+       netdev_dbg(mlxsw_sp_vport->dev, "Left FID=%d\n", f->fid);
+
+       mlxsw_sp_vport_fid_set(mlxsw_sp_vport, NULL);
+       if (--f->ref_count == 0)
+               mlxsw_sp_vport_rif_sp_destroy(mlxsw_sp_vport, f->r);
+}
+
+static int mlxsw_sp_inetaddr_vport_event(struct net_device *l3_dev,
+                                        struct net_device *port_dev,
+                                        unsigned long event, u16 vid)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(port_dev);
+       struct mlxsw_sp_port *mlxsw_sp_vport;
+
+       mlxsw_sp_vport = mlxsw_sp_port_vport_find(mlxsw_sp_port, vid);
+       if (WARN_ON(!mlxsw_sp_vport))
+               return -EINVAL;
+
+       switch (event) {
+       case NETDEV_UP:
+               return mlxsw_sp_vport_rif_sp_join(mlxsw_sp_vport, l3_dev);
+       case NETDEV_DOWN:
+               mlxsw_sp_vport_rif_sp_leave(mlxsw_sp_vport);
+               break;
+       }
+
+       return 0;
+}
+
+static int mlxsw_sp_inetaddr_port_event(struct net_device *port_dev,
+                                       unsigned long event)
+{
+       if (netif_is_bridge_port(port_dev) || netif_is_lag_port(port_dev))
+               return 0;
+
+       return mlxsw_sp_inetaddr_vport_event(port_dev, port_dev, event, 1);
+}
+
+static int __mlxsw_sp_inetaddr_lag_event(struct net_device *l3_dev,
+                                        struct net_device *lag_dev,
+                                        unsigned long event, u16 vid)
+{
+       struct net_device *port_dev;
+       struct list_head *iter;
+       int err;
+
+       netdev_for_each_lower_dev(lag_dev, port_dev, iter) {
+               if (mlxsw_sp_port_dev_check(port_dev)) {
+                       err = mlxsw_sp_inetaddr_vport_event(l3_dev, port_dev,
+                                                           event, vid);
+                       if (err)
+                               return err;
+               }
+       }
+
+       return 0;
+}
+
+static int mlxsw_sp_inetaddr_lag_event(struct net_device *lag_dev,
+                                      unsigned long event)
+{
+       if (netif_is_bridge_port(lag_dev))
+               return 0;
+
+       return __mlxsw_sp_inetaddr_lag_event(lag_dev, lag_dev, event, 1);
+}
+
+static struct mlxsw_sp_fid *mlxsw_sp_bridge_fid_get(struct mlxsw_sp *mlxsw_sp,
+                                                   struct net_device *l3_dev)
+{
+       u16 fid;
+
+       if (is_vlan_dev(l3_dev))
+               fid = vlan_dev_vlan_id(l3_dev);
+       else if (mlxsw_sp->master_bridge.dev == l3_dev)
+               fid = 1;
        else
-               if (mlxsw_sp_port->lagged)
-                       return mlxsw_sp_port_fdb_flush_by_lag_id(mlxsw_sp_port);
-               else
-                       return mlxsw_sp_port_fdb_flush_by_port(mlxsw_sp_port);
+               return mlxsw_sp_vfid_find(mlxsw_sp, l3_dev);
+
+       return mlxsw_sp_fid_find(mlxsw_sp, fid);
+}
+
+static enum mlxsw_reg_ritr_if_type mlxsw_sp_rif_type_get(u16 fid)
+{
+       if (mlxsw_sp_fid_is_vfid(fid))
+               return MLXSW_REG_RITR_FID_IF;
+       else
+               return MLXSW_REG_RITR_VLAN_IF;
 }
 
-static int mlxsw_sp_vport_fdb_flush(struct mlxsw_sp_port *mlxsw_sp_vport)
+static int mlxsw_sp_rif_bridge_op(struct mlxsw_sp *mlxsw_sp,
+                                 struct net_device *l3_dev,
+                                 u16 fid, u16 rif,
+                                 bool create)
 {
-       u16 vfid = mlxsw_sp_vport_vfid_get(mlxsw_sp_vport);
-       u16 fid = mlxsw_sp_vfid_to_fid(vfid);
+       enum mlxsw_reg_ritr_if_type rif_type;
+       char ritr_pl[MLXSW_REG_RITR_LEN];
+
+       rif_type = mlxsw_sp_rif_type_get(fid);
+       mlxsw_reg_ritr_pack(ritr_pl, create, rif_type, rif, l3_dev->mtu,
+                           l3_dev->dev_addr);
+       mlxsw_reg_ritr_fid_set(ritr_pl, rif_type, fid);
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
+}
+
+static int mlxsw_sp_rif_bridge_create(struct mlxsw_sp *mlxsw_sp,
+                                     struct net_device *l3_dev,
+                                     struct mlxsw_sp_fid *f)
+{
+       struct mlxsw_sp_rif *r;
+       u16 rif;
+       int err;
+
+       rif = mlxsw_sp_avail_rif_get(mlxsw_sp);
+       if (rif == MLXSW_SP_RIF_MAX)
+               return -ERANGE;
+
+       err = mlxsw_sp_rif_bridge_op(mlxsw_sp, l3_dev, f->fid, rif, true);
+       if (err)
+               return err;
+
+       err = mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, f->fid, true);
+       if (err)
+               goto err_rif_fdb_op;
+
+       r = mlxsw_sp_rif_alloc(rif, l3_dev, f);
+       if (!r) {
+               err = -ENOMEM;
+               goto err_rif_alloc;
+       }
+
+       f->r = r;
+       mlxsw_sp->rifs[rif] = r;
+
+       netdev_dbg(l3_dev, "RIF=%d created\n", rif);
+
+       return 0;
+
+err_rif_alloc:
+       mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, f->fid, false);
+err_rif_fdb_op:
+       mlxsw_sp_rif_bridge_op(mlxsw_sp, l3_dev, f->fid, rif, false);
+       return err;
+}
+
+void mlxsw_sp_rif_bridge_destroy(struct mlxsw_sp *mlxsw_sp,
+                                struct mlxsw_sp_rif *r)
+{
+       struct net_device *l3_dev = r->dev;
+       struct mlxsw_sp_fid *f = r->f;
+       u16 rif = r->rif;
+
+       mlxsw_sp->rifs[rif] = NULL;
+       f->r = NULL;
+
+       kfree(r);
+
+       mlxsw_sp_rif_fdb_op(mlxsw_sp, l3_dev->dev_addr, f->fid, false);
+
+       mlxsw_sp_rif_bridge_op(mlxsw_sp, l3_dev, f->fid, rif, false);
+
+       netdev_dbg(l3_dev, "RIF=%d destroyed\n", rif);
+}
+
+static int mlxsw_sp_inetaddr_bridge_event(struct net_device *l3_dev,
+                                         struct net_device *br_dev,
+                                         unsigned long event)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(l3_dev);
+       struct mlxsw_sp_fid *f;
+
+       /* FID can either be an actual FID if the L3 device is the
+        * VLAN-aware bridge or a VLAN device on top. Otherwise, the
+        * L3 device is a VLAN-unaware bridge and we get a vFID.
+        */
+       f = mlxsw_sp_bridge_fid_get(mlxsw_sp, l3_dev);
+       if (WARN_ON(!f))
+               return -EINVAL;
+
+       switch (event) {
+       case NETDEV_UP:
+               return mlxsw_sp_rif_bridge_create(mlxsw_sp, l3_dev, f);
+       case NETDEV_DOWN:
+               mlxsw_sp_rif_bridge_destroy(mlxsw_sp, f->r);
+               break;
+       }
+
+       return 0;
+}
+
+static int mlxsw_sp_inetaddr_vlan_event(struct net_device *vlan_dev,
+                                       unsigned long event)
+{
+       struct net_device *real_dev = vlan_dev_real_dev(vlan_dev);
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(vlan_dev);
+       u16 vid = vlan_dev_vlan_id(vlan_dev);
+
+       if (mlxsw_sp_port_dev_check(real_dev))
+               return mlxsw_sp_inetaddr_vport_event(vlan_dev, real_dev, event,
+                                                    vid);
+       else if (netif_is_lag_master(real_dev))
+               return __mlxsw_sp_inetaddr_lag_event(vlan_dev, real_dev, event,
+                                                    vid);
+       else if (netif_is_bridge_master(real_dev) &&
+                mlxsw_sp->master_bridge.dev == real_dev)
+               return mlxsw_sp_inetaddr_bridge_event(vlan_dev, real_dev,
+                                                     event);
+
+       return 0;
+}
+
+static int mlxsw_sp_inetaddr_event(struct notifier_block *unused,
+                                  unsigned long event, void *ptr)
+{
+       struct in_ifaddr *ifa = (struct in_ifaddr *) ptr;
+       struct net_device *dev = ifa->ifa_dev->dev;
+       struct mlxsw_sp *mlxsw_sp;
+       struct mlxsw_sp_rif *r;
+       int err = 0;
+
+       mlxsw_sp = mlxsw_sp_lower_get(dev);
+       if (!mlxsw_sp)
+               goto out;
+
+       r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, dev);
+       if (!mlxsw_sp_rif_should_config(r, event))
+               goto out;
+
+       if (mlxsw_sp_port_dev_check(dev))
+               err = mlxsw_sp_inetaddr_port_event(dev, event);
+       else if (netif_is_lag_master(dev))
+               err = mlxsw_sp_inetaddr_lag_event(dev, event);
+       else if (netif_is_bridge_master(dev))
+               err = mlxsw_sp_inetaddr_bridge_event(dev, dev, event);
+       else if (is_vlan_dev(dev))
+               err = mlxsw_sp_inetaddr_vlan_event(dev, event);
+
+out:
+       return notifier_from_errno(err);
+}
+
+static int mlxsw_sp_rif_edit(struct mlxsw_sp *mlxsw_sp, u16 rif,
+                            const char *mac, int mtu)
+{
+       char ritr_pl[MLXSW_REG_RITR_LEN];
+       int err;
+
+       mlxsw_reg_ritr_rif_pack(ritr_pl, rif);
+       err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
+       if (err)
+               return err;
+
+       mlxsw_reg_ritr_mtu_set(ritr_pl, mtu);
+       mlxsw_reg_ritr_if_mac_memcpy_to(ritr_pl, mac);
+       mlxsw_reg_ritr_op_set(ritr_pl, MLXSW_REG_RITR_RIF_CREATE);
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
+}
+
+static int mlxsw_sp_netdevice_router_port_event(struct net_device *dev)
+{
+       struct mlxsw_sp *mlxsw_sp;
+       struct mlxsw_sp_rif *r;
+       int err;
+
+       mlxsw_sp = mlxsw_sp_lower_get(dev);
+       if (!mlxsw_sp)
+               return 0;
+
+       r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, dev);
+       if (!r)
+               return 0;
+
+       err = mlxsw_sp_rif_fdb_op(mlxsw_sp, r->addr, r->f->fid, false);
+       if (err)
+               return err;
+
+       err = mlxsw_sp_rif_edit(mlxsw_sp, r->rif, dev->dev_addr, dev->mtu);
+       if (err)
+               goto err_rif_edit;
+
+       err = mlxsw_sp_rif_fdb_op(mlxsw_sp, dev->dev_addr, r->f->fid, true);
+       if (err)
+               goto err_rif_fdb_op;
+
+       ether_addr_copy(r->addr, dev->dev_addr);
+       r->mtu = dev->mtu;
+
+       netdev_dbg(dev, "Updated RIF=%d\n", r->rif);
+
+       return 0;
+
+err_rif_fdb_op:
+       mlxsw_sp_rif_edit(mlxsw_sp, r->rif, r->addr, r->mtu);
+err_rif_edit:
+       mlxsw_sp_rif_fdb_op(mlxsw_sp, r->addr, r->f->fid, true);
+       return err;
+}
+
+static bool mlxsw_sp_lag_port_fid_member(struct mlxsw_sp_port *lag_port,
+                                        u16 fid)
+{
+       if (mlxsw_sp_fid_is_vfid(fid))
+               return mlxsw_sp_port_vport_find_by_fid(lag_port, fid);
+       else
+               return test_bit(fid, lag_port->active_vlans);
+}
+
+static bool mlxsw_sp_port_fdb_should_flush(struct mlxsw_sp_port *mlxsw_sp_port,
+                                          u16 fid)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       u8 local_port = mlxsw_sp_port->local_port;
+       u16 lag_id = mlxsw_sp_port->lag_id;
+       int i, count = 0;
+
+       if (!mlxsw_sp_port->lagged)
+               return true;
+
+       for (i = 0; i < MLXSW_SP_PORT_PER_LAG_MAX; i++) {
+               struct mlxsw_sp_port *lag_port;
+
+               lag_port = mlxsw_sp_port_lagged_get(mlxsw_sp, lag_id, i);
+               if (!lag_port || lag_port->local_port == local_port)
+                       continue;
+               if (mlxsw_sp_lag_port_fid_member(lag_port, fid))
+                       count++;
+       }
+
+       return !count;
+}
+
+static int
+mlxsw_sp_port_fdb_flush_by_port_fid(const struct mlxsw_sp_port *mlxsw_sp_port,
+                                   u16 fid)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       char sfdf_pl[MLXSW_REG_SFDF_LEN];
+
+       mlxsw_reg_sfdf_pack(sfdf_pl, MLXSW_REG_SFDF_FLUSH_PER_PORT_AND_FID);
+       mlxsw_reg_sfdf_fid_set(sfdf_pl, fid);
+       mlxsw_reg_sfdf_port_fid_system_port_set(sfdf_pl,
+                                               mlxsw_sp_port->local_port);
+
+       netdev_dbg(mlxsw_sp_port->dev, "FDB flushed using Port=%d, FID=%d\n",
+                  mlxsw_sp_port->local_port, fid);
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfdf), sfdf_pl);
+}
+
+static int
+mlxsw_sp_port_fdb_flush_by_lag_id_fid(const struct mlxsw_sp_port *mlxsw_sp_port,
+                                     u16 fid)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
+       char sfdf_pl[MLXSW_REG_SFDF_LEN];
 
-       if (mlxsw_sp_vport->lagged)
-               return mlxsw_sp_port_fdb_flush_by_lag_id_fid(mlxsw_sp_vport,
+       mlxsw_reg_sfdf_pack(sfdf_pl, MLXSW_REG_SFDF_FLUSH_PER_LAG_AND_FID);
+       mlxsw_reg_sfdf_fid_set(sfdf_pl, fid);
+       mlxsw_reg_sfdf_lag_fid_lag_id_set(sfdf_pl, mlxsw_sp_port->lag_id);
+
+       netdev_dbg(mlxsw_sp_port->dev, "FDB flushed using LAG ID=%d, FID=%d\n",
+                  mlxsw_sp_port->lag_id, fid);
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfdf), sfdf_pl);
+}
+
+int mlxsw_sp_port_fdb_flush(struct mlxsw_sp_port *mlxsw_sp_port, u16 fid)
+{
+       if (!mlxsw_sp_port_fdb_should_flush(mlxsw_sp_port, fid))
+               return 0;
+
+       if (mlxsw_sp_port->lagged)
+               return mlxsw_sp_port_fdb_flush_by_lag_id_fid(mlxsw_sp_port,
                                                             fid);
        else
-               return mlxsw_sp_port_fdb_flush_by_port_fid(mlxsw_sp_vport, fid);
+               return mlxsw_sp_port_fdb_flush_by_port_fid(mlxsw_sp_port, fid);
 }
 
-static bool mlxsw_sp_port_dev_check(const struct net_device *dev)
+static void mlxsw_sp_master_bridge_gone_sync(struct mlxsw_sp *mlxsw_sp)
 {
-       return dev->netdev_ops == &mlxsw_sp_port_netdev_ops;
+       struct mlxsw_sp_fid *f, *tmp;
+
+       list_for_each_entry_safe(f, tmp, &mlxsw_sp->fids, list)
+               if (--f->ref_count == 0)
+                       mlxsw_sp_fid_destroy(mlxsw_sp, f);
+               else
+                       WARN_ON_ONCE(1);
+}
+
+static bool mlxsw_sp_master_bridge_check(struct mlxsw_sp *mlxsw_sp,
+                                        struct net_device *br_dev)
+{
+       return !mlxsw_sp->master_bridge.dev ||
+              mlxsw_sp->master_bridge.dev == br_dev;
+}
+
+static void mlxsw_sp_master_bridge_inc(struct mlxsw_sp *mlxsw_sp,
+                                      struct net_device *br_dev)
+{
+       mlxsw_sp->master_bridge.dev = br_dev;
+       mlxsw_sp->master_bridge.ref_count++;
 }
 
-static int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port)
+static void mlxsw_sp_master_bridge_dec(struct mlxsw_sp *mlxsw_sp)
+{
+       if (--mlxsw_sp->master_bridge.ref_count == 0) {
+               mlxsw_sp->master_bridge.dev = NULL;
+               /* It's possible upper VLAN devices are still holding
+                * references to underlying FIDs. Drop the reference
+                * and release the resources if it was the last one.
+                * If it wasn't, then something bad happened.
+                */
+               mlxsw_sp_master_bridge_gone_sync(mlxsw_sp);
+       }
+}
+
+static int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
+                                    struct net_device *br_dev)
 {
        struct net_device *dev = mlxsw_sp_port->dev;
        int err;
@@ -2646,6 +3645,8 @@ static int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port)
        if (err)
                return err;
 
+       mlxsw_sp_master_bridge_inc(mlxsw_sp_port->mlxsw_sp, br_dev);
+
        mlxsw_sp_port->learning = 1;
        mlxsw_sp_port->learning_sync = 1;
        mlxsw_sp_port->uc_flood = 1;
@@ -2654,16 +3655,14 @@ static int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port)
        return 0;
 }
 
-static int mlxsw_sp_port_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_port,
-                                     bool flush_fdb)
+static void mlxsw_sp_port_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_port)
 {
        struct net_device *dev = mlxsw_sp_port->dev;
 
-       if (flush_fdb && mlxsw_sp_port_fdb_flush(mlxsw_sp_port))
-               netdev_err(mlxsw_sp_port->dev, "Failed to flush FDB\n");
-
        mlxsw_sp_port_pvid_set(mlxsw_sp_port, 1);
 
+       mlxsw_sp_master_bridge_dec(mlxsw_sp_port->mlxsw_sp);
+
        mlxsw_sp_port->learning = 0;
        mlxsw_sp_port->learning_sync = 0;
        mlxsw_sp_port->uc_flood = 0;
@@ -2672,28 +3671,7 @@ static int mlxsw_sp_port_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_port,
        /* Add implicit VLAN interface in the device, so that untagged
         * packets will be classified to the default vFID.
         */
-       return mlxsw_sp_port_add_vid(dev, 0, 1);
-}
-
-static bool mlxsw_sp_master_bridge_check(struct mlxsw_sp *mlxsw_sp,
-                                        struct net_device *br_dev)
-{
-       return !mlxsw_sp->master_bridge.dev ||
-              mlxsw_sp->master_bridge.dev == br_dev;
-}
-
-static void mlxsw_sp_master_bridge_inc(struct mlxsw_sp *mlxsw_sp,
-                                      struct net_device *br_dev)
-{
-       mlxsw_sp->master_bridge.dev = br_dev;
-       mlxsw_sp->master_bridge.ref_count++;
-}
-
-static void mlxsw_sp_master_bridge_dec(struct mlxsw_sp *mlxsw_sp,
-                                      struct net_device *br_dev)
-{
-       if (--mlxsw_sp->master_bridge.ref_count == 0)
-               mlxsw_sp->master_bridge.dev = NULL;
+       mlxsw_sp_port_add_vid(dev, 0, 1);
 }
 
 static int mlxsw_sp_lag_create(struct mlxsw_sp *mlxsw_sp, u16 lag_id)
@@ -2809,6 +3787,45 @@ static int mlxsw_sp_port_lag_index_get(struct mlxsw_sp *mlxsw_sp,
        return -EBUSY;
 }
 
+static void
+mlxsw_sp_port_pvid_vport_lag_join(struct mlxsw_sp_port *mlxsw_sp_port,
+                                 u16 lag_id)
+{
+       struct mlxsw_sp_port *mlxsw_sp_vport;
+       struct mlxsw_sp_fid *f;
+
+       mlxsw_sp_vport = mlxsw_sp_port_vport_find(mlxsw_sp_port, 1);
+       if (WARN_ON(!mlxsw_sp_vport))
+               return;
+
+       /* If vPort is assigned a RIF, then leave it since it's no
+        * longer valid.
+        */
+       f = mlxsw_sp_vport_fid_get(mlxsw_sp_vport);
+       if (f)
+               f->leave(mlxsw_sp_vport);
+
+       mlxsw_sp_vport->lag_id = lag_id;
+       mlxsw_sp_vport->lagged = 1;
+}
+
+static void
+mlxsw_sp_port_pvid_vport_lag_leave(struct mlxsw_sp_port *mlxsw_sp_port)
+{
+       struct mlxsw_sp_port *mlxsw_sp_vport;
+       struct mlxsw_sp_fid *f;
+
+       mlxsw_sp_vport = mlxsw_sp_port_vport_find(mlxsw_sp_port, 1);
+       if (WARN_ON(!mlxsw_sp_vport))
+               return;
+
+       f = mlxsw_sp_vport_fid_get(mlxsw_sp_vport);
+       if (f)
+               f->leave(mlxsw_sp_vport);
+
+       mlxsw_sp_vport->lagged = 0;
+}
+
 static int mlxsw_sp_port_lag_join(struct mlxsw_sp_port *mlxsw_sp_port,
                                  struct net_device *lag_dev)
 {
@@ -2844,6 +3861,9 @@ static int mlxsw_sp_port_lag_join(struct mlxsw_sp_port *mlxsw_sp_port,
        mlxsw_sp_port->lag_id = lag_id;
        mlxsw_sp_port->lagged = 1;
        lag->ref_count++;
+
+       mlxsw_sp_port_pvid_vport_lag_join(mlxsw_sp_port, lag_id);
+
        return 0;
 
 err_col_port_enable:
@@ -2854,65 +3874,35 @@ err_col_port_add:
        return err;
 }
 
-static int mlxsw_sp_vport_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_vport,
-                                      struct net_device *br_dev,
-                                      bool flush_fdb);
-
-static int mlxsw_sp_port_lag_leave(struct mlxsw_sp_port *mlxsw_sp_port,
-                                  struct net_device *lag_dev)
+static void mlxsw_sp_port_lag_leave(struct mlxsw_sp_port *mlxsw_sp_port,
+                                   struct net_device *lag_dev)
 {
        struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
-       struct mlxsw_sp_port *mlxsw_sp_vport;
-       struct mlxsw_sp_upper *lag;
        u16 lag_id = mlxsw_sp_port->lag_id;
-       int err;
+       struct mlxsw_sp_upper *lag;
 
        if (!mlxsw_sp_port->lagged)
-               return 0;
+               return;
        lag = mlxsw_sp_lag_get(mlxsw_sp, lag_id);
        WARN_ON(lag->ref_count == 0);
 
-       err = mlxsw_sp_lag_col_port_disable(mlxsw_sp_port, lag_id);
-       if (err)
-               return err;
-       err = mlxsw_sp_lag_col_port_remove(mlxsw_sp_port, lag_id);
-       if (err)
-               return err;
-
-       /* In case we leave a LAG device that has bridges built on top,
-        * then their teardown sequence is never issued and we need to
-        * invoke the necessary cleanup routines ourselves.
-        */
-       list_for_each_entry(mlxsw_sp_vport, &mlxsw_sp_port->vports_list,
-                           vport.list) {
-               struct net_device *br_dev;
-
-               if (!mlxsw_sp_vport->bridged)
-                       continue;
-
-               br_dev = mlxsw_sp_vport_br_get(mlxsw_sp_vport);
-               mlxsw_sp_vport_bridge_leave(mlxsw_sp_vport, br_dev, false);
-       }
+       mlxsw_sp_lag_col_port_disable(mlxsw_sp_port, lag_id);
+       mlxsw_sp_lag_col_port_remove(mlxsw_sp_port, lag_id);
 
        if (mlxsw_sp_port->bridged) {
                mlxsw_sp_port_active_vlans_del(mlxsw_sp_port);
-               mlxsw_sp_port_bridge_leave(mlxsw_sp_port, false);
-               mlxsw_sp_master_bridge_dec(mlxsw_sp, NULL);
+               mlxsw_sp_port_bridge_leave(mlxsw_sp_port);
        }
 
-       if (lag->ref_count == 1) {
-               if (mlxsw_sp_port_fdb_flush_by_lag_id(mlxsw_sp_port))
-                       netdev_err(mlxsw_sp_port->dev, "Failed to flush FDB\n");
-               err = mlxsw_sp_lag_destroy(mlxsw_sp, lag_id);
-               if (err)
-                       return err;
-       }
+       if (lag->ref_count == 1)
+               mlxsw_sp_lag_destroy(mlxsw_sp, lag_id);
 
        mlxsw_core_lag_mapping_clear(mlxsw_sp->core, lag_id,
                                     mlxsw_sp_port->local_port);
        mlxsw_sp_port->lagged = 0;
        lag->ref_count--;
-       return 0;
+
+       mlxsw_sp_port_pvid_vport_lag_leave(mlxsw_sp_port);
 }
 
 static int mlxsw_sp_lag_dist_port_add(struct mlxsw_sp_port *mlxsw_sp_port,
@@ -2961,42 +3951,25 @@ static int mlxsw_sp_port_vlan_link(struct mlxsw_sp_port *mlxsw_sp_port,
        u16 vid = vlan_dev_vlan_id(vlan_dev);
 
        mlxsw_sp_vport = mlxsw_sp_port_vport_find(mlxsw_sp_port, vid);
-       if (!mlxsw_sp_vport) {
-               WARN_ON(!mlxsw_sp_vport);
+       if (WARN_ON(!mlxsw_sp_vport))
                return -EINVAL;
-       }
 
        mlxsw_sp_vport->dev = vlan_dev;
 
        return 0;
 }
 
-static int mlxsw_sp_port_vlan_unlink(struct mlxsw_sp_port *mlxsw_sp_port,
-                                    struct net_device *vlan_dev)
+static void mlxsw_sp_port_vlan_unlink(struct mlxsw_sp_port *mlxsw_sp_port,
+                                     struct net_device *vlan_dev)
 {
        struct mlxsw_sp_port *mlxsw_sp_vport;
        u16 vid = vlan_dev_vlan_id(vlan_dev);
 
        mlxsw_sp_vport = mlxsw_sp_port_vport_find(mlxsw_sp_port, vid);
-       if (!mlxsw_sp_vport) {
-               WARN_ON(!mlxsw_sp_vport);
-               return -EINVAL;
-       }
-
-       /* When removing a VLAN device while still bridged we should first
-        * remove it from the bridge, as we receive the bridge's notification
-        * when the vPort is already gone.
-        */
-       if (mlxsw_sp_vport->bridged) {
-               struct net_device *br_dev;
-
-               br_dev = mlxsw_sp_vport_br_get(mlxsw_sp_vport);
-               mlxsw_sp_vport_bridge_leave(mlxsw_sp_vport, br_dev, true);
-       }
+       if (WARN_ON(!mlxsw_sp_vport))
+               return;
 
        mlxsw_sp_vport->dev = mlxsw_sp_port->dev;
-
-       return 0;
 }
 
 static int mlxsw_sp_netdevice_port_upper_event(struct net_device *dev,
@@ -3006,7 +3979,7 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *dev,
        struct mlxsw_sp_port *mlxsw_sp_port;
        struct net_device *upper_dev;
        struct mlxsw_sp *mlxsw_sp;
-       int err;
+       int err = 0;
 
        mlxsw_sp_port = netdev_priv(dev);
        mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
@@ -3015,73 +3988,56 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *dev,
        switch (event) {
        case NETDEV_PRECHANGEUPPER:
                upper_dev = info->upper_dev;
-               if (!info->master || !info->linking)
+               if (!is_vlan_dev(upper_dev) &&
+                   !netif_is_lag_master(upper_dev) &&
+                   !netif_is_bridge_master(upper_dev))
+                       return -EINVAL;
+               if (!info->linking)
                        break;
                /* HW limitation forbids to put ports to multiple bridges. */
                if (netif_is_bridge_master(upper_dev) &&
                    !mlxsw_sp_master_bridge_check(mlxsw_sp, upper_dev))
-                       return NOTIFY_BAD;
+                       return -EINVAL;
                if (netif_is_lag_master(upper_dev) &&
                    !mlxsw_sp_master_lag_check(mlxsw_sp, upper_dev,
                                               info->upper_info))
-                       return NOTIFY_BAD;
+                       return -EINVAL;
+               if (netif_is_lag_master(upper_dev) && vlan_uses_dev(dev))
+                       return -EINVAL;
+               if (netif_is_lag_port(dev) && is_vlan_dev(upper_dev) &&
+                   !netif_is_lag_master(vlan_dev_real_dev(upper_dev)))
+                       return -EINVAL;
                break;
        case NETDEV_CHANGEUPPER:
                upper_dev = info->upper_dev;
                if (is_vlan_dev(upper_dev)) {
-                       if (info->linking) {
+                       if (info->linking)
                                err = mlxsw_sp_port_vlan_link(mlxsw_sp_port,
                                                              upper_dev);
-                               if (err) {
-                                       netdev_err(dev, "Failed to link VLAN device\n");
-                                       return NOTIFY_BAD;
-                               }
-                       } else {
-                               err = mlxsw_sp_port_vlan_unlink(mlxsw_sp_port,
-                                                               upper_dev);
-                               if (err) {
-                                       netdev_err(dev, "Failed to unlink VLAN device\n");
-                                       return NOTIFY_BAD;
-                               }
-                       }
+                       else
+                                mlxsw_sp_port_vlan_unlink(mlxsw_sp_port,
+                                                          upper_dev);
                } else if (netif_is_bridge_master(upper_dev)) {
-                       if (info->linking) {
-                               err = mlxsw_sp_port_bridge_join(mlxsw_sp_port);
-                               if (err) {
-                                       netdev_err(dev, "Failed to join bridge\n");
-                                       return NOTIFY_BAD;
-                               }
-                               mlxsw_sp_master_bridge_inc(mlxsw_sp, upper_dev);
-                       } else {
-                               err = mlxsw_sp_port_bridge_leave(mlxsw_sp_port,
-                                                                true);
-                               mlxsw_sp_master_bridge_dec(mlxsw_sp, upper_dev);
-                               if (err) {
-                                       netdev_err(dev, "Failed to leave bridge\n");
-                                       return NOTIFY_BAD;
-                               }
-                       }
+                       if (info->linking)
+                               err = mlxsw_sp_port_bridge_join(mlxsw_sp_port,
+                                                               upper_dev);
+                       else
+                               mlxsw_sp_port_bridge_leave(mlxsw_sp_port);
                } else if (netif_is_lag_master(upper_dev)) {
-                       if (info->linking) {
+                       if (info->linking)
                                err = mlxsw_sp_port_lag_join(mlxsw_sp_port,
                                                             upper_dev);
-                               if (err) {
-                                       netdev_err(dev, "Failed to join link aggregation\n");
-                                       return NOTIFY_BAD;
-                               }
-                       } else {
-                               err = mlxsw_sp_port_lag_leave(mlxsw_sp_port,
-                                                             upper_dev);
-                               if (err) {
-                                       netdev_err(dev, "Failed to leave link aggregation\n");
-                                       return NOTIFY_BAD;
-                               }
-                       }
+                       else
+                               mlxsw_sp_port_lag_leave(mlxsw_sp_port,
+                                                       upper_dev);
+               } else {
+                       err = -EINVAL;
+                       WARN_ON(1);
                }
                break;
        }
 
-       return NOTIFY_DONE;
+       return err;
 }
 
 static int mlxsw_sp_netdevice_port_lower_event(struct net_device *dev,
@@ -3105,7 +4061,7 @@ static int mlxsw_sp_netdevice_port_lower_event(struct net_device *dev,
                break;
        }
 
-       return NOTIFY_DONE;
+       return 0;
 }
 
 static int mlxsw_sp_netdevice_port_event(struct net_device *dev,
@@ -3119,7 +4075,7 @@ static int mlxsw_sp_netdevice_port_event(struct net_device *dev,
                return mlxsw_sp_netdevice_port_lower_event(dev, event, ptr);
        }
 
-       return NOTIFY_DONE;
+       return 0;
 }
 
 static int mlxsw_sp_netdevice_lag_event(struct net_device *lag_dev,
@@ -3132,218 +4088,230 @@ static int mlxsw_sp_netdevice_lag_event(struct net_device *lag_dev,
        netdev_for_each_lower_dev(lag_dev, dev, iter) {
                if (mlxsw_sp_port_dev_check(dev)) {
                        ret = mlxsw_sp_netdevice_port_event(dev, event, ptr);
-                       if (ret == NOTIFY_BAD)
+                       if (ret)
                                return ret;
                }
        }
 
-       return NOTIFY_DONE;
+       return 0;
 }
 
-static struct mlxsw_sp_vfid *
-mlxsw_sp_br_vfid_find(const struct mlxsw_sp *mlxsw_sp,
-                     const struct net_device *br_dev)
+static int mlxsw_sp_master_bridge_vlan_link(struct mlxsw_sp *mlxsw_sp,
+                                           struct net_device *vlan_dev)
 {
-       struct mlxsw_sp_vfid *vfid;
+       u16 fid = vlan_dev_vlan_id(vlan_dev);
+       struct mlxsw_sp_fid *f;
 
-       list_for_each_entry(vfid, &mlxsw_sp->br_vfids.list, list) {
-               if (vfid->br_dev == br_dev)
-                       return vfid;
+       f = mlxsw_sp_fid_find(mlxsw_sp, fid);
+       if (!f) {
+               f = mlxsw_sp_fid_create(mlxsw_sp, fid);
+               if (IS_ERR(f))
+                       return PTR_ERR(f);
        }
 
-       return NULL;
+       f->ref_count++;
+
+       return 0;
 }
 
-static u16 mlxsw_sp_vfid_to_br_vfid(u16 vfid)
+static void mlxsw_sp_master_bridge_vlan_unlink(struct mlxsw_sp *mlxsw_sp,
+                                              struct net_device *vlan_dev)
 {
-       return vfid - MLXSW_SP_VFID_PORT_MAX;
+       u16 fid = vlan_dev_vlan_id(vlan_dev);
+       struct mlxsw_sp_fid *f;
+
+       f = mlxsw_sp_fid_find(mlxsw_sp, fid);
+       if (f && f->r)
+               mlxsw_sp_rif_bridge_destroy(mlxsw_sp, f->r);
+       if (f && --f->ref_count == 0)
+               mlxsw_sp_fid_destroy(mlxsw_sp, f);
 }
 
-static u16 mlxsw_sp_br_vfid_to_vfid(u16 br_vfid)
+static int mlxsw_sp_netdevice_bridge_event(struct net_device *br_dev,
+                                          unsigned long event, void *ptr)
 {
-       return MLXSW_SP_VFID_PORT_MAX + br_vfid;
+       struct netdev_notifier_changeupper_info *info;
+       struct net_device *upper_dev;
+       struct mlxsw_sp *mlxsw_sp;
+       int err;
+
+       mlxsw_sp = mlxsw_sp_lower_get(br_dev);
+       if (!mlxsw_sp)
+               return 0;
+       if (br_dev != mlxsw_sp->master_bridge.dev)
+               return 0;
+
+       info = ptr;
+
+       switch (event) {
+       case NETDEV_CHANGEUPPER:
+               upper_dev = info->upper_dev;
+               if (!is_vlan_dev(upper_dev))
+                       break;
+               if (info->linking) {
+                       err = mlxsw_sp_master_bridge_vlan_link(mlxsw_sp,
+                                                              upper_dev);
+                       if (err)
+                               return err;
+               } else {
+                       mlxsw_sp_master_bridge_vlan_unlink(mlxsw_sp, upper_dev);
+               }
+               break;
+       }
+
+       return 0;
 }
 
-static u16 mlxsw_sp_avail_br_vfid_get(const struct mlxsw_sp *mlxsw_sp)
+static u16 mlxsw_sp_avail_vfid_get(const struct mlxsw_sp *mlxsw_sp)
 {
-       return find_first_zero_bit(mlxsw_sp->br_vfids.mapped,
-                                  MLXSW_SP_VFID_BR_MAX);
+       return find_first_zero_bit(mlxsw_sp->vfids.mapped,
+                                  MLXSW_SP_VFID_MAX);
 }
 
-static struct mlxsw_sp_vfid *mlxsw_sp_br_vfid_create(struct mlxsw_sp *mlxsw_sp,
-                                                    struct net_device *br_dev)
+static int mlxsw_sp_vfid_op(struct mlxsw_sp *mlxsw_sp, u16 fid, bool create)
+{
+       char sfmr_pl[MLXSW_REG_SFMR_LEN];
+
+       mlxsw_reg_sfmr_pack(sfmr_pl, !create, fid, 0);
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfmr), sfmr_pl);
+}
+
+static void mlxsw_sp_vport_vfid_leave(struct mlxsw_sp_port *mlxsw_sp_vport);
+
+static struct mlxsw_sp_fid *mlxsw_sp_vfid_create(struct mlxsw_sp *mlxsw_sp,
+                                                struct net_device *br_dev)
 {
        struct device *dev = mlxsw_sp->bus_info->dev;
-       struct mlxsw_sp_vfid *vfid;
-       u16 n_vfid;
+       struct mlxsw_sp_fid *f;
+       u16 vfid, fid;
        int err;
 
-       n_vfid = mlxsw_sp_br_vfid_to_vfid(mlxsw_sp_avail_br_vfid_get(mlxsw_sp));
-       if (n_vfid == MLXSW_SP_VFID_MAX) {
+       vfid = mlxsw_sp_avail_vfid_get(mlxsw_sp);
+       if (vfid == MLXSW_SP_VFID_MAX) {
                dev_err(dev, "No available vFIDs\n");
                return ERR_PTR(-ERANGE);
        }
 
-       err = __mlxsw_sp_vfid_create(mlxsw_sp, n_vfid);
+       fid = mlxsw_sp_vfid_to_fid(vfid);
+       err = mlxsw_sp_vfid_op(mlxsw_sp, fid, true);
        if (err) {
-               dev_err(dev, "Failed to create vFID=%d\n", n_vfid);
+               dev_err(dev, "Failed to create FID=%d\n", fid);
                return ERR_PTR(err);
        }
 
-       vfid = kzalloc(sizeof(*vfid), GFP_KERNEL);
-       if (!vfid)
+       f = kzalloc(sizeof(*f), GFP_KERNEL);
+       if (!f)
                goto err_allocate_vfid;
 
-       vfid->vfid = n_vfid;
-       vfid->br_dev = br_dev;
+       f->leave = mlxsw_sp_vport_vfid_leave;
+       f->fid = fid;
+       f->dev = br_dev;
 
-       list_add(&vfid->list, &mlxsw_sp->br_vfids.list);
-       set_bit(mlxsw_sp_vfid_to_br_vfid(n_vfid), mlxsw_sp->br_vfids.mapped);
+       list_add(&f->list, &mlxsw_sp->vfids.list);
+       set_bit(vfid, mlxsw_sp->vfids.mapped);
 
-       return vfid;
+       return f;
 
 err_allocate_vfid:
-       __mlxsw_sp_vfid_destroy(mlxsw_sp, n_vfid);
+       mlxsw_sp_vfid_op(mlxsw_sp, fid, false);
        return ERR_PTR(-ENOMEM);
 }
 
-static void mlxsw_sp_br_vfid_destroy(struct mlxsw_sp *mlxsw_sp,
-                                    struct mlxsw_sp_vfid *vfid)
+static void mlxsw_sp_vfid_destroy(struct mlxsw_sp *mlxsw_sp,
+                                 struct mlxsw_sp_fid *f)
 {
-       u16 br_vfid = mlxsw_sp_vfid_to_br_vfid(vfid->vfid);
+       u16 vfid = mlxsw_sp_fid_to_vfid(f->fid);
+       u16 fid = f->fid;
 
-       clear_bit(br_vfid, mlxsw_sp->br_vfids.mapped);
-       list_del(&vfid->list);
+       clear_bit(vfid, mlxsw_sp->vfids.mapped);
+       list_del(&f->list);
 
-       __mlxsw_sp_vfid_destroy(mlxsw_sp, vfid->vfid);
+       if (f->r)
+               mlxsw_sp_rif_bridge_destroy(mlxsw_sp, f->r);
 
-       kfree(vfid);
+       kfree(f);
+
+       mlxsw_sp_vfid_op(mlxsw_sp, fid, false);
 }
 
-static int mlxsw_sp_vport_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_vport,
-                                      struct net_device *br_dev,
-                                      bool flush_fdb)
+static int mlxsw_sp_vport_fid_map(struct mlxsw_sp_port *mlxsw_sp_vport, u16 fid,
+                                 bool valid)
 {
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_vport->mlxsw_sp;
+       enum mlxsw_reg_svfa_mt mt = MLXSW_REG_SVFA_MT_PORT_VID_TO_FID;
        u16 vid = mlxsw_sp_vport_vid_get(mlxsw_sp_vport);
-       struct net_device *dev = mlxsw_sp_vport->dev;
-       struct mlxsw_sp_vfid *vfid, *new_vfid;
-       int err;
-
-       vfid = mlxsw_sp_br_vfid_find(mlxsw_sp, br_dev);
-       if (!vfid) {
-               WARN_ON(!vfid);
-               return -EINVAL;
-       }
 
-       /* We need a vFID to go back to after leaving the bridge's vFID. */
-       new_vfid = mlxsw_sp_vfid_find(mlxsw_sp, vid);
-       if (!new_vfid) {
-               new_vfid = mlxsw_sp_vfid_create(mlxsw_sp, vid);
-               if (IS_ERR(new_vfid)) {
-                       netdev_err(dev, "Failed to create vFID for VID=%d\n",
-                                  vid);
-                       return PTR_ERR(new_vfid);
-               }
-       }
-
-       /* Invalidate existing {Port, VID} to vFID mapping and create a new
-        * one for the new vFID.
-        */
-       err = mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                          MLXSW_REG_SVFA_MT_PORT_VID_TO_FID,
-                                          false,
-                                          mlxsw_sp_vfid_to_fid(vfid->vfid),
-                                          vid);
-       if (err) {
-               netdev_err(dev, "Failed to invalidate {Port, VID} to vFID=%d mapping\n",
-                          vfid->vfid);
-               goto err_port_vid_to_fid_invalidate;
-       }
+       return mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport, mt, valid, fid,
+                                           vid);
+}
 
-       err = mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                          MLXSW_REG_SVFA_MT_PORT_VID_TO_FID,
-                                          true,
-                                          mlxsw_sp_vfid_to_fid(new_vfid->vfid),
-                                          vid);
-       if (err) {
-               netdev_err(dev, "Failed to map {Port, VID} to vFID=%d\n",
-                          new_vfid->vfid);
-               goto err_port_vid_to_fid_validate;
-       }
+static int mlxsw_sp_vport_vfid_join(struct mlxsw_sp_port *mlxsw_sp_vport,
+                                   struct net_device *br_dev)
+{
+       struct mlxsw_sp_fid *f;
+       int err;
 
-       err = mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, false);
-       if (err) {
-               netdev_err(dev, "Failed to disable learning\n");
-               goto err_port_vid_learning_set;
+       f = mlxsw_sp_vfid_find(mlxsw_sp_vport->mlxsw_sp, br_dev);
+       if (!f) {
+               f = mlxsw_sp_vfid_create(mlxsw_sp_vport->mlxsw_sp, br_dev);
+               if (IS_ERR(f))
+                       return PTR_ERR(f);
        }
 
-       err = mlxsw_sp_vport_flood_set(mlxsw_sp_vport, vfid->vfid, false,
-                                      false);
-       if (err) {
-               netdev_err(dev, "Failed clear to clear flooding\n");
+       err = mlxsw_sp_vport_flood_set(mlxsw_sp_vport, f->fid, true);
+       if (err)
                goto err_vport_flood_set;
-       }
-
-       err = mlxsw_sp_port_stp_state_set(mlxsw_sp_vport, vid,
-                                         MLXSW_REG_SPMS_STATE_FORWARDING);
-       if (err) {
-               netdev_err(dev, "Failed to set STP state\n");
-               goto err_port_stp_state_set;
-       }
 
-       if (flush_fdb && mlxsw_sp_vport_fdb_flush(mlxsw_sp_vport))
-               netdev_err(dev, "Failed to flush FDB\n");
+       err = mlxsw_sp_vport_fid_map(mlxsw_sp_vport, f->fid, true);
+       if (err)
+               goto err_vport_fid_map;
 
-       /* Switch between the vFIDs and destroy the old one if needed. */
-       new_vfid->nr_vports++;
-       mlxsw_sp_vport->vport.vfid = new_vfid;
-       vfid->nr_vports--;
-       if (!vfid->nr_vports)
-               mlxsw_sp_br_vfid_destroy(mlxsw_sp, vfid);
+       mlxsw_sp_vport_fid_set(mlxsw_sp_vport, f);
+       f->ref_count++;
 
-       mlxsw_sp_vport->learning = 0;
-       mlxsw_sp_vport->learning_sync = 0;
-       mlxsw_sp_vport->uc_flood = 0;
-       mlxsw_sp_vport->bridged = 0;
+       netdev_dbg(mlxsw_sp_vport->dev, "Joined FID=%d\n", f->fid);
 
        return 0;
 
-err_port_stp_state_set:
+err_vport_fid_map:
+       mlxsw_sp_vport_flood_set(mlxsw_sp_vport, f->fid, false);
 err_vport_flood_set:
-err_port_vid_learning_set:
-err_port_vid_to_fid_validate:
-err_port_vid_to_fid_invalidate:
-       /* Rollback vFID only if new. */
-       if (!new_vfid->nr_vports)
-               mlxsw_sp_vfid_destroy(mlxsw_sp, new_vfid);
+       if (!f->ref_count)
+               mlxsw_sp_vfid_destroy(mlxsw_sp_vport->mlxsw_sp, f);
        return err;
 }
 
+static void mlxsw_sp_vport_vfid_leave(struct mlxsw_sp_port *mlxsw_sp_vport)
+{
+       struct mlxsw_sp_fid *f = mlxsw_sp_vport_fid_get(mlxsw_sp_vport);
+
+       netdev_dbg(mlxsw_sp_vport->dev, "Left FID=%d\n", f->fid);
+
+       mlxsw_sp_vport_fid_map(mlxsw_sp_vport, f->fid, false);
+
+       mlxsw_sp_vport_flood_set(mlxsw_sp_vport, f->fid, false);
+
+       mlxsw_sp_port_fdb_flush(mlxsw_sp_vport, f->fid);
+
+       mlxsw_sp_vport_fid_set(mlxsw_sp_vport, NULL);
+       if (--f->ref_count == 0)
+               mlxsw_sp_vfid_destroy(mlxsw_sp_vport->mlxsw_sp, f);
+}
+
 static int mlxsw_sp_vport_bridge_join(struct mlxsw_sp_port *mlxsw_sp_vport,
                                      struct net_device *br_dev)
 {
-       struct mlxsw_sp_vfid *old_vfid = mlxsw_sp_vport->vport.vfid;
-       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_vport->mlxsw_sp;
+       struct mlxsw_sp_fid *f = mlxsw_sp_vport_fid_get(mlxsw_sp_vport);
        u16 vid = mlxsw_sp_vport_vid_get(mlxsw_sp_vport);
        struct net_device *dev = mlxsw_sp_vport->dev;
-       struct mlxsw_sp_vfid *vfid;
        int err;
 
-       vfid = mlxsw_sp_br_vfid_find(mlxsw_sp, br_dev);
-       if (!vfid) {
-               vfid = mlxsw_sp_br_vfid_create(mlxsw_sp, br_dev);
-               if (IS_ERR(vfid)) {
-                       netdev_err(dev, "Failed to create bridge vFID\n");
-                       return PTR_ERR(vfid);
-               }
-       }
+       if (f && !WARN_ON(!f->leave))
+               f->leave(mlxsw_sp_vport);
 
-       err = mlxsw_sp_vport_flood_set(mlxsw_sp_vport, vfid->vfid, true, false);
+       err = mlxsw_sp_vport_vfid_join(mlxsw_sp_vport, br_dev);
        if (err) {
-               netdev_err(dev, "Failed to setup flooding for vFID=%d\n",
-                          vfid->vfid);
-               goto err_port_flood_set;
+               netdev_err(dev, "Failed to join vFID\n");
+               return err;
        }
 
        err = mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, true);
@@ -3352,38 +4320,6 @@ static int mlxsw_sp_vport_bridge_join(struct mlxsw_sp_port *mlxsw_sp_vport,
                goto err_port_vid_learning_set;
        }
 
-       /* We need to invalidate existing {Port, VID} to vFID mapping and
-        * create a new one for the bridge's vFID.
-        */
-       err = mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                          MLXSW_REG_SVFA_MT_PORT_VID_TO_FID,
-                                          false,
-                                          mlxsw_sp_vfid_to_fid(old_vfid->vfid),
-                                          vid);
-       if (err) {
-               netdev_err(dev, "Failed to invalidate {Port, VID} to vFID=%d mapping\n",
-                          old_vfid->vfid);
-               goto err_port_vid_to_fid_invalidate;
-       }
-
-       err = mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                          MLXSW_REG_SVFA_MT_PORT_VID_TO_FID,
-                                          true,
-                                          mlxsw_sp_vfid_to_fid(vfid->vfid),
-                                          vid);
-       if (err) {
-               netdev_err(dev, "Failed to map {Port, VID} to vFID=%d\n",
-                          vfid->vfid);
-               goto err_port_vid_to_fid_validate;
-       }
-
-       /* Switch between the vFIDs and destroy the old one if needed. */
-       vfid->nr_vports++;
-       mlxsw_sp_vport->vport.vfid = vfid;
-       old_vfid->nr_vports--;
-       if (!old_vfid->nr_vports)
-               mlxsw_sp_vfid_destroy(mlxsw_sp, old_vfid);
-
        mlxsw_sp_vport->learning = 1;
        mlxsw_sp_vport->learning_sync = 1;
        mlxsw_sp_vport->uc_flood = 1;
@@ -3391,20 +4327,25 @@ static int mlxsw_sp_vport_bridge_join(struct mlxsw_sp_port *mlxsw_sp_vport,
 
        return 0;
 
-err_port_vid_to_fid_validate:
-       mlxsw_sp_port_vid_to_fid_set(mlxsw_sp_vport,
-                                    MLXSW_REG_SVFA_MT_PORT_VID_TO_FID, false,
-                                    mlxsw_sp_vfid_to_fid(old_vfid->vfid), vid);
-err_port_vid_to_fid_invalidate:
-       mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, false);
 err_port_vid_learning_set:
-       mlxsw_sp_vport_flood_set(mlxsw_sp_vport, vfid->vfid, false, false);
-err_port_flood_set:
-       if (!vfid->nr_vports)
-               mlxsw_sp_br_vfid_destroy(mlxsw_sp, vfid);
+       mlxsw_sp_vport_vfid_leave(mlxsw_sp_vport);
        return err;
 }
 
+static void mlxsw_sp_vport_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_vport)
+{
+       u16 vid = mlxsw_sp_vport_vid_get(mlxsw_sp_vport);
+
+       mlxsw_sp_port_vid_learning_set(mlxsw_sp_vport, vid, false);
+
+       mlxsw_sp_vport_vfid_leave(mlxsw_sp_vport);
+
+       mlxsw_sp_vport->learning = 0;
+       mlxsw_sp_vport->learning_sync = 0;
+       mlxsw_sp_vport->uc_flood = 0;
+       mlxsw_sp_vport->bridged = 0;
+}
+
 static bool
 mlxsw_sp_port_master_bridge_check(const struct mlxsw_sp_port *mlxsw_sp_port,
                                  const struct net_device *br_dev)
@@ -3413,7 +4354,9 @@ mlxsw_sp_port_master_bridge_check(const struct mlxsw_sp_port *mlxsw_sp_port,
 
        list_for_each_entry(mlxsw_sp_vport, &mlxsw_sp_port->vports_list,
                            vport.list) {
-               if (mlxsw_sp_vport_br_get(mlxsw_sp_vport) == br_dev)
+               struct net_device *dev = mlxsw_sp_vport_dev_get(mlxsw_sp_vport);
+
+               if (dev && dev == br_dev)
                        return false;
        }
 
@@ -3428,56 +4371,39 @@ static int mlxsw_sp_netdevice_vport_event(struct net_device *dev,
        struct netdev_notifier_changeupper_info *info = ptr;
        struct mlxsw_sp_port *mlxsw_sp_vport;
        struct net_device *upper_dev;
-       int err;
+       int err = 0;
 
        mlxsw_sp_vport = mlxsw_sp_port_vport_find(mlxsw_sp_port, vid);
 
        switch (event) {
        case NETDEV_PRECHANGEUPPER:
                upper_dev = info->upper_dev;
-               if (!info->master || !info->linking)
-                       break;
                if (!netif_is_bridge_master(upper_dev))
-                       return NOTIFY_BAD;
+                       return -EINVAL;
+               if (!info->linking)
+                       break;
                /* We can't have multiple VLAN interfaces configured on
                 * the same port and being members in the same bridge.
                 */
                if (!mlxsw_sp_port_master_bridge_check(mlxsw_sp_port,
                                                       upper_dev))
-                       return NOTIFY_BAD;
+                       return -EINVAL;
                break;
        case NETDEV_CHANGEUPPER:
                upper_dev = info->upper_dev;
-               if (!info->master)
-                       break;
                if (info->linking) {
-                       if (!mlxsw_sp_vport) {
-                               WARN_ON(!mlxsw_sp_vport);
-                               return NOTIFY_BAD;
-                       }
+                       if (WARN_ON(!mlxsw_sp_vport))
+                               return -EINVAL;
                        err = mlxsw_sp_vport_bridge_join(mlxsw_sp_vport,
                                                         upper_dev);
-                       if (err) {
-                               netdev_err(dev, "Failed to join bridge\n");
-                               return NOTIFY_BAD;
-                       }
                } else {
-                       /* We ignore bridge's unlinking notifications if vPort
-                        * is gone, since we already left the bridge when the
-                        * VLAN device was unlinked from the real device.
-                        */
                        if (!mlxsw_sp_vport)
-                               return NOTIFY_DONE;
-                       err = mlxsw_sp_vport_bridge_leave(mlxsw_sp_vport,
-                                                         upper_dev, true);
-                       if (err) {
-                               netdev_err(dev, "Failed to leave bridge\n");
-                               return NOTIFY_BAD;
-                       }
+                               return 0;
+                       mlxsw_sp_vport_bridge_leave(mlxsw_sp_vport);
                }
        }
 
-       return NOTIFY_DONE;
+       return err;
 }
 
 static int mlxsw_sp_netdevice_lag_vport_event(struct net_device *lag_dev,
@@ -3492,12 +4418,12 @@ static int mlxsw_sp_netdevice_lag_vport_event(struct net_device *lag_dev,
                if (mlxsw_sp_port_dev_check(dev)) {
                        ret = mlxsw_sp_netdevice_vport_event(dev, event, ptr,
                                                             vid);
-                       if (ret == NOTIFY_BAD)
+                       if (ret)
                                return ret;
                }
        }
 
-       return NOTIFY_DONE;
+       return 0;
 }
 
 static int mlxsw_sp_netdevice_vlan_event(struct net_device *vlan_dev,
@@ -3513,35 +4439,44 @@ static int mlxsw_sp_netdevice_vlan_event(struct net_device *vlan_dev,
                return mlxsw_sp_netdevice_lag_vport_event(real_dev, event, ptr,
                                                          vid);
 
-       return NOTIFY_DONE;
+       return 0;
 }
 
 static int mlxsw_sp_netdevice_event(struct notifier_block *unused,
                                    unsigned long event, void *ptr)
 {
        struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+       int err = 0;
 
-       if (mlxsw_sp_port_dev_check(dev))
-               return mlxsw_sp_netdevice_port_event(dev, event, ptr);
-
-       if (netif_is_lag_master(dev))
-               return mlxsw_sp_netdevice_lag_event(dev, event, ptr);
+       if (event == NETDEV_CHANGEADDR || event == NETDEV_CHANGEMTU)
+               err = mlxsw_sp_netdevice_router_port_event(dev);
+       else if (mlxsw_sp_port_dev_check(dev))
+               err = mlxsw_sp_netdevice_port_event(dev, event, ptr);
+       else if (netif_is_lag_master(dev))
+               err = mlxsw_sp_netdevice_lag_event(dev, event, ptr);
+       else if (netif_is_bridge_master(dev))
+               err = mlxsw_sp_netdevice_bridge_event(dev, event, ptr);
+       else if (is_vlan_dev(dev))
+               err = mlxsw_sp_netdevice_vlan_event(dev, event, ptr);
 
-       if (is_vlan_dev(dev))
-               return mlxsw_sp_netdevice_vlan_event(dev, event, ptr);
-
-       return NOTIFY_DONE;
+       return notifier_from_errno(err);
 }
 
 static struct notifier_block mlxsw_sp_netdevice_nb __read_mostly = {
        .notifier_call = mlxsw_sp_netdevice_event,
 };
 
+static struct notifier_block mlxsw_sp_inetaddr_nb __read_mostly = {
+       .notifier_call = mlxsw_sp_inetaddr_event,
+       .priority = 10, /* Must be called before FIB notifier block */
+};
+
 static int __init mlxsw_sp_module_init(void)
 {
        int err;
 
        register_netdevice_notifier(&mlxsw_sp_netdevice_nb);
+       register_inetaddr_notifier(&mlxsw_sp_inetaddr_nb);
        err = mlxsw_core_driver_register(&mlxsw_sp_driver);
        if (err)
                goto err_core_driver_register;
@@ -3555,6 +4490,7 @@ err_core_driver_register:
 static void __exit mlxsw_sp_module_exit(void)
 {
        mlxsw_core_driver_unregister(&mlxsw_sp_driver);
+       unregister_inetaddr_notifier(&mlxsw_sp_inetaddr_nb);
        unregister_netdevice_notifier(&mlxsw_sp_netdevice_nb);
 }