net: sched: move the can_offload check from binding phase to rule insertion phase
[linux-2.6-microblaze.git] / drivers / net / ethernet / mellanox / mlxsw / spectrum.c
index 696b99e..3f4be95 100644 (file)
@@ -53,6 +53,7 @@
 #include <linux/notifier.h>
 #include <linux/dcbnl.h>
 #include <linux/inetdevice.h>
+#include <linux/netlink.h>
 #include <net/switchdev.h>
 #include <net/pkt_cls.h>
 #include <net/tc_act/tc_mirred.h>
@@ -69,6 +70,7 @@
 #include "txheader.h"
 #include "spectrum_cnt.h"
 #include "spectrum_dpipe.h"
+#include "spectrum_acl_flex_actions.h"
 #include "../mlxfw/mlxfw.h"
 
 #define MLXSW_FWREV_MAJOR 13
@@ -1326,16 +1328,16 @@ static void update_stats_cache(struct work_struct *work)
 {
        struct mlxsw_sp_port *mlxsw_sp_port =
                container_of(work, struct mlxsw_sp_port,
-                            hw_stats.update_dw.work);
+                            periodic_hw_stats.update_dw.work);
 
        if (!netif_carrier_ok(mlxsw_sp_port->dev))
                goto out;
 
        mlxsw_sp_port_get_hw_stats(mlxsw_sp_port->dev,
-                                  mlxsw_sp_port->hw_stats.cache);
+                                  &mlxsw_sp_port->periodic_hw_stats.stats);
 
 out:
-       mlxsw_core_schedule_dw(&mlxsw_sp_port->hw_stats.update_dw,
+       mlxsw_core_schedule_dw(&mlxsw_sp_port->periodic_hw_stats.update_dw,
                               MLXSW_HW_STATS_UPDATE_TIME);
 }
 
