#include <devlink.h>
 
 #include "mlx5_core.h"
+#include "fw_reset.h"
 #include "fs_core.h"
 #include "eswitch.h"
 
        return 0;
 }
 
+static int mlx5_devlink_reload_fw_activate(struct devlink *devlink, struct netlink_ext_ack *extack)
+{
+       struct mlx5_core_dev *dev = devlink_priv(devlink);
+       u8 reset_level, reset_type, net_port_alive;
+       int err;
+
+       err = mlx5_fw_reset_query(dev, &reset_level, &reset_type);
+       if (err)
+               return err;
+       if (!(reset_level & MLX5_MFRL_REG_RESET_LEVEL3)) {
+               NL_SET_ERR_MSG_MOD(extack, "FW activate requires reboot");
+               return -EINVAL;
+       }
+
+       net_port_alive = !!(reset_type & MLX5_MFRL_REG_RESET_TYPE_NET_PORT_ALIVE);
+       err = mlx5_fw_reset_set_reset_sync(dev, net_port_alive);
+       if (err)
+               goto out;
+
+       err = mlx5_fw_reset_wait_reset_done(dev);
+out:
+       if (err)
+               NL_SET_ERR_MSG_MOD(extack, "FW activate command failed");
+       return err;
+}
+
 static int mlx5_devlink_reload_down(struct devlink *devlink, bool netns_change,
                                    enum devlink_reload_action action,
                                    enum devlink_reload_limit limit,
 {
        struct mlx5_core_dev *dev = devlink_priv(devlink);
 
-       mlx5_unload_one(dev, false);
-       return 0;
+       switch (action) {
+       case DEVLINK_RELOAD_ACTION_DRIVER_REINIT:
+               mlx5_unload_one(dev, false);
+               return 0;
+       case DEVLINK_RELOAD_ACTION_FW_ACTIVATE:
+               return mlx5_devlink_reload_fw_activate(devlink, extack);
+       default:
+               /* Unsupported action should not get to this function */
+               WARN_ON(1);
+               return -EOPNOTSUPP;
+       }
 }
 
 static int mlx5_devlink_reload_up(struct devlink *devlink, enum devlink_reload_action action,
 {
        struct mlx5_core_dev *dev = devlink_priv(devlink);
 
-       *actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT);
-       return mlx5_load_one(dev, false);
+       *actions_performed = BIT(action);
+       switch (action) {
+       case DEVLINK_RELOAD_ACTION_DRIVER_REINIT:
+       case DEVLINK_RELOAD_ACTION_FW_ACTIVATE:
+               /* On fw_activate action, also driver is reloaded and reinit performed */
+               *actions_performed |= BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT);
+               return mlx5_load_one(dev, false);
+       default:
+               /* Unsupported action should not get to this function */
+               WARN_ON(1);
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
 }
 
 static const struct devlink_ops mlx5_devlink_ops = {
 #endif
        .flash_update = mlx5_devlink_flash_update,
        .info_get = mlx5_devlink_info_get,
-       .reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT),
+       .reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
+                         BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE),
        .reload_down = mlx5_devlink_reload_down,
        .reload_up = mlx5_devlink_reload_up,
 };
 
 
 enum {
        MLX5_FW_RESET_FLAGS_RESET_REQUESTED,
+       MLX5_FW_RESET_FLAGS_PENDING_COMP
 };
 
 struct mlx5_fw_reset {
        struct work_struct reset_abort_work;
        unsigned long reset_flags;
        struct timer_list timer;
+       struct completion done;
+       int ret;
 };
 
 static int mlx5_reg_mfrl_set(struct mlx5_core_dev *dev, u8 reset_level,
 
 int mlx5_fw_reset_set_reset_sync(struct mlx5_core_dev *dev, u8 reset_type_sel)
 {
-       return mlx5_reg_mfrl_set(dev, MLX5_MFRL_REG_RESET_LEVEL3, reset_type_sel, 0, true);
+       struct mlx5_fw_reset *fw_reset = dev->priv.fw_reset;
+       int err;
+
+       set_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, &fw_reset->reset_flags);
+       err = mlx5_reg_mfrl_set(dev, MLX5_MFRL_REG_RESET_LEVEL3, reset_type_sel, 0, true);
+       if (err)
+               clear_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, &fw_reset->reset_flags);
+       return err;
 }
 
 int mlx5_fw_reset_set_live_patch(struct mlx5_core_dev *dev)
        return mlx5_reg_mfrl_set(dev, MLX5_MFRL_REG_RESET_LEVEL0, 0, 0, false);
 }
 
+static void mlx5_fw_reset_complete_reload(struct mlx5_core_dev *dev)
+{
+       struct mlx5_fw_reset *fw_reset = dev->priv.fw_reset;
+
+       /* if this is the driver that initiated the fw reset, devlink completed the reload */
+       if (test_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, &fw_reset->reset_flags)) {
+               complete(&fw_reset->done);
+       } else {
+               mlx5_load_one(dev, false);
+               devlink_remote_reload_actions_performed(priv_to_devlink(dev), 0,
+                                                       BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
+                                                       BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE));
+       }
+}
+
 static void mlx5_sync_reset_reload_work(struct work_struct *work)
 {
        struct mlx5_fw_reset *fw_reset = container_of(work, struct mlx5_fw_reset,
                                                      reset_reload_work);
        struct mlx5_core_dev *dev = fw_reset->dev;
+       int err;
 
        mlx5_enter_error_state(dev, true);
        mlx5_unload_one(dev, false);
-       if (mlx5_health_wait_pci_up(dev)) {
+       err = mlx5_health_wait_pci_up(dev);
+       if (err)
                mlx5_core_err(dev, "reset reload flow aborted, PCI reads still not working\n");
-               return;
-       }
-       mlx5_load_one(dev, false);
+       fw_reset->ret = err;
+       mlx5_fw_reset_complete_reload(dev);
 }
 
 static void mlx5_stop_sync_reset_poll(struct mlx5_core_dev *dev)
        mlx5_enter_error_state(dev, true);
        mlx5_unload_one(dev, false);
 done:
-       mlx5_load_one(dev, false);
+       fw_reset->ret = err;
+       mlx5_fw_reset_complete_reload(dev);
 }
 
 static void mlx5_sync_reset_abort_event(struct work_struct *work)
        return NOTIFY_OK;
 }
 
+#define MLX5_FW_RESET_TIMEOUT_MSEC 5000
+int mlx5_fw_reset_wait_reset_done(struct mlx5_core_dev *dev)
+{
+       unsigned long timeout = msecs_to_jiffies(MLX5_FW_RESET_TIMEOUT_MSEC);
+       struct mlx5_fw_reset *fw_reset = dev->priv.fw_reset;
+       int err;
+
+       if (!wait_for_completion_timeout(&fw_reset->done, timeout)) {
+               mlx5_core_warn(dev, "FW sync reset timeout after %d seconds\n",
+                              MLX5_FW_RESET_TIMEOUT_MSEC / 1000);
+               err = -ETIMEDOUT;
+               goto out;
+       }
+       err = fw_reset->ret;
+out:
+       clear_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, &fw_reset->reset_flags);
+       return err;
+}
+
 void mlx5_fw_reset_events_start(struct mlx5_core_dev *dev)
 {
        struct mlx5_fw_reset *fw_reset = dev->priv.fw_reset;
        INIT_WORK(&fw_reset->reset_now_work, mlx5_sync_reset_now_event);
        INIT_WORK(&fw_reset->reset_abort_work, mlx5_sync_reset_abort_event);
 
+       init_completion(&fw_reset->done);
        return 0;
 }