summary refs log tree commit diff
path: root/drivers
diff options
context:
space:
mode:
authorJiri Pirko <jiri@nvidia.com>2022-04-18 09:42:37 +0300
committerDavid S. Miller <davem@davemloft.net>2022-04-18 11:00:19 +0100
commitb217127e5e4ee0ecfce7c5f84cfe082238123bda (patch)
tree06e7bfa0bf2bbb5337ce662b3b3d1e279fa464e4 /drivers
parent5bade5aa4afc914dac9086500e0bfabec0d3ffbe (diff)
downloadlinux-b217127e5e4ee0ecfce7c5f84cfe082238123bda.tar.gz
mlxsw: core_linecards: Add line card objects and implement provisioning
Introduce objects for line cards and an infrastructure around that.
Use devlink_linecard_create/destroy() to register the line card with
devlink core. Implement provisioning ops with a list of supported
line cards.

Signed-off-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/ethernet/mellanox/mlxsw/Makefile3
-rw-r--r--drivers/net/ethernet/mellanox/mlxsw/core.c19
-rw-r--r--drivers/net/ethernet/mellanox/mlxsw/core.h46
-rw-r--r--drivers/net/ethernet/mellanox/mlxsw/core_linecards.c929
-rw-r--r--drivers/net/ethernet/mellanox/mlxsw/spectrum.c6
-rw-r--r--drivers/net/ethernet/mellanox/mlxsw/trap.h4
6 files changed, 1006 insertions, 1 deletions
diff --git a/drivers/net/ethernet/mellanox/mlxsw/Makefile b/drivers/net/ethernet/mellanox/mlxsw/Makefile
index 196adeb33495..1a465fd5d8b3 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/Makefile
+++ b/drivers/net/ethernet/mellanox/mlxsw/Makefile
@@ -1,7 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_MLXSW_CORE)	+= mlxsw_core.o
 mlxsw_core-objs			:= core.o core_acl_flex_keys.o \