@@ -1348,7 +1350,7 @@ mlxsw_sp_port_get_stats64(struct net_device *dev,
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
 
-       memcpy(stats, mlxsw_sp_port->hw_stats.cache, sizeof(*stats));
+       memcpy(stats, &mlxsw_sp_port->periodic_hw_stats.stats, sizeof(*stats));
 }
 
 static int __mlxsw_sp_port_vlan_set(struct mlxsw_sp_port *mlxsw_sp_port,
@@ -1695,17 +1697,9 @@ static void mlxsw_sp_port_del_cls_matchall(struct mlxsw_sp_port *mlxsw_sp_port,
 }
 
 static int mlxsw_sp_setup_tc_cls_matchall(struct mlxsw_sp_port *mlxsw_sp_port,
-                                         struct tc_cls_matchall_offload *f)
+                                         struct tc_cls_matchall_offload *f,
+                                         bool ingress)
 {
-       bool ingress;
-
-       if (is_classid_clsact_ingress(f->common.classid))
-               ingress = true;
-       else if (is_classid_clsact_egress(f->common.classid))
-               ingress = false;
-       else
-               return -EOPNOTSUPP;
-
        if (f->common.chain_index)
                return -EOPNOTSUPP;
 
@@ -1723,17 +1717,9 @@ static int mlxsw_sp_setup_tc_cls_matchall(struct mlxsw_sp_port *mlxsw_sp_port,
 
 static int
 mlxsw_sp_setup_tc_cls_flower(struct mlxsw_sp_port *mlxsw_sp_port,
-                            struct tc_cls_flower_offload *f)
+                            struct tc_cls_flower_offload *f,
+                            bool ingress)
 {
-       bool ingress;
-
-       if (is_classid_clsact_ingress(f->common.classid))
-               ingress = true;
-       else if (is_classid_clsact_egress(f->common.classid))
-               ingress = false;
-       else
-               return -EOPNOTSUPP;
-
        switch (f->command) {
        case TC_CLSFLOWER_REPLACE:
                return mlxsw_sp_flower_replace(mlxsw_sp_port, ingress, f);
@@ -1747,16 +1733,70 @@ mlxsw_sp_setup_tc_cls_flower(struct mlxsw_sp_port *mlxsw_sp_port,
        }
 }
 
+static int mlxsw_sp_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
+                                     void *cb_priv, bool ingress)
+{
+       struct mlxsw_sp_port *mlxsw_sp_port = cb_priv;
+
+       if (!tc_can_offload(mlxsw_sp_port->dev))
+               return -EOPNOTSUPP;
+
+       switch (type) {
+       case TC_SETUP_CLSMATCHALL:
+               return mlxsw_sp_setup_tc_cls_matchall(mlxsw_sp_port, type_data,
+                                                     ingress);
+       case TC_SETUP_CLSFLOWER:
+               return mlxsw_sp_setup_tc_cls_flower(mlxsw_sp_port, type_data,
+                                                   ingress);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int mlxsw_sp_setup_tc_block_cb_ig(enum tc_setup_type type,
+                                        void *type_data, void *cb_priv)
+{
+       return mlxsw_sp_setup_tc_block_cb(type, type_data, cb_priv, true);
+}
+
+static int mlxsw_sp_setup_tc_block_cb_eg(enum tc_setup_type type,
+                                        void *type_data, void *cb_priv)
+{
+       return mlxsw_sp_setup_tc_block_cb(type, type_data, cb_priv, false);
+}
+
+static int mlxsw_sp_setup_tc_block(struct mlxsw_sp_port *mlxsw_sp_port,
+                                  struct tc_block_offload *f)
+{
+       tc_setup_cb_t *cb;
+
+       if (f->binder_type == TCF_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
+               cb = mlxsw_sp_setup_tc_block_cb_ig;
+       else if (f->binder_type == TCF_BLOCK_BINDER_TYPE_CLSACT_EGRESS)
+               cb = mlxsw_sp_setup_tc_block_cb_eg;
+       else
+               return -EOPNOTSUPP;
+
+       switch (f->command) {
+       case TC_BLOCK_BIND:
+               return tcf_block_cb_register(f->block, cb, mlxsw_sp_port,
+                                            mlxsw_sp_port);
+       case TC_BLOCK_UNBIND:
+               tcf_block_cb_unregister(f->block, cb, mlxsw_sp_port);
+               return 0;
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
 static int mlxsw_sp_setup_tc(struct net_device *dev, enum tc_setup_type type,
                             void *type_data)
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
 
        switch (type) {
-       case TC_SETUP_CLSMATCHALL:
-               return mlxsw_sp_setup_tc_cls_matchall(mlxsw_sp_port, type_data);
-       case TC_SETUP_CLSFLOWER:
-               return mlxsw_sp_setup_tc_cls_flower(mlxsw_sp_port, type_data);
+       case TC_SETUP_BLOCK:
+               return mlxsw_sp_setup_tc_block(mlxsw_sp_port, type_data);
        default:
                return -EOPNOTSUPP;
        }
@@ -2868,14 +2908,7 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
                goto err_alloc_sample;
        }
 
-       mlxsw_sp_port->hw_stats.cache =
-               kzalloc(sizeof(*mlxsw_sp_port->hw_stats.cache), GFP_KERNEL);
-
-       if (!mlxsw_sp_port->hw_stats.cache) {
-               err = -ENOMEM;
-               goto err_alloc_hw_stats;
-       }
-       INIT_DELAYED_WORK(&mlxsw_sp_port->hw_stats.update_dw,
+       INIT_DELAYED_WORK(&mlxsw_sp_port->periodic_hw_stats.update_dw,
                          &update_stats_cache);
 
        dev->netdev_ops = &mlxsw_sp_port_netdev_ops;
@@ -2989,7 +3022,7 @@ static int mlxsw_sp_port_create(struct mlxsw_sp *mlxsw_sp, u8 local_port,
        mlxsw_core_port_eth_set(mlxsw_sp->core, mlxsw_sp_port->local_port,
                                mlxsw_sp_port, dev, mlxsw_sp_port->split,
                                module);
-       mlxsw_core_schedule_dw(&mlxsw_sp_port->hw_stats.update_dw, 0);
+       mlxsw_core_schedule_dw(&mlxsw_sp_port->periodic_hw_stats.update_dw, 0);
        return 0;
 
 err_register_netdev:
@@ -3012,8 +3045,6 @@ err_dev_addr_init:
 err_port_swid_set:
        mlxsw_sp_port_module_unmap(mlxsw_sp_port);
 err_port_module_map:
-       kfree(mlxsw_sp_port->hw_stats.cache);
-err_alloc_hw_stats:
        kfree(mlxsw_sp_port->sample);
 err_alloc_sample:
        free_percpu(mlxsw_sp_port->pcpu_stats);
@@ -3028,7 +3059,7 @@ 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];
 
-       cancel_delayed_work_sync(&mlxsw_sp_port->hw_stats.update_dw);
+       cancel_delayed_work_sync(&mlxsw_sp_port->periodic_hw_stats.update_dw);
        mlxsw_core_port_clear(mlxsw_sp->core, local_port, mlxsw_sp);
        unregister_netdev(mlxsw_sp_port->dev); /* This calls ndo_stop */
        mlxsw_sp->ports[local_port] = NULL;
@@ -3038,7 +3069,6 @@ static void mlxsw_sp_port_remove(struct mlxsw_sp *mlxsw_sp, u8 local_port)
        mlxsw_sp_port_dcb_fini(mlxsw_sp_port);
        mlxsw_sp_port_swid_set(mlxsw_sp_port, MLXSW_PORT_SWID_DISABLED_PORT);
        mlxsw_sp_port_module_unmap(mlxsw_sp_port);
-       kfree(mlxsw_sp_port->hw_stats.cache);
        kfree(mlxsw_sp_port->sample);
        free_percpu(mlxsw_sp_port->pcpu_stats);
        WARN_ON_ONCE(!list_empty(&mlxsw_sp_port->vlans_list));
@@ -3311,6 +3341,14 @@ static void mlxsw_sp_rx_listener_mark_func(struct sk_buff *skb, u8 local_port,
        return mlxsw_sp_rx_listener_no_mark_func(skb, local_port, priv);
 }
 
+static void mlxsw_sp_rx_listener_mr_mark_func(struct sk_buff *skb,
+                                             u8 local_port, void *priv)
+{
+       skb->offload_mr_fwd_mark = 1;
+       skb->offload_fwd_mark = 1;
+       return mlxsw_sp_rx_listener_no_mark_func(skb, local_port, priv);
+}
+
 static void mlxsw_sp_rx_listener_sample_func(struct sk_buff *skb, u8 local_port,
                                             void *priv)
 {
@@ -3354,6 +3392,10 @@ out:
        MLXSW_RXL(mlxsw_sp_rx_listener_mark_func, _trap_id, _action,    \
                _is_ctrl, SP_##_trap_group, DISCARD)
 
+#define MLXSW_SP_RXL_MR_MARK(_trap_id, _action, _trap_group, _is_ctrl) \
+       MLXSW_RXL(mlxsw_sp_rx_listener_mr_mark_func, _trap_id, _action, \
+               _is_ctrl, SP_##_trap_group, DISCARD)
+
 #define MLXSW_SP_EVENTL(_func, _trap_id)               \
        MLXSW_EVENTL(_func, _trap_id, SP_EVENT)
 
@@ -3420,6 +3462,11 @@ static const struct mlxsw_listener mlxsw_sp_listener[] = {
                  false, SP_IP2ME, DISCARD),
        /* ACL trap */
        MLXSW_SP_RXL_NO_MARK(ACL0, TRAP_TO_CPU, IP2ME, false),
+       /* Multicast Router Traps */
+       MLXSW_SP_RXL_MARK(IPV4_PIM, TRAP_TO_CPU, PIM, false),
+       MLXSW_SP_RXL_MARK(RPF, TRAP_TO_CPU, RPF, false),
+       MLXSW_SP_RXL_MARK(ACL1, TRAP_TO_CPU, MULTICAST, false),
+       MLXSW_SP_RXL_MR_MARK(ACL2, TRAP_TO_CPU, MULTICAST, false),
 };
 
 static int mlxsw_sp_cpu_policers_set(struct mlxsw_core *mlxsw_core)
@@ -3445,6 +3492,8 @@ static int mlxsw_sp_cpu_policers_set(struct mlxsw_core *mlxsw_core)
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_LACP:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_LLDP:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_OSPF:
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_PIM:
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_RPF:
                        rate = 128;
                        burst_size = 7;
                        break;
@@ -3460,6 +3509,7 @@ static int mlxsw_sp_cpu_policers_set(struct mlxsw_core *mlxsw_core)
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_ROUTER_EXP:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_REMOTE_ROUTE:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_IPV6_ND:
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_MULTICAST:
                        rate = 1024;
                        burst_size = 7;
                        break;
@@ -3505,6 +3555,7 @@ static int mlxsw_sp_trap_groups_set(struct mlxsw_core *mlxsw_core)
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_LACP:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_LLDP:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_OSPF:
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_PIM:
                        priority = 5;
                        tc = 5;
                        break;
@@ -3521,12 +3572,14 @@ static int mlxsw_sp_trap_groups_set(struct mlxsw_core *mlxsw_core)
                        break;
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_ARP:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_IPV6_ND:
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_RPF:
                        priority = 2;
                        tc = 2;
                        break;
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_HOST_MISS:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_ROUTER_EXP:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_REMOTE_ROUTE:
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_MULTICAST:
                        priority = 1;
                        tc = 1;
                        break;
@@ -3642,6 +3695,9 @@ static int mlxsw_sp_basic_trap_groups_set(struct mlxsw_core *mlxsw_core)
        return mlxsw_reg_write(mlxsw_core, MLXSW_REG(htgt), htgt_pl);
 }
 
+static int mlxsw_sp_netdevice_event(struct notifier_block *unused,
+                                   unsigned long event, void *ptr);
+
 static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
                         const struct mlxsw_bus_info *mlxsw_bus_info)
 {
@@ -3663,10 +3719,16 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
                return err;
        }
 
+       err = mlxsw_sp_kvdl_init(mlxsw_sp);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Failed to initialize KVDL\n");
+               return err;
+       }
+
        err = mlxsw_sp_fids_init(mlxsw_sp);
        if (err) {
                dev_err(mlxsw_sp->bus_info->dev, "Failed to initialize FIDs\n");
-               return err;
+               goto err_fids_init;
        }
 
        err = mlxsw_sp_traps_init(mlxsw_sp);
@@ -3693,12 +3755,34 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
                goto err_switchdev_init;
        }
 
+       err = mlxsw_sp_counter_pool_init(mlxsw_sp);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Failed to init counter pool\n");
+               goto err_counter_pool_init;
+       }
+
+       err = mlxsw_sp_afa_init(mlxsw_sp);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Failed to initialize ACL actions\n");
+               goto err_afa_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;
        }
 
+       /* Initialize netdevice notifier after router is initialized, so that
+        * the event handler can use router structures.
+        */
+       mlxsw_sp->netdevice_nb.notifier_call = mlxsw_sp_netdevice_event;
+       err = register_netdevice_notifier(&mlxsw_sp->netdevice_nb);
+       if (err) {
+               dev_err(mlxsw_sp->bus_info->dev, "Failed to register netdev notifier\n");
+               goto err_netdev_notifier;
+       }
+
        err = mlxsw_sp_span_init(mlxsw_sp);
        if (err) {
                dev_err(mlxsw_sp->bus_info->dev, "Failed to init span system\n");
@@ -3711,12 +3795,6 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
                goto err_acl_init;
        }
 
-       err = mlxsw_sp_counter_pool_init(mlxsw_sp);
-       if (err) {
-               dev_err(mlxsw_sp->bus_info->dev, "Failed to init counter pool\n");
-               goto err_counter_pool_init;
-       }
-
        err = mlxsw_sp_dpipe_init(mlxsw_sp);
        if (err) {
                dev_err(mlxsw_sp->bus_info->dev, "Failed to init pipeline debug\n");
@@ -3734,14 +3812,18 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
 err_ports_create:
        mlxsw_sp_dpipe_fini(mlxsw_sp);
 err_dpipe_init:
-       mlxsw_sp_counter_pool_fini(mlxsw_sp);
-err_counter_pool_init:
        mlxsw_sp_acl_fini(mlxsw_sp);
 err_acl_init:
        mlxsw_sp_span_fini(mlxsw_sp);
 err_span_init:
+       unregister_netdevice_notifier(&mlxsw_sp->netdevice_nb);
+err_netdev_notifier:
        mlxsw_sp_router_fini(mlxsw_sp);
 err_router_init:
+       mlxsw_sp_afa_fini(mlxsw_sp);
+err_afa_init:
+       mlxsw_sp_counter_pool_fini(mlxsw_sp);
+err_counter_pool_init:
        mlxsw_sp_switchdev_fini(mlxsw_sp);
 err_switchdev_init:
        mlxsw_sp_lag_fini(mlxsw_sp);
@@ -3751,6 +3833,8 @@ err_buffers_init:
        mlxsw_sp_traps_fini(mlxsw_sp);
 err_traps_init:
        mlxsw_sp_fids_fini(mlxsw_sp);
+err_fids_init:
+       mlxsw_sp_kvdl_fini(mlxsw_sp);
        return err;
 }
 
@@ -3760,15 +3844,18 @@ static void mlxsw_sp_fini(struct mlxsw_core *mlxsw_core)
 
        mlxsw_sp_ports_remove(mlxsw_sp);
        mlxsw_sp_dpipe_fini(mlxsw_sp);
-       mlxsw_sp_counter_pool_fini(mlxsw_sp);
        mlxsw_sp_acl_fini(mlxsw_sp);
        mlxsw_sp_span_fini(mlxsw_sp);
+       unregister_netdevice_notifier(&mlxsw_sp->netdevice_nb);
        mlxsw_sp_router_fini(mlxsw_sp);
+       mlxsw_sp_afa_fini(mlxsw_sp);
+       mlxsw_sp_counter_pool_fini(mlxsw_sp);
        mlxsw_sp_switchdev_fini(mlxsw_sp);
        mlxsw_sp_lag_fini(mlxsw_sp);
        mlxsw_sp_buffers_fini(mlxsw_sp);
        mlxsw_sp_traps_fini(mlxsw_sp);
        mlxsw_sp_fids_fini(mlxsw_sp);
+       mlxsw_sp_kvdl_fini(mlxsw_sp);
 }
 
 static const struct mlxsw_config_profile mlxsw_sp_config_profile = {
@@ -3791,8 +3878,8 @@ static const struct mlxsw_config_profile mlxsw_sp_config_profile = {
        .max_pkey                       = 0,
        .used_kvd_split_data            = 1,
        .kvd_hash_granularity           = MLXSW_SP_KVD_GRANULARITY,
-       .kvd_hash_single_parts          = 2,
-       .kvd_hash_double_parts          = 1,
+       .kvd_hash_single_parts          = 59,
+       .kvd_hash_double_parts          = 41,
        .kvd_linear_size                = MLXSW_SP_KVD_LINEAR_SIZE,
        .swid_config                    = {
                {
@@ -3986,14 +4073,21 @@ static int mlxsw_sp_lag_index_get(struct mlxsw_sp *mlxsw_sp,
 static bool
 mlxsw_sp_master_lag_check(struct mlxsw_sp *mlxsw_sp,
                          struct net_device *lag_dev,
-                         struct netdev_lag_upper_info *lag_upper_info)
+                         struct netdev_lag_upper_info *lag_upper_info,
+                         struct netlink_ext_ack *extack)
 {
        u16 lag_id;
 
-       if (mlxsw_sp_lag_index_get(mlxsw_sp, lag_dev, &lag_id) != 0)
+       if (mlxsw_sp_lag_index_get(mlxsw_sp, lag_dev, &lag_id) != 0) {
+               NL_SET_ERR_MSG(extack,
+                              "spectrum: Exceeded number of supported LAG devices");
                return false;
-       if (lag_upper_info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
+       }
+       if (lag_upper_info->tx_type != NETDEV_LAG_TX_TYPE_HASH) {
+               NL_SET_ERR_MSG(extack,
+                              "spectrum: LAG device using unsupported Tx type");
                return false;
+       }
        return true;
 }
 
@@ -4198,6 +4292,7 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
 {
        struct netdev_notifier_changeupper_info *info;
        struct mlxsw_sp_port *mlxsw_sp_port;
+       struct netlink_ext_ack *extack;
        struct net_device *upper_dev;
        struct mlxsw_sp *mlxsw_sp;
        int err = 0;
@@ -4205,6 +4300,7 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
        mlxsw_sp_port = netdev_priv(dev);
        mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
        info = ptr;
+       extack = netdev_notifier_info_to_extack(&info->info);
 
        switch (event) {
        case NETDEV_PRECHANGEUPPER:
@@ -4212,25 +4308,43 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
                if (!is_vlan_dev(upper_dev) &&
                    !netif_is_lag_master(upper_dev) &&
                    !netif_is_bridge_master(upper_dev) &&
-                   !netif_is_ovs_master(upper_dev))
+                   !netif_is_ovs_master(upper_dev)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "spectrum: Unknown upper device type");
                        return -EINVAL;
+               }
                if (!info->linking)
                        break;
-               if (netdev_has_any_upper_dev(upper_dev))
+               if (netdev_has_any_upper_dev(upper_dev)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "spectrum: Enslaving a port to a device that already has an upper device is not supported");
                        return -EINVAL;
+               }
                if (netif_is_lag_master(upper_dev) &&
                    !mlxsw_sp_master_lag_check(mlxsw_sp, upper_dev,
-                                              info->upper_info))
+                                              info->upper_info, extack))
                        return -EINVAL;
-               if (netif_is_lag_master(upper_dev) && vlan_uses_dev(dev))
+               if (netif_is_lag_master(upper_dev) && vlan_uses_dev(dev)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "spectrum: Master device is a LAG master and this device has a VLAN");
                        return -EINVAL;
+               }
                if (netif_is_lag_port(dev) && is_vlan_dev(upper_dev) &&
-                   !netif_is_lag_master(vlan_dev_real_dev(upper_dev)))
+                   !netif_is_lag_master(vlan_dev_real_dev(upper_dev))) {
+                       NL_SET_ERR_MSG(extack,
+                                      "spectrum: Can not put a VLAN on a LAG port");
                        return -EINVAL;
-               if (netif_is_ovs_master(upper_dev) && vlan_uses_dev(dev))
+               }
+               if (netif_is_ovs_master(upper_dev) && vlan_uses_dev(dev)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "spectrum: Master device is an OVS master and this device has a VLAN");
                        return -EINVAL;
-               if (netif_is_ovs_port(dev) && is_vlan_dev(upper_dev))
+               }
+               if (netif_is_ovs_port(dev) && is_vlan_dev(upper_dev)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "spectrum: Can not put a VLAN on an OVS port");
                        return -EINVAL;
