mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Doug Ledford <doug.ledford@hpe.com>
To: "Slingshot Open Source Team"
	<slingshot-os-team@groups.ext.hpe.com>,
	"Nathan Chancellor" <nathan@kernel.org>,
	"Nicolas Schier" <nsc@kernel.org>,
	"Richard Cochran" <richardcochran@gmail.com>,
	"Andrew Lunn" <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@kernel.org>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Sumit Semwal" <sumit.semwal@linaro.org>,
	"Christian König" <christian.koenig@amd.com>,
	"Guenter Roeck" <linux@roeck-us.net>
Cc: <linux-kernel@vger.kernel.org>, <netdev@vger.kernel.org>,
	<linux-kbuild@vger.kernel.org>,
	Doug Ledford <doug.ledford@hpe.com>,
	"Iraimani P" <iraimani.pavadai@hpe.com>,
	Mamila Santosh <santosh-kumar.mamila@hpe.com>
Subject: [PATCH 03/23] net: hpe: sbl: add the core instance and configuration layer
Date: Fri, 30 Oct 2026 13:58:59 -0700	[thread overview]
Message-ID: <20261030-cxi-netdev-submission-v1-3-8c2c391cc283@hpe.com> (raw)
In-Reply-To: <20261030-cxi-netdev-submission-v1-0-8c2c391cc283@hpe.com>

Add the SBL instance management: allocation and teardown of an SBL
instance for a NIC, the configuration list, timers, debug helpers,
and the NIC specific module glue.  Everything else in SBL hangs off
the instance created here.

checkpatch --strict (100 columns) still reports the following in this
patch. They need coordination with the internal repositories this code
is maintained in and will be fixed in V2:
- WARNING: LONG_LINE (8), ALLOC_WITH_SIZEOF (3), TYPO_SPELLING (1)
- CHECK: ALLOC_SIZEOF_STRUCT (4), BRACES (2), PARENTHESIS_ALIGNMENT (2),
  MACRO_ARG_PRECEDENCE (1), MACRO_ARG_REUSE (1), OPEN_ENDED_LINE (1)

Co-developed-by: Iraimani P <iraimani.pavadai@hpe.com>
Signed-off-by: Iraimani P <iraimani.pavadai@hpe.com>
Co-developed-by: Mamila Santosh <santosh-kumar.mamila@hpe.com>
Signed-off-by: Mamila Santosh <santosh-kumar.mamila@hpe.com>
Signed-off-by: Doug Ledford <doug.ledford@hpe.com>
---
 drivers/net/ethernet/hpe/sbl/sbl_debug.c      | 167 ++++++++
 drivers/net/ethernet/hpe/sbl/sbl_inst.c       | 529 ++++++++++++++++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_inst_nic.c   |  70 ++++
 drivers/net/ethernet/hpe/sbl/sbl_internal.c   |  23 ++
 drivers/net/ethernet/hpe/sbl/sbl_misc.c       | 505 ++++++++++++++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_module_nic.c | 132 +++++++
 drivers/net/ethernet/hpe/sbl/sbl_test.c       | 406 ++++++++++++++++++++
 drivers/net/ethernet/hpe/sbl/sbl_timers.c     | 228 +++++++++++
 8 files changed, 2060 insertions(+)

