mlxsw_sp_port_vp_mode_set(mlxsw_sp_port, false);
 }
 
+static bool mlxsw_sp_bridge_has_multiple_vxlans(struct net_device *br_dev)
+{
+       unsigned int num_vxlans = 0;
+       struct net_device *dev;
+       struct list_head *iter;
+
+       netdev_for_each_lower_dev(br_dev, dev, iter) {
+               if (netif_is_vxlan(dev))
+                       num_vxlans++;
+       }
+
+       return num_vxlans > 1;
+}
+
+static bool mlxsw_sp_bridge_vxlan_is_valid(struct net_device *br_dev,
+                                          struct netlink_ext_ack *extack)
+{
+       if (br_multicast_enabled(br_dev)) {
+               NL_SET_ERR_MSG_MOD(extack, "Multicast can not be enabled on a bridge with a VxLAN device");
+               return false;
+       }
+
+       if (br_vlan_enabled(br_dev)) {
+               NL_SET_ERR_MSG_MOD(extack, "VLAN filtering can not be enabled on a bridge with a VxLAN device");
+               return false;
+       }
+
+       if (mlxsw_sp_bridge_has_multiple_vxlans(br_dev)) {
+               NL_SET_ERR_MSG_MOD(extack, "Multiple VxLAN devices are not supported in a VLAN-unaware bridge");
+               return false;
+       }
+
+       return true;
+}
+
 static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
                                               struct net_device *dev,
                                               unsigned long event, void *ptr)
                }
                if (!info->linking)
                        break;