+               }
                break;
        case NETDEV_CHANGEUPPER:
                upper_dev = info->upper_dev;
@@ -4238,7 +4352,8 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
                        if (info->linking)
                                err = mlxsw_sp_port_bridge_join(mlxsw_sp_port,
                                                                lower_dev,
-                                                               upper_dev);
+                                                               upper_dev,
+                                                               extack);
                        else
                                mlxsw_sp_port_bridge_leave(mlxsw_sp_port,
                                                           lower_dev,
@@ -4329,18 +4444,25 @@ static int mlxsw_sp_netdevice_port_vlan_event(struct net_device *vlan_dev,
 {
        struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
        struct netdev_notifier_changeupper_info *info = ptr;
+       struct netlink_ext_ack *extack;
        struct net_device *upper_dev;
        int err = 0;
 
+       extack = netdev_notifier_info_to_extack(&info->info);
+
        switch (event) {
        case NETDEV_PRECHANGEUPPER:
                upper_dev = info->upper_dev;
-               if (!netif_is_bridge_master(upper_dev))
+               if (!netif_is_bridge_master(upper_dev)) {
+                       NL_SET_ERR_MSG(extack, "spectrum: VLAN devices only support bridge and VRF uppers");
                        return -EINVAL;
+               }
                if (!info->linking)
                        break;
-               if (netdev_has_any_upper_dev(upper_dev))
+               if (netdev_has_any_upper_dev(upper_dev)) {
+                       NL_SET_ERR_MSG(extack, "spectrum: Enslaving a port to a device that already has an upper device is not supported");
                        return -EINVAL;
+               }
                break;
        case NETDEV_CHANGEUPPER:
                upper_dev = info->upper_dev;
@@ -4348,7 +4470,8 @@ static int mlxsw_sp_netdevice_port_vlan_event(struct net_device *vlan_dev,
                        if (info->linking)
                                err = mlxsw_sp_port_bridge_join(mlxsw_sp_port,
                                                                vlan_dev,
-                                                               upper_dev);
+                                                               upper_dev,
+                                                               extack);
                        else
                                mlxsw_sp_port_bridge_leave(mlxsw_sp_port,
                                                           vlan_dev,
@@ -4411,13 +4534,17 @@ static bool mlxsw_sp_is_vrf_event(unsigned long event, void *ptr)
        return netif_is_l3_master(info->upper_dev);
 }
 
-static int mlxsw_sp_netdevice_event(struct notifier_block *unused,
+static int mlxsw_sp_netdevice_event(struct notifier_block *nb,
                                    unsigned long event, void *ptr)
 {
        struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+       struct mlxsw_sp *mlxsw_sp;
        int err = 0;
 
-       if (event == NETDEV_CHANGEADDR || event == NETDEV_CHANGEMTU)
+       mlxsw_sp = container_of(nb, struct mlxsw_sp, netdevice_nb);
+       if (mlxsw_sp_netdev_is_ipip(mlxsw_sp, dev))
+               err = mlxsw_sp_netdevice_ipip_event(mlxsw_sp, dev, event, ptr);
+       else if (event == NETDEV_CHANGEADDR || event == NETDEV_CHANGEMTU)
                err = mlxsw_sp_netdevice_router_port_event(dev);
        else if (mlxsw_sp_is_vrf_event(event, ptr))
                err = mlxsw_sp_netdevice_vrf_event(dev, event, ptr);
@@ -4431,13 +4558,16 @@ static int mlxsw_sp_netdevice_event(struct notifier_block *unused,
        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_valid_nb __read_mostly = {
+       .notifier_call = mlxsw_sp_inetaddr_valid_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 struct notifier_block mlxsw_sp_inet6addr_valid_nb __read_mostly = {
+       .notifier_call = mlxsw_sp_inet6addr_valid_event,
 };
 
 static struct notifier_block mlxsw_sp_inet6addr_nb __read_mostly = {
@@ -4462,8 +4592,9 @@ static int __init mlxsw_sp_module_init(void)
 {
        int err;
 
-       register_netdevice_notifier(&mlxsw_sp_netdevice_nb);
+       register_inetaddr_validator_notifier(&mlxsw_sp_inetaddr_valid_nb);
        register_inetaddr_notifier(&mlxsw_sp_inetaddr_nb);
+       register_inet6addr_validator_notifier(&mlxsw_sp_inet6addr_valid_nb);
        register_inet6addr_notifier(&mlxsw_sp_inet6addr_nb);
        register_netevent_notifier(&mlxsw_sp_router_netevent_nb);
 
@@ -4482,8 +4613,9 @@ err_pci_driver_register:
 err_core_driver_register:
        unregister_netevent_notifier(&mlxsw_sp_router_netevent_nb);
        unregister_inet6addr_notifier(&mlxsw_sp_inet6addr_nb);
+       unregister_inet6addr_validator_notifier(&mlxsw_sp_inet6addr_valid_nb);
        unregister_inetaddr_notifier(&mlxsw_sp_inetaddr_nb);
-       unregister_netdevice_notifier(&mlxsw_sp_netdevice_nb);
+       unregister_inetaddr_validator_notifier(&mlxsw_sp_inetaddr_valid_nb);
        return err;
 }
 
@@ -4493,8 +4625,9 @@ static void __exit mlxsw_sp_module_exit(void)
        mlxsw_core_driver_unregister(&mlxsw_sp_driver);
        unregister_netevent_notifier(&mlxsw_sp_router_netevent_nb);
        unregister_inet6addr_notifier(&mlxsw_sp_inet6addr_nb);
+       unregister_inet6addr_validator_notifier(&mlxsw_sp_inet6addr_valid_nb);
        unregister_inetaddr_notifier(&mlxsw_sp_inetaddr_nb);
-       unregister_netdevice_notifier(&mlxsw_sp_netdevice_nb);
+       unregister_inetaddr_validator_notifier(&mlxsw_sp_inetaddr_valid_nb);
 }
 
 module_init(mlxsw_sp_module_init);