diff --git a/drivers/net/ethernet/hpe/sbl/sbl_debug.c b/drivers/net/ethernet/hpe/sbl/sbl_debug.c
new file mode 100644
index 000000000000..2ddc07e6ab03
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_debug.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2022 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include "sbl_internal.h"
+
+/**
+ * sbl_debug_clear_config() - Clear debug config
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Set debug_config to 0
+ */
+void sbl_debug_clear_config(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	atomic_set(&link->debug_config, 0);
+}
+EXPORT_SYMBOL(sbl_debug_clear_config);
+
+/**
+ * sbl_debug_update_config() - Update debug config
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @clear_flags: Flag which is used to clear a config
+ * @set_flags: Flag which is used to set a config
+ *
+ * A 'new' value is picked based on flags provided.
+ * debug_config will have 'new' value if debug_old
+ * and 'old' are the same.
+ */
+void sbl_debug_update_config(struct sbl_inst *sbl, int port_num, u32 clear_flags, u32 set_flags)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 old;
+	u32 new;
+
+	do {
+		old = atomic_read(&link->debug_config);
+		new = (old & ~clear_flags) | set_flags;
+	} while (atomic_cmpxchg(&link->debug_config, old, new) != old);
+}
+EXPORT_SYMBOL(sbl_debug_update_config);
+
+/**
+ * sbl_debug_get_config() - Get value of debug config
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @config: param used to get the value of the config
+ *
+ * 'config' will have debug_config value
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_debug_get_config(struct sbl_inst *sbl, int port_num, u32 *config)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	if (!config)
+		return -EINVAL;
+
+	*config = atomic_read(&link->debug_config);
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_debug_get_config);
+
+bool sbl_debug_option(struct sbl_inst *sbl, int port_num, u32 flags)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	bool flag_set;
+
+	flag_set = atomic_read(&link->debug_config) & flags;
+
+	return flag_set;
+}
+
+#ifdef CONFIG_SYSFS
+int sbl_debug_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 debug_config = atomic_read(&link->debug_config);
+	int s = 0;
+
+	if (!debug_config)
+		return 0;
+
+	s += scnprintf(buf + s, size - s, "base link debug: ");
+	if (debug_config & SBL_DEBUG_TRACE_LINK_DOWN)
+		s += scnprintf(buf + s, size - s, "async-down ");
+	if (debug_config & SBL_DEBUG_IGNORE_HISER)
+		s += scnprintf(buf + s, size - s, "ignore-hiser ");
+	if (debug_config & SBL_DEBUG_INHIBIT_CLEANUP)
+		s += scnprintf(buf + s, size - s, "inhibit-cleanup ");
+	if (debug_config & SBL_DEBUG_INHIBIT_SPLL_RESET)
+		s += scnprintf(buf + s, size - s, "inhibit-spll-reset ");
+
+	if (debug_config & SBL_DEBUG_BAD_PARAM_1)
+		s += scnprintf(buf + s, size - s, "bad-param-1 ");
+	if (debug_config & SBL_DEBUG_BAD_PARAM_2)
+		s += scnprintf(buf + s, size - s, "bad-param-2 ");
+	if (debug_config & SBL_DEBUG_INHIBIT_RELOAD_FW)
+		s += scnprintf(buf + s, size - s, "inhibit-reload-fw ");
+	if (debug_config & SBL_DEBUG_FORCE_RELOAD_FW)
+		s += scnprintf(buf + s, size - s, "force-reload-fw ");
+
+	if (debug_config & SBL_DEBUG_FORCE_MAX_EFFORT)
+		s += scnprintf(buf + s, size - s, "max-effort ");
+	if (debug_config & SBL_DEBUG_FORCE_MED_EFFORT)
+		s += scnprintf(buf + s, size - s, "med-effort ");
+	if (debug_config & SBL_DEBUG_FORCE_MIN_EFFORT)
+		s += scnprintf(buf + s, size - s, "min-effort ");
+	if (debug_config & SBL_DEBUG_INHIBIT_USE_SAVED_TP)
+		s += scnprintf(buf + s, size - s, "inhibit-use-saved-tp ");
+
+	if (debug_config & SBL_DEBUG_FORCE_PRECODING_ON)
+		s += scnprintf(buf + s, size - s, "precoding-on ");
+	if (debug_config & SBL_DEBUG_FORCE_PRECODING_OFF)
+		s += scnprintf(buf + s, size - s, "precoding-off ");
+	if (debug_config & SBL_DEBUG_ALLOW_MEDIA_BAD_MODE)
+		s += scnprintf(buf + s, size - s, "allow-media-bad-mode ");
+	if (debug_config & SBL_DEBUG_ALLOW_MEDIA_BAD_LEN)
+		s += scnprintf(buf + s, size - s, "allow-media-bad-len ");
+
+	if (debug_config & SBL_DEBUG_INHIBIT_PCAL)
+		s += scnprintf(buf + s, size - s, "inhibit-pcal ");
+	if (debug_config & SBL_DEBUG_INHIBIT_RELOAD_SBM_FW)
+		s += scnprintf(buf + s, size - s, "inhibit-reload-sbm-fw ");
+	if (debug_config & SBL_DEBUG_FORCE_RELOAD_SBM_FW)
+		s += scnprintf(buf + s, size - s, "force-reload-sbm-fw ");
+	if (debug_config & SBL_DEBUG_DISABLE_AN_NEXT_PAGES)
+		s += scnprintf(buf + s, size - s, "disable-an-next-pages ");
+
+	if (debug_config & SBL_DEBUG_KEEP_SERDES_UP)
+		s += scnprintf(buf + s, size - s, "keep-serdes-up ");
+	if (debug_config & SBL_DEBUG_SERDES_MAP_DELAY)
+		s += scnprintf(buf + s, size - s, "serdes-map-delay ");
+	if (debug_config & SBL_DEBUG_FORCE_RELOAD_SERDES_FW)
+		s += scnprintf(buf + s, size - s, "force-reload-serdes-fw ");
+	if (debug_config & SBL_DEBUG_ALLOW_LOOP_TIME_FAIL)
+		s += scnprintf(buf + s, size - s, "allow-loop-time-fail ");
+	if (debug_config & SBL_DEBUG_IGNORE_ALIGN)
+		s += scnprintf(buf + s, size - s, "ignore-align ");
+	if (debug_config & SBL_DEBUG_TRACE_PML_INT)
+		s += scnprintf(buf + s, size - s, "pml-interrupt ");
+	if (debug_config & SBL_DEBUG_REMOTE_FAULT_RECOVERY)
+		s += scnprintf(buf + s, size - s, "remote-fault-recovery ");
+
+	if (debug_config & SBL_DEBUG_DEV0)
+		s += scnprintf(buf + s, size - s, "dev0 ");
+	if (debug_config & SBL_DEBUG_TEST)
+		s += scnprintf(buf + s, size - s, "test ");
+
+	s += scnprintf(buf + s, size - s, "\n");
+
+	return s;
+}
+#endif
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_inst.c b/drivers/net/ethernet/hpe/sbl/sbl_inst.c
new file mode 100644
index 000000000000..af55ac619031
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_inst.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2026 Hewlett Packard Enterprise Development LP */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_fec.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <uapi/ethernet/sbl_sbm_constants.h>
+
+#include "sbl_constants.h"
+#include "sbl_serdes.h"
+#include "sbl_config_list.h"
+#include "sbl_serdes_map.h"
+#include "sbl_pml_fn.h"
+#include "sbl_internal.h"
+
+static atomic_t sbl_inst_id = ATOMIC_INIT(-1);
+
+static struct sbl_link *sbl_create_link_db(struct sbl_switch_info *switch_info)
+{
+	int i;
+	int j;
+	int err;
+	struct sbl_link *link;
+
+	link = kcalloc(switch_info->num_ports, sizeof(struct sbl_link), GFP_KERNEL);
+	if (!link)
+		return ERR_PTR(-ENOMEM);
+
+	for (i = 0; i < switch_info->num_ports; ++i) {
+		err = sbl_link_counters_init(link + i);
+		if (err)
+			goto out_free_sbl_link_counters;
+
+		link[i].num = i;
+		link[i].mconfigured = false;
+		link[i].blconfigured = false;
+		atomic_set(&link[i].debug_config, 0);
+		/* Initialize blattr with default values; these are applied on first config */
+		link[i].blattr.pec.an_mode   = SBL_AN_MODE_OFF;
+		link[i].blattr.link_mode     = SBL_LINK_MODE_BS_200G;
+		link[i].blattr.loopback_mode = SBL_LOOPBACK_MODE_OFF;
+		link[i].blattr.llr_mode      = SBL_LLR_MODE_OFF;
+		link[i].blattr.ifg_config    = SBL_IFG_CONFIG_HPC;
+		link[i].sstate = SBL_SERDES_STATUS_DOWN;
+		link[i].serr = 0;
+		link[i].blstate = SBL_BASE_LINK_STATUS_UNCONFIGURED;
+		link[i].blerr = 0;
+		link[i].link_info = 0;
+		link[i].link_mode     = SBL_LINK_MODE_INVALID;
+		link[i].ifg_config    = SBL_IFG_CONFIG_INVALID;
+		link[i].loopback_mode = SBL_LOOPBACK_MODE_INVALID;
+		link[i].llr_mode      = SBL_LLR_MODE_INVALID;
+		link[i].llr_loop_time = 0;
+		link[i].pcs_config = false;
+		link[i].intr_err_flgs = 0ULL;
+		link[i].an_rx_count = 0;
+		link[i].an_tx_count = 0;
+		link[i].an_timeout_active = false;
+		init_completion(&link[i].an_hw_change);
+		link[i].lp_subtype = SBL_LP_SUBTYPE_INVALID;
+		link[i].tuning_params.tp_state_hash0 = 0;
+		link[i].tuning_params.tp_state_hash1 = 0;
+		link[i].last_start_jiffy = jiffies;
+		link[i].start_cancelled = false;
+		link[i].dfe_tune_count = 0;
+		link[i].optical_delay_active = false;
+		link[i].dfe_predelay_active = false;
+		link[i].pcal_running = false;
+		link[i].tune_param_oob_count = 0;
+		link[i].reload_serdes_fw = false;
+		link[i].pcs_recovery_flag = false;
+		link[i].pml_recovery.started = false;
+		link[i].pml_recovery.rl_window_start = 0;
+		sbl_pml_recovery_init(&link[i].pml_recovery);
+		link[i].fec_discard_time = 0;
+		link[i].fec_discard_type = SBL_FEC_DISCARD_TYPE_INVALID;
+
+		spin_lock_init(&link[i].lock);
+		spin_lock_init(&link[i].timeout_lock);
+		spin_lock_init(&link[i].pcs_recovery_lock);
+		spin_lock_init(&link[i].is_degraded_lock);
+		spin_lock_init(&link[i].fec_discard_lock);
+		mutex_init(&link[i].busy_mtx);
+		mutex_init(&link[i].serdes_mtx);
+		mutex_init(&link[i].tuning_params_mtx);
+	}
+
+	return link;
+
+out_free_sbl_link_counters:
+	for (j = 0; j < i; ++j)
+		sbl_link_counters_term(link + j);
+	kfree(link);
+	return ERR_PTR(-ENOMEM);
+}
+
+/* All operations must be supplied or SBL cannot function */
+#define SBL_SETUP_OP_TBL_ENTRY(e)  \
+	if (ops->e) \
+		sbl->ops.e = ops->e; \
+	else { \
+		sbl_dev_err(sbl->dev, "missing ops tbl entry " #e); \
+		error = true; \
+	}
+
+static int sbl_setup_ops(struct sbl_inst *sbl, const struct sbl_ops *ops)
+{
+	bool error = false;
+
+	if (!ops) {
+		sbl_dev_err(sbl->dev, "missing ops table\n");
+		return -EINVAL;
+	}
+
+	SBL_SETUP_OP_TBL_ENTRY(sbl_read32);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_read64);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_write32);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_write64);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_sbus_op);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_sbus_op_reset);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_is_fabric_link);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_get_max_frame_size);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_pml_install_intr_handler);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_pml_enable_intr_handler);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_pml_disable_intr_handler);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_pml_remove_intr_handler);
+	SBL_SETUP_OP_TBL_ENTRY(sbl_async_alert);
+
+	if (error)
+		return -ENOENT;
+	else
+		return 0;
+}
+
+static int sbl_setup_serdes_configs(struct sbl_inst *sbl)
+{
+	struct sbl_serdes_config default_config;
+
+	spin_lock_init(&sbl->serdes_config_lock);
+	INIT_LIST_HEAD(&sbl->serdes_config_list);
+
+	default_config = (struct sbl_serdes_config)SBL_SERDES_CONFIG_INITIALIZER;
+
+	return sbl_serdes_add_config(sbl, default_config.tp_state_mask0,
+					default_config.tp_state_mask1,
+					default_config.tp_state_match0,
+					default_config.tp_state_match1,
+					default_config.port_mask,
+					default_config.serdes_mask,
+					&default_config.vals, true);
+}
+
+static int sbl_fec_init(struct sbl_inst *sbl)
+{
+	int i, j;
+	struct sbl_link *link;
+
+	for (i = 0; i < SBL_NUM_PORTS; ++i) {
+		link = sbl->link + i;
+
+		link->fec_data = kzalloc(sizeof(struct fec_data), GFP_KERNEL);
+
+		if (!link->fec_data)
+			goto out_free_fec_data;
+
+		link->fec_data->fec_prmts = kzalloc(sizeof(struct sbl_fec), GFP_KERNEL);
+
+		if (!link->fec_data->fec_prmts)
+			goto out_free_fec_data;
+
+		link->fec_data->fec_prmts->fec_curr_cnts = &link->fec_data->fec_prmts->fec_cntrs[0];
+		link->fec_data->fec_prmts->fec_prev_cnts = &link->fec_data->fec_prmts->fec_cntrs[1];
+		link->fec_data->fec_prmts->fec_rates = kzalloc(sizeof(struct sbl_pcs_fec_cntrs), GFP_KERNEL);
+		if (!link->fec_data->fec_prmts->fec_rates)
+			goto out_free_fec_data;
+
+		spin_lock_init(&link->fec_data->fec_prmts->fec_cnt_lock);
+
+		link->fec_data->fec_prmts->fec_ucw_thresh = 0;
+		link->fec_data->fec_prmts->fec_ucw_up_thresh_adj = 100;
+		link->fec_data->fec_prmts->fec_ucw_down_thresh_adj = 100;
+		link->fec_data->fec_prmts->fec_ucw_hwm = 0;
+		link->fec_data->fec_prmts->fec_ccw_thresh = 0;
+		link->fec_data->fec_prmts->fec_ccw_up_thresh_adj = 100;
+		link->fec_data->fec_prmts->fec_ccw_down_thresh_adj = 100;
+		link->fec_data->fec_prmts->fec_stp_ccw_thresh = 0;
+		link->fec_data->fec_prmts->fec_stp_ccw_up_thresh_adj = 100;
+		link->fec_data->fec_prmts->fec_ccw_hwm = 0;
+		link->fec_data->fec_prmts->fecl_warn = 0;
+
+		spin_lock_init(&link->fec_data->fec_prmts->fec_cw_lock);
+		timer_setup(&link->fec_data->fec_timer, sbl_fec_timer, 0);
+		link->fec_data->sbl		= sbl;
+		link->fec_data->port_num = i;
+
+		INIT_WORK(&link->fec_data->fec_timer_work, sbl_fec_timer_work);
+	}
+
+	return 0;
+
+out_free_fec_data:
+	for (j = 0; j <= i; ++j) {
+		link = sbl->link + j;
+		if (link->fec_data) {
+			if (link->fec_data->fec_prmts) {
+				kfree(link->fec_data->fec_prmts->fec_rates);
+				kfree(link->fec_data->fec_prmts);
+			}
+			kfree(link->fec_data);
+			link->fec_data = NULL;
+		}
+	}
+	return -ENOMEM;
+}
+
+/**
+ * sbl_get_version() - Get SBL Version
+ * @major: Major version number
+ * @minor: Minor version number
+ * @inc: Incremented version
+ */
+void sbl_get_version(int *major, int *minor, int *inc)
+{
+	if (major)
+		*major = SBL_VERSION_MAJOR;
+	if (minor)
+		*minor = SBL_VERSION_MINOR;
+	if (inc)
+		*inc = SBL_VERSION_INC;
+}
+EXPORT_SYMBOL(sbl_get_version);
+
+/**
+ * sbl_get_num_sbus_rings() - Get the number of sbus rings
+ * @sbl: A slingshot base link device instance
+ *
+ * Return: number of sbus rings
+ */
+int sbl_get_num_sbus_rings(struct sbl_inst *sbl)
+{
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	return sbl->switch_info->num_sbus_rings;
+}
+EXPORT_SYMBOL(sbl_get_num_sbus_rings);
+
+/**
+ * sbl_new_instance() - Create new sbl instance
+ * @accessor: Determined by the client driver, Which acts
+ *            as abstraction to perform low-level operation
+ * @pci_accessor: Points to bus/vendor specific PCI access
+ * @ops: callback function operations
+ * @init_attr: Pointer to struct sbl_init_attr
+ *
+ * This function initializes multiple members which
+ * helps to create new sbl instance
+ *
+ * Return: sbl instance on success, negative error code on failure
+ */
+struct sbl_inst *sbl_new_instance(void *accessor, void *pci_accessor,
+				  const struct sbl_ops *ops,
+				  struct sbl_init_attr *init_attr)
+{
+	struct sbl_inst *sbl;
+	struct device *dev = sbl_get_device();
+	int err;
+	int i;
+
+	if (!accessor || !pci_accessor || !ops || !init_attr) {
+		err = -EINVAL;
+		goto out;
+	}
+	if (init_attr->magic != SBL_INIT_ATTR_MAGIC) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	sbl = kzalloc(sizeof(struct sbl_inst), GFP_KERNEL);
+	if (!sbl) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	sbl->magic = SBL_MAGIC;
+	spin_lock_init(&sbl->name_lock);
+	sbl->id = atomic_inc_return(&sbl_inst_id);
+	sbl->dev = dev;
+	sbl->accessor = accessor;
+	sbl->pci_accessor = pci_accessor;
+	sbl->sbus_op_flags = SBL_DFLT_SBUS_OP_FLAGS_SLOW;
+
+	sbl->workq = alloc_workqueue("%s", WQ_MEM_RECLAIM | WQ_UNBOUND, 0, "sbl-fec");
+	if (!sbl->workq) {
+		err = -ENOMEM;
+		goto out_free_sbl;
+	}
+
+	/* Initialize the hardware-specific map */
+	err = sbl_switch_info_get(sbl, init_attr);
+
+	if (err)
+		goto out_free;
+
+	sbl_dev_info(dev, "new instance (%d): %d ports x %d serdes\n", sbl->id,
+		     sbl->switch_info->num_ports, sbl->switch_info->num_serdes);
+
+	sbl->sbus_ring_mtx =
+		kcalloc(sbl->switch_info->num_sbus_rings, sizeof(struct mutex), GFP_KERNEL);
+	if (!sbl->sbus_ring_mtx) {
+		err = -ENOMEM;
+		goto out_free;
+	}
+
+	sbl->sbm_fw_mtx =
+		kzalloc(sbl->switch_info->num_sbus_rings * sizeof(struct mutex), GFP_KERNEL);
+	if (!sbl->sbm_fw_mtx) {
+		err = -ENOMEM;
+		goto out_free_sbus_mtx;
+	}
+
+	sbl->reload_sbm_fw =
+		kzalloc(sbl->switch_info->num_sbus_rings * sizeof(bool), GFP_KERNEL);
+	if (!sbl->reload_sbm_fw) {
+		err = -ENOMEM;
+		goto out_free_sbm_fw_mtx;
+	}
+
+	sbl->sbm_fw_reload_count =
+		kzalloc(sbl->switch_info->num_sbus_rings * sizeof(atomic_t), GFP_KERNEL);
+	if (!sbl->sbm_fw_reload_count) {
+		err = -ENOMEM;
+		goto out_free_reload_sbm;
+	}
+
+	for (i = 0; i < sbl->switch_info->num_sbus_rings; ++i) {
+		mutex_init(&sbl->sbus_ring_mtx[i]);
+		mutex_init(&sbl->sbm_fw_mtx[i]);
+		sbl->reload_sbm_fw[i] = false;
+		atomic_set(&sbl->sbm_fw_reload_count[i], 0);
+	}
+
+	/* setup the op table */
+	err = sbl_setup_ops(sbl, ops);
+	if (err) {
+		sbl_dev_err(sbl->dev, "op table setup failed [%d]\n", err);
+		goto out_free_sbm_fw_reload_count;
+	}
+
+	/* setup serdes lock, configuration list and add default */
+	err = sbl_setup_serdes_configs(sbl);
+	if (err) {
+		sbl_dev_err(sbl->dev, "serdes setup failed [%d]\n", err);
+		goto out_free_sbm_fw_reload_count;
+	}
+
+	/* create link database */
+	sbl->link = sbl_create_link_db(sbl->switch_info);
+	if (IS_ERR(sbl->link)) {
+		err = PTR_ERR(sbl->link);
+		sbl_dev_err(sbl->dev, "link db creation failed [%d]\n", err);
+		goto out_free_configs;
+	}
+
+	err = sbl_fec_init(sbl);
+	if (err) {
+		sbl_dev_err(sbl->dev, "fec init failed [%d]\n", err);
+		goto out_free_link;
+	}
+
+	for (i = 0; i < sbl->switch_info->num_ports; ++i) {
+		/* ensure no valid saved tuning params */
+		sbl_serdes_invalidate_tuning_params(sbl, i);
+
+		/* set default values for PML */
+		sbl_pml_set_defaults(sbl, i);
+	}
+
+	return sbl;
+
+out_free_link:
+	for (i = 0; i < sbl->switch_info->num_ports; ++i)
+		sbl_link_counters_term(sbl->link + i);
+	kfree(sbl->link);
+out_free_configs:
+	sbl_serdes_clear_all_configs(sbl, true /* clear default */);
+out_free_sbm_fw_reload_count:
+	kfree(sbl->sbm_fw_reload_count);
+out_free_reload_sbm:
+	kfree(sbl->reload_sbm_fw);
+out_free_sbm_fw_mtx:
+	kfree(sbl->sbm_fw_mtx);
+out_free_sbus_mtx:
+	kfree(sbl->sbus_ring_mtx);
+out_free:
+	destroy_workqueue(sbl->workq);
+out_free_sbl:
+	kfree(sbl);
+out:
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL(sbl_new_instance);
+
+/**
+ * sbl_delete_instance() - Delete sbl instance
+ * @sbl: A slingshot base link device instance
+ *
+ * This function clears the config and frees all
+ * memory associated with an SBL.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_delete_instance(struct sbl_inst *sbl)
+{
+	struct sbl_link *link;
+	int err;
+	int i;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	for (i = 0; i < sbl->switch_info->num_ports; ++i) {
+		link = sbl->link + i;
+		if (!link->intr_err_flgs)
+			continue;
+		err = sbl_pml_disable_intr_handler(sbl, i, link->intr_err_flgs);
+		if (err)
+			return err;
+		err = sbl_pml_remove_intr_handler(sbl, i);
+		if (err)
+			return err;
+	}
+
+	for (i = 0; i < sbl->switch_info->num_ports; ++i)
+		sbl_pml_recovery_cancel(sbl, i);
+	for (i = 0; i < SBL_NUM_PORTS; ++i) {
+		link = sbl->link + i;
+
+		timer_shutdown_sync(&link->fec_data->fec_timer);
+		cancel_work_sync(&link->fec_data->fec_timer_work);
+		kfree(link->fec_data->fec_prmts->fec_rates);
+		link->fec_data->fec_prmts->fec_rates = NULL;
+		kfree(link->fec_data->fec_prmts);
+		link->fec_data->fec_prmts = NULL;
+		kfree(link->fec_data);
+		link->fec_data = NULL;
+	}
+
+	for (i = 0; i < sbl->switch_info->num_ports; ++i)
+		sbl_link_counters_term(sbl->link + i);
+
+	kfree(sbl->link);
+	sbl_serdes_clear_all_configs(sbl, true /* clear default */);
+	kfree(sbl->sbm_fw_reload_count);
+	kfree(sbl->reload_sbm_fw);
+	kfree(sbl->sbm_fw_mtx);
+	kfree(sbl->sbus_ring_mtx);
+	flush_workqueue(sbl->workq);
+	destroy_workqueue(sbl->workq);
+	sbl->magic = 0;   /* paranoia */
+	kfree(sbl);
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_delete_instance);
+
+/**
+ * sbl_initialise_instance() - Initialize the instance
+ * @sbl: A slingshot base link device instance
+ * @attr: Pointer to struct sbl_instance_attr
+ *
+ * This function loads required firmware to SBM/Serdes as part of
+ * Initialising the base link instance
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int sbl_initialise_instance(struct sbl_inst *sbl, struct sbl_instance_attr *attr)
+{
+	unsigned long flags;
+	int err;
+
+	if (!attr)
+		return -EINVAL;
+
+	if (attr->magic != SBL_INSTANCE_ATTR_MAGIC)
+		return -EINVAL;
+
+	spin_lock_irqsave(&sbl->name_lock, flags);
+	memcpy(&sbl->iattr, attr, sizeof(struct sbl_instance_attr));
+	spin_unlock_irqrestore(&sbl->name_lock, flags);
+
+	/* load the required firmware
+	 * force load all ports if serdes firmware validation fails
+	 */
+	err = sbl_serdes_load(sbl, SBL_ALL_PORTS, false);
+	if (err) {
+		sbl_dev_err(sbl->dev, "initial serdes fw load failed [%d]", err);
+		return err;
+	}
+	sbl_dev_err(sbl->dev, "serdes fw loaded");
+
+	return 0;
+
+}
+EXPORT_SYMBOL(sbl_initialise_instance);
+
+/**
+ * sbl_restore_instance() - Restoring instance
+ * @sbl: A slingshot base link device instance
+ *
+ * Return: negative error code on failure
+ */
+int sbl_restore_instance(struct sbl_inst *sbl)
+{
+	sbl_dev_err(sbl->dev, "Restoring an instance not supported (yet)\n");
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(sbl_restore_instance);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_inst_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_inst_nic.c
new file mode 100644
index 000000000000..cc3d3b570fc9
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_inst_nic.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_fec.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <uapi/ethernet/sbl_cassini.h>
+
+#include "sbl_serdes_map.h"
+#include "sbl_internal.h"
+
+static struct sbl_switch_info cas_brazos_switch_info = SBL_CASSINI_BRAZOS_SW_INFO_INITIALIZER;
+static struct sbl_switch_info cas_nic0_switch_info = SBL_CASSINI_NIC0_SW_INFO_INITIALIZER;
+static struct sbl_switch_info cas_nic1_switch_info = SBL_CASSINI_NIC1_SW_INFO_INITIALIZER;
+
+/**
+ * sbl_set_eth_name() - Set ethernet interface name
+ * @sbl: A slingshot base link device instance
+ * @name: String used to set as interface name
+ */
+void sbl_set_eth_name(struct sbl_inst *sbl, const char *name)
+{
+	unsigned long flags;
+
+	if (!sbl || !name)
+		return;
+
+	spin_lock_irqsave(&sbl->name_lock, flags);
+	strscpy(sbl->iattr.eth_if_name, name, sizeof(sbl->iattr.eth_if_name));
+	spin_unlock_irqrestore(&sbl->name_lock, flags);
+
+	sbl_dev_info(sbl->dev, "eth if name changed to %s", name);
+}
+EXPORT_SYMBOL(sbl_set_eth_name);
+
+int sbl_switch_info_get(struct sbl_inst *sbl, struct sbl_init_attr *init_attr)
+{
+	sbl->is_hw = init_attr->is_hw;
+	switch (init_attr->uc_platform) {
+	case SBL_UC_PLATFORM_SAWTOOTH:
+		if (init_attr->uc_nic == 0) {
+			sbl->switch_info = &cas_nic0_switch_info;
+		} else if (init_attr->uc_nic == 1) {
+			sbl->switch_info = &cas_nic1_switch_info;
+		} else {
+			sbl_dev_err(sbl->dev, "Bad NIC index (%d)!\n",
+				    init_attr->uc_nic);
+			return -EINVAL;
+		}
+		break;
+	case SBL_UC_PLATFORM_BRAZOS:
+		sbl->switch_info = &cas_brazos_switch_info;
+		break;
+	case SBL_UC_PLATFORM_UNDEFINED:
+		sbl_dev_err(sbl->dev, "Undefined uC platform!\n");
+		return -EINVAL;
+	default:
+		sbl_dev_err(sbl->dev, "Unknown uC platform (%d)!\n", init_attr->uc_platform);
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_internal.c b/drivers/net/ethernet/hpe/sbl/sbl_internal.c
new file mode 100644
index 000000000000..aeac466f3c47
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_internal.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2019-2020,2022-2024 Hewlett Packard Enterprise Development LP */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <uapi/ethernet/sbl-abi.h>
+
+#include "sbl_constants.h"
+#include "sbl_internal.h"
+#include "sbl_serdes_map.h"
+
+/* validate a port (link) number */
+int sbl_validate_port_num(struct sbl_inst *sbl, int port_num)
+{
+	if (port_num < 0 || port_num >= sbl->switch_info->num_ports)
+		return -EINVAL;
+	return 0;
+}
+
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_misc.c b/drivers/net/ethernet/hpe/sbl/sbl_misc.c
new file mode 100644
index 000000000000..2ed99dea9511
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_misc.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2024 Hewlett Packard Enterprise Development LP */
+
+#include <linux/kernel.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include <uapi/ethernet/sbl-abi.h>
+
+#include "sbl_constants.h"
+#include "sbl_internal.h"
+
+/**
+ * sbl_link_state_str() - Get link status as string
+ * @state: This Value used to find status
+ *
+ * Get link status as string based on value 'state'
+ * provided from caller.
+ *
+ * Return: link status as string
+ */
+const char *sbl_link_state_str(enum sbl_base_link_status state)
+{
+	switch (state) {
+	case SBL_BASE_LINK_STATUS_UNKNOWN:      return "unknown";
+	case SBL_BASE_LINK_STATUS_RESETTING:    return "resetting";
+	case SBL_BASE_LINK_STATUS_UNCONFIGURED: return "unconfigured";
+	case SBL_BASE_LINK_STATUS_STARTING:     return "starting";
+	case SBL_BASE_LINK_STATUS_UP:           return "up";
+	case SBL_BASE_LINK_STATUS_STOPPING:     return "stopping";
+	case SBL_BASE_LINK_STATUS_DOWN:         return "down";
+	case SBL_BASE_LINK_STATUS_ERROR:        return "error";
+	default:                                return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_link_state_str);
+
+/**
+ * sbl_link_len_str() - Get cable length as string
+ * @len: Value used to find link length
+ *
+ * Get Cable length as string based on the 'len' provided
+ * from the caller.
+ *
+ * Return: Link length as string
+ */
+const char *sbl_link_len_str(enum sbl_link_len len)
+{
+	switch (len) {
+	case SBL_LINK_LEN_INVALID:   return "invalid";
+	case SBL_LINK_LEN_BACKPLANE: return "backplane";
+	case SBL_LINK_LEN_000_300:   return "0.30m";
+	case SBL_LINK_LEN_000_400:   return "0.40m";
+	case SBL_LINK_LEN_000_750:   return "0.75m";
+	case SBL_LINK_LEN_000_800:   return "0.80m";
+	case SBL_LINK_LEN_001_000:   return "1.00m";
+	case SBL_LINK_LEN_001_100:   return "1.10m";
+	case SBL_LINK_LEN_001_150:   return "1.15m";
+	case SBL_LINK_LEN_001_200:   return "1.20m";
+	case SBL_LINK_LEN_001_400:   return "1.40m";
+	case SBL_LINK_LEN_001_420:   return "1.42m";
+	case SBL_LINK_LEN_001_500:   return "1.50m";
+	case SBL_LINK_LEN_001_600:   return "1.60m";
+	case SBL_LINK_LEN_001_640:   return "1.64m";
+	case SBL_LINK_LEN_001_700:   return "1.70m";
+	case SBL_LINK_LEN_001_800:   return "1.80m";
+	case SBL_LINK_LEN_001_900:   return "1.90m";
+	case SBL_LINK_LEN_001_910:   return "1.91m";
+	case SBL_LINK_LEN_002_000:   return "2.00m";
+	case SBL_LINK_LEN_002_100:   return "2.10m";
+	case SBL_LINK_LEN_002_130:   return "2.13m";
+	case SBL_LINK_LEN_002_200:   return "2.20m";
+	case SBL_LINK_LEN_002_300:   return "2.30m";
+	case SBL_LINK_LEN_002_390:   return "2.39m";
+	case SBL_LINK_LEN_002_400:   return "2.40m";
+	case SBL_LINK_LEN_002_500:   return "2.50m";
+	case SBL_LINK_LEN_002_600:   return "2.60m";
+	case SBL_LINK_LEN_002_620:   return "2.62m";
+	case SBL_LINK_LEN_002_700:   return "2.70m";
+	case SBL_LINK_LEN_002_800:   return "2.80m";
+	case SBL_LINK_LEN_002_900:   return "2.90m";
+	case SBL_LINK_LEN_002_990:   return "2.99m";
+	case SBL_LINK_LEN_003_000:   return "3.00m";
+	case SBL_LINK_LEN_004_000:   return "4.00m";
+	case SBL_LINK_LEN_005_000:   return "5.00m";
+	case SBL_LINK_LEN_006_000:   return "6.00m";
+	case SBL_LINK_LEN_007_000:   return "7.00m";
+	case SBL_LINK_LEN_008_000:   return "8.00m";
+	case SBL_LINK_LEN_010_000:   return "10.00m";
+	case SBL_LINK_LEN_014_000:   return "14.00m";
+	case SBL_LINK_LEN_015_000:   return "15.00m";
+	case SBL_LINK_LEN_019_000:   return "19.00m";
+	case SBL_LINK_LEN_025_000:   return "25.00m";
+	case SBL_LINK_LEN_030_000:   return "30.00m";
+	case SBL_LINK_LEN_035_000:   return "35.00m";
+	case SBL_LINK_LEN_050_000:   return "50.00m";
+	case SBL_LINK_LEN_075_000:   return "75.00m";
+	case SBL_LINK_LEN_100_000:   return "100.00m";
+	default:                     return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_link_len_str);
+
+/**
+ * sbl_link_vendor_str() - Get cable vendor as string
+ * @vendor: Value to find vendor
+ *
+ * Get cable vendor information as string
+ * based on 'vendor' value
+ *
+ * Return: Vendor name as string
+ */
+const char *sbl_link_vendor_str(enum sbl_link_vendor vendor)
+{
+	switch (vendor) {
+	case SBL_LINK_VENDOR_INVALID:        return "invalid";
+	case SBL_LINK_VENDOR_TE:             return "TE Connectivity";
+	case SBL_LINK_VENDOR_LEONI:          return "Leoni";
+	case SBL_LINK_VENDOR_MOLEX:          return "Molex";
+	case SBL_LINK_VENDOR_HISENSE:        return "Hisense";
+	case SBL_LINK_VENDOR_DUST_PHOTONICS: return "Dust Photonics";
+	case SBL_LINK_VENDOR_FINISAR:        return "Finisar";
+	case SBL_LINK_VENDOR_LUXSHARE:       return "Luxshare";
+	case SBL_LINK_VENDOR_FIT:            return "FIT";
+	case SBL_LINK_VENDOR_FT:             return "FT";
+	case SBL_LINK_VENDOR_MELLANOX:       return "Mellanox";
+	case SBL_LINK_VENDOR_HITACHI:        return "Hitachi";
+	case SBL_LINK_VENDOR_HPE:            return "HPE";
+	case SBL_LINK_VENDOR_CLOUD_LIGHT:    return "Cloud Light";
+	case SBL_LINK_VENDOR_AMPHENOL:       return "Amphenol";
+	default:                             return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_link_vendor_str);
+
+/**
+ * sbl_link_media_str() - Get type of the link media as string
+ * @media: Value used to find media used
+ *
+ * Get physical type of the link media as string
+ *  based on 'media' value provided from caller.
+ *
+ * Return: Media used as string
+ */
+const char *sbl_link_media_str(enum sbl_link_media media)
+{
+	switch (media) {
+	case SBL_LINK_MEDIA_INVALID:         return "invalid";
+	case SBL_LINK_MEDIA_UNKNOWN:         return "unknown";
+	case SBL_LINK_MEDIA_ELECTRICAL:      return "electrical";
+	case SBL_LINK_MEDIA_OPTICAL:         return "optical";
+	default:                             return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_link_media_str);
+
+/**
+ * sbl_an_mode_str() - Get AN modes for a link as a string
+ * @mode: Value used to find the auto negotiation mode
+ *
+ * Get auto negotiation mode as string based on value 'mode'
+ *
+ * Return: Auto negotiation status as string
+ */
+const char *sbl_an_mode_str(enum sbl_an_mode mode)
+{
+	switch (mode) {
+	case SBL_AN_MODE_INVALID: return "invalid";
+	case SBL_AN_MODE_UNKNOWN: return "unknown";
+	case SBL_AN_MODE_OFF:     return "off";
+	case SBL_AN_MODE_ON:      return "on";
+	case SBL_AN_MODE_FIXED:   return "fixed";
+	default:                  return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_an_mode_str);
+
+/**
+ * sbl_link_mode_str() - Get IEEE mode for a link as string
+ * @mode: Value used to find the link mode
+ *
+ * Get Link mode as string based on value 'mode'
+ *
+ * Return: Link mode as string
+ */
+const char *sbl_link_mode_str(enum sbl_link_mode mode)
+{
+	switch (mode) {
+	case SBL_LINK_MODE_INVALID: return "invalid";
+	case SBL_LINK_MODE_BS_200G: return "BS_200G";
+	case SBL_LINK_MODE_BJ_100G: return "BJ_100G";
+	case SBL_LINK_MODE_CD_100G: return "CD_100G";
+	case SBL_LINK_MODE_CD_50G:  return "CD_50G";
+	default:                    return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_link_mode_str);
+
+/**
+ * sbl_rs_mode_str() - Get FEC (Reed-Solomon) mode
+ * @mode: Contains RS MODE selection value
+ *
+ * Return: RS MODE as string
+ */
+const char *sbl_rs_mode_str(enum sbl_rs_mode mode)
+{
+	switch (mode) {
+	case SBL_RS_MODE_INVALID:        return "invalid";
+	case SBL_RS_MODE_UNKNOWN:        return "unknown";
+	case SBL_RS_MODE_OFF:            return "error correction OFF error checking OFF syndrome checking OFF marking OFF";
+	case SBL_RS_MODE_OFF_SYN:        return "error correction OFF error checking OFF syndrome checking  ON marking OFF";
+	case SBL_RS_MODE_OFF_CHK:        return "error correction OFF error checking  ON syndrome checking OFF marking OFF";
+	case SBL_RS_MODE_ON:             return "error correction  ON error checking  ON syndrome checking OFF marking OFF";
+	case SBL_RS_MODE_ON_SYN_MRK:     return "error correction  ON error checking OFF syndrome checking  ON marking  ON";
+	case SBL_RS_MODE_ON_CHK_SYN_MRK: return "error correction  ON error checking  ON syndrome checking  ON marking  ON";
+	default:                         return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_rs_mode_str);
+
+/**
+ * sbl_ifg_mode_str() - Get Inter-frame gap mode as string
+ * @mode: Value
+ *
+ * Return: IFG mode as string
+ */
+const char *sbl_ifg_mode_str(enum sbl_ifg_mode mode)
+{
+	switch (mode) {
+	case SBL_IFG_MODE_INVALID:  return "invalid";
+	case SBL_IFG_MODE_UNKNOWN:  return "unknown";
+	case SBL_IFG_MODE_HPC:      return "hpc";
+	case SBL_IFG_MODE_IEEE:     return "ieee";
+	default:                    return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_ifg_mode_str);
+
+/**
+ * sbl_ifg_config_str() - Get IFG config as string
+ * @config: Value
+ *
+ * Get IFG config as string based on 'config' value.
+ *
+ * Return: IFG Config as string
+ */
+const char *sbl_ifg_config_str(enum sbl_ifg_config config)
+{
+	switch (config) {
+	case SBL_IFG_CONFIG_INVALID:   return "invalid";
+	case SBL_IFG_CONFIG_UNKNOWN:   return "unknown";
+	case SBL_IFG_CONFIG_HPC:       return "hpc";
+	case SBL_IFG_CONFIG_IEEE_200G: return "ieee 200G";
+	case SBL_IFG_CONFIG_IEEE_100G: return "ieee 100G";
+	case SBL_IFG_CONFIG_IEEE_50G:  return "ieee 50G";
+	default:                       return "unrecognized";
+}
+
+}
+EXPORT_SYMBOL(sbl_ifg_config_str);
+
+/**
+ * sbl_llr_mode_str() - Get link-level retry (LLR) mode as string
+ * @mode: Value
+ *
+ * Get llr mode as string based on value 'mode'
+ *
+ * Return: LLR mode as string
+ */
+const char *sbl_llr_mode_str(enum sbl_llr_mode mode)
+{
+	switch (mode) {
+	case SBL_LLR_MODE_INVALID:  return "invalid";
+	case SBL_LLR_MODE_UNKNOWN:  return "unknown";
+	case SBL_LLR_MODE_OFF:      return "off";
+	case SBL_LLR_MODE_MONITOR:  return "monitor";
+	case SBL_LLR_MODE_ON:       return "on";
+	case SBL_LLR_MODE_AUTO:     return "auto";
+	default:                    return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_llr_mode_str);
+
+/**
+ * sbl_llr_down_behaviour_str() - Get LLR link down behaviour
+ * @behaviour: Value used to find the link down behaviour
+ *
+ * Get LLR link down behaviour as string based on 'behaviour'
+ * value provided.
+ *
+ * Return: LLR Link down behaviour as string
+ */
+const char *sbl_llr_down_behaviour_str(
+		enum sbl_llr_down_behaviour behaviour)
+{
+	switch (behaviour) {
+	case SBL_LLR_LINK_DOWN_INVALID:      return "invalid";
+	case SBL_LLR_LINK_DOWN_UNKNOWN:      return "unknown";
+	case SBL_LLR_LINK_DOWN_DISCARD:      return "discard";
+	case SBL_LLR_LINK_DOWN_BLOCK:        return "block";
+	case SBL_LLR_LINK_DOWN_BEST_EFFORT:  return "best-effort";
+	default:                             return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_llr_down_behaviour_str);
+
+/**
+ * sbl_loopback_mode_str() - Get loopback mode status
+ * @state: Value used to find loopback mode
+ *
+ * Get loopback mode as string based on 'state' provided.
+ *
+ * Return: Loopback mode as string
+ */
+const char *sbl_loopback_mode_str(enum sbl_loopback_mode state)
+{
+	switch (state) {
+	case SBL_LOOPBACK_MODE_INVALID:  return "invalid";
+	case SBL_LOOPBACK_MODE_LOCAL:    return "local";
+	case SBL_LOOPBACK_MODE_REMOTE:   return "remote";
+	case SBL_LOOPBACK_MODE_OFF:      return "off";
+	default:                         return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_loopback_mode_str);
+
+const char *sbl_serdes_effort_str(u32 effort)
+{
+	switch (effort) {
+	case SPICO_INT_DATA_ICAL_EFFORT_1:   return "max";
+	case SPICO_INT_DATA_ICAL_EFFORT_10:  return "med";
+	case SPICO_INT_DATA_ICAL_EFFORT_0:   return "min";
+	default:                             return "unrecognized";
+	}
+}
+
+/**
+ * sbl_serdes_state_str() - Get serdes state as string
+ * @state: Value used to find status
+ *
+ * Get serdes state as string based on 'state' value.
+ *
+ * Return: Serdes state as string
+ */
+const char *sbl_serdes_state_str(enum sbl_serdes_status state)
+{
+	switch (state) {
+	case SBL_SERDES_STATUS_UNKNOWN:             return "unknown";
+	case SBL_SERDES_STATUS_AUTONEG:             return "autoneg-mode";
+	case SBL_SERDES_STATUS_LPD_MT:              return "lpd-mt";
+	case SBL_SERDES_STATUS_DOWN:                return "down";
+	case SBL_SERDES_STATUS_TUNING:              return "tuning";
+	case SBL_SERDES_STATUS_RUNNING:             return "running";
+	case SBL_SERDES_STATUS_ERROR:               return "error";
+	case SBL_SERDES_STATUS_RESETTING:           return "resetting";
+	default:                                    return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_serdes_state_str);
+
+const char *sbl_an_state_str(int state)
+{
+	switch (state) {
+	case SBL_PML_AUTONEG_STATE_AUTONEG_OFF:    return "off";
+	case SBL_PML_AUTONEG_STATE_AUTONEG_ENABLE: return "enable";
+	case SBL_PML_AUTONEG_STATE_TX_DISABLE:     return "tx_disable";
+	case SBL_PML_AUTONEG_STATE_ABILITY_DETECT: return "ability_detect";
+	case SBL_PML_AUTONEG_STATE_ACK_DETECT:     return "ack_detect";
+	case SBL_PML_AUTONEG_STATE_COMPLETE_ACK:   return "complete_ack";
+	case SBL_PML_AUTONEG_STATE_NEXT_PAGE_WAIT: return "next_page_wait";
+	case SBL_PML_AUTONEG_STATE_AN_GOOD_CHECK:  return "good_check";
+	case SBL_PML_AUTONEG_STATE_AN_GOOD:        return "good";
+	default:                                   return "unrecognized";
+	}
+}
+
+/**
+ * sbl_async_alert_str() - Get Async alert type as string
+ * @alert_type: Value
+ *
+ * Get Async alert type as string based on 'alert_type' provided.
+ *
+ * Return: ASYNC ALERT as string
+ */
+const char *sbl_async_alert_str(enum sbl_async_alert_type alert_type)
+{
+	switch (alert_type) {
+	case SBL_ASYNC_ALERT_INVALID:              return "invalid";
+	case SBL_ASYNC_ALERT_LINK_DOWN:            return "link down";
+	case SBL_ASYNC_ALERT_SERDES_FW_CORRUPTION: return "serdes fw corruption";
+	case SBL_ASYNC_ALERT_TX_DEGRADE:           return "tx degrade";
+	case SBL_ASYNC_ALERT_RX_DEGRADE:           return "rx degrade";
+	case SBL_ASYNC_ALERT_TX_DEGRADE_FAILURE:   return "tx degrade failure";
+	case SBL_ASYNC_ALERT_RX_DEGRADE_FAILURE:   return "rx degrade failure";
+	case SBL_ASYNC_ALERT_SBM_FW_LOAD_FAILURE:  return "sbus master fw load failure";
+	default:                                   return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_async_alert_str);
+
+/**
+ * sbl_fec_discard_str() - Get discard fec type as string
+ * @discard_type: Value used to find discard type
+ *
+ * Get discard fec type as string based on 'discard_type' provided.
+ *
+ * Return: Discard type as string
+ */
+const char *sbl_fec_discard_str(enum sbl_fec_discard_type discard_type)
+{
+	switch (discard_type) {
+	case SBL_FEC_DISCARD_TYPE_INVALID:         return "invalid";
+	case SBL_FEC_DISCARD_TYPE_RX_DEGRADE:      return "rx lane degraded";
+	case SBL_FEC_DISCARD_TYPE_PML_REC_START:   return "pml recovery started";
+	case SBL_FEC_DISCARD_TYPE_PML_REC_END:     return "pml recovery ended";
+	default:                                   return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_fec_discard_str);
+
+/**
+ * sbl_down_origin_str() - Get link down reason as string
+ * @down_origin: Value used to find link down origin
+ *
+ * Get the reason for taking link down asynchronously as string
+ * based on 'down_origin' provided.
+ *
+ * Return: Link down origin as string
+ */
+const char *sbl_down_origin_str(enum sbl_link_down_origin down_origin)
+{
+	switch (down_origin) {
+	case SBL_LINK_DOWN_ORIGIN_UNKNOWN:         return "unknown";
+	case SBL_LINK_DOWN_ORIGIN_LINK_DOWN:       return "link down";
+	case SBL_LINK_DOWN_ORIGIN_LOCAL_FAULT:     return "local fault";
+	case SBL_LINK_DOWN_ORIGIN_REMOTE_FAULT:    return "remote fault";
+	case SBL_LINK_DOWN_ORIGIN_ALIGN:           return "align";
+	case SBL_LINK_DOWN_ORIGIN_HISER:           return "hiser";
+	case SBL_LINK_DOWN_ORIGIN_LLR_MAX:         return "max llr replay";
+	case SBL_LINK_DOWN_ORIGIN_DEGRADE_FAILURE: return "degrade failure";
+	case SBL_LINK_DOWN_ORIGIN_UCW:             return "uncorrected fec";
+	case SBL_LINK_DOWN_ORIGIN_CCW:             return "corrected fec";
+	case SBL_LINK_DOWN_ORIGIN_LLR_TX_REPLAY:   return "llr tx replay";
+	default:                                   return "unrecognized";
+	}
+}
+EXPORT_SYMBOL(sbl_down_origin_str);
+
+/**
+ * sbl_flags_get_poll_interval_from_flags() - Get interval for sbus operations
+ * @flags: Value
+ *
+ * This function get Interval value which is needed time to wait
+ * between sbus operations
+ *
+ * Return: Interval value on success, 0 if no flag is found
+ */
+int sbl_flags_get_poll_interval_from_flags(unsigned int flags)
+{
+	if (flags & SBL_FLAG_INTERVAL_1MS)
+		return 1;
+	else if (flags & SBL_FLAG_INTERVAL_10MS)
+		return 10;
+	else if (flags & SBL_FLAG_INTERVAL_100MS)
+		return 100;
+	else if (flags & SBL_FLAG_INTERVAL_1S)
+		return 1000;
+	else
+		return 0;
+}
+EXPORT_SYMBOL(sbl_flags_get_poll_interval_from_flags);
+
+/**
+ * sbl_flags_get_delay_from_flags() - Get delay for sbus operations
+ * @flags: Value
+ *
+ * This function get sbus delay. Delays are the time to pause
+ * before issuing first operation of sbus
+ *
+ * Return: Delay value on success, 0 if no flag is found
+ */
+int sbl_flags_get_delay_from_flags(unsigned int flags)
+{
+	if (flags & SBL_FLAG_DELAY_3US)
+		return 3;
+	else if (flags & SBL_FLAG_DELAY_4US)
+		return 4;
+	else if (flags & SBL_FLAG_DELAY_5US)
+		return 5;
+	else if (flags & SBL_FLAG_DELAY_10US)
+		return 10;
+	else if (flags & SBL_FLAG_DELAY_20US)
+		return 20;
+	else if (flags & SBL_FLAG_DELAY_50US)
+		return 50;
+	else if (flags & SBL_FLAG_DELAY_100US)
+		return 100;
+	else
+		return 0;
+}
+EXPORT_SYMBOL(sbl_flags_get_delay_from_flags);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_module_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_module_nic.c
new file mode 100644
index 000000000000..a26eb357adb6
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_module_nic.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2025-2026 Hewlett Packard Enterprise Development LP */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+#include <linux/of.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include "sbl_internal.h"
+#include "sbl_serdes_map.h"
+
+#define DEVICE_NAME         "sbl"
+
+static struct class  *sbl_cls;
+static struct device *sbl_dev;
+static dev_t          sbl_devt;
+
+#define tostr(x) #x
+#define version_str(a, b, c) tostr(a.b.c)
+#define SBL_VERSION_STR version_str(SBL_VERSION_MAJOR,	\
+				    SBL_VERSION_MINOR,	\
+				    SBL_VERSION_INC)
+
+/*   module init function
+ *
+ *   The char device is only used for fw loading and printing to the console
+ *   For now multiple instances all share the same device
+ */
+static int __init sbl_init(void)
+{
+	int err;
+
+	pr_info("%s : v%s loading (%s build)\n", module_name(THIS_MODULE),
+		SBL_VERSION_STR, SBL_BUILD_NAME);
+
+	/* create a device */
+	err = alloc_chrdev_region(&sbl_devt, 0, 1, DEVICE_NAME);
+	if (err) {
+		pr_err("%s : failed to get dev region [%d]\n",
+		       module_name(THIS_MODULE), err);
+		goto out;
+	}
+
+		sbl_cls = class_create(DEVICE_NAME);
+	if (IS_ERR(sbl_cls)) {
+		pr_err("%s : class create failed [%ld]\n",
+		       module_name(THIS_MODULE), PTR_ERR(sbl_cls));
+		err = PTR_ERR(sbl_cls);
+		goto out_region;
+	}
+
+	sbl_dev = device_create(sbl_cls, NULL, sbl_devt, NULL,
+				module_name(THIS_MODULE));
+	if (IS_ERR(sbl_dev)) {
+		err = PTR_ERR(sbl_dev);
+		pr_err("%s : failed to add dev [%d]\n",
+		       module_name(THIS_MODULE), err);
+		goto out_class;
+	}
+
+	return 0;
+
+out_class:
+	class_destroy(sbl_cls);
+out_region:
+	unregister_chrdev_region(sbl_devt, 1);
+out:
+	return err;
+}
+module_init(sbl_init);
+
+struct device *sbl_get_device(void)
+{
+	return sbl_dev;
+}
+
+/* module exit function */
+static void __exit sbl_exit(void)
+{
+	pr_info("%s : module unloading\n", module_name(THIS_MODULE));
+
+	device_destroy(sbl_cls, sbl_devt);
+	class_destroy(sbl_cls);
+	unregister_chrdev_region(sbl_devt, 1);
+}
+module_exit(sbl_exit);
+
+static int llr_cap_set(const char *val, const struct kernel_param *kp)
+{
+	int           err;
+	unsigned long data;
+
+	err = kstrtoul(val, 0, &data);
+	if (err || data > 0x800)
+		return -EINVAL;
+
+	return param_set_ulong(val, kp);
+}
+
+static const struct kernel_param_ops llr_cap_ops = {
+	.set = llr_cap_set,
+	.get = param_get_ulong,
+};
+
+static unsigned long llr_edge_cap_data_max = 0x320;
+module_param_cb(llr_edge_cap_data_max, &llr_cap_ops, &llr_edge_cap_data_max, 0664);
+MODULE_PARM_DESC(llr_edge_cap_data_max, "LLR data capacity max for edge link");
+u64 sbl_llr_edge_cap_data_max_get(void)
+{
+	return llr_edge_cap_data_max;
+}
+
+static unsigned long llr_edge_cap_seq_max = 0x160;
+module_param_cb(llr_edge_cap_seq_max, &llr_cap_ops, &llr_edge_cap_seq_max, 0664);
+MODULE_PARM_DESC(llr_edge_cap_seq_max, "LLR sequence num capacity max for edge link");
+u64 sbl_llr_edge_cap_seq_max_get(void)
+{
+	return llr_edge_cap_seq_max;
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("HPE Slingshot <support@hpe.com>");
+MODULE_DESCRIPTION("Slingshot Base Link module");
+MODULE_VERSION(SBL_VERSION_STR);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_test.c b/drivers/net/ethernet/hpe/sbl/sbl_test.c
new file mode 100644
index 000000000000..56f9f19ae5c4
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_test.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright 2019-2021 Hewlett Packard Enterprise Development LP */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+#include <linux/hpe/sbl/sbl_test.h>
+
+#include <uapi/ethernet/sbl_serdes_defaults.h>
+
+#include "sbl_serdes.h"
+#include "sbl_serdes_fn.h"
+#include "sbl_pml_fn.h"
+#include "sbl_internal.h"
+#include "sbl_constants.h"
+
+static bool sbl_test_crc_failure;
+
+/**
+ * sbl_test_core_intr() - test core interrupt access to the serdes
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * This function is used to test the serdes operations
+ * and check its results
+ *
+ * Return: 0 on success, error on failure
+ */
+int sbl_test_core_intr(struct sbl_inst *sbl, int port_num)
+{
+	int err;
+	u16 result = 0;
+	unsigned int flags = SBL_FLAG_DELAY_5US | SBL_FLAG_INTERVAL_1MS;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s starting\n", __func__);
+
+	err = sbl_pml_serdes_op(sbl,
+				port_num, /* port num */
+				0,        /* serdes_sel */
+				0,        /* op */
+				0,        /* data */
+				&result,
+				100,      /* timeout ms */
+				flags);   /* flags */
+	if (err)
+		dev_info(sbl->dev, "op 0, err %d\n", err);
+	else
+		dev_info(sbl->dev, "op 0, result 0x%x\n", result);
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_test_core_intr);
+
+static void sbl_blattr_init(struct sbl_base_link_attr *blattr,
+			    int loopback_mode)
+{
+	/* simple PEC, fabric link config */
+	memset(blattr, 0, sizeof(struct sbl_base_link_attr));
+
+	blattr->magic                = SBL_LINK_ATTR_MAGIC;
+
+	blattr->options              = SBL_OPT_FABRIC_LINK |
+				       SBL_OPT_DFE_SAVE_PARAMS |
+				       SBL_OPT_USE_SAVED_PARAMS |
+				       SBL_OPT_ENABLE_PCAL;
+
+	blattr->start_timeout        = SBL_LINK_START_TIMEOUT_PEC;
+
+	blattr->config_target        = SBL_BASE_LINK_CONFIG_PEC;
+	blattr->pec.an_mode          = SBL_AN_MODE_OFF;
+	blattr->pec.an_retry_timeout = SBL_LINK_DFLT_AN_RETRY_TIMEOUT;
+	blattr->pec.an_max_retry     = SBL_LINK_DFLT_AN_MAX_RETRY;
+
+	blattr->lpd_timeout          = SBL_DFLT_LPD_TIMEOUT;        /* not enabled in options */
+	blattr->lpd_poll_interval    = SBL_DFLT_LPD_POLL_INTERVAL;  /* not enabled in options */
+
+	blattr->link_mode            = SBL_LINK_MODE_BS_200G;
+	blattr->loopback_mode        = loopback_mode;
+	blattr->link_partner         = SBL_LINK_PARTNER_SWITCH;
+	blattr->tuning_pattern       = SBL_TUNING_PATTERN_CORE;
+
+	blattr->dfe_pre_delay        = SBL_DFLT_DFE_PRE_DELAY_PEC;
+	blattr->dfe_timeout          = SBL_DFLT_DFE_TIMEOUT_PEC;
+	blattr->dfe_poll_interval    = SBL_DFLT_DFE_POLL_INTERVAL;
+
+	blattr->nrz_min_eye_height   = SBL_DFLT_NRZ_PEC_MIN_EYE_HEIGHT;
+	blattr->nrz_max_eye_height   = SBL_DFLT_NRZ_PEC_MAX_EYE_HEIGHT;
+	blattr->pam4_min_eye_height  = SBL_DFLT_PAM4_PEC_MIN_EYE_HEIGHT;
+	blattr->pam4_max_eye_height  = SBL_DFLT_PAM4_PEC_MAX_EYE_HEIGHT;
+
+	blattr->fec_mode             = SBL_RS_MODE_ON;
+	blattr->enable_autodegrade   = 0;
+	blattr->llr_mode             = SBL_LLR_MODE_ON;
+	blattr->ifg_config           = SBL_IFG_CONFIG_HPC;
+}
+
+/**
+ * sbl_test_link_up() - test link up
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @loopback_mode: selected enum value of loopback mode
+ *
+ * Return: 0 on success, error on failure
+ */
+int sbl_test_link_up(struct sbl_inst *sbl, int port_num, int loopback_mode)
+{
+	struct sbl_media_attr mattr;
+	struct sbl_base_link_attr blattr;
+	int blstate;
+	int blerr;
+	int sstate;
+	int serr;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s starting\n", __func__);
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	/* configure media */
+	mattr.magic = SBL_MEDIA_ATTR_MAGIC;
+	mattr.media = SBL_LINK_MEDIA_ELECTRICAL;
+	mattr.len   = 2;
+
+	err = sbl_media_config(sbl, port_num, &mattr);
+	if (err) {
+		dev_err(sbl->dev, "%d: media config failed [%d]\n", port_num, err);
+		goto out;
+	}
+
+	/* configure base-link */
+	sbl_blattr_init(&blattr, loopback_mode);
+
+	err = sbl_base_link_config(sbl, port_num, &blattr);
+	if (err) {
+		dev_err(sbl->dev, "%d: base link config failed [%d]\n", port_num, err);
+		goto out;
+	}
+
+	/* start the base link */
+	err = sbl_base_link_start(sbl, port_num);
+	if (err)
+		dev_err(sbl->dev, "%d: base link start failed [%d]\n", port_num, err);
+
+out:
+	sbl_base_link_get_status(sbl, port_num, &blstate, &blerr, &sstate, &serr, NULL, NULL);
+	dev_info(sbl->dev, "%d: base link %s (%d), serdes %s (%d)\n", port_num,
+		 sbl_link_state_str(blstate), blerr,
+		 sbl_serdes_state_str(sstate), serr);
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_test_link_up);
+
+/**
+ * sbl_test_link_reup() - Test to link reup
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ * @loopback_mode: selected enum value of loopback mode
+ *
+ * This function is used to test base link
+ * operation of start/stop.
+ *
+ * Return: 0 on success, error on failure
+ */
+int sbl_test_link_reup(struct sbl_inst *sbl, int port_num, int loopback_mode)
+{
+	struct sbl_base_link_attr blattr;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s starting\n", __func__);
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	sbl_blattr_init(&blattr, loopback_mode);
+
+	err = sbl_base_link_config(sbl, port_num, &blattr);
+	if (err)
+		dev_err(sbl->dev, "%d: base link config failed [%d]\n", port_num, err);
+
+	err = sbl_base_link_start(sbl, port_num);
+	if (err)
+		dev_err(sbl->dev, "%d: base link start failed [%d]\n", port_num, err);
+
+	err = sbl_base_link_stop(sbl, port_num);
+	if (err)
+		dev_err(sbl->dev, "%d: base link stop failed [%d]\n", port_num, err);
+
+	err = sbl_base_link_start(sbl, port_num);
+	if (err)
+		dev_err(sbl->dev, "%d: base link start failed [%d]\n", port_num, err);
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_test_link_reup);
+
+/**
+ * sbl_test_link_down() - Test to link down
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Return: 0 on success, error on failure
+ */
+int sbl_test_link_down(struct sbl_inst *sbl, int port_num)
+{
+	int blstate;
+	int blerr;
+	int sstate;
+	int serr;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s starting\n", __func__);
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	err = sbl_base_link_stop(sbl, port_num);
+	if (err)
+		dev_err(sbl->dev, "%d: base link stop failed [%d]\n", port_num, err);
+
+	sbl_base_link_get_status(sbl, port_num, &blstate, &blerr, &sstate, &serr, NULL, NULL);
+	dev_info(sbl->dev, "%d: base link %s (%d), serdes %s (%d)\n", port_num,
+		 sbl_link_state_str(blstate), blerr,
+		 sbl_serdes_state_str(sstate), serr);
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_test_link_down);
+
+/**
+ * sbl_test_link_reset() - Test to reset link
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Return: 0 on success, error on failure
+ */
+int sbl_test_link_reset(struct sbl_inst *sbl, int port_num)
+{
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s starting\n", __func__);
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	err = sbl_base_link_reset(sbl, port_num);
+	if (err)
+		dev_err(sbl->dev, "%d: base link reset failed [%d]\n", port_num, err);
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_test_link_reset);
+
+/**
+ * sbl_test_serdes_stop() - Test to stop serdes
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Stopping the serdes provides a way to test link error pathways
+ * can be used to simulate link failure
+ *
+ * Context: Process context. May sleep. The caller must keep @sbl alive and
+ * must not hold this port's busy_mtx.
+ *
+ * Return: 0 on success, error on failure
+ */
+int sbl_test_serdes_stop(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link;
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s starting\n", __func__);
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	link = sbl->link + port_num;
+	err = mutex_lock_interruptible(&link->busy_mtx);
+	if (err)
+		return -ERESTARTSYS;
+
+	err = sbl_spico_reset(sbl, port_num);
+	mutex_unlock(&link->busy_mtx);
+	if (err)
+		dev_err(sbl->dev, "%d: sbl serdes stop failed [%d]\n", port_num, err);
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_test_serdes_stop);
+
+/**
+ * sbl_test_pcs_tx_rf() - set pcs to transmit remote fault
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Return: 0 on success, error on failure
+ */
+int sbl_test_pcs_tx_rf(struct sbl_inst *sbl, int port_num)
+{
+	int err;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s starting\n", __func__);
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	sbl_pml_pcs_set_tx_rf(sbl, port_num);
+
+	return 0;
+}
+EXPORT_SYMBOL(sbl_test_pcs_tx_rf);
+
+/**
+ * sbl_test_scratch() - Test to reload serdes firmware
+ * @sbl: A slingshot base link device instance
+ * @port_num: port number
+ *
+ * Return: 0 on success,  error on failure
+ */
+int sbl_test_scratch(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link;
+	int err = 0;
+
+	err = sbl_validate_instance(sbl);
+	if (err)
+		return err;
+
+	dev_dbg(sbl->dev, "%s toggling reload serdes fw option\n", __func__);
+
+	err = sbl_validate_port_num(sbl, port_num);
+	if (err)
+		return err;
+
+	link = sbl->link + port_num;
+
+	if (link->blattr.options & SBL_OPT_RELOAD_FW_ON_TIMEOUT)
+		link->blattr.options &= ~SBL_OPT_RELOAD_FW_ON_TIMEOUT;
+	else
+		link->blattr.options |= SBL_OPT_RELOAD_FW_ON_TIMEOUT;
+
+	return err;
+}
+EXPORT_SYMBOL(sbl_test_scratch);
+
+/* Updates return value of SerDes FW CRC check.
+ * If sbl_test_crc_failure set by sbl_test_inject_serdes_fw_crc_failure is 1,
+ * CRC returns failure.
+ */
+void sbl_test_manipulate_serdes_fw_crc_result(u16 *crc_result)
+{
+	if (sbl_test_crc_failure)
+		*crc_result = SPICO_RESULT_SERDES_CRC_FAIL;
+}
+
+/**
+ * sbl_test_inject_serdes_fw_crc_failure() - Test to inject crc failure for serdes fw
+ * @set: if set then inject crc
+ */
+void sbl_test_inject_serdes_fw_crc_failure(bool set)
+{
+	sbl_test_crc_failure = set;
+}
+EXPORT_SYMBOL(sbl_test_inject_serdes_fw_crc_failure);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_timers.c b/drivers/net/ethernet/hpe/sbl/sbl_timers.c
new file mode 100644
index 000000000000..9af676193667
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_timers.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2019-2025 Hewlett Packard Enterprise Development LP */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+
+#include <linux/hpe/sbl/sbl.h>
+#include <linux/hpe/sbl/sbl_kconfig.h>
+
+#include <linux/hpe/sbl/sbl_pml.h>
+
+#include "sbl_pml_fn.h"
+#include "sbl_link.h"
+#include "sbl_internal.h"
+
+/* start timeout is the limit for sbl_base_link_start() to bring the link up
+ * this includes all stages of link bringup
+ */
+void sbl_link_init_start_timeout(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->start_timeout = link->blattr.start_timeout;
+	link->an_timeout_active = false;
+	link->last_start_jiffy = jiffies +
+		min_t(u64, (u64)link->start_timeout * HZ, MAX_JIFFY_OFFSET);
+	link->start_time_begin = ktime_get();
+	link->total_tune_time.tv_sec = 0;
+	link->total_tune_time.tv_nsec = 0;
+	spin_unlock(&link->timeout_lock);
+}
+
+/* change the start timeout */
+void sbl_link_update_start_timeout(struct sbl_inst *sbl, int port_num, unsigned int timeout_ms)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->start_timeout = DIV_ROUND_UP(timeout_ms, 1000);
+	link->last_start_jiffy = jiffies + msecs_to_jiffies(timeout_ms);
+	spin_unlock(&link->timeout_lock);
+
+	sbl_dev_dbg(sbl->dev, "%d: update start timeout to %d s, %d ms\n",
+		    port_num, link->start_timeout, timeout_ms);
+}
+
+/* test if we have timed out - returns true if we have */
+bool sbl_start_timeout(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	unsigned long last_start_jiffy;
+	bool timed_out;
+
+	spin_lock(&link->timeout_lock);
+	last_start_jiffy = link->last_start_jiffy;
+	timed_out = time_is_before_jiffies(last_start_jiffy);
+	spin_unlock(&link->timeout_lock);
+
+	if (timed_out)
+		sbl_dev_dbg(sbl->dev, "%d: %s timed out\n", port_num, __func__);
+
+	return timed_out;
+}
+
+u32 sbl_get_start_timeout(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u32 start_timeout;
+
+	spin_lock(&link->timeout_lock);
+	start_timeout = link->start_timeout;
+	spin_unlock(&link->timeout_lock);
+
+	return start_timeout;
+}
+
+/* update start timeout to ensure there is a minimum  amout of time remaining */
+void sbl_start_timeout_ensure_remaining(struct sbl_inst *sbl, int port_num, unsigned int remaining_s)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	unsigned long now;
+	unsigned long left;
+	unsigned long required = min_t(u64, (u64)remaining_s * HZ,
+				       MAX_JIFFY_OFFSET);
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+
+	/* Don't mess with timeouts setup by autoneg */
+	if (link->an_timeout_active) {
+		spin_unlock(&link->timeout_lock);
+		return;
+	}
+
+	now = jiffies;
+	if (time_before(now, link->last_start_jiffy)) {
+		left = link->last_start_jiffy - now;
+		if (left < required) {
+			sbl_dev_info(sbl->dev, "%d: extending timeout by %ld\n", port_num,
+					(required - left) / HZ);
+			link->start_timeout += DIV_ROUND_UP(required - left, HZ);
+			link->last_start_jiffy = now + required;
+		} else {
+			sbl_dev_dbg(sbl->dev, "%d: timeout OK - now %ld, last %ld, left %ld, req %ld\n",
+				    port_num, now, link->last_start_jiffy, left, required);
+		}
+	} else {
+		/* we should timeout but extend instead */
+		sbl_dev_info(sbl->dev, "%d: extending timeout by %ld\n", port_num,
+				required / HZ);
+		link->start_timeout += DIV_ROUND_UP(now - link->last_start_jiffy +
+						 required, HZ);
+		link->last_start_jiffy = now + required;
+	}
+
+	spin_unlock(&link->timeout_lock);
+}
+
+int sbl_link_start_elapsed(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct timespec64 elapsed;
+
+	spin_lock(&link->timeout_lock);
+	elapsed = ktime_to_timespec64(ktime_sub(ktime_get(), link->start_time_begin));
+	spin_unlock(&link->timeout_lock);
+
+	return elapsed.tv_sec;
+}
+
+void sbl_link_start_record_timespec(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->start_time = ktime_to_timespec64(ktime_sub(ktime_get(), link->start_time_begin));
+	spin_unlock(&link->timeout_lock);
+}
+
+/* up time is the time to tune serdes and bring-up pml */
+void sbl_link_up_begin(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->up_time_begin = ktime_get();
+	spin_unlock(&link->timeout_lock);
+}
+
+void sbl_link_up_record_timespec(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->up_time = ktime_to_timespec64(ktime_sub(ktime_get(), link->up_time_begin));
+	spin_unlock(&link->timeout_lock);
+}
+
+/* tune time is the time serdes spent actually tuning */
+void sbl_link_tune_begin(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->tune_time_begin = ktime_get();
+	spin_unlock(&link->timeout_lock);
+}
+
+int sbl_link_tune_elapsed(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	struct timespec64 elapsed;
+
+	spin_lock(&link->timeout_lock);
+	elapsed = ktime_to_timespec64(ktime_sub(ktime_get(), link->tune_time_begin));
+	spin_unlock(&link->timeout_lock);
+
+	return elapsed.tv_sec;
+}
+
+void sbl_link_tune_zero_total_timespec(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->total_tune_time.tv_sec = 0;
+	link->total_tune_time.tv_nsec = 0;
+	spin_unlock(&link->timeout_lock);
+}
+
+void sbl_link_tune_update_total_timespec(struct sbl_inst *sbl, int port_num)
+{
+	struct sbl_link *link = sbl->link + port_num;
+	u64 ns;
+
+	sbl_dev_dbg(sbl->dev, "%d: %s\n", port_num, __func__);
+
+	spin_lock(&link->timeout_lock);
+	link->tune_time = ktime_to_timespec64(ktime_sub(ktime_get(), link->tune_time_begin));
+
+	link->total_tune_time.tv_sec += link->tune_time.tv_sec;
+	ns = link->total_tune_time.tv_nsec + link->tune_time.tv_nsec;
+	if (ns >= NSEC_PER_SEC) {
+		link->total_tune_time.tv_sec += 1;
+		ns -= NSEC_PER_SEC;
+	}
+	link->total_tune_time.tv_nsec = ns;
+	spin_unlock(&link->timeout_lock);
+}

-- 
2.56.0


  parent reply	other threads:[~2026-10-30 21:00 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-30 20:58 [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE Slingshot NIC drivers Doug Ledford
2026-10-30 20:58 ` [PATCH 01/23] net: hpe: ss1: add the Cassini headers Doug Ledford
2026-10-30 20:58 ` [PATCH 02/23] net: hpe: sbl: add the Slingshot Base Link headers Doug Ledford
2026-10-30 20:58 ` Doug Ledford [this message]
2026-10-30 20:59 ` [PATCH 04/23] net: hpe: sbl: add the SerDes layer Doug Ledford
2026-10-30 20:59 ` [PATCH 05/23] net: hpe: sbl: add the PML link layer Doug Ledford
2026-10-30 20:59 ` [PATCH 06/23] net: hpe: sbl: add link state management Doug Ledford
2026-10-30 20:59 ` [PATCH 07/23] net: hpe: sbl: add the SBM serial bus layer Doug Ledford
2026-10-30 20:59 ` [PATCH 08/23] net: hpe: sbl: add media, FEC and counter support Doug Ledford
2026-10-30 20:59 ` [PATCH 09/23] net: hpe: sl: add the Slingshot Link headers Doug Ledford
2026-10-30 20:59 ` [PATCH 10/23] net: hpe: sl: add the module entry points Doug Ledford
2026-10-30 20:59 ` [PATCH 11/23] net: hpe: sl: add the core link state machines Doug Ledford
2026-10-30 20:59 ` [PATCH 12/23] net: hpe: sl: add the control layer Doug Ledford
2026-10-30 20:59 ` [PATCH 13/23] net: hpe: sl: add media and cable handling Doug Ledford
2026-10-30 20:59 ` [PATCH 14/23] net: hpe: sl: add the sysfs interface Doug Ledford
2026-10-30 20:59 ` [PATCH 15/23] net: hpe: sl: add the NIC binding and logging helpers Doug Ledford
2026-10-30 20:59 ` [PATCH 16/23] net: hpe: ss1: add the Cassini core device layer Doug Ledford
2026-10-30 20:59 ` [PATCH 17/23] net: hpe: ss1: add the datapath and memory translation layers Doug Ledford
2026-10-30 20:59 ` [PATCH 18/23] net: hpe: ss1: add the link layer glue Doug Ledford
2026-10-30 20:59 ` [PATCH 19/23] net: hpe: ss1: add SR-IOV support Doug Ledford
2026-10-30 20:59 ` [PATCH 20/23] net: hpe: ss1: add the CXI core interfaces Doug Ledford
2026-10-30 20:59 ` [PATCH 21/23] net: hpe: ss1: add resource groups, QoS and services Doug Ledford
2026-10-30 20:59 ` [PATCH 22/23] net: hpe: ss1: add the Ethernet personality Doug Ledford
2026-10-30 20:59 ` [PATCH 23/23] net: hpe: wire up the Slingshot NIC drivers Doug Ledford
2026-10-30 22:11 ` [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE " Jakub Kicinski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261030-cxi-netdev-submission-v1-3-8c2c391cc283@hpe.com \
    --to=doug.ledford@hpe.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=christian.koenig@amd.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=iraimani.pavadai@hpe.com \
    --cc=kuba@kernel.org \
    --cc=linux-kbuild@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=nathan@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=nsc@kernel.org \
    --cc=pabeni@redhat.com \
    --cc=richardcochran@gmail.com \
    --cc=santosh-kumar.mamila@hpe.com \
    --cc=slingshot-os-team@groups.ext.hpe.com \
    --cc=sumit.semwal@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®