+               if (netif_is_bridge_master(upper_dev) &&
+                   !mlxsw_sp_bridge_device_is_offloaded(mlxsw_sp, upper_dev) &&
+                   mlxsw_sp_bridge_has_vxlan(upper_dev) &&
+                   !mlxsw_sp_bridge_vxlan_is_valid(upper_dev, extack))
+                       return -EOPNOTSUPP;
                if (netdev_has_any_upper_dev(upper_dev) &&
                    (!netif_is_bridge_master(upper_dev) ||
                     !mlxsw_sp_bridge_device_is_offloaded(mlxsw_sp,
                }
                if (!info->linking)
                        break;
+               if (netif_is_bridge_master(upper_dev) &&
+                   !mlxsw_sp_bridge_device_is_offloaded(mlxsw_sp, upper_dev) &&
+                   mlxsw_sp_bridge_has_vxlan(upper_dev) &&
+                   !mlxsw_sp_bridge_vxlan_is_valid(upper_dev, extack))
+                       return -EOPNOTSUPP;
                if (netdev_has_any_upper_dev(upper_dev) &&
                    (!netif_is_bridge_master(upper_dev) ||
                     !mlxsw_sp_bridge_device_is_offloaded(mlxsw_sp,
        return netif_is_l3_master(info->upper_dev);
 }
 
+static int mlxsw_sp_netdevice_vxlan_event(struct mlxsw_sp *mlxsw_sp,
+                                         struct net_device *dev,
+                                         unsigned long event, void *ptr)
+{
+       struct netdev_notifier_changeupper_info *cu_info;
+       struct netdev_notifier_info *info = ptr;
+       struct netlink_ext_ack *extack;
+       struct net_device *upper_dev;
+
+       extack = netdev_notifier_info_to_extack(info);
+
+       switch (event) {
+       case NETDEV_CHANGEUPPER:
+               cu_info = container_of(info,
+                                      struct netdev_notifier_changeupper_info,
+                                      info);
+               upper_dev = cu_info->upper_dev;
+               if (!netif_is_bridge_master(upper_dev))
+                       return 0;
+               if (!mlxsw_sp_lower_get(upper_dev))
+                       return 0;
+               if (!mlxsw_sp_bridge_vxlan_is_valid(upper_dev, extack))
+                       return -EOPNOTSUPP;
+               if (cu_info->linking) {
+                       if (!netif_running(dev))
+                               return 0;
+                       return mlxsw_sp_bridge_vxlan_join(mlxsw_sp, upper_dev,
+                                                         dev, extack);
+               } else {
+                       mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, upper_dev, dev);
+               }
+               break;
+       case NETDEV_PRE_UP:
+               upper_dev = netdev_master_upper_dev_get(dev);
+               if (!upper_dev)
+                       return 0;
+               if (!netif_is_bridge_master(upper_dev))
+                       return 0;
+               if (!mlxsw_sp_lower_get(upper_dev))
+                       return 0;
+               return mlxsw_sp_bridge_vxlan_join(mlxsw_sp, upper_dev, dev,
+                                                 extack);
+       case NETDEV_DOWN:
+               upper_dev = netdev_master_upper_dev_get(dev);
+               if (!upper_dev)
+                       return 0;
+               if (!netif_is_bridge_master(upper_dev))
+                       return 0;
+               if (!mlxsw_sp_lower_get(upper_dev))
+                       return 0;
+               mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, upper_dev, dev);
+               break;
+       }
+
+       return 0;
+}
+
 static int mlxsw_sp_netdevice_event(struct notifier_block *nb,
                                    unsigned long event, void *ptr)
 {
        }
        mlxsw_sp_span_respin(mlxsw_sp);
 
+       if (netif_is_vxlan(dev))
+               err = mlxsw_sp_netdevice_vxlan_event(mlxsw_sp, dev, event, ptr);
        if (mlxsw_sp_netdev_is_ipip_ol(mlxsw_sp, dev))
                err = mlxsw_sp_netdevice_ipip_ol_event(mlxsw_sp, dev,
                                                       event, ptr);
 
 #include <net/psample.h>
 #include <net/pkt_cls.h>
 #include <net/red.h>
+#include <net/vxlan.h>
 
 #include "port.h"
 #include "core.h"
        struct mlxsw_sp_acl_block *eg_acl_block;
 };
 
+static inline struct net_device *
+mlxsw_sp_bridge_vxlan_dev_find(struct net_device *br_dev)
+{
+       struct net_device *dev;
+       struct list_head *iter;
+
+       netdev_for_each_lower_dev(br_dev, dev, iter) {
+               if (netif_is_vxlan(dev))
+                       return dev;
+       }
+
+       return NULL;
+}
+
+static inline bool mlxsw_sp_bridge_has_vxlan(struct net_device *br_dev)
+{
+       return !!mlxsw_sp_bridge_vxlan_dev_find(br_dev);
+}
+
 static inline bool
 mlxsw_sp_port_is_pause_en(const struct mlxsw_sp_port *mlxsw_sp_port)
 {
                                struct net_device *br_dev);
 bool mlxsw_sp_bridge_device_is_offloaded(const struct mlxsw_sp *mlxsw_sp,
                                         const struct net_device *br_dev);
+int mlxsw_sp_bridge_vxlan_join(struct mlxsw_sp *mlxsw_sp,
+                              const struct net_device *br_dev,
+                              const struct net_device *vxlan_dev,
+                              struct netlink_ext_ack *extack);
+void mlxsw_sp_bridge_vxlan_leave(struct mlxsw_sp *mlxsw_sp,
+                                const struct net_device *br_dev,
+                                const struct net_device *vxlan_dev);
 
 /* spectrum.c */
 int mlxsw_sp_port_ets_set(struct mlxsw_sp_port *mlxsw_sp_port,
 
 #include <linux/rtnetlink.h>
 #include <linux/netlink.h>
 #include <net/switchdev.h>
+#include <net/vxlan.h>
 
 #include "spectrum_span.h"
 #include "spectrum_switchdev.h"
        void (*port_leave)(struct mlxsw_sp_bridge_device *bridge_device,
                           struct mlxsw_sp_bridge_port *bridge_port,
                           struct mlxsw_sp_port *mlxsw_sp_port);
+       int (*vxlan_join)(struct mlxsw_sp_bridge_device *bridge_device,
+                         const struct net_device *vxlan_dev,
+                         struct netlink_ext_ack *extack);
+       void (*vxlan_leave)(struct mlxsw_sp_bridge_device *bridge_device,
+                           const struct net_device *vxlan_dev);
        struct mlxsw_sp_fid *
                (*fid_get)(struct mlxsw_sp_bridge_device *bridge_device,
                           u16 vid);
        mlxsw_sp_port_pvid_set(mlxsw_sp_port, 1);
 }
 
+static int
+mlxsw_sp_bridge_8021q_vxlan_join(struct mlxsw_sp_bridge_device *bridge_device,
+                                const struct net_device *vxlan_dev,
+                                struct netlink_ext_ack *extack)
+{
+       WARN_ON(1);
+       return -EINVAL;
+}
+
+static void
+mlxsw_sp_bridge_8021q_vxlan_leave(struct mlxsw_sp_bridge_device *bridge_device,
+                                 const struct net_device *vxlan_dev)
+{
+}
+
 static struct mlxsw_sp_fid *
 mlxsw_sp_bridge_8021q_fid_get(struct mlxsw_sp_bridge_device *bridge_device,
                              u16 vid)
 static const struct mlxsw_sp_bridge_ops mlxsw_sp_bridge_8021q_ops = {
        .port_join      = mlxsw_sp_bridge_8021q_port_join,
        .port_leave     = mlxsw_sp_bridge_8021q_port_leave,
+       .vxlan_join     = mlxsw_sp_bridge_8021q_vxlan_join,
+       .vxlan_leave    = mlxsw_sp_bridge_8021q_vxlan_leave,
        .fid_get        = mlxsw_sp_bridge_8021q_fid_get,
 };
 
        mlxsw_sp_port_vlan_bridge_leave(mlxsw_sp_port_vlan);
 }
 