-				   core_acl_flex_actions.o core_env.o
+				   core_acl_flex_actions.o core_env.o \
+				   core_linecards.o
 mlxsw_core-$(CONFIG_MLXSW_CORE_HWMON) += core_hwmon.o
 mlxsw_core-$(CONFIG_MLXSW_CORE_THERMAL) += core_thermal.o
 obj-$(CONFIG_MLXSW_PCI)		+= mlxsw_pci.o
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c
index b13e0f8d232a..5e1855f752d0 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/core.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/core.c
@@ -82,6 +82,7 @@ struct mlxsw_core {
 	struct mlxsw_res res;
 	struct mlxsw_hwmon *hwmon;
 	struct mlxsw_thermal *thermal;
+	struct mlxsw_linecards *linecards;
 	struct mlxsw_core_port *ports;
 	unsigned int max_ports;
 	atomic_t active_ports_count;
@@ -94,6 +95,17 @@ struct mlxsw_core {
 	/* driver_priv has to be always the last item */
 };
 
+struct mlxsw_linecards *mlxsw_core_linecards(struct mlxsw_core *mlxsw_core)
+{
+	return mlxsw_core->linecards;
+}
+
+void mlxsw_core_linecards_set(struct mlxsw_core *mlxsw_core,
+			      struct mlxsw_linecards *linecards)
+{
+	mlxsw_core->linecards = linecards;
+}
+
 #define MLXSW_PORT_MAX_PORTS_DEFAULT	0x40
 
 static u64 mlxsw_ports_occ_get(void *priv)
@@ -2145,6 +2157,10 @@ __mlxsw_core_bus_device_register(const struct mlxsw_bus_info *mlxsw_bus_info,
 	if (err)
 		goto err_fw_rev_validate;
 
+	err = mlxsw_linecards_init(mlxsw_core, mlxsw_bus_info);
+	if (err)
+		goto err_linecards_init;
+
 	err = mlxsw_core_health_init(mlxsw_core);
 	if (err)
 		goto err_health_init;
@@ -2183,6 +2199,8 @@ err_thermal_init:
 err_hwmon_init:
 	mlxsw_core_health_fini(mlxsw_core);
 err_health_init:
+	mlxsw_linecards_fini(mlxsw_core);
+err_linecards_init:
 err_fw_rev_validate:
 	if (!reload)
 		mlxsw_core_params_unregister(mlxsw_core);
@@ -2255,6 +2273,7 @@ void mlxsw_core_bus_device_unregister(struct mlxsw_core *mlxsw_core,
 	mlxsw_thermal_fini(mlxsw_core->thermal);
 	mlxsw_hwmon_fini(mlxsw_core->hwmon);
 	mlxsw_core_health_fini(mlxsw_core);
+	mlxsw_linecards_fini(mlxsw_core);
 	if (!reload)
 		mlxsw_core_params_unregister(mlxsw_core);
 	mlxsw_emad_fini(mlxsw_core);
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.h b/drivers/net/ethernet/mellanox/mlxsw/core.h
index 16ee5e90973d..44c8a7888985 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/core.h
+++ b/drivers/net/ethernet/mellanox/mlxsw/core.h
@@ -35,6 +35,11 @@ unsigned int mlxsw_core_max_ports(const struct mlxsw_core *mlxsw_core);
 
 void *mlxsw_core_driver_priv(struct mlxsw_core *mlxsw_core);
 
+struct mlxsw_linecards *mlxsw_core_linecards(struct mlxsw_core *mlxsw_core);
+
+void mlxsw_core_linecards_set(struct mlxsw_core *mlxsw_core,
+			      struct mlxsw_linecards *linecard);
+
 bool
 mlxsw_core_fw_rev_minor_subminor_validate(const struct mlxsw_fw_rev *rev,
 					  const struct mlxsw_fw_rev *req_rev);
@@ -543,4 +548,45 @@ static inline struct mlxsw_skb_cb *mlxsw_skb_cb(struct sk_buff *skb)
 	return (struct mlxsw_skb_cb *) skb->cb;
 }
 
+struct mlxsw_linecards;
+
+enum mlxsw_linecard_status_event_type {
+	MLXSW_LINECARD_STATUS_EVENT_TYPE_PROVISION,
+	MLXSW_LINECARD_STATUS_EVENT_TYPE_UNPROVISION,
+};
+
+struct mlxsw_linecard {
+	u8 slot_index;
+	struct mlxsw_linecards *linecards;
+	struct devlink_linecard *devlink_linecard;
+	struct mutex lock; /* Locks accesses to the linecard structure */
+	char name[MLXSW_REG_MDDQ_SLOT_ASCII_NAME_LEN];
+	char mbct_pl[MLXSW_REG_MBCT_LEN]; /* Too big for stack */
+	enum mlxsw_linecard_status_event_type status_event_type_to;
+	struct delayed_work status_event_to_dw;
+	u8 provisioned:1;
+	u16 hw_revision;
+	u16 ini_version;
+};
+
+struct mlxsw_linecard_types_info;
+
+struct mlxsw_linecards {
+	struct mlxsw_core *mlxsw_core;
+	const struct mlxsw_bus_info *bus_info;
+	u8 count;
+	struct mlxsw_linecard_types_info *types_info;
+	struct mlxsw_linecard linecards[];
+};
+
+static inline struct mlxsw_linecard *
+mlxsw_linecard_get(struct mlxsw_linecards *linecards, u8 slot_index)
+{
+	return &linecards->linecards[slot_index - 1];
+}
+
+int mlxsw_linecards_init(struct mlxsw_core *mlxsw_core,
+			 const struct mlxsw_bus_info *bus_info);
+void mlxsw_linecards_fini(struct mlxsw_core *mlxsw_core);
+
 #endif
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_linecards.c b/drivers/net/ethernet/mellanox/mlxsw/core_linecards.c
new file mode 100644
index 000000000000..1401f6d34635
--- /dev/null
+++ b/drivers/net/ethernet/mellanox/mlxsw/core_linecards.c
@@ -0,0 +1,929 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+/* Copyright (c) 2022 NVIDIA Corporation and Mellanox Technologies. All rights reserved */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+#include <linux/gfp.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/vmalloc.h>
+
+#include "core.h"
+
+struct mlxsw_linecard_ini_file {
+	__le16 size;
+	union {
+		u8 data[0];
+		struct {
+			__be16 hw_revision;
+			__be16 ini_version;
+			u8 __dontcare[3];
+			u8 type;
+			u8 name[20];
+		} format;
+	};
+};
+
+struct mlxsw_linecard_types_info {
+	struct mlxsw_linecard_ini_file **ini_files;
+	unsigned int count;
+	size_t data_size;
+	char *data;
+};
+
+#define MLXSW_LINECARD_STATUS_EVENT_TO (10 * MSEC_PER_SEC)
+
+static void
+mlxsw_linecard_status_event_to_schedule(struct mlxsw_linecard *linecard,
+					enum mlxsw_linecard_status_event_type status_event_type)
+{
+	cancel_delayed_work_sync(&linecard->status_event_to_dw);
+	linecard->status_event_type_to = status_event_type;
+	mlxsw_core_schedule_dw(&linecard->status_event_to_dw,
+			       msecs_to_jiffies(MLXSW_LINECARD_STATUS_EVENT_TO));
+}
+
+static void
+mlxsw_linecard_status_event_done(struct mlxsw_linecard *linecard,
+				 enum mlxsw_linecard_status_event_type status_event_type)
+{
+	if (linecard->status_event_type_to == status_event_type)
+		cancel_delayed_work_sync(&linecard->status_event_to_dw);
+}
+
+static const char *
+mlxsw_linecard_types_lookup(struct mlxsw_linecards *linecards, u8 card_type)
+{
+	struct mlxsw_linecard_types_info *types_info;
+	struct mlxsw_linecard_ini_file *ini_file;
+	int i;
+
+	types_info = linecards->types_info;
+	if (!types_info)
+		return NULL;
+	for (i = 0; i < types_info->count; i++) {
+		ini_file = linecards->types_info->ini_files[i];
+		if (ini_file->format.type == card_type)
+			return ini_file->format.name;
+	}
+	return NULL;
+}
+
+static const char *mlxsw_linecard_type_name(struct mlxsw_linecard *linecard)
+{
+	struct mlxsw_core *mlxsw_core = linecard->linecards->mlxsw_core;
+	char mddq_pl[MLXSW_REG_MDDQ_LEN];
+	int err;
+
+	mlxsw_reg_mddq_slot_name_pack(mddq_pl, linecard->slot_index);
+	err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddq), mddq_pl);
+	if (err)
+		return ERR_PTR(err);
+	mlxsw_reg_mddq_slot_name_unpack(mddq_pl, linecard->name);
+	return linecard->name;
+}
+
+static void mlxsw_linecard_provision_fail(struct mlxsw_linecard *linecard)
+{
+	linecard->provisioned = false;
+	devlink_linecard_provision_fail(linecard->devlink_linecard);
+}
+
+static int
+mlxsw_linecard_provision_set(struct mlxsw_linecard *linecard, u8 card_type,
+			     u16 hw_revision, u16 ini_version)
+{
+	struct mlxsw_linecards *linecards = linecard->linecards;
+	const char *type;
+
+	type = mlxsw_linecard_types_lookup(linecards, card_type);
+	mlxsw_linecard_status_event_done(linecard,
+					 MLXSW_LINECARD_STATUS_EVENT_TYPE_PROVISION);
+	if (!type) {
+		/* It is possible for a line card to be provisioned before
+		 * driver initialization. Due to a missing INI bundle file
+		 * or an outdated one, the queried card's type might not
+		 * be recognized by the driver. In this case, try to query
+		 * the card's name from the device.
+		 */
+		type = mlxsw_linecard_type_name(linecard);
+		if (IS_ERR(type)) {
+			mlxsw_linecard_provision_fail(linecard);
+			return PTR_ERR(type);
+		}
+	}
+	linecard->provisioned = true;
+	linecard->hw_revision = hw_revision;
+	linecard->ini_version = ini_version;
+	devlink_linecard_provision_set(linecard->devlink_linecard, type);
+	return 0;
+}
+
+static void mlxsw_linecard_provision_clear(struct mlxsw_linecard *linecard)
+{
+	mlxsw_linecard_status_event_done(linecard,
+					 MLXSW_LINECARD_STATUS_EVENT_TYPE_UNPROVISION);
+	linecard->provisioned = false;
+	devlink_linecard_provision_clear(linecard->devlink_linecard);
+}
+
+static int mlxsw_linecard_status_process(struct mlxsw_linecards *linecards,
+					 struct mlxsw_linecard *linecard,
+					 const char *mddq_pl)
+{
+	enum mlxsw_reg_mddq_slot_info_ready ready;
+	bool provisioned, sr_valid, active;
+	u16 ini_version, hw_revision;
+	u8 slot_index, card_type;
+	int err = 0;
+
+	mlxsw_reg_mddq_slot_info_unpack(mddq_pl, &slot_index, &provisioned,
+					&sr_valid, &ready, &active,
+					&hw_revision, &ini_version,
+					&card_type);
+
+	if (linecard) {
+		if (WARN_ON(slot_index != linecard->slot_index))
+			return -EINVAL;
+	} else {
+		if (WARN_ON(slot_index > linecards->count))
+			return -EINVAL;
+		linecard = mlxsw_linecard_get(linecards, slot_index);
+	}
+
+	mutex_lock(&linecard->lock);
+
+	if (provisioned && linecard->provisioned != provisioned) {
+		err = mlxsw_linecard_provision_set(linecard, card_type,
+						   hw_revision, ini_version);
+		if (err)
+			goto out;
+	}
+
+	if (!provisioned && linecard->provisioned != provisioned)
+		mlxsw_linecard_provision_clear(linecard);
+
+out:
+	mutex_unlock(&linecard->lock);
+	return err;
+}
+
+static int mlxsw_linecard_status_get_and_process(struct mlxsw_core *mlxsw_core,
+						 struct mlxsw_linecards *linecards,
+						 struct mlxsw_linecard *linecard)
+{
+	char mddq_pl[MLXSW_REG_MDDQ_LEN];
+	int err;
+
+	mlxsw_reg_mddq_slot_info_pack(mddq_pl, linecard->slot_index, false);
+	err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mddq), mddq_pl);
+	if (err)
+		return err;
+
+	return mlxsw_linecard_status_process(linecards, linecard, mddq_pl);
+}
+
+static const char * const mlxsw_linecard_status_event_type_name[] = {
+	[MLXSW_LINECARD_STATUS_EVENT_TYPE_PROVISION] = "provision",
+	[MLXSW_LINECARD_STATUS_EVENT_TYPE_UNPROVISION] = "unprovision",
+};
+
+static void mlxsw_linecard_status_event_to_work(struct work_struct *work)
+{
+	struct mlxsw_linecard *linecard =
+		container_of(work, struct mlxsw_linecard,
+			     status_event_to_dw.work);
+
+	mutex_lock(&linecard->lock);
+	dev_err(linecard->linecards->bus_info->dev, "linecard %u: Timeout reached waiting on %s status event",
+		linecard->slot_index,
+		mlxsw_linecard_status_event_type_name[linecard->status_event_type_to]);
+	mlxsw_linecard_provision_fail(linecard);
+	mutex_unlock(&linecard->lock);
+}
+
+static int __mlxsw_linecard_fix_fsm_state(struct mlxsw_linecard *linecard)
+{
+	dev_info(linecard->linecards->bus_info->dev, "linecard %u: Clearing FSM state error",
+		 linecard->slot_index);
+	mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index,
+			    MLXSW_REG_MBCT_OP_CLEAR_ERRORS, false);
+	return mlxsw_reg_write(linecard->linecards->mlxsw_core,
+			       MLXSW_REG(mbct), linecard->mbct_pl);
+}
+
+static int mlxsw_linecard_fix_fsm_state(struct mlxsw_linecard *linecard,
+					enum mlxsw_reg_mbct_fsm_state fsm_state)
+{
+	if (fsm_state != MLXSW_REG_MBCT_FSM_STATE_ERROR)
+		return 0;
+	return __mlxsw_linecard_fix_fsm_state(linecard);
+}
+
+static int
+mlxsw_linecard_query_ini_status(struct mlxsw_linecard *linecard,
+				enum mlxsw_reg_mbct_status *status,
+				enum mlxsw_reg_mbct_fsm_state *fsm_state,
+				struct netlink_ext_ack *extack)
+{
+	int err;
+
+	mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index,
+			    MLXSW_REG_MBCT_OP_QUERY_STATUS, false);
+	err = mlxsw_reg_query(linecard->linecards->mlxsw_core, MLXSW_REG(mbct),
+			      linecard->mbct_pl);
+	if (err) {
+		NL_SET_ERR_MSG_MOD(extack, "Failed to query linecard INI status");
+		return err;
+	}
+	mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL, status, fsm_state);
+	return err;
+}
+
+static int
+mlxsw_linecard_ini_transfer(struct mlxsw_core *mlxsw_core,
+			    struct mlxsw_linecard *linecard,
+			    const struct mlxsw_linecard_ini_file *ini_file,
+			    struct netlink_ext_ack *extack)
+{
+	enum mlxsw_reg_mbct_fsm_state fsm_state;
+	enum mlxsw_reg_mbct_status status;
+	size_t size_left;
+	const u8 *data;
+	int err;
+
+	size_left = le16_to_cpu(ini_file->size);
+	data = ini_file->data;
+	while (size_left) {
+		size_t data_size = MLXSW_REG_MBCT_DATA_LEN;
+		bool is_last = false;
+
+		if (size_left <= MLXSW_REG_MBCT_DATA_LEN) {
+			data_size = size_left;
+			is_last = true;
+		}
+
+		mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index,
+				    MLXSW_REG_MBCT_OP_DATA_TRANSFER, false);
+		mlxsw_reg_mbct_dt_pack(linecard->mbct_pl, data_size,
+				       is_last, data);
+		err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mbct),
+				      linecard->mbct_pl);
+		if (err) {
+			NL_SET_ERR_MSG_MOD(extack, "Failed to issue linecard INI data transfer");
+			return err;
+		}
+		mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL,
+				      &status, &fsm_state);
+		if ((!is_last && status != MLXSW_REG_MBCT_STATUS_PART_DATA) ||
+		    (is_last && status != MLXSW_REG_MBCT_STATUS_LAST_DATA)) {
+			NL_SET_ERR_MSG_MOD(extack, "Failed to transfer linecard INI data");
+			mlxsw_linecard_fix_fsm_state(linecard, fsm_state);
+			return -EINVAL;
+		}
+		size_left -= data_size;
+		data += data_size;
+	}
+
+	return 0;
+}
+
+static int
+mlxsw_linecard_ini_erase(struct mlxsw_core *mlxsw_core,
+			 struct mlxsw_linecard *linecard,
+			 struct netlink_ext_ack *extack)
+{
+	enum mlxsw_reg_mbct_fsm_state fsm_state;
+	enum mlxsw_reg_mbct_status status;
+	int err;
+
+	mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index,
+			    MLXSW_REG_MBCT_OP_ERASE_INI_IMAGE, false);
+	err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mbct),
+			      linecard->mbct_pl);
+	if (err) {
+		NL_SET_ERR_MSG_MOD(extack, "Failed to issue linecard INI erase");
+		return err;
+	}
+	mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL, &status, &fsm_state);
+	switch (status) {
+	case MLXSW_REG_MBCT_STATUS_ERASE_COMPLETE:
+		break;
+	default:
+		/* Should not happen */
+		fallthrough;
+	case MLXSW_REG_MBCT_STATUS_ERASE_FAILED:
+		NL_SET_ERR_MSG_MOD(extack, "Failed to erase linecard INI");
+		goto fix_fsm_err_out;
+	case MLXSW_REG_MBCT_STATUS_ERROR_INI_IN_USE:
+		NL_SET_ERR_MSG_MOD(extack, "Failed to erase linecard INI while being used");
+		goto fix_fsm_err_out;
+	}
+	return 0;
+
+fix_fsm_err_out:
+	mlxsw_linecard_fix_fsm_state(linecard, fsm_state);
+	return -EINVAL;
+}
+
+static void mlxsw_linecard_bct_process(struct mlxsw_core *mlxsw_core,
+				       const char *mbct_pl)
+{
+	struct mlxsw_linecards *linecards = mlxsw_core_linecards(mlxsw_core);
+	enum mlxsw_reg_mbct_fsm_state fsm_state;
+	enum mlxsw_reg_mbct_status status;
+	struct mlxsw_linecard *linecard;
+	u8 slot_index;
+
+	mlxsw_reg_mbct_unpack(mbct_pl, &slot_index, &status, &fsm_state);
+	if (WARN_ON(slot_index > linecards->count))
+		return;
+	linecard = mlxsw_linecard_get(linecards, slot_index);
+	mutex_lock(&linecard->lock);
+	if (status == MLXSW_REG_MBCT_STATUS_ACTIVATION_FAILED) {
+		dev_err(linecards->bus_info->dev, "linecard %u: Failed to activate INI",
+			linecard->slot_index);
+		goto fix_fsm_out;
+	}
+	mutex_unlock(&linecard->lock);
+	return;
+
+fix_fsm_out:
+	mlxsw_linecard_fix_fsm_state(linecard, fsm_state);
+	mlxsw_linecard_provision_fail(linecard);
+	mutex_unlock(&linecard->lock);
+}
+
+static int
+mlxsw_linecard_ini_activate(struct mlxsw_core *mlxsw_core,
+			    struct mlxsw_linecard *linecard,
+			    struct netlink_ext_ack *extack)
+{
+	enum mlxsw_reg_mbct_fsm_state fsm_state;
+	enum mlxsw_reg_mbct_status status;
+	int err;
+
+	mlxsw_reg_mbct_pack(linecard->mbct_pl, linecard->slot_index,
+			    MLXSW_REG_MBCT_OP_ACTIVATE, true);
+	err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mbct), linecard->mbct_pl);
+	if (err) {
+		NL_SET_ERR_MSG_MOD(extack, "Failed to issue linecard INI activation");
+		return err;
+	}
+	mlxsw_reg_mbct_unpack(linecard->mbct_pl, NULL, &status, &fsm_state);
+	if (status == MLXSW_REG_MBCT_STATUS_ACTIVATION_FAILED) {
+		NL_SET_ERR_MSG_MOD(extack, "Failed to activate linecard INI");
+		goto fix_fsm_err_out;
+	}
+
+	return 0;
+
+fix_fsm_err_out:
+	mlxsw_linecard_fix_fsm_state(linecard, fsm_state);
+	return -EINVAL;
+}
+
+#define MLXSW_LINECARD_INI_WAIT_RETRIES 10
+#define MLXSW_LINECARD_INI_WAIT_MS 500
+
+static int
+mlxsw_linecard_ini_in_use_wait(struct mlxsw_core *mlxsw_core,
+			       struct mlxsw_linecard *linecard,
+			       struct netlink_ext_ack *extack)
+{
+	enum mlxsw_reg_mbct_fsm_state fsm_state;
+	enum mlxsw_reg_mbct_status status;
+	unsigned int ini_wait_retries = 0;
+	int err;
+
+query_ini_status:
+	err = mlxsw_linecard_query_ini_status(linecard, &status,
+					      &fsm_state, extack);
+	if (err)
+		return err;
+
+	switch (fsm_state) {
+	case MLXSW_REG_MBCT_FSM_STATE_INI_IN_USE:
+		if (ini_wait_retries++ > MLXSW_LINECARD_INI_WAIT_RETRIES) {
+			NL_SET_ERR_MSG_MOD(extack, "Failed to wait for linecard INI to be unused");
+			return -EINVAL;
+		}
+		mdelay(MLXSW_LINECARD_INI_WAIT_MS);
+		goto query_ini_status;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int mlxsw_linecard_provision(struct devlink_linecard *devlink_linecard,
+				    void *priv, const char *type,
+				    const void *type_priv,
+				    struct netlink_ext_ack *extack)
+{
+	const struct mlxsw_linecard_ini_file *ini_file = type_priv;
+	struct mlxsw_linecard *linecard = priv;
+	struct mlxsw_core *mlxsw_core;
+	int err;
+
+	mutex_lock(&linecard->lock);
+
+	mlxsw_core = linecard->linecards->mlxsw_core;
+
+	err = mlxsw_linecard_ini_erase(mlxsw_core, linecard, extack);
+	if (err)
+		goto err_out;
+
+	err = mlxsw_linecard_ini_transfer(mlxsw_core, linecard,
+					  ini_file, extack);
+	if (err)
+		goto err_out;
+
+	mlxsw_linecard_status_event_to_schedule(linecard,
+						MLXSW_LINECARD_STATUS_EVENT_TYPE_PROVISION);
+	err = mlxsw_linecard_ini_activate(mlxsw_core, linecard, extack);
+	if (err)
+		goto err_out;
+
+	goto out;
+
+err_out:
+	mlxsw_linecard_provision_fail(linecard);
+out:
+	mutex_unlock(&linecard->lock);
+	return err;
+}
+
+static int mlxsw_linecard_unprovision(struct devlink_linecard *devlink_linecard,
+				      void *priv,
+				      struct netlink_ext_ack *extack)
+{
+	struct mlxsw_linecard *linecard = priv;
+	struct mlxsw_core *mlxsw_core;
+	int err;
+
+	mutex_lock(&linecard->lock);
+
+	mlxsw_core = linecard->linecards->mlxsw_core;
+
+	err = mlxsw_linecard_ini_in_use_wait(mlxsw_core, linecard, extack);
+	if (err)
+		goto err_out;
+
+	mlxsw_linecard_status_event_to_schedule(linecard,
+						MLXSW_LINECARD_STATUS_EVENT_TYPE_UNPROVISION);
+	err = mlxsw_linecard_ini_erase(mlxsw_core, linecard, extack);
+	if (err)
+		goto err_out;
+
+	goto out;
+
+err_out:
+	mlxsw_linecard_provision_fail(linecard);
+out:
+	mutex_unlock(&linecard->lock);
+	return err;
+}
+
+static bool mlxsw_linecard_same_provision(struct devlink_linecard *devlink_linecard,
+					  void *priv, const char *type,
+					  const void *type_priv)
+{
+	const struct mlxsw_linecard_ini_file *ini_file = type_priv;
+	struct mlxsw_linecard *linecard = priv;
+	bool ret;
+
+	mutex_lock(&linecard->lock);
+	ret = linecard->hw_revision == be16_to_cpu(ini_file->format.hw_revision) &&
+	      linecard->ini_version == be16_to_cpu(ini_file->format.ini_version);
+	mutex_unlock(&linecard->lock);
+	return ret;
+}
+
+static unsigned int
+mlxsw_linecard_types_count(struct devlink_linecard *devlink_linecard,
+			   void *priv)
+{
+	struct mlxsw_linecard *linecard = priv;
+
+	return linecard->linecards->types_info ?
+	       linecard->linecards->types_info->count : 0;
+}
+
+static void mlxsw_linecard_types_get(struct devlink_linecard *devlink_linecard,
+				     void *priv, unsigned int index,
+				     const char **type, const void **type_priv)
+{
+	struct mlxsw_linecard_types_info *types_info;
+	struct mlxsw_linecard_ini_file *ini_file;
+	struct mlxsw_linecard *linecard = priv;
+
+	types_info = linecard->linecards->types_info;
+	if (WARN_ON_ONCE(!types_info))
+		return;
+	ini_file = types_info->ini_files[index];
+	*type = ini_file->format.name;
+	*type_priv = ini_file;
+}
+
+static const struct devlink_linecard_ops mlxsw_linecard_ops = {
+	.provision = mlxsw_linecard_provision,
+	.unprovision = mlxsw_linecard_unprovision,
+	.same_provision = mlxsw_linecard_same_provision,
+	.types_count = mlxsw_linecard_types_count,
+	.types_get = mlxsw_linecard_types_get,
+};
+
+struct mlxsw_linecard_status_event {
+	struct mlxsw_core *mlxsw_core;
+	char mddq_pl[MLXSW_REG_MDDQ_LEN];
+	struct work_struct work;
+};
+
+static void mlxsw_linecard_status_event_work(struct work_struct *work)
+{
+	struct mlxsw_linecard_status_event *event;
+	struct mlxsw_linecards *linecards;
+	struct mlxsw_core *mlxsw_core;
+
+	event = container_of(work, struct mlxsw_linecard_status_event, work);
+	mlxsw_core = event->mlxsw_core;
+	linecards = mlxsw_core_linecards(mlxsw_core);
+	mlxsw_linecard_status_process(linecards, NULL, event->mddq_pl);
+	kfree(event);
+}
+
+static void
+mlxsw_linecard_status_listener_func(const struct mlxsw_reg_info *reg,
+				    char *mddq_pl, void *priv)
+{
+	struct mlxsw_linecard_status_event *event;
+	struct mlxsw_core *mlxsw_core = priv;
+
+	event = kmalloc(sizeof(*event), GFP_ATOMIC);
+	if (!event)
+		return;
+	event->mlxsw_core = mlxsw_core;
+	memcpy(event->mddq_pl, mddq_pl, sizeof(event->mddq_pl));
+	INIT_WORK(&event->work, mlxsw_linecard_status_event_work);
+	mlxsw_core_schedule_work(&event->work);
+}
+
+struct mlxsw_linecard_bct_event {
+	struct mlxsw_core *mlxsw_core;
+	char mbct_pl[MLXSW_REG_MBCT_LEN];
+	struct work_struct work;
+};
+
+static void mlxsw_linecard_bct_event_work(struct work_struct *work)
+{
+	struct mlxsw_linecard_bct_event *event;
+	struct mlxsw_core *mlxsw_core;
+
+	event = container_of(work, struct mlxsw_linecard_bct_event, work);
+	mlxsw_core = event->mlxsw_core;
+	mlxsw_linecard_bct_process(mlxsw_core, event->mbct_pl);
+	kfree(event);
+}
+
+static void
+mlxsw_linecard_bct_listener_func(const struct mlxsw_reg_info *reg,
+				 char *mbct_pl, void *priv)
+{
+	struct mlxsw_linecard_bct_event *event;
+	struct mlxsw_core *mlxsw_core = priv;
+
+	event = kmalloc(sizeof(*event), GFP_ATOMIC);
+	if (!event)
+		return;
+	event->mlxsw_core = mlxsw_core;
+	memcpy(event->mbct_pl, mbct_pl, sizeof(event->mbct_pl));
+	INIT_WORK(&event->work, mlxsw_linecard_bct_event_work);
+	mlxsw_core_schedule_work(&event->work);
+}
+
+static const struct mlxsw_listener mlxsw_linecard_listener[] = {
+	MLXSW_CORE_EVENTL(mlxsw_linecard_status_listener_func, DSDSC),
+	MLXSW_CORE_EVENTL(mlxsw_linecard_bct_listener_func, BCTOE),
+};
+
+static int mlxsw_linecard_event_delivery_set(struct mlxsw_core *mlxsw_core,
+					     struct mlxsw_linecard *linecard,
+					     bool enable)
+{
+	char mddq_pl[MLXSW_REG_MDDQ_LEN];
+
+	mlxsw_reg_mddq_slot_info_pack(mddq_pl, linecard->slot_index, enable);
+	return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mddq), mddq_pl);
+}
+
+static int mlxsw_linecard_init(struct mlxsw_core *mlxsw_core,
+			       struct mlxsw_linecards *linecards,
+			       u8 slot_index)
+{
+	struct devlink_linecard *devlink_linecard;
+	struct mlxsw_linecard *linecard;
+	int err;
+
+	linecard = mlxsw_linecard_get(linecards, slot_index);
+	linecard->slot_index = slot_index;
+	linecard->linecards = linecards;
+	mutex_init(&linecard->lock);
+
+	devlink_linecard = devlink_linecard_create(priv_to_devlink(mlxsw_core),
+						   slot_index, &mlxsw_linecard_ops,
+						   linecard);
+	if (IS_ERR(devlink_linecard)) {
+		err = PTR_ERR(devlink_linecard);
+		goto err_devlink_linecard_create;
+	}
+	linecard->devlink_linecard = devlink_linecard;
+	INIT_DELAYED_WORK(&linecard->status_event_to_dw,
+			  &mlxsw_linecard_status_event_to_work);
+
+	err = mlxsw_linecard_event_delivery_set(mlxsw_core, linecard, true);
+	if (err)
+		goto err_event_delivery_set;
+
+	err = mlxsw_linecard_status_get_and_process(mlxsw_core, linecards,
+						    linecard);
+	if (err)
+		goto err_status_get_and_process;
+
+	return 0;
+
+err_status_get_and_process:
+	mlxsw_linecard_event_delivery_set(mlxsw_core, linecard, false);
+err_event_delivery_set:
+	devlink_linecard_destroy(linecard->devlink_linecard);
+err_devlink_linecard_create:
+	mutex_destroy(&linecard->lock);
+	return err;
+}
+
+static void mlxsw_linecard_fini(struct mlxsw_core *mlxsw_core,
+				struct mlxsw_linecards *linecards,
+				u8 slot_index)
+{
+	struct mlxsw_linecard *linecard;
+
+	linecard = mlxsw_linecard_get(linecards, slot_index);
+	mlxsw_linecard_event_delivery_set(mlxsw_core, linecard, false);
+	cancel_delayed_work_sync(&linecard->status_event_to_dw);
+	/* Make sure all scheduled events are processed */
+	mlxsw_core_flush_owq();
+	devlink_linecard_destroy(linecard->devlink_linecard);
+	mutex_destroy(&linecard->lock);
+}
+
+/*       LINECARDS INI BUNDLE FILE
+ *  +----------------------------------+
+ *  |        MAGIC ("NVLCINI+")        |
+ *  +----------------------------------+     +--------------------+
+ *  |  INI 0                           +---> | __le16 size        |
+ *  +----------------------------------+     | __be16 hw_revision |
+ *  |  INI 1                           |     | __be16 ini_version |
+ *  +----------------------------------+     | u8 __dontcare[3]   |
+ *  |  ...                             |     | u8 type            |
+ *  +----------------------------------+     | u8 name[20]        |
+ *  |  INI N                           |     | ...                |
+ *  +----------------------------------+     +--------------------+
+ */
+
+#define MLXSW_LINECARDS_INI_BUNDLE_MAGIC "NVLCINI+"
+
+static int
+mlxsw_linecard_types_file_validate(struct mlxsw_linecards *linecards,
+				   struct mlxsw_linecard_types_info *types_info)
+{
+	size_t magic_size = strlen(MLXSW_LINECARDS_INI_BUNDLE_MAGIC);
+	struct mlxsw_linecard_ini_file *ini_file;
+	size_t size = types_info->data_size;
+	const u8 *data = types_info->data;
+	unsigned int count = 0;
+	u16 ini_file_size;
+
+	if (size < magic_size) {
+		dev_warn(linecards->bus_info->dev, "Invalid linecards INIs file size, smaller than magic size\n");
+		return -EINVAL;
+	}
+	if (memcmp(data, MLXSW_LINECARDS_INI_BUNDLE_MAGIC, magic_size)) {
+		dev_warn(linecards->bus_info->dev, "Invalid linecards INIs file magic pattern\n");
+		return -EINVAL;
+	}
+
+	data += magic_size;
+	size -= magic_size;
+
+	while (size > 0) {
+		if (size < sizeof(*ini_file)) {
+			dev_warn(linecards->bus_info->dev, "Linecards INIs file contains INI which is smaller than bare minimum\n");
+			return -EINVAL;
+		}
+		ini_file = (struct mlxsw_linecard_ini_file *) data;
+		ini_file_size = le16_to_cpu(ini_file->size);
+		if (ini_file_size + sizeof(__le16) > size) {
+			dev_warn(linecards->bus_info->dev, "Linecards INIs file appears to be truncated\n");
+			return -EINVAL;
+		}
+		if (ini_file_size % 4) {
+			dev_warn(linecards->bus_info->dev, "Linecards INIs file contains INI with invalid size\n");
+			return -EINVAL;
+		}
+		data += ini_file_size + sizeof(__le16);
+		size -= ini_file_size + sizeof(__le16);
+		count++;
+	}
+	if (!count) {
+		dev_warn(linecards->bus_info->dev, "Linecards INIs file does not contain any INI\n");
+		return -EINVAL;
+	}
+	types_info->count = count;
+	return 0;
+}
+
+static void
+mlxsw_linecard_types_file_parse(struct mlxsw_linecard_types_info *types_info)
+{
+	size_t magic_size = strlen(MLXSW_LINECARDS_INI_BUNDLE_MAGIC);
+	size_t size = types_info->data_size - magic_size;
+	const u8 *data = types_info->data + magic_size;
+	struct mlxsw_linecard_ini_file *ini_file;
+	unsigned int count = 0;
+	u16 ini_file_size;
+	int i;
+
+	while (size) {
+		ini_file = (struct mlxsw_linecard_ini_file *) data;
+		ini_file_size = le16_to_cpu(ini_file->size);
+		for (i = 0; i < ini_file_size / 4; i++) {
+			u32 *val = &((u32 *) ini_file->data)[i];
+
+			*val = swab32(*val);
+		}
+		types_info->ini_files[count] = ini_file;
+		data += ini_file_size + sizeof(__le16);
+		size -= ini_file_size + sizeof(__le16);
+		count++;
+	}
+}
+
+#define MLXSW_LINECARDS_INI_BUNDLE_FILENAME_FMT \
+	"mellanox/lc_ini_bundle_%u_%u.bin"
+#define MLXSW_LINECARDS_INI_BUNDLE_FILENAME_LEN \
+	(sizeof(MLXSW_LINECARDS_INI_BUNDLE_FILENAME_FMT) + 4)
+
+static int mlxsw_linecard_types_init(struct mlxsw_core *mlxsw_core,
+				     struct mlxsw_linecards *linecards)
+{
+	const struct mlxsw_fw_rev *rev = &linecards->bus_info->fw_rev;
+	char filename[MLXSW_LINECARDS_INI_BUNDLE_FILENAME_LEN];
+	struct mlxsw_linecard_types_info *types_info;
+	const struct firmware *firmware;
+	int err;
+
+	err = snprintf(filename, sizeof(filename),
+		       MLXSW_LINECARDS_INI_BUNDLE_FILENAME_FMT,
+		       rev->minor, rev->subminor);
+	WARN_ON(err >= sizeof(filename));
+
+	err = request_firmware_direct(&firmware, filename,
+				      linecards->bus_info->dev);
+	if (err) {
+		dev_warn(linecards->bus_info->dev, "Could not request linecards INI file \"%s\", provisioning will not be possible\n",
+			 filename);
+		return 0;
+	}
+
+	types_info = kzalloc(sizeof(*types_info), GFP_KERNEL);
+	if (!types_info) {
+		release_firmware(firmware);
+		return -ENOMEM;
+	}
+	linecards->types_info = types_info;
+
+	types_info->data_size = firmware->size;
+	types_info->data = vmalloc(types_info->data_size);
+	if (!types_info->data) {
+		err = -ENOMEM;
+		release_firmware(firmware);
+		goto err_data_alloc;
+	}
+	memcpy(types_info->data, firmware->data, types_info->data_size);
+	release_firmware(firmware);
+
+	err = mlxsw_linecard_types_file_validate(linecards, types_info);
+	if (err) {
+		err = 0;
+		goto err_type_file_file_validate;
+	}
+
+	types_info->ini_files = kmalloc_array(types_info->count,
+					      sizeof(struct mlxsw_linecard_ini_file),
+					      GFP_KERNEL);
+	if (!types_info->ini_files) {
+		err = -ENOMEM;
+		goto err_ini_files_alloc;
+	}
+
+	mlxsw_linecard_types_file_parse(types_info);
+
+	return 0;
+
+err_ini_files_alloc:
+err_type_file_file_validate:
+	vfree(types_info->data);
+err_data_alloc:
+	kfree(types_info);
+	return err;
+}
+
+static void mlxsw_linecard_types_fini(struct mlxsw_linecards *linecards)
+{
+	struct mlxsw_linecard_types_info *types_info = linecards->types_info;
+
+	if (!types_info)
+		return;
+	kfree(types_info->ini_files);
+	vfree(types_info->data);
+	kfree(types_info);
+}
+
+int mlxsw_linecards_init(struct mlxsw_core *mlxsw_core,
+			 const struct mlxsw_bus_info *bus_info)
+{
+	char mgpir_pl[MLXSW_REG_MGPIR_LEN];
+	struct mlxsw_linecards *linecards;
+	u8 slot_count;
+	int err;
+	int i;
+
+	mlxsw_reg_mgpir_pack(mgpir_pl, 0);
+	err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mgpir), mgpir_pl);
+	if (err)
+		return err;
+
+	mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL,
+			       NULL, &slot_count);
+	if (!slot_count)
+		return 0;
+
+	linecards = vzalloc(struct_size(linecards, linecards, slot_count));
+	if (!linecards)
+		return -ENOMEM;
+	linecards->count = slot_count;
+	linecards->mlxsw_core = mlxsw_core;
+	linecards->bus_info = bus_info;
+
+	err = mlxsw_linecard_types_init(mlxsw_core, linecards);
+	if (err)
+		goto err_types_init;
+
+	err = mlxsw_core_traps_register(mlxsw_core, mlxsw_linecard_listener,
+					ARRAY_SIZE(mlxsw_linecard_listener),
+					mlxsw_core);
+	if (err)
+		goto err_traps_register;
+
+	mlxsw_core_linecards_set(mlxsw_core, linecards);
+
+	for (i = 0; i < linecards->count; i++) {
+		err = mlxsw_linecard_init(mlxsw_core, linecards, i + 1);
+		if (err)
+			goto err_linecard_init;
+	}
+
+	return 0;
+
+err_linecard_init:
+	for (i--; i >= 0; i--)
+		mlxsw_linecard_fini(mlxsw_core, linecards, i + 1);
+	mlxsw_core_traps_unregister(mlxsw_core, mlxsw_linecard_listener,
+				    ARRAY_SIZE(mlxsw_linecard_listener),
+				    mlxsw_core);
+err_traps_register:
+	mlxsw_linecard_types_fini(linecards);
+err_types_init:
+	vfree(linecards);
+	return err;
+}
+
+void mlxsw_linecards_fini(struct mlxsw_core *mlxsw_core)
+{
+	struct mlxsw_linecards *linecards = mlxsw_core_linecards(mlxsw_core);
+	int i;
+
+	if (!linecards)
+		return;
+	for (i = 0; i < linecards->count; i++)
+		mlxsw_linecard_fini(mlxsw_core, linecards, i + 1);
+	mlxsw_core_traps_unregister(mlxsw_core, mlxsw_linecard_listener,
+				    ARRAY_SIZE(mlxsw_linecard_listener),
+				    mlxsw_core);
+	mlxsw_linecard_types_fini(linecards);
+	vfree(linecards);
+}
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c
index c3457a216642..b4e064bd77bc 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c
@@ -89,6 +89,11 @@ static const struct mlxsw_fw_rev mlxsw_sp3_fw_rev = {
 	"." __stringify(MLXSW_SP_FWREV_MINOR) \
 	"." __stringify(MLXSW_SP_FWREV_SUBMINOR) ".mfa2"
 
+#define MLXSW_SP_LINECARDS_INI_BUNDLE_FILENAME \
+	"mellanox/lc_ini_bundle_" \
+	__stringify(MLXSW_SP_FWREV_MINOR) "_" \
+	__stringify(MLXSW_SP_FWREV_SUBMINOR) ".bin"
+
 static const char mlxsw_sp1_driver_name[] = "mlxsw_spectrum";
 static const char mlxsw_sp2_driver_name[] = "mlxsw_spectrum2";
 static const char mlxsw_sp3_driver_name[] = "mlxsw_spectrum3";
@@ -5159,3 +5164,4 @@ MODULE_DEVICE_TABLE(pci, mlxsw_sp4_pci_id_table);
 MODULE_FIRMWARE(MLXSW_SP1_FW_FILENAME);
 MODULE_FIRMWARE(MLXSW_SP2_FW_FILENAME);
 MODULE_FIRMWARE(MLXSW_SP3_FW_FILENAME);
+MODULE_FIRMWARE(MLXSW_SP_LINECARDS_INI_BUNDLE_FILENAME);
diff --git a/drivers/net/ethernet/mellanox/mlxsw/trap.h b/drivers/net/ethernet/mellanox/mlxsw/trap.h
index 7405c400f09b..d888498aed33 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/trap.h
+++ b/drivers/net/ethernet/mellanox/mlxsw/trap.h
@@ -133,6 +133,10 @@ enum mlxsw_event_trap_id {
 	MLXSW_TRAP_ID_PTP_ING_FIFO = 0x2D,
 	/* PTP Egress FIFO has a new entry */
 	MLXSW_TRAP_ID_PTP_EGR_FIFO = 0x2E,
+	/* Downstream Device Status Change */
+	MLXSW_TRAP_ID_DSDSC = 0x321,
+	/* Binary Code Transfer Operation Executed Event */
+	MLXSW_TRAP_ID_BCTOE = 0x322,
 	/* Port mapping change */
 	MLXSW_TRAP_ID_PMLPE = 0x32E,
 };