[PATCH 08/23] net: hpe: sbl: add media, FEC and counter support
flat view
WARM1d
From: Doug Ledford <hidden>
Date: 2026-10-30 21:01:03
Also in:
linux-kbuild, lkml
Subsystem:
networking drivers, the rest · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Add media detection, FEC (forward error correction) configuration and
the link counters. sbl_platform.h is updated for the media fields
added 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 (5)
- CHECK: BRACES (16), UNNECESSARY_PARENTHESES (10)
Co-developed-by: Sandeep Bhoot <redacted>
Signed-off-by: Sandeep Bhoot <redacted>
Co-developed-by: Mamila Santosh <redacted>
Signed-off-by: Mamila Santosh <redacted>
Signed-off-by: Doug Ledford <redacted>
---
drivers/net/ethernet/hpe/sbl/sbl_counters.c | 137 ++++
drivers/net/ethernet/hpe/sbl/sbl_fec.c | 971 +++++++++++++++++++++++++++
drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c | 36 +
drivers/net/ethernet/hpe/sbl/sbl_media.c | 482 +++++++++++++
drivers/net/ethernet/hpe/sbl/sbl_media_nic.c | 47 ++
5 files changed, 1673 insertions(+)
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_counters.c b/drivers/net/ethernet/hpe/sbl/sbl_counters.c
new file mode 100644
index 000000000000..90feb6436105
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_counters.c@@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright 2019, 2022 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/device.h> + +#include <linux/hpe/sbl/sbl.h> + +#include "sbl_internal.h" + +/* create and initialise the SBL link's counter array */ +int sbl_link_counters_init(struct sbl_link *link) +{ + int i; + + if (!link->counters) { + link->counters = kzalloc(sizeof(atomic_t) * SBL_LINK_NUM_COUNTERS, + GFP_KERNEL); + if (!link->counters) + return -ENOMEM; + } + + for (i = 0; i < SBL_LINK_NUM_COUNTERS; ++i) + atomic_set(link->counters + i, 0); + + return 0; +} + +/* destroy the SBL link's counter array */ +void sbl_link_counters_term(struct sbl_link *link) +{ + kfree(link->counters); + link->counters = NULL; +} + +/** + * sbl_link_counters_get() - Get a block of SBL link counters + * @sbl: A slingshot base link device instance + * @port_num: port number + * @counters: Number of counters to read from 'first' + * @first: Index of the first counter to read + * @count: Number of consecutive counters to read starting from 'first' + * + * Reads a sequence of counters starting from index 'first' + * until the given 'count' and stores them to output + * pointed by 'counters' + * + * Return: 0 on success, negative error code on failure + */ +int sbl_link_counters_get(struct sbl_inst *sbl, int port_num, + int *counters, u16 first, u16 count) +{ + struct sbl_link *link; + int i; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return -EINVAL; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return -EINVAL; + + link = sbl->link + port_num; + + if ((first + count) > SBL_LINK_NUM_COUNTERS) + return -EINVAL; + + if (!counters) + return -EINVAL; + + for (i = first; i < first + count; ++i) + *counters++ = atomic_read(link->counters + i); + + return 0; +} +EXPORT_SYMBOL(sbl_link_counters_get); + +/** + * sbl_link_counters_read() - Read value of a SBL link counter + * @sbl: A slingshot base link device instance + * @port_num: port number + * @counter: Index at which reads the counter value + * + * Read link count value based on the 'counter' index + * + * Return: value of a SBL link counter on success, 0 on failure + */ +int sbl_link_counters_read(struct sbl_inst *sbl, int port_num, u16 counter) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return 0; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return 0; + + link = sbl->link + port_num; + + if (counter >= SBL_LINK_NUM_COUNTERS) + return 0; + + return atomic_read(link->counters + counter); +} +EXPORT_SYMBOL(sbl_link_counters_read); + +/* Increment a SBL link counter */ +int sbl_link_counters_incr(struct sbl_inst *sbl, int port_num, u16 counter) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return -EINVAL; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return -EINVAL; + + link = sbl->link + port_num; + + if (counter >= SBL_LINK_NUM_COUNTERS) + return -EINVAL; + + atomic_inc(link->counters + counter); + + return 0; +}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_fec.c b/drivers/net/ethernet/hpe/sbl/sbl_fec.c
new file mode 100644
index 000000000000..d458ca5e1269
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_fec.c@@ -0,0 +1,971 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright 2024-2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> + +#include <linux/hpe/sbl/sbl.h> +#include <linux/hpe/sbl/sbl_an.h> +#include <linux/hpe/sbl/sbl_fec.h> +#include <linux/hpe/sbl/sbl_kconfig.h> + +#include <linux/hpe/sbl/sbl_pml.h> + +#include "sbl_serdes_map.h" +#include "sbl_pml_fn.h" +#include "sbl_link.h" +#include "sbl_serdes.h" +#include "sbl_serdes_fn.h" +#include "sbl_internal.h" + +static void sbl_fec_counts_zero(struct sbl_inst *sbl, int port_num, + struct sbl_pcs_fec_cntrs *cntrs) +{ + memset(cntrs, 0, sizeof(struct sbl_pcs_fec_cntrs)); +} + +static u64 sbl_fec_rate_calc(struct sbl_inst *sbl, int port_num, + u64 curr, u64 prev, unsigned long tdiff) +{ + if (curr < prev) { + sbl_dev_err(sbl->dev, "%d: fec counter went backwards", port_num); + + return 0; + } else + return (curr - prev) * HZ / tdiff; +} + +static int sbl_fec_rates_update(struct sbl_inst *sbl, int port_num, + u32 window) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + struct sbl_pcs_fec_cntrs *temp; + unsigned long tdiff; + int i; + bool discard_rates; + enum sbl_fec_discard_type reason; + unsigned long irq_flags; + + spin_lock(&fec_prmts->fec_cnt_lock); + + /* make sure the period is sufficient to update rates + * otherwise zero rates so nothing fires + */ + if (time_is_after_jiffies(fec_prmts->fec_curr_cnts->time + msecs_to_jiffies(window))) { + sbl_fec_counts_zero(sbl, port_num, fec_prmts->fec_rates); + spin_unlock(&fec_prmts->fec_cnt_lock); + + return -EINPROGRESS; + } + + temp = fec_prmts->fec_prev_cnts; + fec_prmts->fec_prev_cnts = fec_prmts->fec_curr_cnts; + fec_prmts->fec_curr_cnts = temp; + sbl_fec_counts_get(sbl, port_num, fec_prmts->fec_curr_cnts); + + spin_lock_irqsave(&link->fec_discard_lock, irq_flags); + discard_rates = (link->fec_discard_time >= fec_prmts->fec_prev_cnts->time) && + (link->fec_discard_time < fec_prmts->fec_curr_cnts->time); + reason = link->fec_discard_type; + spin_unlock_irqrestore(&link->fec_discard_lock, irq_flags); + + if (discard_rates) { + sbl_dev_dbg(sbl->dev, "%d: %s - ignoring FEC rates for the current window", port_num, + sbl_fec_discard_str(reason)); + sbl_fec_counts_zero(sbl, port_num, fec_prmts->fec_rates); + spin_unlock(&fec_prmts->fec_cnt_lock); + return -EINTR; + } + + /* calc time difference (HZ) + * (unsigned so rollover ok) + */ + tdiff = fec_prmts->fec_curr_cnts->time - fec_prmts->fec_prev_cnts->time; + + /* calculate rates */ + fec_prmts->fec_rates->ccw = + sbl_fec_rate_calc(sbl, port_num, + fec_prmts->fec_curr_cnts->ccw, + fec_prmts->fec_prev_cnts->ccw, tdiff); + fec_prmts->fec_rates->ucw = + sbl_fec_rate_calc(sbl, port_num, + fec_prmts->fec_curr_cnts->ucw, + fec_prmts->fec_prev_cnts->ucw, tdiff); + fec_prmts->fec_rates->llr_tx_replay = + sbl_fec_rate_calc(sbl, port_num, + fec_prmts->fec_curr_cnts->llr_tx_replay, + fec_prmts->fec_prev_cnts->llr_tx_replay, tdiff); + + for (i = 0; i < SBL_PCS_NUM_FECL_CNTRS; ++i) + fec_prmts->fec_rates->fecl[i] = + sbl_fec_rate_calc(sbl, port_num, + fec_prmts->fec_curr_cnts->fecl[i], + fec_prmts->fec_prev_cnts->fecl[i], tdiff); + + fec_prmts->fec_rates->time = jiffies_to_msecs(tdiff); + + spin_unlock(&fec_prmts->fec_cnt_lock); + + return 0; + +} + +/* returns true if the corrected code word rate is bad */ +static bool sbl_fec_ccw_rate_bad(struct sbl_inst *sbl, int port_num, + u32 thresh_adj, bool use_stp_thresh) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + u64 ccw_bad; + bool ignore_err; + u64 ccw_hwm; + + sbl_fec_ccw_bad_get(fec_prmts, use_stp_thresh, &ccw_bad, &ccw_hwm); + + /* update high water mark + * we record this even if no test is performed so we can use it for + * debug/calibration + */ + if (fec_prmts->fec_rates->ccw > ccw_hwm) { + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_ccw_hwm = fec_prmts->fec_rates->ccw; + spin_unlock(&fec_prmts->fec_cw_lock); + } + + /* apply percentage adjustment */ + ccw_bad = ccw_bad * thresh_adj / 100; + + if (ccw_bad == 0) { + sbl_dev_dbg(sbl->dev, "%d: fec ccw test ignored, threshold is zero", port_num); + + return false; + } + + /* check corrected code words */ + if (fec_prmts->fec_rates->ccw > ccw_bad) { + sbl_link_counters_incr(sbl, port_num, fec_ccw_err); + ignore_err = sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_HIGH_FEC_CCW); + + sbl_dev_err(sbl->dev, "%d: bad ccw, ccw %lld (>%lld), ucw %lld, (%lld %lld %lld %lld %lld %lld %lld %lld), window %ldms%s\n", + port_num, fec_prmts->fec_rates->ccw, ccw_bad, + fec_prmts->fec_rates->ucw, + fec_prmts->fec_rates->fecl[0], fec_prmts->fec_rates->fecl[1], + fec_prmts->fec_rates->fecl[2], fec_prmts->fec_rates->fecl[3], + fec_prmts->fec_rates->fecl[4], fec_prmts->fec_rates->fecl[5], + fec_prmts->fec_rates->fecl[6], fec_prmts->fec_rates->fecl[7], + fec_prmts->fec_rates->time, + ignore_err ? " -ignored" : ""); + if (ignore_err) + return false; + else + return true; + } + return false; +} + +/* returns true if the uncorrected code word rate is bad + * threshold adjustment is a percentage + */ +static bool sbl_fec_ucw_rate_bad(struct sbl_inst *sbl, int port_num, + u32 thresh_adj) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + bool ignore_err; + u64 ucw_bad; + u64 ucw_hwm; + + sbl_fec_ucw_bad_get(fec_prmts, &ucw_bad, &ucw_hwm); + + /* update high water mark + * we record this even if no test is performed so we can use it for + * debug/calibration + */ + if (fec_prmts->fec_rates->ucw > ucw_hwm) { + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_ucw_hwm = fec_prmts->fec_rates->ucw; + spin_unlock(&fec_prmts->fec_cw_lock); + } + + /* apply percentage adjustment */ + ucw_bad = ucw_bad * thresh_adj / 100; + + if (ucw_bad == 0) { + sbl_dev_dbg(sbl->dev, "%d: fec ucw test ignored, threshold is zero", port_num); + return false; + } + + /* check uncorrected code words */ + if (fec_prmts->fec_rates->ucw > ucw_bad) { + sbl_link_counters_incr(sbl, port_num, fec_ucw_err); + ignore_err = sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_HIGH_FEC_UCW); + + sbl_dev_err(sbl->dev, "%d: bad ucw, ccw %lld, ucw %lld (>%lld), (%lld %lld %lld %lld %lld %lld %lld %lld), window %ldms%s\n", + port_num, fec_prmts->fec_rates->ccw, + fec_prmts->fec_rates->ucw, ucw_bad, + fec_prmts->fec_rates->fecl[0], fec_prmts->fec_rates->fecl[1], + fec_prmts->fec_rates->fecl[2], fec_prmts->fec_rates->fecl[3], + fec_prmts->fec_rates->fecl[4], fec_prmts->fec_rates->fecl[5], + fec_prmts->fec_rates->fecl[6], fec_prmts->fec_rates->fecl[7], + fec_prmts->fec_rates->time, + ignore_err ? " -ignored" : ""); + if (ignore_err) + return false; + else + return true; + } + return false; + +} + +static bool sbl_fec_txr_rate_bad(struct sbl_inst *sbl, int port_num, + u32 thresh_adj) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + u64 llr_tx_replay_bad; + u64 llr_tx_replay_hwm; + bool ignore_err; + + spin_lock(&fec_prmts->fec_cw_lock); + llr_tx_replay_bad = fec_prmts->fec_llr_tx_replay_thresh; + llr_tx_replay_hwm = fec_prmts->fec_llr_tx_replay_hwm; + spin_unlock(&fec_prmts->fec_cw_lock); + + /* update high water mark + * we record this even if no test is performed so we can use it for + * debug/calibration + */ + if (fec_prmts->fec_rates->llr_tx_replay > llr_tx_replay_hwm) { + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_llr_tx_replay_hwm = fec_prmts->fec_rates->llr_tx_replay; + spin_unlock(&fec_prmts->fec_cw_lock); + } + if (llr_tx_replay_bad == 0) { + sbl_dev_dbg(sbl->dev, "%d: fec llr_tx_replay test ignored, threshold is zero", port_num); + return false; + } + + /* check llr_tx_replay rate */ + if (fec_prmts->fec_rates->llr_tx_replay > llr_tx_replay_bad) { + sbl_link_counters_incr(sbl, port_num, fec_txr_err); + ignore_err = sbl_debug_option(sbl, port_num, SBL_DEBUG_IGNORE_HIGH_FEC_TXR); + + sbl_dev_err(sbl->dev, "%d: bad llr_tx_replay, llr_tx_replay %lld (>%lld), window %ldms%s\n", + port_num, + fec_prmts->fec_rates->llr_tx_replay, llr_tx_replay_bad, + fec_prmts->fec_rates->time, + ignore_err ? " -ignored" : ""); + + if (ignore_err) + return false; + else + return true; + } + return false; +} + +/* print a few warnings (so we can see how things are changing) about high + * fec lane error rates + */ +static void sbl_fec_rates_warnings(struct sbl_inst *sbl, int port_num, + int *warning_count) +{ + + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + int fecl_warn; + int i; + + spin_lock(&fec_prmts->fec_cw_lock); + fecl_warn = fec_prmts->fecl_warn; + spin_unlock(&fec_prmts->fec_cw_lock); + + if (fecl_warn == 0) { + dev_dbg(sbl->dev, "%d: fec ccw warn ignored, threshold is zero", port_num); + + return; + } + + for (i = 0; i < SBL_PCS_NUM_FECL_CNTRS; ++i) { + if (fec_prmts->fec_rates->fecl[i] > fecl_warn) { + sbl_link_counters_incr(sbl, port_num, fec_warn); + + if (*warning_count > 0) { + dev_warn(sbl->dev, + "sbl %d: warning, ccw %lld, (%lld %lld %lld %lld %lld %lld %lld %lld)%s\n", + port_num, fec_prmts->fec_rates->ccw, + fec_prmts->fec_rates->fecl[0], + fec_prmts->fec_rates->fecl[1], + fec_prmts->fec_rates->fecl[2], + fec_prmts->fec_rates->fecl[3], + fec_prmts->fec_rates->fecl[4], + fec_prmts->fec_rates->fecl[5], + fec_prmts->fec_rates->fecl[6], + fec_prmts->fec_rates->fecl[7], + (*warning_count == 1) ? " - last" : ""); + --*warning_count; + } else { + dev_dbg(sbl->dev, + "sbl %d: warning, ccw %lld, (%lld %lld %lld %lld %lld %lld %lld %lld)\n", + port_num, fec_prmts->fec_rates->ccw, + fec_prmts->fec_rates->fecl[0], + fec_prmts->fec_rates->fecl[1], + fec_prmts->fec_rates->fecl[2], + fec_prmts->fec_rates->fecl[3], + fec_prmts->fec_rates->fecl[4], + fec_prmts->fec_rates->fecl[5], + fec_prmts->fec_rates->fecl[6], + fec_prmts->fec_rates->fecl[7]); + } + return; + } + } +} + +/** + * sbl_fec_thresholds_clear() - Clear fec ccw/ucw/warn thresholds to zero + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Clears fec ccw/ucw/warn parameters to zero. This update is performed + * while holding a spinlock to ensure safe concurrent access. + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + */ +void sbl_fec_thresholds_clear(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + sbl_dev_dbg(sbl->dev, "%d: clearing fec thresholds", port_num); + + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_ucw_thresh = 0; + fec_prmts->fec_ccw_thresh = 0; + fec_prmts->fec_stp_ccw_thresh = 0; + fec_prmts->fecl_warn = 0; + spin_unlock(&fec_prmts->fec_cw_lock); +} +EXPORT_SYMBOL(sbl_fec_thresholds_clear); + +/** + * sbl_fec_thresholds_set() - setup the fec thresholds + * @sbl: A slingshot base link device instance + * @port_num: port number + * @ucw_in: value for link mode and media + * @ccw_in: value for link mode and media + * + * This function gets ccw/ucw/ccw warning values using arguments + * provided and copies it to the fec parameters. + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + * + * Return: 0 on success, negative error code on failure + */ +int sbl_fec_thresholds_set(struct sbl_inst *sbl, int port_num, + s32 ucw_in, s32 ccw_in) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + int link_mode; + u64 ucw; + u64 ccw; + u64 stp_ccw; + u64 fecl_warn = 0; + int err = 0; + + /* Uncorrected codeword threshold */ + switch (ucw_in) { + case SBL_LINK_FEC_INVALID: + sbl_dev_err(sbl->dev, "%d: link ucw invalid", port_num); + goto fail; + + case SBL_LINK_FEC_OFF: + ucw = 0; + break; + + case SBL_LINK_FEC_IEEE: + ucw = sbl_link_get_ucw_thresh_ieee(sbl, port_num); + break; + + case SBL_LINK_FEC_HPE: + ucw = sbl_link_get_ucw_thresh_hpe(sbl, port_num); + break; + + default: + + if (ucw_in < 0) { + sbl_dev_err(sbl->dev, "%d: bad link attr ucw (%d)", port_num, + ucw_in); + goto fail; + } + + /* explicit number, just use it */ + ucw = ucw_in; + break; + } + + /* Corrected codeword threshold */ + switch (ccw_in) { + case SBL_LINK_FEC_INVALID: + sbl_dev_err(sbl->dev, "%d: attr ccw invalid", port_num); + goto fail; + + case SBL_LINK_FEC_OFF: + ccw = 0; + stp_ccw = 0; + break; + + case SBL_LINK_FEC_IEEE: + ccw = sbl_link_get_ccw_thresh_ieee(sbl, port_num); + stp_ccw = sbl_link_get_stp_ccw_thresh_ieee(sbl, port_num); + break; + + case SBL_LINK_FEC_HPE: + ccw = sbl_link_get_ccw_thresh_hpe(sbl, port_num); + stp_ccw = sbl_link_get_stp_ccw_thresh_hpe(sbl, port_num); + break; + + default: + + if (ccw_in < 0) { + sbl_dev_err(sbl->dev, "%d:bad link attr ccw (%d)", port_num, + ccw_in); + goto fail; + } + + /* explicit number, just use it */ + ccw = ccw_in; + stp_ccw = ccw_in; + break; + } + + /* ccw warning thresholds */ + err = sbl_base_link_get_status(sbl, port_num, + NULL, NULL, NULL, NULL, NULL, &link_mode); + + if (err) { + sbl_dev_warn(sbl->dev, "%d:thresh, get mode failed [%d]-warnings disabled", port_num, err); + fecl_warn = 0; + } else { + + switch (link_mode) { + case SBL_LINK_MODE_BS_200G: + fecl_warn = ccw / 8; + break; + case SBL_LINK_MODE_BJ_100G: + fecl_warn = ccw / 8; + break; + case SBL_LINK_MODE_CD_100G: + fecl_warn = ccw / 4; + break; + case SBL_LINK_MODE_CD_50G: + fecl_warn = ccw / 2; + break; + default: + fecl_warn = 0; + break; + } + } + + sbl_dev_dbg(sbl->dev, "%d: Setting fec thresh ucw %lld, ccw %lld, warn %lld", + port_num, ucw, ccw, fecl_warn); + + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_ucw_thresh = ucw; + fec_prmts->fec_ccw_thresh = ccw; + fec_prmts->fec_stp_ccw_thresh = stp_ccw; + fec_prmts->fec_llr_tx_replay_thresh = SBL_FEC_LLR_TX_REPLAY_THRESH; + fec_prmts->fecl_warn = fecl_warn; + spin_unlock(&fec_prmts->fec_cw_lock); + + return 0; +fail: + sbl_dev_warn(sbl->dev, "%d: fec monitoring disabled", port_num); + sbl_fec_thresholds_clear(sbl, port_num); + return -ENAVAIL; +} +EXPORT_SYMBOL(sbl_fec_thresholds_set); + +/** + * sbl_fec_adjustments_set() - setup the fec threshold adjustments + * @sbl: A slingshot base link device instance + * @port_num: port number + * @ucw_adj: Value (An adjustment of zero means disable the test) + * @ccw_adj: Value (An adjustment of zero means disable the test) + * + * This function copies provided parameters(ucw adj/ccw adj) from caller + * into fec parameters of fec_ucw_down_thresh_adj and fec_ccw_down_thresh_adj + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + * + * Return: 0 on success + */ +int sbl_fec_adjustments_set(struct sbl_inst *sbl, int port_num, + u32 ucw_adj, u32 ccw_adj) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_ucw_down_thresh_adj = ucw_adj; + fec_prmts->fec_ccw_down_thresh_adj = ccw_adj; + spin_unlock(&fec_prmts->fec_cw_lock); + + sbl_dev_dbg(sbl->dev, "%d: Setting fec thresh adjustments ucw_adj %d, ccw_adj %d", + port_num, ucw_adj, ccw_adj); + + return 0; +} +EXPORT_SYMBOL(sbl_fec_adjustments_set); + +int sbl_fec_up_check(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + bool ucw_err; + bool ccw_err = false; + bool stp_ccw_err = false; + u32 ucw_thresh_adj; + u32 ccw_thresh_adj; + u32 stp_ccw_thresh_adj; + int i; + + spin_lock(&fec_prmts->fec_cw_lock); + ucw_thresh_adj = fec_prmts->fec_ucw_up_thresh_adj; + ccw_thresh_adj = fec_prmts->fec_ccw_up_thresh_adj; + stp_ccw_thresh_adj = fec_prmts->fec_stp_ccw_up_thresh_adj; + spin_unlock(&fec_prmts->fec_cw_lock); + + /* initial measurement */ + msleep(SBL_FEC_UP_SETTLE_PERIOD); /* time for fec rates to settle */ + sbl_fec_counts_get(sbl, port_num, fec_prmts->fec_curr_cnts); + + for (i = 0; i < (link->blattr.options & SBL_OPT_FABRIC_LINK ? + SBL_FEC_UP_COUNT_FABRIC : SBL_FEC_UP_COUNT_EDGE); ++i) { + + msleep(SBL_FEC_UP_WINDOW); + sbl_fec_rates_update(sbl, port_num, SBL_FEC_UP_WINDOW); + + ucw_err = sbl_fec_ucw_rate_bad(sbl, port_num, ucw_thresh_adj); + if (ucw_err) + sbl_dev_err(sbl->dev, "%d: fec up check: ucw fail", port_num); + + if ((link->dfe_tune_count == SBL_DFE_USED_SAVED_PARAMS) && (stp_ccw_thresh_adj > 0)) + stp_ccw_err = sbl_fec_ccw_rate_bad(sbl, port_num, stp_ccw_thresh_adj, true); + else + ccw_err = sbl_fec_ccw_rate_bad(sbl, port_num, ccw_thresh_adj, false); + + if (ccw_err) + sbl_dev_err(sbl->dev, "%d: fec up check: ccw fail", port_num); + else if (stp_ccw_err) + sbl_dev_err(sbl->dev, "%d: fec up check: stp ccw fail", port_num); + + if (ucw_err || ccw_err || stp_ccw_err) { + sbl_link_counters_incr(sbl, port_num, fec_up_fail); + return -EOVERFLOW; + } + } + + return 0; +} + +void sbl_fec_counts_get(struct sbl_inst *sbl, int port_num, + struct sbl_pcs_fec_cntrs *cntrs) +{ + cntrs->fecl[0] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_00_ADDR(port_num)); + cntrs->fecl[1] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_01_ADDR(port_num)); + cntrs->fecl[2] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_02_ADDR(port_num)); + cntrs->fecl[3] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_03_ADDR(port_num)); + cntrs->fecl[4] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_04_ADDR(port_num)); + cntrs->fecl[5] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_05_ADDR(port_num)); + cntrs->fecl[6] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_06_ADDR(port_num)); + cntrs->fecl[7] = sbl_read64(sbl, SBL_PCS_FECL_ERRORS_07_ADDR(port_num)); + + cntrs->ccw = sbl_read64(sbl, SBL_PCS_CORRECTED_CW_ADDR(port_num)); + cntrs->ucw = sbl_read64(sbl, SBL_PCS_UNCORRECTED_CW_ADDR(port_num)); + + cntrs->llr_tx_replay = sbl_read64(sbl, SBL_LLR_TX_REPLAY_EVENT_ADDR(port_num)); + + cntrs->time = jiffies; +} + +void sbl_fec_monitor_start(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + unsigned long flags; + + spin_lock_irqsave(&link->lock, flags); + if (!link->start_cancelled) { + link->fec_monitor_enabled = true; + mod_timer(&link->fec_data->fec_timer, + jiffies + msecs_to_jiffies(SBL_FEC_MON_PERIOD)); + } + spin_unlock_irqrestore(&link->lock, flags); +} + +void sbl_fec_monitor_stop(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + unsigned long flags; + + spin_lock_irqsave(&link->lock, flags); + link->fec_monitor_enabled = false; + spin_unlock_irqrestore(&link->lock, flags); + + timer_delete_sync(&link->fec_data->fec_timer); + cancel_work_sync(&link->fec_data->fec_timer_work); +} + +static void sbl_fec_monitor_rearm(struct sbl_link *link) +{ + unsigned long flags; + + spin_lock_irqsave(&link->lock, flags); + if (link->fec_monitor_enabled) + mod_timer(&link->fec_data->fec_timer, + jiffies + msecs_to_jiffies(SBL_FEC_MON_PERIOD)); + spin_unlock_irqrestore(&link->lock, flags); +} + +void sbl_fec_timer_work(struct work_struct *work) +{ + u32 ucw_thresh_adj; + u32 ccw_thresh_adj; + int port_num; + unsigned long irq_flags; + struct sbl_link *link; + enum sbl_link_down_origin down_origin = SBL_LINK_DOWN_ORIGIN_UNKNOWN; + struct sbl_fec *fec_prmts; + int warning_count = SBL_MAX_FEC_WARNINGS; + int err; + struct fec_data *fec_data = container_of(work, struct fec_data, fec_timer_work); + struct sbl_inst *sbl = fec_data->sbl; + + port_num = fec_data->port_num; + link = sbl->link + port_num; + fec_prmts = fec_data->fec_prmts; + + if ((link->blstate == SBL_BASE_LINK_STATUS_UP) && (sbl_pml_pcs_aligned(sbl, port_num))) { + err = sbl_fec_rates_update(sbl, port_num, SBL_FEC_MON_PERIOD); + + if (!err) { + spin_lock_irqsave(&fec_prmts->fec_cw_lock, irq_flags); + ucw_thresh_adj = fec_prmts->fec_ucw_down_thresh_adj; + ccw_thresh_adj = fec_prmts->fec_ccw_down_thresh_adj; + spin_unlock_irqrestore(&fec_prmts->fec_cw_lock, irq_flags); + + if (sbl_fec_ucw_rate_bad(sbl, port_num, ucw_thresh_adj)) { + /* take the link down */ + down_origin = SBL_LINK_DOWN_ORIGIN_UCW; + sbl_pml_link_down_async_alert(sbl, port_num, down_origin); + return; + } + + if (sbl_fec_ccw_rate_bad(sbl, port_num, ccw_thresh_adj, false)) { + /* take the link down */ + down_origin = SBL_LINK_DOWN_ORIGIN_CCW; + sbl_pml_link_down_async_alert(sbl, port_num, down_origin); + return; + } + + if (sbl_fec_txr_rate_bad(sbl, port_num, 0)) { + /* take the link down */ + down_origin = SBL_LINK_DOWN_ORIGIN_LLR_TX_REPLAY; + sbl_pml_link_down_async_alert(sbl, port_num, down_origin); + return; + } + sbl_fec_rates_warnings(sbl, port_num, &warning_count); + + } + + } + sbl_fec_monitor_rearm(link); +} + +void sbl_zero_all_fec_counts(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + spin_lock(&fec_prmts->fec_cnt_lock); + memset(fec_prmts->fec_curr_cnts, 0, sizeof(struct sbl_pcs_fec_cntrs)); + memset(fec_prmts->fec_prev_cnts, 0, sizeof(struct sbl_pcs_fec_cntrs)); + memset(fec_prmts->fec_rates, 0, sizeof(struct sbl_pcs_fec_cntrs)); + spin_unlock(&fec_prmts->fec_cnt_lock); +} + +void sbl_fec_timer(struct timer_list *timer) +{ + int port_num; + struct sbl_link *link; + struct fec_data *fec_data = timer_container_of(fec_data, timer, + fec_timer); + struct sbl_inst *sbl = fec_data->sbl; + + port_num = fec_data->port_num; + link = sbl->link + port_num; + + if (!queue_work(sbl->workq, &(link->fec_data->fec_timer_work))) + sbl_dev_warn(sbl->dev, "fec timer work already queued"); +} + +#ifdef CONFIG_SYSFS +/** + * sbl_fec_sysfs_sprint() - Format FEC parameters status into buffer + * @sbl: A slingshot base link device instance + * @port_num: port number + * @buf: Destination buffer to write the data + * @size: Size of @buf in bytes, the output is truncated to fit + * + * This function formats FEC parameters and writes the result into the + * provided buffer typically for use in sysfs output. + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + * + * Return: Number of characters written, at most @size - 1 + */ +int sbl_fec_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size) +{ + u64 ucw_thresh; + u64 ucw_hwm; + u64 ccw_thresh; + u64 ccw_hwm; + u64 llr_tx_replay_thresh; + u64 llr_tx_replay_hwm; + u64 fecl_warn; + int s = 0; + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + spin_lock(&fec_prmts->fec_cw_lock); + if (link->blstate == SBL_BASE_LINK_STATUS_STARTING) { + ucw_thresh = fec_prmts->fec_ucw_thresh * fec_prmts->fec_ucw_up_thresh_adj / 100; + ccw_thresh = fec_prmts->fec_ccw_thresh * fec_prmts->fec_ccw_up_thresh_adj / 100; + } else { + ucw_thresh = fec_prmts->fec_ucw_thresh * fec_prmts->fec_ucw_down_thresh_adj / 100; + ccw_thresh = fec_prmts->fec_ccw_thresh * fec_prmts->fec_ccw_down_thresh_adj / 100; + } + + llr_tx_replay_thresh = fec_prmts->fec_llr_tx_replay_thresh; + ucw_hwm = fec_prmts->fec_ucw_hwm; + ccw_hwm = fec_prmts->fec_ccw_hwm; + llr_tx_replay_hwm = fec_prmts->fec_llr_tx_replay_hwm; + fecl_warn = fec_prmts->fecl_warn; + spin_unlock(&fec_prmts->fec_cw_lock); + if (link->blstate == SBL_BASE_LINK_STATUS_UP) { + + s += scnprintf(buf + s, size - s, + "fec monitor: hwm- ccw %lld/%lld, ucw %lld/%lld", + ccw_hwm, ccw_thresh, ucw_hwm, ucw_thresh); + s += scnprintf(buf + s, size - s, + ", llr_tx_replay %lld/%lld, warn %lld", + llr_tx_replay_hwm, llr_tx_replay_thresh, fecl_warn); + + if (ucw_thresh == 0) + s += scnprintf(buf + s, size - s, ", ucw-off"); + + if (ccw_thresh == 0) + s += scnprintf(buf + s, size - s, ", ccw-off"); + + s += scnprintf(buf + s, size - s, "\n"); + + if (sbl_pml_pcs_aligned(sbl, port_num)) { + spin_lock(&fec_prmts->fec_cnt_lock); + if (fec_prmts->fec_rates) { + s += scnprintf(buf + s, size - s, + "fec monitor: rates- ccw %lld", + fec_prmts->fec_rates->ccw); + s += scnprintf(buf + s, size - s, ", ucw %lld/%lld", + fec_prmts->fec_rates->ucw, + fec_prmts->fec_curr_cnts->ucw); + s += scnprintf(buf + s, size - s, + ", llr_tx_replay %lld, window %ld", + fec_prmts->fec_rates->llr_tx_replay, + fec_prmts->fec_rates->time); + s += scnprintf(buf + s, size - s, + ", (%lld %lld %lld %lld %lld %lld %lld %lld)\n", + fec_prmts->fec_rates->fecl[0], + fec_prmts->fec_rates->fecl[1], + fec_prmts->fec_rates->fecl[2], + fec_prmts->fec_rates->fecl[3], + fec_prmts->fec_rates->fecl[4], + fec_prmts->fec_rates->fecl[5], + fec_prmts->fec_rates->fecl[6], + fec_prmts->fec_rates->fecl[7]); + } + spin_unlock(&fec_prmts->fec_cnt_lock); + } + } + return s; +} +EXPORT_SYMBOL(sbl_fec_sysfs_sprint); +#endif + +/** + * sbl_fec_txr_rate_set() - set the llr replay threshold + * @sbl: A slingshot base link device instance + * @port_num: port number + * @txr_rate: Value - An adjustment of zero means disable the test + * + * This function sets the LLR (Link-Level Retry) replay threshold + * used by the FEC logic. The threshold value is provided by the caller. + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + * + * Return: 0 on success + */ +int sbl_fec_txr_rate_set(struct sbl_inst *sbl, int port_num, + u32 txr_rate) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_llr_tx_replay_thresh = txr_rate; + spin_unlock(&fec_prmts->fec_cw_lock); + + sbl_dev_dbg(sbl->dev, "%d: Setting txr_rate %d", + port_num, txr_rate); + + return 0; +} +EXPORT_SYMBOL(sbl_fec_txr_rate_set); + +/** + * sbl_fec_modify_adjustments() - Update FEC threshold adjustments + * @sbl: A slingshot base link device instance + * @port_num: port number + * @ucw_up_adj: Adjusting un-corrected code word thresholds + * @ccw_up_adj: Adjusting corrected code word thresholds + * @ucw_down_adj: Adjusting un-corrected code word thresholds + * @ccw_down_adj: Adjusting corrected code word thresholds + * @stp_ccw_up_adj: stp un-corrected code word thresholds + * + * This function adjusts max/min of uncorrected/corrected + * words for tuning/debug/test purposes at various levels. + * Used at test/debug/tuning interface. + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + */ +void sbl_fec_modify_adjustments(struct sbl_inst *sbl, int port_num, + u32 *ucw_up_adj, u32 *ccw_up_adj, + u32 *ucw_down_adj, u32 *ccw_down_adj, + u32 *stp_ccw_up_adj) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + spin_lock(&fec_prmts->fec_cw_lock); + if (ucw_up_adj) + fec_prmts->fec_ucw_up_thresh_adj = *ucw_up_adj; + if (ccw_up_adj) + fec_prmts->fec_ccw_up_thresh_adj = *ccw_up_adj; + if (ucw_down_adj) + fec_prmts->fec_ucw_down_thresh_adj = *ucw_down_adj; + if (ccw_down_adj) + fec_prmts->fec_ccw_down_thresh_adj = *ccw_down_adj; + if (stp_ccw_up_adj) + fec_prmts->fec_stp_ccw_up_thresh_adj = *stp_ccw_up_adj; + spin_unlock(&fec_prmts->fec_cw_lock); +} +EXPORT_SYMBOL(sbl_fec_modify_adjustments); + +/** + * sbl_fec_dump() - Print FEC info + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + */ +void sbl_fec_dump(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + u64 ucw_thresh; + u64 ccw_thresh; + u64 stp_ccw_thresh; + u32 ucw_up_adj; + u32 ccw_up_adj; + u32 stp_ccw_up_adj; + u32 ucw_down_adj; + u32 ccw_down_adj; + + spin_lock(&fec_prmts->fec_cw_lock); + ucw_thresh = fec_prmts->fec_ucw_thresh; + ccw_thresh = fec_prmts->fec_ccw_thresh; + stp_ccw_thresh = fec_prmts->fec_stp_ccw_thresh; + ucw_up_adj = fec_prmts->fec_ucw_up_thresh_adj; + ccw_up_adj = fec_prmts->fec_ccw_up_thresh_adj; + stp_ccw_up_adj = fec_prmts->fec_stp_ccw_up_thresh_adj; + ucw_down_adj = fec_prmts->fec_ucw_down_thresh_adj; + ccw_down_adj = fec_prmts->fec_ccw_down_thresh_adj; + spin_unlock(&fec_prmts->fec_cw_lock); + + sbl_dev_info(sbl->dev, "%d: ucw : thresh %lld", port_num, ucw_thresh); + sbl_dev_info(sbl->dev, "%d: ucw up: x%d%%, %lld", port_num, + ucw_up_adj, ucw_thresh * ucw_up_adj / 100); + sbl_dev_info(sbl->dev, "%d: ucw down: x%d%%, %lld", port_num, + ucw_down_adj, ucw_thresh * ucw_down_adj / 100); + + sbl_dev_info(sbl->dev, "%d: ccw : thresh %lld", port_num, ccw_thresh); + sbl_dev_info(sbl->dev, "%d: stp ccw : thresh %lld", port_num, stp_ccw_thresh); + sbl_dev_info(sbl->dev, "%d: ccw up: x%d%%, %lld", port_num, + ccw_up_adj, ccw_thresh * ccw_up_adj / 100); + sbl_dev_info(sbl->dev, "%d: stp ccw up: x%d%%, %lld", port_num, + stp_ccw_up_adj, stp_ccw_thresh * stp_ccw_up_adj / 100); + sbl_dev_info(sbl->dev, "%d: ccw down: x%d%%, %lld", port_num, + ccw_down_adj, ccw_thresh * ccw_down_adj / 100); +} +EXPORT_SYMBOL(sbl_fec_dump); + +/** + * sbl_fec_hwms_clear() - Clear fec HW management config + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function clears the FEC HWM configuration + * + * Context: Process context, Acquires lock and release fec_cw_lock <spin_lock> + */ +void sbl_fec_hwms_clear(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + struct fec_data *fec_data = link->fec_data; + struct sbl_fec *fec_prmts = fec_data->fec_prmts; + + sbl_dev_dbg(sbl->dev, "%d: clearing fec thresholds", port_num); + + spin_lock(&fec_prmts->fec_cw_lock); + fec_prmts->fec_ucw_hwm = 0; + fec_prmts->fec_ccw_hwm = 0; + fec_prmts->fec_llr_tx_replay_hwm = 0; + spin_unlock(&fec_prmts->fec_cw_lock); +} +EXPORT_SYMBOL(sbl_fec_hwms_clear);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c
new file mode 100644
index 000000000000..e7a06c8895d7
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_fec_nic.c@@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright 2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> + +#include <linux/hpe/sbl/sbl.h> +#include <linux/hpe/sbl/sbl_fec.h> +#include <linux/hpe/sbl/sbl_kconfig.h> + +#include <linux/hpe/sbl/sbl_pml.h> + +#include "sbl_internal.h" + +void sbl_fec_ucw_bad_get(struct sbl_fec *fec_prmts, u64 *ucw_bad, u64 *ucw_hwm) +{ + spin_lock(&fec_prmts->fec_cw_lock); + *ucw_hwm = fec_prmts->fec_ucw_hwm; + *ucw_bad = fec_prmts->fec_ucw_thresh; + spin_unlock(&fec_prmts->fec_cw_lock); +} + +void sbl_fec_ccw_bad_get(struct sbl_fec *fec_prmts, bool use_stp_thresh, + u64 *ccw_bad, u64 *ccw_hwm) +{ + spin_lock(&fec_prmts->fec_cw_lock); + *ccw_hwm = fec_prmts->fec_ccw_hwm; + *ccw_bad = use_stp_thresh ? fec_prmts->fec_stp_ccw_thresh : + fec_prmts->fec_ccw_thresh; + spin_unlock(&fec_prmts->fec_cw_lock); +}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_media.c b/drivers/net/ethernet/hpe/sbl/sbl_media.c
new file mode 100644
index 000000000000..ec2b4574565e
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_media.c@@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright 2019, 2021-2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#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_pml.h> + +#include "sbl_internal.h" + +/** + * sbl_media_config() - Config media + * @sbl: A slingshot base link device instance + * @port_num: port number + * @mattr: media attributes + * + * This function sets the media attributes for the link + * and sets the state as configured. + * + * Context: Process, Acquires locks and releases link->lock <spin_lock> + * + * Return: 0 on success, negative error code on failure + */ +int sbl_media_config(struct sbl_inst *sbl, int port_num, + struct sbl_media_attr *mattr) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + sbl_dev_dbg(sbl->dev, "media config\n"); + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + if (!mattr) + return -EINVAL; + + if (mattr->magic != SBL_MEDIA_ATTR_MAGIC) + return -EINVAL; + + link = sbl->link + port_num; + + spin_lock(&link->lock); + + if (!(link->blstate & (SBL_BASE_LINK_STATUS_UNCONFIGURED | SBL_BASE_LINK_STATUS_DOWN))) { + + sbl_dev_err(sbl->dev, "%d: wrong state (%s) to configure media\n", port_num, + sbl_link_state_str(link->blstate)); + spin_unlock(&link->lock); + return -EUCLEAN; + } + + memcpy(&link->mattr, mattr, sizeof(struct sbl_media_attr)); + link->mconfigured = true; + + if (link->blconfigured) + link->blstate = SBL_BASE_LINK_STATUS_DOWN; + + /* the media might have changed so invalidate the llr loop time */ + link->llr_loop_time = 0; + + spin_unlock(&link->lock); + + return 0; +} +EXPORT_SYMBOL(sbl_media_config); + +/** + * sbl_media_unconfig() - Unconfig media + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Clear media attributes and set base link status to unconfigure. + * + * Context: Process, Acquires Lock and releases link->lock <spin_lock> + * + * Return: negative error code on failure + */ +int sbl_media_unconfig(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link; + int err = 0; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + sbl_dev_dbg(sbl->dev, "media unconfig\n"); + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + link = sbl->link + port_num; + + spin_lock(&link->lock); + + if (!(link->blstate & (SBL_BASE_LINK_STATUS_UNCONFIGURED | + SBL_BASE_LINK_STATUS_DOWN | + SBL_BASE_LINK_STATUS_STOPPING))) { + + sbl_dev_err(sbl->dev, "%d: wrong state (%s) to unconfigure media\n", port_num, + sbl_link_state_str(link->blstate)); + spin_unlock(&link->lock); + return -EUCLEAN; + } + + memset(&link->mattr, 0, sizeof(struct sbl_media_attr)); + link->mconfigured = false; + + link->blstate = SBL_BASE_LINK_STATUS_UNCONFIGURED; + + spin_unlock(&link->lock); + + return err; +} +EXPORT_SYMBOL(sbl_media_unconfig); + +bool sbl_media_check_mode_supported(struct sbl_inst *sbl, int port_num, u32 link_mode) +{ + struct sbl_link *link = sbl->link + port_num; + u32 info_mode_bit = 0; + u32 media_info; + bool allow_bad_mode; + unsigned long flags; + + spin_lock_irqsave(&link->lock, flags); + media_info = link->mattr.info; + allow_bad_mode = link->blattr.options & SBL_OPT_ALLOW_MEDIA_BAD_MODE; + spin_unlock_irqrestore(&link->lock, flags); + + if (!media_info) { + /* We have no info about media + * assume its OK so we can carry on + * if its not we will fail later anyway + */ + sbl_dev_warn(sbl->dev, "%d: no media info - assuming link mode OK", port_num); + return true; + } + + switch (link_mode) { + case SBL_LINK_MODE_BS_200G: + info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_BS_200G; + break; + case SBL_LINK_MODE_BJ_100G: + info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_BJ_100G; + break; + case SBL_LINK_MODE_CD_100G: + info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_CD_100G; + break; + case SBL_LINK_MODE_CD_50G: + info_mode_bit = SBL_MEDIA_INFO_SUPPORTS_CD_50G; + break; + default: + /* do nothing here - below will catch invalid link mode */ + break; + } + + if (media_info & info_mode_bit) + return true; + + if (allow_bad_mode || + sbl_debug_option(sbl, port_num, SBL_DEBUG_ALLOW_MEDIA_BAD_MODE)) { + sbl_dev_warn(sbl->dev, "%d: link mode (%s) not supported by media - ignored", + port_num, sbl_link_mode_str(link_mode)); + return true; + } + sbl_dev_err(sbl->dev, "%d: link mode (%s) not supported by media", + port_num, sbl_link_mode_str(link_mode)); + return false; +} + +/** + * sbl_media_check_headshell_reset_supported() - Check headshell based on media info + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Return: True if reset is supported else false + */ +bool sbl_media_check_headshell_reset_supported(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + if (!link->mattr.info) { + /* We have no info about media */ + sbl_dev_dbg(sbl->dev, "%d: no media info available to check headshell reset supported bit", port_num); + return false; + } + + if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_HEADSHELL_RESET) + return true; + + return false; +} +EXPORT_SYMBOL(sbl_media_check_headshell_reset_supported); + +static bool sbl_media_check_len_supported(struct sbl_inst *sbl, int port_num, u64 len) +{ + struct sbl_link *link = sbl->link + port_num; + + switch (len) { + case SBL_LINK_LEN_BACKPLANE: + case SBL_LINK_LEN_000_300: + case SBL_LINK_LEN_000_400: + case SBL_LINK_LEN_000_750: + case SBL_LINK_LEN_000_800: + case SBL_LINK_LEN_001_000: + case SBL_LINK_LEN_001_100: + case SBL_LINK_LEN_001_150: + case SBL_LINK_LEN_001_200: + case SBL_LINK_LEN_001_400: + case SBL_LINK_LEN_001_420: + case SBL_LINK_LEN_001_500: + case SBL_LINK_LEN_001_600: + case SBL_LINK_LEN_001_640: + case SBL_LINK_LEN_001_700: + case SBL_LINK_LEN_001_800: + case SBL_LINK_LEN_001_900: + case SBL_LINK_LEN_001_910: + case SBL_LINK_LEN_002_000: + case SBL_LINK_LEN_002_100: + case SBL_LINK_LEN_002_130: + case SBL_LINK_LEN_002_200: + case SBL_LINK_LEN_002_300: + case SBL_LINK_LEN_002_390: + case SBL_LINK_LEN_002_400: + case SBL_LINK_LEN_002_500: + case SBL_LINK_LEN_002_600: + case SBL_LINK_LEN_002_620: + case SBL_LINK_LEN_002_700: + case SBL_LINK_LEN_002_800: + case SBL_LINK_LEN_002_900: + case SBL_LINK_LEN_002_990: + case SBL_LINK_LEN_003_000: + case SBL_LINK_LEN_004_000: + case SBL_LINK_LEN_005_000: + case SBL_LINK_LEN_006_000: + case SBL_LINK_LEN_007_000: + case SBL_LINK_LEN_008_000: + case SBL_LINK_LEN_010_000: + case SBL_LINK_LEN_014_000: + case SBL_LINK_LEN_015_000: + case SBL_LINK_LEN_019_000: + case SBL_LINK_LEN_025_000: + case SBL_LINK_LEN_030_000: + case SBL_LINK_LEN_035_000: + case SBL_LINK_LEN_050_000: + case SBL_LINK_LEN_075_000: + case SBL_LINK_LEN_100_000: + return true; + default: + if ((link->blattr.options & SBL_OPT_ALLOW_MEDIA_BAD_LEN) || + sbl_debug_option(sbl, port_num, SBL_DEBUG_ALLOW_MEDIA_BAD_LEN)) { + sbl_dev_warn(sbl->dev, "%d: unsupported media len (%lld) - ignored", port_num, len); + return true; + } + sbl_dev_err(sbl->dev, "%d: unsupported media len (%lld)", port_num, len); + return false; + } +} + +/* return valid length in cm or 0 */ +u32 sbl_media_get_len_cm(struct sbl_inst *sbl, int port_num, u64 len) +{ + if (sbl_media_check_len_supported(sbl, port_num, len)) { + switch (len) { + case SBL_LINK_LEN_BACKPLANE: return 25; + case SBL_LINK_LEN_000_300: return 30; + case SBL_LINK_LEN_000_400: return 40; + case SBL_LINK_LEN_000_750: return 75; + case SBL_LINK_LEN_000_800: return 80; + case SBL_LINK_LEN_001_000: return 100; + case SBL_LINK_LEN_001_100: return 110; + case SBL_LINK_LEN_001_150: return 115; + case SBL_LINK_LEN_001_200: return 120; + case SBL_LINK_LEN_001_400: return 140; + case SBL_LINK_LEN_001_420: return 142; + case SBL_LINK_LEN_001_500: return 150; + case SBL_LINK_LEN_001_600: return 160; + case SBL_LINK_LEN_001_640: return 164; + case SBL_LINK_LEN_001_700: return 170; + case SBL_LINK_LEN_001_800: return 180; + case SBL_LINK_LEN_001_900: return 190; + case SBL_LINK_LEN_001_910: return 191; + case SBL_LINK_LEN_002_000: return 200; + case SBL_LINK_LEN_002_100: return 210; + case SBL_LINK_LEN_002_130: return 213; + case SBL_LINK_LEN_002_200: return 220; + case SBL_LINK_LEN_002_300: return 230; + case SBL_LINK_LEN_002_390: return 239; + case SBL_LINK_LEN_002_400: return 240; + case SBL_LINK_LEN_002_500: return 250; + case SBL_LINK_LEN_002_600: return 260; + case SBL_LINK_LEN_002_620: return 262; + case SBL_LINK_LEN_002_700: return 270; + case SBL_LINK_LEN_002_800: return 280; + case SBL_LINK_LEN_002_900: return 290; + case SBL_LINK_LEN_002_990: return 299; + case SBL_LINK_LEN_003_000: return 300; + case SBL_LINK_LEN_004_000: return 400; + case SBL_LINK_LEN_005_000: return 500; + case SBL_LINK_LEN_006_000: return 600; + case SBL_LINK_LEN_007_000: return 700; + case SBL_LINK_LEN_008_000: return 800; + case SBL_LINK_LEN_010_000: return 1000; + case SBL_LINK_LEN_014_000: return 1400; + case SBL_LINK_LEN_015_000: return 1500; + case SBL_LINK_LEN_019_000: return 1900; + case SBL_LINK_LEN_025_000: return 2500; + case SBL_LINK_LEN_030_000: return 3000; + case SBL_LINK_LEN_035_000: return 3500; + case SBL_LINK_LEN_050_000: return 5000; + case SBL_LINK_LEN_075_000: return 7500; + case SBL_LINK_LEN_100_000: return 10000; + } + } + + return 0; +} + +/* calc loop time based on cable length */ +int sbl_media_calc_loop_time(struct sbl_inst *sbl, int port_num, u64 *calc_loop_time) +{ + struct sbl_link *link; + u32 len; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + if (!calc_loop_time) + return -EINVAL; + + link = sbl->link + port_num; + *calc_loop_time = 0; + + if ((link->blattr.loopback_mode == SBL_LOOPBACK_MODE_OFF) || + (link->blattr.loopback_mode == SBL_LOOPBACK_MODE_INVALID)) { + len = sbl_media_get_len_cm(sbl, port_num, link->mattr.len); + if (!len) + return -EBADRQC; + } else + len = 0; + + sbl_dev_dbg(sbl->dev, "%d: len = %d, lb = %s", + port_num, len, sbl_loopback_mode_str(link->blattr.loopback_mode)); + + if (link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL) + *calc_loop_time = 2 * len * SBL_MEDIA_NS_PER_M / 100; + else + *calc_loop_time = (2 * len * SBL_MEDIA_OPTICAL_NS_PER_M / 100) + + (4 * SBL_MEDIA_OPTICAL_TRANCEIVER_DELAY); + *calc_loop_time += 2 * (SBL_ASIC_TX_DELAY + SBL_ASIC_RX_DELAY); + + sbl_dev_dbg(sbl->dev, "%d: calc loop time %lld cm %lld ns", + port_num, link->mattr.len, *calc_loop_time); + + return 0; +} + +/** + * sbl_media_get_loop_time_ns() - Get loop time in ns + * @sbl: A slingshot base link device instance + * @port_num: port number + * @measurement_only: set to false when reading + * + * This function returns available loop time. If not and + * 'measurement_only' is true, returns 0 with a warning. + * Otherwise attempts to calculate the loop time. + * + * Return: calculate loop time on success, 0 on failure + */ +u64 sbl_media_get_loop_time_ns(struct sbl_inst *sbl, int port_num, + bool measurement_only) +{ + struct sbl_link *link = sbl->link + port_num; + u64 calc_loop_time; + int err; + + if (!link->mconfigured) + return 0; + + if (link->llr_loop_time) + return link->llr_loop_time; + + if (measurement_only) { + sbl_dev_warn(sbl->dev, "%d: measured loop time not found", port_num); + return 0; + } + + if (!sbl_media_check_len_supported(sbl, port_num, link->mattr.len)) + return 0; + + err = sbl_media_calc_loop_time(sbl, port_num, &calc_loop_time); + if (err) + return 0; + + return calc_loop_time; +} +EXPORT_SYMBOL(sbl_media_get_loop_time_ns); + +int sbl_media_validate_config(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + if (!(link->mconfigured && link->blconfigured)) + return -EUCLEAN; + + if ((link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL) && + (link->blattr.config_target == SBL_BASE_LINK_CONFIG_PEC)) + return 0; + + if ((link->mattr.media == SBL_LINK_MEDIA_OPTICAL) && + (link->blattr.config_target == SBL_BASE_LINK_CONFIG_AOC)) + return 0; + + return -EMEDIUMTYPE; +} + +#ifdef CONFIG_SYSFS +/** + * sbl_media_sysfs_sprint() - Format media info into buffer + * @sbl: A slingshot base link device instance + * @port_num: port number + * @buf: Destination buffer to write the data + * @size: Size of @buf in bytes, the output is truncated to fit + * + * This function checks the state of media configuration and info. + * Based on that it will format the string and write into the + * provided buffer. + * + * Return: Number of characters written, at most @size - 1 + */ +int sbl_media_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size) +{ + struct sbl_link *link = sbl->link + port_num; + int s = 0; + + spin_lock(&link->lock); + if (link->mconfigured) { + s += scnprintf(buf + s, size - s, "media: %s, %s, (%s), info ", + sbl_link_media_str(link->mattr.media), + sbl_link_len_str(link->mattr.len), + sbl_link_vendor_str(link->mattr.vendor)); + if (link->mattr.info) { + if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_BS_200G) + s += scnprintf(buf + s, size - s, "200 "); + if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_BJ_100G) + s += scnprintf(buf + s, size - s, "100bj "); + if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_CD_100G) + s += scnprintf(buf + s, size - s, "100cd "); + if (link->mattr.info & SBL_MEDIA_INFO_SUPPORTS_CD_50G) + s += scnprintf(buf + s, size - s, "50 "); + if (link->mattr.info & SBL_MEDIA_INFO_ANALOG) + s += scnprintf(buf + s, size - s, "analog "); + if (link->mattr.info & SBL_MEDIA_INFO_DIGITAL) + s += scnprintf(buf + s, size - s, "digital "); + } else + s += scnprintf(buf + s, size - s, "none"); + s += scnprintf(buf + s, size - s, "\n"); + } + spin_unlock(&link->lock); + + return s; +} +EXPORT_SYMBOL(sbl_media_sysfs_sprint); +#endif
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_media_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_media_nic.c
new file mode 100644
index 000000000000..dc462e17dd72
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_media_nic.c@@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright 2025 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#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_pml.h> + +#include "sbl_internal.h" + +#ifdef CONFIG_SYSFS + +/** + * sbl_media_type_sysfs_sprint() - Report media type into buffer + * @sbl: A slingshot base link device instance + * @port_num: port number + * @buf: Destination buffer to write the data + * @size: Size of @buf in bytes, the output is truncated to fit + * + * This function reads the physical type of media used based on mconfigured + * and writes a formatted description of the type into the provided buffer + * + * Return: Number of characters written, at most @size - 1 + */ +int sbl_media_type_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size) +{ + struct sbl_link *link = sbl->link + port_num; + int s = 0; + + spin_lock(&link->lock); + + if (link->mconfigured) + s += scnprintf(buf + s, size - s, "%s", sbl_link_media_str(link->mattr.media)); + else + s += scnprintf(buf + s, size - s, "NA"); + + spin_unlock(&link->lock); + + return s; +} +EXPORT_SYMBOL(sbl_media_type_sysfs_sprint); +#endif
--
2.56.0