+static int
+mlxsw_sp_bridge_8021d_vxlan_join(struct mlxsw_sp_bridge_device *bridge_device,
+                                const struct net_device *vxlan_dev,
+                                struct netlink_ext_ack *extack)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(bridge_device->dev);
+       struct vxlan_dev *vxlan = netdev_priv(vxlan_dev);
+       struct mlxsw_sp_nve_params params = {
+               .type = MLXSW_SP_NVE_TYPE_VXLAN,
+               .vni = vxlan->cfg.vni,
+               .dev = vxlan_dev,
+       };
+       struct mlxsw_sp_fid *fid;
+       int err;
+
+       fid = mlxsw_sp_fid_8021d_lookup(mlxsw_sp, bridge_device->dev->ifindex);
+       if (!fid)
+               return -EINVAL;
+
+       if (mlxsw_sp_fid_vni_is_set(fid))
+               return -EINVAL;
+
+       err = mlxsw_sp_nve_fid_enable(mlxsw_sp, fid, ¶ms, extack);
+       if (err)
+               goto err_nve_fid_enable;
+
+       /* The tunnel port does not hold a reference on the FID. Only
+        * local ports and the router port
+        */
+       mlxsw_sp_fid_put(fid);
+
+       return 0;
+
+err_nve_fid_enable:
+       mlxsw_sp_fid_put(fid);
+       return err;
+}
+
+static void
+mlxsw_sp_bridge_8021d_vxlan_leave(struct mlxsw_sp_bridge_device *bridge_device,
+                                 const struct net_device *vxlan_dev)
+{
+       struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(bridge_device->dev);
+       struct mlxsw_sp_fid *fid;
+
+       fid = mlxsw_sp_fid_8021d_lookup(mlxsw_sp, bridge_device->dev->ifindex);
+       if (WARN_ON(!fid))
+               return;
+
+       /* If the VxLAN device is down, then the FID does not have a VNI */
+       if (!mlxsw_sp_fid_vni_is_set(fid))
+               goto out;
+
+       mlxsw_sp_nve_fid_disable(mlxsw_sp, fid);
+out:
+       mlxsw_sp_fid_put(fid);
+}
+
 static struct mlxsw_sp_fid *
 mlxsw_sp_bridge_8021d_fid_get(struct mlxsw_sp_bridge_device *bridge_device,
                              u16 vid)
 {
        struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(bridge_device->dev);
+       struct net_device *vxlan_dev;
+       struct mlxsw_sp_fid *fid;
+       int err;
 
-       return mlxsw_sp_fid_8021d_get(mlxsw_sp, bridge_device->dev->ifindex);
+       fid = mlxsw_sp_fid_8021d_get(mlxsw_sp, bridge_device->dev->ifindex);
+       if (IS_ERR(fid))
+               return fid;
+
+       if (mlxsw_sp_fid_vni_is_set(fid))
+               return fid;
+
+       vxlan_dev = mlxsw_sp_bridge_vxlan_dev_find(bridge_device->dev);
+       if (!vxlan_dev)
+               return fid;
+
+       if (!netif_running(vxlan_dev))
+               return fid;
+
+       err = mlxsw_sp_bridge_8021d_vxlan_join(bridge_device, vxlan_dev, NULL);
+       if (err)
+               goto err_vxlan_join;
+
+       return fid;
+
+err_vxlan_join:
+       mlxsw_sp_fid_put(fid);
+       return ERR_PTR(err);
 }
 
 static const struct mlxsw_sp_bridge_ops mlxsw_sp_bridge_8021d_ops = {
        .port_join      = mlxsw_sp_bridge_8021d_port_join,
        .port_leave     = mlxsw_sp_bridge_8021d_port_leave,
+       .vxlan_join     = mlxsw_sp_bridge_8021d_vxlan_join,
+       .vxlan_leave    = mlxsw_sp_bridge_8021d_vxlan_leave,
        .fid_get        = mlxsw_sp_bridge_8021d_fid_get,
 };
 
        mlxsw_sp_bridge_port_put(mlxsw_sp->bridge, bridge_port);
 }
 
+int mlxsw_sp_bridge_vxlan_join(struct mlxsw_sp *mlxsw_sp,
+                              const struct net_device *br_dev,
+                              const struct net_device *vxlan_dev,
+                              struct netlink_ext_ack *extack)
+{
+       struct mlxsw_sp_bridge_device *bridge_device;
+
+       bridge_device = mlxsw_sp_bridge_device_find(mlxsw_sp->bridge, br_dev);
+       if (WARN_ON(!bridge_device))
+               return -EINVAL;
+
+       return bridge_device->ops->vxlan_join(bridge_device, vxlan_dev, extack);
+}
+
+void mlxsw_sp_bridge_vxlan_leave(struct mlxsw_sp *mlxsw_sp,
+                                const struct net_device *br_dev,
+                                const struct net_device *vxlan_dev)
+{
+       struct mlxsw_sp_bridge_device *bridge_device;
+
+       bridge_device = mlxsw_sp_bridge_device_find(mlxsw_sp->bridge, br_dev);
+       if (WARN_ON(!bridge_device))
+               return;
+
+       bridge_device->ops->vxlan_leave(bridge_device, vxlan_dev);
+}
+
 static void
 mlxsw_sp_fdb_call_notifiers(enum switchdev_notifier_type type,
                            const char *mac, u16 vid,