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
next prev 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®