[PATCH 06/23] net: hpe: sbl: add link state management
flat view
HOTtoday
From: Doug Ledford <hidden>
Date: 2026-10-30 21:00:54
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 the link state machine: bring-up, auto-negotiation and the NIC
specific link ops. This ties the PML and SerDes layers together and
is what the NIC driver calls to take a link up or down.
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 (22), LONG_LINE_COMMENT (2)
- CHECK: BRACES (22), UNNECESSARY_PARENTHESES (8), COMPARISON_TO_NULL
(1)
Co-developed-by: Mamila Santosh <redacted>
Signed-off-by: Mamila Santosh <redacted>
Signed-off-by: Doug Ledford <redacted>
---
drivers/net/ethernet/hpe/sbl/sbl_link.c | 1739 ++++++++++++++++++++++++
drivers/net/ethernet/hpe/sbl/sbl_link_an.c | 1010 ++++++++++++++
drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c | 111 ++
drivers/net/ethernet/hpe/sbl/sbl_link_nic.c | 196 +++
4 files changed, 3056 insertions(+)
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link.c b/drivers/net/ethernet/hpe/sbl/sbl_link.c
new file mode 100644
index 000000000000..706fa9ba9676
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link.c@@ -0,0 +1,1739 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright 2019-2026 Hewlett Packard Enterprise Development LP */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/delay.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" + +/* detect the presence of our link partner */ +static int sbl_base_link_lp_detect(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + int err = 0; + + /* already detected - probably from autoneg */ + if (link->lp_detected) + return 0; + + sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_LP_DET); + + /* currently we have only one way to do this */ + if (link->blattr.options & SBL_OPT_SERDES_LPD) { + err = sbl_serdes_lp_detect(sbl, port_num); + if (!err) + link->lp_detected = true; + } + + sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_LP_DET); + + return err; +} + +/* Check/recover SBL FW */ +static int sbl_base_link_check_fix_fw(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + int serdes; + int err; + + for (serdes = 0; serdes < sbl->switch_info->num_serdes; ++serdes) { + err = sbl_validate_serdes_fw_crc(sbl, port_num, serdes); + if (err) { + /* Any lane with corrputed FW will cause all lanes for + * the port to be reloaded. + */ + goto reload_fw; + } + } + return 0; + + reload_fw: + err = sbl_serdes_firmware_flash_safe(sbl, port_num, false); + if (err) { + /* All we can do is report failure here */ + sbl_dev_err(sbl->dev, "%d: check/fix: fw flash failed [%d]\n", + port_num, err); + link->sstate = SBL_SERDES_STATUS_ERROR; + } + return err; +} + +static int sbl_link_get_mode_electrical(struct sbl_inst *sbl, int port_num) +{ + sbl_dev_dbg(sbl->dev, "bl %d: elec get mode", port_num); + + return sbl_link_autoneg(sbl, port_num); +} + +/* For now we will just use the configured speed */ +static int sbl_link_get_mode_optical(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + sbl_dev_dbg(sbl->dev, "bl %d: optical link - fixing speed to config value (%s)\n", + port_num, sbl_link_mode_str(link->blattr.link_mode)); + link->link_mode = link->blattr.link_mode; + + return 0; +} + +/* determine the link mode (speed) */ +static int sbl_base_link_get_mode(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + int err = 0; + + /* determine required mode */ + if (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL) { + /* directly use the mode specified - no media check */ + link->link_mode = link->blattr.link_mode; + return 0; + } + + switch (link->mattr.media) { + + case SBL_LINK_MEDIA_ELECTRICAL: + err = sbl_link_get_mode_electrical(sbl, port_num); + break; + + case SBL_LINK_MEDIA_OPTICAL: + err = sbl_link_get_mode_optical(sbl, port_num); + break; + + default: + sbl_dev_err(sbl->dev, "bl %d: bad media to determine mode", port_num); + err = -ENOMEDIUM; + break; + } + if (err) + return err; + + /* ensure the media supports the required mode */ + if (!sbl_media_check_mode_supported(sbl, port_num, link->link_mode)) { + sbl_dev_err(sbl->dev, "bl %d: config mode (%s) not supported by media", + port_num, sbl_link_mode_str(link->blattr.link_mode)); + return -EMEDIUMTYPE; + } else + return 0; +} + +/* return true if get_mode failed because autoneg timed out */ +static bool sbl_base_link_an_timed_out(struct sbl_inst *sbl, int port_num, int err) +{ + struct sbl_link *link = sbl->link + port_num; + + if (err != -ETIME) + return false; + + if (link->mattr.media != SBL_LINK_MEDIA_ELECTRICAL) + return false; + + if (link->blattr.pec.an_mode == SBL_AN_MODE_OFF) + return false; + + return true; +} + +static int sbl_link_fault_monitor_start(struct sbl_inst *sbl, int port_num) +{ + int err; + u32 base = SBL_PML_BASE(port_num); + u64 val64; + u64 err_flags; + + val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET); + if (SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_GET(val64) < SBL_LLR_REPLAY_CT_MAX_UNLIMITED) + err_flags = SBL_PML_FAULT_ERR_FLAGS; + else + err_flags = SBL_PML_REC_FAULT_ERR_FLAGS; + + /* make sure we have not already had an error */ + if (sbl_pml_err_flgs_test(sbl, port_num, err_flags)) { + sbl_dev_err(sbl->dev, "fm %d: start - errors already present\n", + port_num); + err = -ENETDOWN; + goto out; + } + + sbl_pml_recovery_enable(sbl, port_num); + + err = sbl_pml_install_intr_handler(sbl, port_num, err_flags); + if (err) { + sbl_dev_err(sbl->dev, "fm %d: intr install failed [%d]\n", port_num, err); + goto out; + } + + err = sbl_pml_enable_intr_handler(sbl, port_num, err_flags); + if (err) { + sbl_dev_err(sbl->dev, "fm %d: intr enable failed [%d]\n", port_num, err); + goto out_uninstall; + return err; + } + + if (sbl_pml_err_flgs_test(sbl, port_num, err_flags)) { + sbl_dev_err(sbl->dev, "fm %d: link down during start\n", port_num); + err = -ENETDOWN; + goto out_disable; + } else { + /* all up and running */ + sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_FAULT_MON); + } + return 0; + +out_disable: + sbl_pml_disable_intr_handler(sbl, port_num, err_flags); +out_uninstall: + sbl_pml_remove_intr_handler(sbl, port_num); +out: + sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_FAULT_MON); + + return err; +} + +static int sbl_link_fault_monitor_stop(struct sbl_inst *sbl, int port_num) +{ + int err; + u32 base = SBL_PML_BASE(port_num); + u64 val64; + u64 err_flags; + + val64 = sbl_read64(sbl, base | SBL_PML_CFG_LLR_SM_OFFSET); + if (SBL_PML_CFG_LLR_SM_REPLAY_CT_MAX_GET(val64) < SBL_LLR_REPLAY_CT_MAX_UNLIMITED) + err_flags = SBL_PML_FAULT_ERR_FLAGS; + else + err_flags = SBL_PML_REC_FAULT_ERR_FLAGS; + + err = sbl_pml_disable_intr_handler(sbl, port_num, err_flags); + if (err) { + sbl_dev_err(sbl->dev, "fm %d: intr disable failed [%d]\n", port_num, err); + return err; + } + + err = sbl_pml_remove_intr_handler(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "fm %d: intr remove failed [%d]\n", port_num, err); + return err; + } + + sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_FAULT_MON); + return 0; +} + +static void sbl_base_link_report_err(struct sbl_inst *sbl, char *txt, int port_num, int err) +{ + switch (err) { + case -ECANCELED: + sbl_dev_dbg(sbl->dev, "bl %d: %s cancelled\n", port_num, txt); + break; + case -ETIMEDOUT: + sbl_dev_dbg(sbl->dev, "bl %d: %s timed out\n", port_num, txt); + break; + default: + sbl_dev_err(sbl->dev, "bl %d: %s failed [%d]\n", port_num, txt, err); + } +} + +/** + * sbl_get_lp_subtype() - Get link partner sub type + * @sbl: A slingshot base link device instance + * @port_num: port number + * @lp_subtype: Value to get Link partner subtype + * + * This function reads the versions used (cas(v1/v2)) + * which refers to link partner sub type. Client driver + * uses this information for later process + * + * Return: 0 on success, negative error code on failure + */ +int sbl_get_lp_subtype(struct sbl_inst *sbl, int port_num, enum sbl_lp_subtype *lp_subtype) +{ + int err; + struct sbl_link *link; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + if (lp_subtype == NULL) + return -EINVAL; + + sbl_dev_dbg(sbl->dev, "%d: get lp subtype", port_num); + + link = sbl->link + port_num; + + *lp_subtype = link->lp_subtype; + + return 0; +} +EXPORT_SYMBOL(sbl_get_lp_subtype); + +/** + * sbl_base_link_config() - config base link + * @sbl: A slingshot base link device instance + * @port_num: port number + * @blattr: Pointer to struct sbl_base_link_attr + * + * Set base link config based on attributes provided + * + * Context: Process context, Acquires lock and release link->lock <spin_lock> + * + * Return: 0 on success, negative error code on failure + */ +int sbl_base_link_config(struct sbl_inst *sbl, int port_num, struct sbl_base_link_attr *blattr) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + sbl_dev_dbg(sbl->dev, "bl %d: config\n", port_num); + + if (!blattr) + return -EINVAL; + + if (blattr->magic != SBL_LINK_ATTR_MAGIC) + return -EINVAL; + + link = sbl->link + port_num; + + spin_lock(&link->lock); + + if (!(link->blstate & (SBL_BASE_LINK_STATUS_DOWN | SBL_BASE_LINK_STATUS_UNCONFIGURED))) { + + sbl_dev_err(sbl->dev, "bl %d: wrong state (%s) for config", port_num, + sbl_link_state_str(link->blstate)); + spin_unlock(&link->lock); + return -EUCLEAN; + } + + memcpy(&link->blattr, blattr, sizeof(struct sbl_base_link_attr)); + + link->blconfigured = true; + + if (link->blattr.loopback_mode == SBL_LOOPBACK_MODE_LOCAL) { + /* we dont need media to go to down */ + link->blstate = SBL_BASE_LINK_STATUS_DOWN; + } else if (link->mconfigured) { + /* we need media as well if not loopback */ + link->blstate = SBL_BASE_LINK_STATUS_DOWN; + } + + spin_unlock(&link->lock); + + return 0; +} +EXPORT_SYMBOL(sbl_base_link_config); + +/** + * sbl_base_link_start() - Initialize and bring up links + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function performs full sequence to bring up serdes link. + * It handles firmware validation, link mode detection, alignment, + * SerDes startup and link integrity checks. This process ensures + * link partner is detected and all lanes are properly initialized + * and active before reporting the link is up + * + * Context: Process, May sleep. Uses mutex_lock_interruptible + * + * Return: 0 on success, negative error code on failure + */ +int sbl_base_link_start(struct sbl_inst *sbl, int port_num) +{ + int err; + int tmp_err; + u32 base = SBL_PML_BASE(port_num); + u64 cfg_pcs_reg; + u64 sts_pcs_lane_degrade_reg; + struct lane_degrade lanes; + u32 ucw_adj = 100; + u32 ccw_adj = 0; + s32 ucw_in = SBL_LINK_FEC_HPE; + s32 ccw_in = SBL_LINK_FEC_HPE; + struct sbl_link *link; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + sbl_dev_dbg(sbl->dev, "bl %d: start\n", port_num); + + link = sbl->link + port_num; + + err = mutex_lock_interruptible(&link->busy_mtx); + if (err) + return -ERESTARTSYS; + + if (link->blstate == SBL_BASE_LINK_STATUS_UP) { + /* link came up while waiting for the mutex */ + mutex_unlock(&link->busy_mtx); + return 0; + } + + if (link->blstate != SBL_BASE_LINK_STATUS_DOWN) { + + sbl_dev_err(sbl->dev, "bl %d: wrong state (%s) for start", port_num, + sbl_link_state_str(link->blstate)); + mutex_unlock(&link->busy_mtx); + return -EUCLEAN; + } + + /* only local loopback is possible without media */ + if (link->blattr.loopback_mode != SBL_LOOPBACK_MODE_LOCAL) { + err = sbl_media_validate_config(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "bl %d: config unsuitable for media type", port_num); + mutex_unlock(&link->busy_mtx); + return err; + } + } + + link->blstate = SBL_BASE_LINK_STATUS_STARTING; + + /* check for loopback mode change */ + if (link->blattr.loopback_mode != link->loopback_mode) + link->llr_loop_time = 0; + link->loopback_mode = link->blattr.loopback_mode; + + /* setup the timeout for start to complete + * (autoneg might modify this later) + */ + sbl_link_init_start_timeout(sbl, port_num); + + /* clear out any residual pcs state */ + sbl_pml_set_defaults(sbl, port_num); + + /* reset state */ + link->lp_detected = false; + + /* validate serdes firmwares are (still) uncorrupted, recover them if + * needed + */ + err = sbl_base_link_check_fix_fw(sbl, port_num); + if (err) { + sbl_base_link_report_err(sbl, "ensure_healthly", port_num, err); + goto out; + } + + /* determine the link mode + * this may do autoneg for electrical links + */ + err = sbl_base_link_get_mode(sbl, port_num); + if (err) { + if (sbl_base_link_an_timed_out(sbl, port_num, err)) + sbl_dev_dbg(sbl->dev, "bl %d: autoneg timeout", port_num); + else + sbl_base_link_report_err(sbl, "get_mode", port_num, err); + goto out; + } + + /* start sending alignment markers for lp to tune against */ + err = sbl_pml_pcs_am_start(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "bl %d: am_start failed [%d]\n", port_num, err); + goto out; + } + + /* wait until we detect the link partner */ + err = sbl_base_link_lp_detect(sbl, port_num); + if (err) { + sbl_base_link_report_err(sbl, "lpd", port_num, err); + goto out; + } + + /* record when we really start to bring up the link */ + sbl_link_up_begin(sbl, port_num); + + /* start the serdes */ + err = sbl_serdes_start(sbl, port_num); + if (err) { + sbl_base_link_report_err(sbl, "serdes_start", port_num, err); + goto out; + } + + /* start the pml block (pcs,mac,llr) */ + err = sbl_pml_start(sbl, port_num); + if (err) { + sbl_base_link_report_err(sbl, "pml_start", port_num, err); + goto out_serdes; + } + + /* clearing fec values before start */ + sbl_zero_all_fec_counts(sbl, port_num); + + /* setup thresholds and adjustments for fec monitoring */ + err = sbl_fec_thresholds_set(sbl, port_num, ucw_in, ccw_in); + if (err) { + sbl_dev_err(sbl->dev, "%d: setting fec thresholds failed [%d]", + port_num, err); + goto out_pcs; + } + + err = sbl_fec_adjustments_set(sbl, port_num, ucw_adj, ccw_adj); + if (err) { + sbl_dev_err(sbl->dev, "%d: setting fec adjustments failed [%d]", + port_num, err); + goto out_pcs; + } + + /* start fec checking */ + err = sbl_fec_up_check(sbl, port_num); + if (err) { + unsigned long flags; + + sbl_serdes_invalidate_tuning_params(sbl, port_num); + sbl_dev_info(sbl->dev, "%d: failed start fec check", port_num); + + /* SSHOTPLAT-5697 forces a maximum effort tune + * straight away after FEC up check fails. + */ + sbl_dev_dbg(sbl->dev, "bl %d: forcing a maximum effort tune for next retry", port_num); + spin_lock_irqsave(&link->lock, flags); + link->blattr.options |= SBL_OPT_DFE_ALWAYS_MAX_EFFORT; + spin_unlock_irqrestore(&link->lock, flags); + + goto out_pcs; + } + + /* start monitoring for link faults */ + err = sbl_link_fault_monitor_start(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "bl %d: link fault detect start failed [%d]\n", port_num, err); + goto out_pcs; + } + + /* final check the link is still up and all lanes remain active */ + if (!sbl_pml_pcs_up(sbl, port_num)) { + sbl_dev_err(sbl->dev, "bl %d: link failed during startup\n", port_num); + err = -ENETDOWN; + goto out_fm; + } + + cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); + if (SBL_PML_CFG_PCS_ENABLE_AUTO_LANE_DEGRADE_GET(cfg_pcs_reg)) { + + sts_pcs_lane_degrade_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_LANE_DEGRADE_OFFSET); + lanes.tx = SBL_PML_STS_PCS_LANE_DEGRADE_LP_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg); + lanes.rx = SBL_PML_STS_PCS_LANE_DEGRADE_RX_PLS_AVAILABLE_GET(sts_pcs_lane_degrade_reg); + + if (lanes.tx != SBL_LINK_ALL_LANES || lanes.rx != SBL_LINK_ALL_LANES) { + sbl_dev_err(sbl->dev, "bl %d: lane failed during startup - TX: 0x%llx - RX: 0x%llx", + port_num, lanes.tx, lanes.rx); + err = -ENETDOWN; + goto out_fm; + } + } + + /* success ! */ + sbl_link_up_record_timespec(sbl, port_num); + sbl_link_start_record_timespec(sbl, port_num); + + link->blstate = SBL_BASE_LINK_STATUS_UP; + link->blerr = 0; + + sbl_dev_dbg(sbl->dev, "starting timer port_num:%d ", port_num); + sbl_fec_monitor_start(sbl, port_num); + mutex_unlock(&link->busy_mtx); + + return 0; + + /* failed */ +out_fm: + sbl_link_fault_monitor_stop(sbl, port_num); +out_pcs: + if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP)) + sbl_pml_link_down(sbl, port_num); +out_serdes: + if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP)) + sbl_serdes_stop(sbl, port_num); +out: + if (sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP) || + sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_RELOAD_FW)) + link->reload_serdes_fw = false; + else if (sbl_debug_option(sbl, port_num, SBL_DEBUG_FORCE_RELOAD_FW)) + link->reload_serdes_fw = true; + + if (link->reload_serdes_fw) { + sbl_dev_info(sbl->dev, "bl %d: reloading serdes fw\n", port_num); + tmp_err = sbl_serdes_firmware_flash_safe(sbl, port_num, true); + if (tmp_err) { + /* All we can do is report failure here */ + sbl_dev_err(sbl->dev, "bl %d: fw flash failed [%d]\n", + port_num, tmp_err); + link->sstate = SBL_SERDES_STATUS_ERROR; + } else + link->sstate = SBL_SERDES_STATUS_DOWN; + link->serr = tmp_err; + link->reload_serdes_fw = false; + } + link->blstate = SBL_BASE_LINK_STATUS_ERROR; + link->blerr = err; + mutex_unlock(&link->busy_mtx); + + return err; +} +EXPORT_SYMBOL(sbl_base_link_start); + +/** + * sbl_ignore_save_tuning_param() - Config ignore save tuning parameters + * @sbl: A slingshot base link device instance + * @port_num: port number + * @ignore: If set then ignore save tuning + * + * This function performs the Load saved tuning params before tuning + * when 'ignore' is set false from caller. + */ +void sbl_ignore_save_tuning_param(struct sbl_inst *sbl, int port_num, bool ignore) +{ + struct sbl_link *link = sbl->link + port_num; + unsigned long flags; + + spin_lock_irqsave(&link->lock, flags); + if (ignore) + link->blattr.options &= ~SBL_OPT_USE_SAVED_PARAMS; + else + link->blattr.options |= SBL_OPT_USE_SAVED_PARAMS; + spin_unlock_irqrestore(&link->lock, flags); +} +EXPORT_SYMBOL(sbl_ignore_save_tuning_param); + +/** + * sbl_enable_opt_lane_degrade() - Enable opt lane degrade + * @sbl: A slingshot base link device instance + * @port_num: port number + * @enable: if set then enable OPT LANE DEGRADE + * + * This function performs enable auto lane degrade when + * 'enable' is set from the caller + */ +void sbl_enable_opt_lane_degrade(struct sbl_inst *sbl, int port_num, bool enable) +{ + struct sbl_link *link = sbl->link + port_num; + unsigned long flags; + + spin_lock_irqsave(&link->lock, flags); + if (enable) + link->blattr.options |= SBL_OPT_LANE_DEGRADE; + else + link->blattr.options &= ~SBL_OPT_LANE_DEGRADE; + spin_unlock_irqrestore(&link->lock, flags); +} +EXPORT_SYMBOL(sbl_enable_opt_lane_degrade); + +/** + * sbl_disable_pml_recovery() - Disable PML recovery + * @sbl: A slingshot base link device instance + * @port_num: port number + * @disable: if set, then disable PML recovery + * + * This function performs disable PML recovery + * when 'disable' is set from the caller + */ +void sbl_disable_pml_recovery(struct sbl_inst *sbl, int port_num, bool disable) +{ + struct sbl_link *link = sbl->link + port_num; + unsigned long flags; + + spin_lock_irqsave(&link->lock, flags); + if (disable) + link->blattr.options |= SBL_DISABLE_PML_RECOVERY; + else + link->blattr.options &= ~SBL_DISABLE_PML_RECOVERY; + spin_unlock_irqrestore(&link->lock, flags); +} +EXPORT_SYMBOL(sbl_disable_pml_recovery); + +/** + * sbl_set_degraded_flag() - Set degraded flag + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Context: Process context, Acquires lock and releases is_degraded_lock <spin_lock> + */ +void sbl_set_degraded_flag(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + spin_lock(&link->is_degraded_lock); + link->is_degraded = true; + spin_unlock(&link->is_degraded_lock); +} +EXPORT_SYMBOL(sbl_set_degraded_flag); + +/** + * sbl_clear_degraded_flag() - Clear degraded flag + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Context: Process context, Acquires lock and releases is_degraded_lock <spin_lock> + */ +void sbl_clear_degraded_flag(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + spin_lock(&link->is_degraded_lock); + link->is_degraded = false; + spin_unlock(&link->is_degraded_lock); +} +EXPORT_SYMBOL(sbl_clear_degraded_flag); + +/** + * sbl_get_degraded_flag() - Get degraded flag + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Context: Process context, Acquires lock and releases is_degraded_lock <spin_lock> + * + * Return: status of degraded flag + */ +bool sbl_get_degraded_flag(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + bool degraded_flag; + + spin_lock(&link->is_degraded_lock); + degraded_flag = link->is_degraded; + spin_unlock(&link->is_degraded_lock); + + return degraded_flag; +} +EXPORT_SYMBOL(sbl_get_degraded_flag); + +/** + * sbl_base_link_enable_start() - Enable base link start + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Context: Process context, Acquires lock and releases link->lock <spin_lock> + * + * Return: 0 + */ +int sbl_base_link_enable_start(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + sbl_dev_dbg(sbl->dev, "bl %d: enable start\n", port_num); + + spin_lock(&link->lock); + link->start_cancelled = false; + spin_unlock(&link->lock); + + return 0; +} +EXPORT_SYMBOL(sbl_base_link_enable_start); + +/** + * sbl_base_link_cancel_start() - Cancel base link start + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Context: Process context, Acquires lock and releases link->lock <spin_lock> + * + * Return: 0 + */ +int sbl_base_link_cancel_start(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + sbl_dev_dbg(sbl->dev, "bl %d: cancelling start\n", port_num); + + spin_lock(&link->lock); + link->start_cancelled = true; + complete_all(&link->an_hw_change); + spin_unlock(&link->lock); + sbl_fec_monitor_stop(sbl, port_num); + + return 0; +} +EXPORT_SYMBOL(sbl_base_link_cancel_start); + +bool sbl_base_link_start_cancelled(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + bool cancelled; + + spin_lock(&link->lock); + cancelled = link->start_cancelled; + spin_unlock(&link->lock); + + return cancelled; +} + +/** + * sbl_base_link_stop() - Stop base link + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function cleanly shuts down previously initialized + * SerDes link, link monitoring and stops the PML block. + * + * Context: Process, May sleep. Uses mutex_lock_interruptible + * + * Return: 0 on success, negative error code on failure + */ +int sbl_base_link_stop(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + sbl_dev_dbg(sbl->dev, "bl %d: stop", port_num); + + link = sbl->link + port_num; + + err = mutex_lock_interruptible(&link->busy_mtx); + if (err) + return -ERESTARTSYS; + + sbl_fec_monitor_stop(sbl, port_num); + + if (link->blstate == SBL_BASE_LINK_STATUS_DOWN) { + /* link went down while waiting for the mutex */ + mutex_unlock(&link->busy_mtx); + return 0; + } + + if (link->blstate != SBL_BASE_LINK_STATUS_UP) { + sbl_dev_err(sbl->dev, "bl %d: not up (%s) for stop - ignored", port_num, + sbl_link_state_str(link->blstate)); + mutex_unlock(&link->busy_mtx); + return -EBUSY; + } + + /* if keeping serdes up, don't change state to stopping */ + if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_KEEP_SERDES_UP)) + link->blstate = SBL_BASE_LINK_STATUS_STOPPING; + + /* We stop the serdes before stopping the pml to avoid breaking AOC firmware, + * really we should stop the pml first. This should be harmless provided + * down detection is off. + */ + err = sbl_link_fault_monitor_stop(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "bl %d: link_fault_monitor_stop failed [%d]", port_num, err); + goto out; + } + + sbl_pml_recovery_cancel(sbl, port_num); + + err = sbl_serdes_stop(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "bl %d: serdes_stop failed [%d]", port_num, err); + goto out; + } + + err = sbl_pml_link_down(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "bl %d: pml_link_down failed [%d]", port_num, err); + goto out; + } + +out: + spin_lock(&link->lock); + if (err) + link->blstate = SBL_BASE_LINK_STATUS_ERROR; + /* if keep serdes up, don't change state to down */ + else if (!sbl_debug_option(sbl, port_num, SBL_DEBUG_KEEP_SERDES_UP)) + link->blstate = SBL_BASE_LINK_STATUS_DOWN; + link->blerr = err; + spin_unlock(&link->lock); + + mutex_unlock(&link->busy_mtx); + + sbl_dev_dbg(sbl->dev, "bl %d: stop done", port_num); + + return link->blerr; +} +EXPORT_SYMBOL(sbl_base_link_stop); + +/** + * sbl_base_link_reset() - Reset base link + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function resets the base link by disabling + * pml interrupt handlers, resets serdes and PML block. + * + * Context: Process, May sleep. Uses mutex_lock_interruptible + * + * Return: 0 on success, negative error code on failure + */ +int sbl_base_link_reset(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + sbl_dev_dbg(sbl->dev, "bl %d: reset\n", port_num); + + link = sbl->link + port_num; + + err = mutex_lock_interruptible(&link->busy_mtx); + if (err) + return -ERESTARTSYS; + + sbl_fec_monitor_stop(sbl, port_num); + + link->blstate = SBL_BASE_LINK_STATUS_RESETTING; + + /* disable and remove any pml intr handlers */ + if (link->intr_err_flgs) { + sbl_pml_disable_intr_handler(sbl, port_num, link->intr_err_flgs); + sbl_pml_remove_intr_handler(sbl, port_num); + link->intr_err_flgs = 0; + } + + sbl_pml_recovery_cancel(sbl, port_num); + + err = sbl_serdes_reset(sbl, port_num); + if (err) + sbl_dev_err(sbl->dev, "bl %d: reset: serdes_reset failed [%d]", port_num, err); + + err = sbl_pml_link_down(sbl, port_num); + if (err) + sbl_dev_warn(sbl->dev, "bl %d: reset: pcs_link_down failed [%d]", port_num, err); + + link->blstate = SBL_BASE_LINK_STATUS_UNCONFIGURED; + link->blerr = 0; + link->blconfigured = false; + link->pcs_config = false; + link->llr_loop_time = 0; + link->start_cancelled = false; + WRITE_ONCE(link->link_info, 0); + link->lp_subtype = SBL_LP_SUBTYPE_INVALID; + /* dont reset media attribute */ + + /* reset hw */ + sbl_pml_set_defaults(sbl, port_num); + + sbl_pml_err_flgs_clear_all(sbl, port_num); + + mutex_unlock(&link->busy_mtx); + + return 0; +} +EXPORT_SYMBOL(sbl_base_link_reset); + +/** + * sbl_base_link_try_start_fail_cleanup() - Clean up resources + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Try to clear any error states that are non-fatal so we can directly + * attempt another start up. + * + * Context: Process, May sleep. Uses mutex_lock_interruptible + */ +void sbl_base_link_try_start_fail_cleanup(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return; + + sbl_dev_dbg(sbl->dev, "bl %d: try start fail cleanup\n", port_num); + + link = sbl->link + port_num; + + err = mutex_lock_interruptible(&link->busy_mtx); + if (err) + return; + + /* disable and remove any pml intr handlers */ + if (link->intr_err_flgs) { + sbl_pml_disable_intr_handler(sbl, port_num, link->intr_err_flgs); + sbl_pml_remove_intr_handler(sbl, port_num); + link->intr_err_flgs = 0; + } + + /* when debugging we can leave the hw in its current state */ + if (sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_CLEANUP)) { + sbl_dev_warn(sbl->dev, "%d: hw cleanup inhibited\n", port_num); + mutex_unlock(&link->busy_mtx); + return; + } + + /* clean up the serdes state + * We have to do this before resetting the PML or the optical transceivers can + * fail. + */ + switch (link->sstate) { + case SBL_SERDES_STATUS_RUNNING: + sbl_serdes_stop(sbl, port_num); + break; + case SBL_SERDES_STATUS_AUTONEG: + sbl_an_serdes_stop(sbl, port_num); + break; + default: + break; + } + + /* still not down! */ + if (link->sstate != SBL_SERDES_STATUS_DOWN) + sbl_serdes_reset(sbl, port_num); + + /* some errors we can clean up and move to down so + * we can try to come up again directly + */ + if (link->blstate == SBL_BASE_LINK_STATUS_ERROR) { + switch (link->blerr) { + + case -EADV: /* pcs startup saw high serdes error rate */ + case -EBADE: /* bad page exchange during auto neg */ + case -ENETDOWN: /* pcs dropped out after starting */ + case -ECHRNG: /* serdes eye heights went bad during pcs startup */ + case -ELNRNG: /* unable to tune properly */ + sbl_serdes_invalidate_all_tuning_params(sbl); + fallthrough; + case -ETIME: + case -ETIMEDOUT: + case -EOVERFLOW: + case -ECANCELED: + case -ENOSR: /* llr failed to start */ + + link->blstate = SBL_BASE_LINK_STATUS_DOWN; + link->blerr = 0; + link->pcs_config = false; + link->llr_loop_time = 0; + link->start_cancelled = false; + + sbl_pml_link_down(sbl, port_num); + sbl_pml_set_defaults(sbl, port_num); + break; + + default: + sbl_dev_err(sbl->dev, "%d: start fail cleanup not clearing err %d\n", + port_num, link->blerr); + break; + } + } + + mutex_unlock(&link->busy_mtx); +} +EXPORT_SYMBOL(sbl_base_link_try_start_fail_cleanup); + +/** + * sbl_base_link_get_status() - Get link status + * @sbl: A slingshot base link device instance + * @port_num: port number + * @blstate: link state + * @blerr: link err + * @sstate: state + * @serr: err + * @media_type: media type + * @link_mode: link mode + * + * This function gets base link status from params + * used + * + * Return: 0 on success, negative error code on failure + */ +int sbl_base_link_get_status(struct sbl_inst *sbl, int port_num, + int *blstate, int *blerr, int *sstate, int *serr, + int *media_type, int *link_mode) +{ + struct sbl_link *link; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + link = sbl->link + port_num; + + if (blstate) + *blstate = link->blstate; + if (blerr) + *blerr = link->blerr; + if (sstate) + *sstate = link->sstate; + if (serr) + *serr = link->serr; + if (media_type) + *media_type = link->mattr.media; + + if (link_mode) { + if (link->sstate == SBL_SERDES_STATUS_RUNNING) + *link_mode = link->link_mode; /* actual mode */ + else + *link_mode = link->blattr.link_mode; /* target mode */ + } + + return 0; +} +EXPORT_SYMBOL(sbl_base_link_get_status); + +/** + * sbl_base_link_state_str() - Read the state of the pcs and serdes + * @sbl: A slingshot base link device instance + * @port_num: port number + * @buf: Destination buffer to write the data + * @len: Length of data to write + * + * This function reads the state of pcs and serdes + * and return in the string format + * + * Return: Number of characters written on success + */ +char *sbl_base_link_state_str(struct sbl_inst *sbl, int port_num, char *buf, int len) +{ + char pcs_buf[SBL_PCS_STATE_STR_LEN]; + u8 not_idle_map; /* not signalling electrical idle */ + u8 good_eyes_map; + u8 active_map; + + if (!buf || !len) + return ""; + + sbl_pml_pcs_state_str(sbl, port_num, pcs_buf, SBL_PCS_STATE_STR_LEN); + + if (sbl_port_get_serdes_state_maps(sbl, port_num, &active_map, + ¬_idle_map, &good_eyes_map)) { + /* only active map unavailable */ + snprintf(buf, len, "pcs %s, serdes -,-/%x", pcs_buf, active_map); + } else + snprintf(buf, len, "pcs %s, serdes %x,%x/%x", pcs_buf, + not_idle_map, good_eyes_map, active_map); + + return buf; +} +EXPORT_SYMBOL(sbl_base_link_state_str); + +/** + * sbl_link_get_ucw_thresh_hpe() - uncorrected codeword thresholds(hpe) + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function reads ucw(hpe) based on link mode + * + * Return: Threshold value on success, 0 on failure. + */ +u64 sbl_link_get_ucw_thresh_hpe(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + /* return a little under 1e-10 for all modes and cable types */ + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + return 21; + case SBL_LINK_MODE_BJ_100G: + case SBL_LINK_MODE_CD_100G: + return 11; + case SBL_LINK_MODE_CD_50G: + return 5; + default: + sbl_dev_err(sbl->dev, "%d: cannot determine ucw rate", port_num); + return 0; + } +} +EXPORT_SYMBOL(sbl_link_get_ucw_thresh_hpe); + +/** + * sbl_link_get_ucw_thresh_ieee() - uncorrected codeword thresholds(ieee) + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function reads ucw(ieee). + * + * Return: Threshold value on success, 0 on failure. + */ +u64 sbl_link_get_ucw_thresh_ieee(struct sbl_inst *sbl, int port_num) +{ + /* currently these are the same as the hpe values */ + return sbl_link_get_ucw_thresh_hpe(sbl, port_num); +} +EXPORT_SYMBOL(sbl_link_get_ucw_thresh_ieee); + +/** + * sbl_link_get_ccw_thresh_hpe() - corrected codeword thresholds + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function reads ccw(hpe) based on link mode + * + * Return: Threshold value on success, 0 on failure. + */ +u64 sbl_link_get_ccw_thresh_hpe(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + /* for electrical links use a threshold of 1.e-5 + * (we consider loopback links to be electrical) + */ + if ((link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL) || + (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL)) { + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + return 21250000; + case SBL_LINK_MODE_BJ_100G: + return 10312500; + case SBL_LINK_MODE_CD_100G: + return 10625000; + case SBL_LINK_MODE_CD_50G: + return 5312500; + default: + sbl_dev_err(sbl->dev, "%d: cannot determine PEC ccw rate", port_num); + return 0; + } + + } + + /* optical links seem to require a threshold of about 4e-5 + * (otherwise pcal can sometimes take us over the threshold) + */ + if (link->mattr.media == SBL_LINK_MEDIA_OPTICAL) { + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + return 85000000; + case SBL_LINK_MODE_BJ_100G: + return 41250000; + case SBL_LINK_MODE_CD_100G: + return 42500000; + case SBL_LINK_MODE_CD_50G: + return 21250000; + default: + sbl_dev_err(sbl->dev, "%d: cannot determine AOC ccw rate", port_num); + return 0; + } + } + + /* dont recognise media */ + sbl_dev_err(sbl->dev, "%d: cannot determine ccw rate - unrecognised media", port_num); + return 0; +} +EXPORT_SYMBOL(sbl_link_get_ccw_thresh_hpe); + +/** + * sbl_link_get_ccw_thresh_ieee() - Get correcred code words threshold + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * This function reads ccw(ieee) based on link mode + * + * Return: Threshold value on success, 0 on failure. + */ +u64 sbl_link_get_ccw_thresh_ieee(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + /* ieee thresholds are about 2e-4 + * We dont care about media type + */ + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + return 42500000; + case SBL_LINK_MODE_BJ_100G: + case SBL_LINK_MODE_CD_100G: + return 21250000; + case SBL_LINK_MODE_CD_50G: + return 10625000; + default: + sbl_dev_err(sbl->dev, "%d: cannot determine ccw rate", port_num); + return 0; + } +} +EXPORT_SYMBOL(sbl_link_get_ccw_thresh_ieee); + +/** + * sbl_link_get_stp_ccw_thresh_hpe() - Get Corrected code words(hpe) threshold + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Get Corrected code words(hpe) threshold when STP is used + * + * Return: Threshold value on success, 0 on failure. + */ +u64 sbl_link_get_stp_ccw_thresh_hpe(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + /* for electrical links use a threshold of 1.e-8 + * (we consider loopback links to be electrical) + */ + if ((link->mattr.media == SBL_LINK_MEDIA_ELECTRICAL) || + (link->loopback_mode == SBL_LOOPBACK_MODE_LOCAL)) { + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + return 2125; + case SBL_LINK_MODE_BJ_100G: + return 1031; + case SBL_LINK_MODE_CD_100G: + return 1062; + case SBL_LINK_MODE_CD_50G: + return 531; + default: + sbl_dev_err(sbl->dev, "%d: cannot determine PEC stp ccw rate", port_num); + return 0; + } + + } + + /* optical links seem to require a threshold of about 4e-8 + * (otherwise pcal can sometimes take us over the threshold) + */ + if (link->mattr.media == SBL_LINK_MEDIA_OPTICAL) { + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + return 8500; + case SBL_LINK_MODE_BJ_100G: + return 4125; + case SBL_LINK_MODE_CD_100G: + return 4250; + case SBL_LINK_MODE_CD_50G: + return 2125; + default: + sbl_dev_err(sbl->dev, "%d: cannot determine AOC stp ccw rate", port_num); + return 0; + } + } + + /* dont recognise media */ + sbl_dev_err(sbl->dev, "%d: cannot determine stp ccw rate - unrecognised media", port_num); + return 0; +} +EXPORT_SYMBOL(sbl_link_get_stp_ccw_thresh_hpe); + +/** + * sbl_link_get_stp_ccw_thresh_ieee() - Get corrected code word(ieee) threshold + * @sbl: A slingshot base link device instance + * @port_num: port number + * + * Get Corrected code words(ieee) threshold when STP is used + * + * Return: Threshold value on success, 0 on failure. + */ +u64 sbl_link_get_stp_ccw_thresh_ieee(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + /* ieee thresholds are about 2e-8 + * We dont care about media type + */ + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + return 4250; + case SBL_LINK_MODE_BJ_100G: + case SBL_LINK_MODE_CD_100G: + return 2125; + case SBL_LINK_MODE_CD_50G: + return 1062; + default: + sbl_dev_err(sbl->dev, "%d: cannot determine stp ccw rate", port_num); + return 0; + } +} +EXPORT_SYMBOL(sbl_link_get_stp_ccw_thresh_ieee); + +/* Link info + * lets you see the current state of the link + * + * Atomic mask updates preserve concurrently changed flags. + */ +void sbl_link_info_set(struct sbl_inst *sbl, int port_num, u32 flag) +{ + switch (flag) { + + case SBL_LINK_INFO_LP_DET: + case SBL_LINK_INFO_PCS_ANEG: + case SBL_LINK_INFO_PCS_TX_AM: + case SBL_LINK_INFO_PCS_TX_RF: + case SBL_LINK_INFO_PCS_ALIGN_EN: + case SBL_LINK_INFO_PCS_A_WAIT: + case SBL_LINK_INFO_PCS_F_WAIT: + case SBL_LINK_INFO_PCS_UP: + case SBL_LINK_INFO_MAC_OP: + case SBL_LINK_INFO_OS: + case SBL_LINK_INFO_LLR_LOOP: + case SBL_LINK_INFO_LLR_MEASURE: + case SBL_LINK_INFO_LLR_WAIT: + case SBL_LINK_INFO_LLR_RUN: + case SBL_LINK_INFO_LLR_DISABLED: + case SBL_LINK_INFO_FAULT_MON: + case SBL_LINK_INFO_LLR_DETECT: + set_mask_bits(&sbl->link[port_num].link_info, 0, flag); + break; + + default: + sbl_dev_dbg(sbl->dev, "bl %d: unrecognised set info flag (%d)\n", + port_num, flag); + break; + } +} + +void sbl_link_info_clear(struct sbl_inst *sbl, int port_num, u32 flag) +{ + switch (flag) { + + case SBL_LINK_INFO_LP_DET: + case SBL_LINK_INFO_PCS_ANEG: + case SBL_LINK_INFO_PCS_TX_AM: + case SBL_LINK_INFO_PCS_TX_RF: + case SBL_LINK_INFO_PCS_ALIGN_EN: + case SBL_LINK_INFO_PCS_A_WAIT: + case SBL_LINK_INFO_PCS_F_WAIT: + case SBL_LINK_INFO_PCS_UP: + case SBL_LINK_INFO_MAC_OP: + case SBL_LINK_INFO_OS: + case SBL_LINK_INFO_LLR_LOOP: + case SBL_LINK_INFO_LLR_MEASURE: + case SBL_LINK_INFO_LLR_WAIT: + case SBL_LINK_INFO_LLR_RUN: + case SBL_LINK_INFO_LLR_DISABLED: + case SBL_LINK_INFO_FAULT_MON: + case SBL_LINK_INFO_LLR_DETECT: + set_mask_bits(&sbl->link[port_num].link_info, flag, 0); + break; + + default: + sbl_dev_dbg(sbl->dev, "bl %d: unrecognised clear info flag (%d)\n", + port_num, flag); + break; + } +} + +#ifdef CONFIG_SYSFS +/** + * sbl_base_link_sysfs_sprint() - Format link 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 + * + * Context: Process, Acquires lock and release link->lock <spin_lock> + * + * Return: Number of characters written, at most @size - 1 + */ +int sbl_base_link_sysfs_sprint(struct sbl_inst *sbl, int port_num, char *buf, size_t size) +{ + struct sbl_link *link = sbl->link + port_num; + bool mac_tx_op; + bool mac_rx_op; + u32 mac_ifg_mode; + u32 link_info; + u64 llr_cfg; + u32 llr_mode; + u32 llr_down_behavior; + u64 llr_sts; + int s = 0; + + spin_lock(&link->lock); + link_info = READ_ONCE(link->link_info); + + s += scnprintf(buf + s, size - s, "base link state: %s", + sbl_link_state_str(link->blstate)); + if (link->blstate == SBL_BASE_LINK_STATUS_ERROR) + s += scnprintf(buf + s, size - s, " [%d]", link->blerr); + if (link->blstate == SBL_BASE_LINK_STATUS_STARTING) + s += scnprintf(buf + s, size - s, " (%d/%d)", + sbl_link_start_elapsed(sbl, port_num), + sbl_get_start_timeout(sbl, port_num)); + if (link->blstate == SBL_BASE_LINK_STATUS_UP) + s += scnprintf(buf + s, size - s, " (%lld.%.3ld, %lld.%.3ld)", + (long long)link->start_time.tv_sec, + link->start_time.tv_nsec / 1000000, + (long long)link->up_time.tv_sec, + link->up_time.tv_nsec / 1000000); + s += scnprintf(buf + s, size - s, "\n"); + + if (link_info) { + s += scnprintf(buf + s, size - s, "base link info: "); + if (link_info & SBL_LINK_INFO_PCS_ANEG) + s += scnprintf(buf + s, size - s, "pcs-aneg "); + if (link_info & SBL_LINK_INFO_PCS_TX_AM) + s += scnprintf(buf + s, size - s, "pcs-tx-am "); + if (link_info & SBL_LINK_INFO_PCS_ALIGN_EN) + s += scnprintf(buf + s, size - s, "pcs-a-en "); + if (link_info & SBL_LINK_INFO_PCS_A_WAIT) + s += scnprintf(buf + s, size - s, "pcs-a-wait "); + if (link_info & SBL_LINK_INFO_PCS_TX_RF) + s += scnprintf(buf + s, size - s, "pcs-tx-rf "); + if (link_info & SBL_LINK_INFO_PCS_F_WAIT) + s += scnprintf(buf + s, size - s, "pcs-f-wait "); + if (link_info & SBL_LINK_INFO_PCS_UP) + s += scnprintf(buf + s, size - s, "pcs-up "); + if (link_info & SBL_LINK_INFO_LP_DET) + s += scnprintf(buf + s, size - s, "lp-det "); + if (link_info & SBL_LINK_INFO_MAC_OP) + s += scnprintf(buf + s, size - s, "mac "); + if (link_info & SBL_LINK_INFO_OS) + s += scnprintf(buf + s, size - s, "os "); + if (link_info & SBL_LINK_INFO_LLR_LOOP) + s += scnprintf(buf + s, size - s, "llr-loop-en "); + if (link_info & SBL_LINK_INFO_LLR_DETECT) + s += scnprintf(buf + s, size - s, "llr-detect "); + if (link_info & SBL_LINK_INFO_LLR_MEASURE) + s += scnprintf(buf + s, size - s, "llr-measure "); + if (link_info & SBL_LINK_INFO_LLR_WAIT) + s += scnprintf(buf + s, size - s, "llr-wait "); + if (link_info & SBL_LINK_INFO_LLR_RUN) + s += scnprintf(buf + s, size - s, "llr "); + if (link_info & SBL_LINK_INFO_LLR_DISABLED) + s += scnprintf(buf + s, size - s, "llr-dis "); + if (link_info & SBL_LINK_INFO_FAULT_MON) + s += scnprintf(buf + s, size - s, "fm "); + s += scnprintf(buf + s, size - s, "\n"); + } + + if (link->blconfigured) { + + if (link->blattr.config_target == SBL_BASE_LINK_CONFIG_PEC) { + s += scnprintf(buf + s, size - s, "base link: an: mode %s", + sbl_an_mode_str(link->blattr.pec.an_mode)); + if (link_info & SBL_LINK_INFO_PCS_ANEG) { + if (link->blattr.pec.an_mode == SBL_AN_MODE_FIXED) + s += scnprintf(buf + s, size - s, "(%s)", + sbl_link_mode_str(link->blattr.link_mode)); + s += scnprintf(buf + s, size - s, ", tries %d, nonce %x, state %s", + link->an_try_count, link->an_nonce, + sbl_an_get_sm_state(sbl, port_num)); + } + if ((link->blattr.pec.an_mode != SBL_AN_MODE_OFF) && (link->an_rx_count)) + s += scnprintf(buf + s, size - s, ", received %d", + link->an_rx_count); + if (link->an_100cr4_fixup_applied) + s += scnprintf(buf + s, size - s, ", 100cr4-fixup"); + if (link->an_options & AN_OPT_LLR) + s += scnprintf(buf + s, size - s, ", llr"); + + s += scnprintf(buf + s, size - s, "\n"); + } + sbl_pml_mac_hw_status(sbl, port_num, &mac_tx_op, &mac_rx_op, &mac_ifg_mode, NULL); + if (mac_tx_op || mac_rx_op) { + s += scnprintf(buf + s, size - s, "base link: mac: tx %d, rx %d, ifg %s\n", + mac_tx_op, mac_rx_op, sbl_ifg_config_str(link->ifg_config)); + } + + llr_cfg = sbl_read64(sbl, SBL_PML_BASE(port_num) | SBL_PML_CFG_LLR_OFFSET); + switch (SBL_PML_CFG_LLR_LLR_MODE_GET(llr_cfg)) { + case 0: + llr_mode = SBL_LLR_MODE_OFF; + break; + case 1: + llr_mode = SBL_LLR_MODE_MONITOR; + break; + case 2: + llr_mode = SBL_LLR_MODE_ON; + break; + default: + llr_mode = SBL_LLR_MODE_INVALID; + } + switch (SBL_PML_CFG_LLR_LINK_DOWN_BEHAVIOR_GET(llr_cfg)) { + case 0: + llr_down_behavior = SBL_LLR_LINK_DOWN_DISCARD; + break; + case 1: + llr_down_behavior = SBL_LLR_LINK_DOWN_BLOCK; + break; + case 2: + llr_down_behavior = SBL_LLR_LINK_DOWN_BEST_EFFORT; + break; + default: + llr_down_behavior = SBL_LLR_LINK_DOWN_INVALID; + } + llr_sts = sbl_read64(sbl, SBL_PML_BASE(port_num) | + SBL_PML_STS_LLR_MAX_USAGE_OFFSET); + s += scnprintf(buf + s, size - s, + "base link: llr: mode %s, down %s, loop %lld %s %s\n", + sbl_llr_mode_str(llr_mode), + sbl_llr_down_behaviour_str(llr_down_behavior), + link->llr_loop_time, + SBL_PML_STS_LLR_MAX_USAGE_BUFF_SPC_STARVED_GET(llr_sts) ? + "buf starved" : "", + SBL_PML_STS_LLR_MAX_USAGE_SEQ_STARVED_GET(llr_sts) ? + "seq starved" : ""); + + if (link->blattr.options & SBL_DISABLE_PML_RECOVERY) + s += scnprintf(buf + s, size - s, "base link: pml rec: disabled\n"); + else + s += scnprintf(buf + s, size - s, + "base link: pml rec: enabled, to %u, rl %u/%u\n", + link->blattr.pml_recovery.timeout, + link->blattr.pml_recovery.rl_max_duration, + link->blattr.pml_recovery.rl_window_size); + } + + spin_unlock(&link->lock); + + s += sbl_debug_sysfs_sprint(sbl, port_num, buf + s, size - s); + + return s; +} +EXPORT_SYMBOL(sbl_base_link_sysfs_sprint); +#endif + +/** + * sbl_base_link_dump_attr() - Format all link attributes 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 + * + * Return: Number of characters written, at most @size - 1 + */ +int sbl_base_link_dump_attr(struct sbl_inst *sbl, int port_num, char *buf, size_t size) +{ + struct sbl_link *link = sbl->link + port_num; + struct sbl_base_link_attr *attr = &link->blattr; + int s = 0; + + s += scnprintf(buf + s, size - s, "%d: base link attributes:\n", port_num); + + if (!link->blconfigured) { + s += scnprintf(buf + s, size - s, "not configured\n"); + return s; + } + + s += scnprintf(buf + s, size - s, "options 0x%x", attr->options); + if (attr->options) { + s += scnprintf(buf + s, size - s, " -"); + if (attr->options & SBL_OPT_FABRIC_LINK) + s += scnprintf(buf + s, size - s, " fabric"); + if (attr->options & SBL_OPT_SERDES_LPD) + s += scnprintf(buf + s, size - s, " serdes-lpd"); + if (attr->options & SBL_OPT_DFE_SAVE_PARAMS) + s += scnprintf(buf + s, size - s, " save-params"); + if (attr->options & SBL_OPT_USE_SAVED_PARAMS) + s += scnprintf(buf + s, size - s, " use-params"); + if (attr->options & SBL_OPT_RESET_CLEAR_PARAMS) + s += scnprintf(buf + s, size - s, " reset-clear-params"); + if (attr->options & SBL_OPT_ENABLE_PCAL) + s += scnprintf(buf + s, size - s, " pcal"); + if (attr->options & SBL_OPT_DFE_ALWAYS_MAX_EFFORT) + s += scnprintf(buf + s, size - s, " max-effort"); + if (attr->options & SBL_OPT_DFE_ALWAYS_MED_EFFORT) + s += scnprintf(buf + s, size - s, " med-effort"); + if (attr->options & SBL_OPT_DFE_ALWAYS_MIN_EFFORT) + s += scnprintf(buf + s, size - s, " min-effort"); + if (attr->options & SBL_OPT_AUTONEG_TIMEOUT_IEEE) + s += scnprintf(buf + s, size - s, " an-timeout-ieee"); + if (attr->options & SBL_OPT_AUTONEG_TIMEOUT_SSHOT) + s += scnprintf(buf + s, size - s, " an-timeout-sshot"); + if (attr->options & SBL_OPT_AUTONEG_100CR4_FIXUP) + s += scnprintf(buf + s, size - s, " an-100cr4-fixup"); + if (attr->options & SBL_OPT_RELOAD_FW_ON_TIMEOUT) + s += scnprintf(buf + s, size - s, " timeout-reload-fw"); + if (attr->options & SBL_OPT_ALLOW_MEDIA_BAD_MODE) + s += scnprintf(buf + s, size - s, " allow-media-bad-mode"); + if (attr->options & SBL_OPT_ALLOW_MEDIA_BAD_LEN) + s += scnprintf(buf + s, size - s, " allow-media-bad-len"); + if (attr->options & SBL_OPT_ENABLE_ETHER_LLR) + s += scnprintf(buf + s, size - s, " enable-ether-llr"); + if (attr->options & SBL_OPT_ENABLE_IFG_HPC_WITH_LLR) + s += scnprintf(buf + s, size - s, " enable-ifg-hpc-with-llr"); + if (attr->options & SBL_OPT_ENABLE_IFG_CONFIG) + s += scnprintf(buf + s, size - s, " enable-ifg-config"); + if (attr->options & SBL_OPT_DISABLE_AN_LLR) + s += scnprintf(buf + s, size - s, " disable-an-llr"); + if (attr->options & SBL_OPT_LANE_DEGRADE) + s += scnprintf(buf + s, size - s, " enable-lane-degrade"); + if (attr->options & SBL_DISABLE_PML_RECOVERY) + s += scnprintf(buf + s, size - s, " disable-pml-recovery"); + } + s += scnprintf(buf + s, size - s, "\n"); + s += scnprintf(buf + s, size - s, "start_timeout %d\n", attr->start_timeout); + if (attr->config_target == SBL_BASE_LINK_CONFIG_PEC) { + s += scnprintf(buf + s, size - s, "config_target PEC\n"); + s += scnprintf(buf + s, size - s, "an_mode %d (%s)\n", + attr->pec.an_mode, + sbl_an_mode_str(attr->pec.an_mode)); + s += scnprintf(buf + s, size - s, "an_retry_timeout %d\n", + attr->pec.an_retry_timeout); + s += scnprintf(buf + s, size - s, "an_max_retry %d\n", attr->pec.an_max_retry); + } + if (attr->config_target == SBL_BASE_LINK_CONFIG_AOC) { + s += scnprintf(buf + s, size - s, "config_target AOC\n"); + s += scnprintf(buf + s, size - s, "optical_lock_delay %d\n", + attr->aoc.optical_lock_delay); + s += scnprintf(buf + s, size - s, "optical_lock_interval %d\n", + attr->aoc.optical_lock_interval); + } + s += scnprintf(buf + s, size - s, "lpd_timeout %d\n", attr->lpd_timeout); + s += scnprintf(buf + s, size - s, "lpd_poll_interval %d\n", attr->lpd_poll_interval); + s += scnprintf(buf + s, size - s, "link_mode %d (%s)\n", + attr->link_mode, sbl_link_mode_str(attr->link_mode)); + s += scnprintf(buf + s, size - s, "loopback_mode %d (%s)\n", + attr->loopback_mode, + sbl_loopback_mode_str(attr->loopback_mode)); + s += scnprintf(buf + s, size - s, "link_partner %d\n", attr->link_partner); + s += scnprintf(buf + s, size - s, "tuning_pattern %d\n", attr->tuning_pattern); + s += scnprintf(buf + s, size - s, "precoding %d\n", attr->precoding); + s += scnprintf(buf + s, size - s, "dfe_pre_delay %d\n", attr->dfe_pre_delay); + s += scnprintf(buf + s, size - s, "dfe_timeout %d\n", attr->dfe_timeout); + s += scnprintf(buf + s, size - s, "dfe_poll_interval %d\n", attr->dfe_poll_interval); + s += scnprintf(buf + s, size - s, "pcal_eyecheck_holdoff %d\n", + attr->pcal_eyecheck_holdoff); + s += scnprintf(buf + s, size - s, "nrz_min_eye_height 0x%x\n", attr->nrz_min_eye_height); + s += scnprintf(buf + s, size - s, "nrz_max_eye_height 0x%x\n", attr->nrz_max_eye_height); + s += scnprintf(buf + s, size - s, "pam4_min_eye_height 0x%x\n", attr->pam4_min_eye_height); + s += scnprintf(buf + s, size - s, "pam4_max_eye_height 0x%x\n", attr->pam4_max_eye_height); + s += scnprintf(buf + s, size - s, "fec_mode %d\n", attr->fec_mode); + s += scnprintf(buf + s, size - s, "enable_autodegrade %d\n", attr->enable_autodegrade); + s += scnprintf(buf + s, size - s, "llr_mode %d (%s)\n", + attr->llr_mode, sbl_llr_mode_str(attr->llr_mode)); + s += scnprintf(buf + s, size - s, "ifg_config %d (%s)\n", + attr->ifg_config, sbl_ifg_config_str(attr->ifg_config)); + s += scnprintf(buf + s, size - s, "pml_recovery.timeout %u\n", attr->pml_recovery.timeout); + s += scnprintf(buf + s, size - s, "pml_recovery.rl_max_duration %u\n", + attr->pml_recovery.rl_max_duration); + s += scnprintf(buf + s, size - s, "pml_recovery.rl_window_size %u\n", + attr->pml_recovery.rl_window_size); + + return s; +} +EXPORT_SYMBOL(sbl_base_link_dump_attr);
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link_an.c b/drivers/net/ethernet/hpe/sbl/sbl_link_an.c
new file mode 100644
index 000000000000..aecadbceb593
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link_an.c@@ -0,0 +1,1010 @@ +// 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/random.h> +#include <linux/delay.h> + +#include <linux/hpe/sbl/sbl.h> +#include <linux/hpe/sbl/sbl_an.h> + +#include <linux/hpe/sbl/sbl_pml.h> + +#include "sbl_pml_fn.h" +#include "sbl_link.h" +#include "sbl_serdes.h" +#include "sbl_constants.h" +#include "sbl_serdes_map.h" +#include "sbl_internal.h" + +/* for debug purposes we can add an extra null message page for fabric links */ +#define SBL_SEND_EXTRA_FABRIC_NULL_PAGE 0 + +/* set the reset state */ +static void sbl_an_pml_an_reset(struct sbl_inst *sbl, int port_num, u64 reset_state) +{ + u32 base = SBL_PML_BASE(port_num); + u64 cfg_pcs_autoneg_reg; + + /* reset */ + cfg_pcs_autoneg_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET); + cfg_pcs_autoneg_reg = SBL_PML_CFG_PCS_AUTONEG_RESET_UPDATE(cfg_pcs_autoneg_reg, reset_state); + sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET, + cfg_pcs_autoneg_reg); + sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET); +} + +static void sbl_an_send_base_page(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + u32 base = SBL_PML_BASE(port_num); + + /* first page is the base page (whole reg) */ + sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_BASE_PAGE_OFFSET, link->an_tx_page[0]); + sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_BASE_PAGE_OFFSET); + + sbl_dev_dbg(sbl->dev, "an %d: tx base page: 0x%llx", port_num, link->an_tx_page[0]); + + /* start autoneg by taking an sm out of reset*/ + sbl_an_pml_an_reset(sbl, port_num, 0); +} + +static u32 sbl_an_get_nonce(void) +{ + u16 buf; + + while (1) { + get_random_bytes(&buf, 2); + buf &= 0x1f; + if (buf) + return buf; + } +} + +static bool sbl_an_base_is_complete(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 sts_autoneg_base_reg; + + sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET); + + return SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_COMPLETE_GET(sts_autoneg_base_reg); +} + +static bool sbl_an_base_is_page_recv(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 sts_autoneg_base_reg; + bool val; + + sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET); + + val = (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_ABILITY_GET(sts_autoneg_base_reg)) && + (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_PAGE_RECEIVED_GET(sts_autoneg_base_reg)); + + return val; +} + +static bool sbl_an_is_base_page(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 sts_autoneg_base_reg; + u64 state; + bool val; + + sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET); + + state = SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_STATE_GET(sts_autoneg_base_reg); + + val = (state == SBL_PML_AUTONEG_STATE_COMPLETE_ACK) || + (state == SBL_PML_AUTONEG_STATE_AN_GOOD_CHECK); + + val = val && (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_PAGE_RECEIVED_GET(sts_autoneg_base_reg)) && + (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_BASE_PAGE_GET(sts_autoneg_base_reg)) && + (SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_ABILITY_GET(sts_autoneg_base_reg)); + + return val; +} + +/* Setup the pages */ +static int sbl_an_setup_tx_pages(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + sbl_dev_dbg(sbl->dev, "an %d: run setup tx pages", port_num); + + /* init to known non-zero value so it's easy to tell used pages from init pages */ + memset(link->an_tx_page, 0xAA, sizeof(link->an_tx_page)); + + /* build the base page */ + + link->an_tx_page[0] = 0; + link->an_tx_page[0] |= AN_CW_S_802_3; + + if (link->blattr.pec.an_mode == SBL_AN_MODE_FIXED) { + + switch (link->blattr.link_mode) { + + case SBL_LINK_MODE_BS_200G: + link->an_tx_page[0] |= (AN_CW_A_200GBASE_CR4) << AN_CW_A_BASE_BIT; + break; + + case SBL_LINK_MODE_BJ_100G: + link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR4) << AN_CW_A_BASE_BIT; + break; + + case SBL_LINK_MODE_CD_100G: + link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR2) << AN_CW_A_BASE_BIT; + break; + + case SBL_LINK_MODE_CD_50G: + link->an_tx_page[0] |= (AN_CW_A_50GBASE_CR) << AN_CW_A_BASE_BIT; + break; + + default: + sbl_dev_err(sbl->dev, "an %d: bad an blattr mode", port_num); + return -EINVAL; + } + } else { + /* all modes we support */ + link->an_tx_page[0] |= (AN_CW_A_200GBASE_CR4) << AN_CW_A_BASE_BIT; + link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR4) << AN_CW_A_BASE_BIT; + link->an_tx_page[0] |= (AN_CW_A_100GBASE_CR2) << AN_CW_A_BASE_BIT; + link->an_tx_page[0] |= (AN_CW_A_50GBASE_CR) << AN_CW_A_BASE_BIT; + } + + /* Not sure what to set here - both Arista and Mellanox say zero so for + * now we will do the same and set nothing + */ + + /* TODO: Enhance PAUSE support. Currently only symmetric PAUSE is advertised. + * Should support: asymmetric PAUSE modes, flow control negotiation per standard, + * and link partner capability reporting. + */ + link->an_tx_page[0] |= (AN_CW_C_SYMMETRIC) << AN_CW_C_BASE_BIT; + + sbl_dev_dbg(sbl->dev, "an %d: bp = 0x%llx", port_num, link->an_tx_page[0]); + + /* check for disabled next pages */ + if (sbl_debug_option(sbl, port_num, SBL_DEBUG_DISABLE_AN_NEXT_PAGES)) { + sbl_dev_dbg(sbl->dev, "an %d: next pages disabled", port_num); + link->an_tx_count = 1; + return 0; + } + + /* build HPE OUI next pages */ + + /* have next page (set in previous page) */ + link->an_tx_page[0] |= AN_CW_NP_MASK; + + /* OUI message page */ + link->an_tx_page[1] = 0ULL; + link->an_tx_page[1] |= AN_NP_MP_MASK; + link->an_tx_page[1] |= AN_NP_CODE_OUI_EXTENDED_MSG; + link->an_tx_page[1] |= AN_NP_OUI_HPE; + + sbl_dev_dbg(sbl->dev, "an %d: np mp = 0x%llx", port_num, link->an_tx_page[1]); + + /* have next page (set in previous page) */ + link->an_tx_page[1] |= AN_CW_NP_MASK; + + /* unformatted page with OUI message page */ + link->an_tx_page[2] = 0ULL; + link->an_tx_page[2] |= AN_NP_OUI_VER_0_1; + /* LLR options here */ + if (link->blattr.llr_mode == SBL_LLR_MODE_AUTO) { + if (!(link->blattr.options & SBL_OPT_DISABLE_AN_LLR)) { + link->an_tx_page[2] |= (AN_OPT_LLR << AN_OPT_BASE_BIT); + if (link->blattr.options & SBL_OPT_ENABLE_ETHER_LLR) + link->an_tx_page[2] |= (AN_OPT_ETHER_LLR << AN_OPT_BASE_BIT); + + if (link->blattr.options & SBL_OPT_ENABLE_IFG_HPC_WITH_LLR) + link->an_tx_page[2] |= (AN_OPT_HPC_WITH_LLR << AN_OPT_BASE_BIT); + /* TODO: add IPV4 option */ + } + } + sbl_an_version_read(sbl, port_num); + + sbl_dev_dbg(sbl->dev, "an %d: np ufp = 0x%llx", port_num, link->an_tx_page[2]); + + link->an_tx_count = SBL_AN_MAX_TX_PAGES; + + return 0; +} + +static int sbl_an_pml_setup(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 cfg_pcs_autoneg_reg; + u64 cfg_pcs_reg; + u64 cfg_rx_pcs_reg; + u64 val64; + + /* disable pcs autoneg + * as pcs is also disabled this will reset the pcs-serdes CDC logic + */ + cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); + cfg_pcs_reg = SBL_PML_CFG_PCS_ENABLE_AUTO_NEG_UPDATE(cfg_pcs_reg, 0ULL); + sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, cfg_pcs_reg); + sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); + + /* configure an */ + cfg_pcs_autoneg_reg = SBL_PML_CFG_PCS_AUTONEG_RX_LANE_SET(sbl->switch_info->ports[port_num].rx_an_swizzle) | + SBL_PML_CFG_PCS_AUTONEG_TX_LANE_SET(sbl->switch_info->ports[port_num].tx_an_swizzle) | + SBL_PML_CFG_PCS_AUTONEG_RESET_SET(1ULL) | /* start held in reset */ + SBL_PML_CFG_PCS_AUTONEG_RESTART_SET(0ULL) | /* not used - always zero */ + SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_LOADED_SET(0ULL); + sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET, + cfg_pcs_autoneg_reg); + sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET); + + /* reset alignment locking */ + cfg_rx_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET); + cfg_rx_pcs_reg = SBL_PML_CFG_RX_PCS_ACTIVE_LANES_UPDATE(cfg_rx_pcs_reg, 0ULL); + cfg_rx_pcs_reg = SBL_PML_CFG_RX_PCS_ENABLE_LOCK_UPDATE(cfg_rx_pcs_reg, 0ULL); + sbl_write64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET, cfg_rx_pcs_reg); + sbl_read64(sbl, base | SBL_PML_CFG_RX_PCS_OFFSET); + + /* config an timers */ + val64 = SBL_PML_CFG_PCS_AUTONEG_TIMERS_DFLT; + /* disable fault timeout by setting it to its max value */ + val64 = SBL_PML_CFG_PCS_AUTONEG_TIMERS_LINK_FAIL_INHIBIT_TIMER_MAX_UPDATE(val64, 0xffffffffULL); + + sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_TIMERS_OFFSET, val64); + + /* clear err flags */ + sbl_pml_err_flgs_clear_all(sbl, port_num); + + /* enable autoneg + * this will take CDC logic out of reset + */ + cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); + cfg_pcs_reg = SBL_PML_CFG_PCS_ENABLE_AUTO_NEG_UPDATE(cfg_pcs_reg, 1ULL); + sbl_write64(sbl, base | SBL_PML_CFG_PCS_OFFSET, cfg_pcs_reg); + + /* an reset is still asserted, it will be deasserted after base page is loaded */ + + sbl_link_info_set(sbl, port_num, SBL_LINK_INFO_PCS_ANEG); + + return 0; +} + +/* perform page exchange + * + * Rosetta has a hw bug whereby some error flags cannot be cleared by the normal method + * as the an state machine will continually reassert them. (The failed, complete and + * next page received err flags) so instead we disable intrs for these flags when they + * are received and reset them in wait prepare. + * + * For next pages we would like to do: + * + * wait_prepare() + * send_next_page() + * wait_for_completion() + * + * However this sequence will cause the err flags to be immediately set in the wait + * prepare and the intr fire. + * + * The best we can do is + * + * preempt_disable() + * send_next_page() + * wait_prepare() + * preempt_enable() + * wait_for_completion() + * + * But we can still miss intrs + * There would seem to be nothing we can do about this + * + * 26 Feb 2021 - added SM exchange to work around rosetta interrupt bug + * + */ +static int sbl_an_exchange(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + u32 base = SBL_PML_BASE(port_num); + unsigned long timeout = msecs_to_jiffies(1000 * link->blattr.pec.an_retry_timeout); + unsigned long remaining_jiffies; + u64 sts_autoneg_base_reg; + int err; + + sbl_dev_dbg(sbl->dev, "an %d: exchange start", port_num); + + if (sbl_base_link_start_cancelled(sbl, port_num)) + return -ECANCELED; + if (sbl_start_timeout(sbl, port_num)) + return -ETIMEDOUT; + + if (!sbl->is_hw) + return 0; + + /* clear any previous pages */ + memset(link->an_rx_page, 0, SBL_AN_MAX_RX_PAGES * sizeof(u64)); + link->an_rx_count = 0; + + /* update the nonce */ + link->an_nonce = sbl_an_get_nonce(); + link->an_tx_page[0] &= ~AN_CW_T_MASK; + link->an_tx_page[0] |= ((link->an_nonce << AN_CW_T_BASE_BIT) & AN_CW_T_MASK); + + /* base page exchange */ + + err = sbl_an_hw_wait_prepare(sbl, port_num); + if (err) + return err; + + sbl_an_send_base_page(sbl, port_num); + + remaining_jiffies = wait_for_completion_timeout(&link->an_hw_change, timeout); + if (sbl_base_link_start_cancelled(sbl, port_num)) + return -ECANCELED; + if (remaining_jiffies == 0) { + sbl_dev_dbg(sbl->dev, "an %d: base page exchange timeout (nonce %.2x, sm_state %s)", + port_num, link->an_nonce, sbl_an_get_sm_state(sbl, port_num)); + sbl_an_dump_state(sbl, port_num); + + /* check we have not missed the err flg */ + if (sbl_an_base_is_complete(sbl, port_num) || sbl_an_base_is_page_recv(sbl, port_num)) + sbl_dev_err(sbl->dev, "an %d: MISSED A BASE PAGE!!!!", port_num); + + return -ETIME; + } + + /* check it is a base page */ + if (!sbl_an_is_base_page(sbl, port_num)) { + sbl_dev_err(sbl->dev, "an %d: missing base page indication", port_num); + sbl_an_dump_state(sbl, port_num); + return -EBADE; + } + + /* copy page to return buffer */ + sts_autoneg_base_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET); + link->an_rx_page[0] = SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_BASE_PAGE_GET(sts_autoneg_base_reg); + link->an_rx_count = 1; + + /* check if an is complete i.e. no one has more to send */ + if (sbl_an_base_is_complete(sbl, port_num)) + goto out_success; + + /* since not complete, we must have received a page - check this */ + if (!sbl_an_base_is_page_recv(sbl, port_num)) { + sbl_dev_err(sbl->dev, "an %d: expected page received indication", port_num); + sbl_an_dump_state(sbl, port_num); + return -EBADE; + } + + /* next page exchange */ + err = sbl_an_page_exchange(sbl, port_num, remaining_jiffies); + + if (err) + return err; + +out_success: + + /* dump the final state on the way out */ + sbl_an_dump_state(sbl, port_num); + + sbl_dev_dbg(sbl->dev, "an %d: exchange complete", port_num); + + return 0; +} + +/* save away the LLR options here */ +static void sbl_an_save_llr_options(struct sbl_link *link, int x) +{ + if (link->blattr.llr_mode == SBL_LLR_MODE_AUTO) { + if (!(link->blattr.options & SBL_OPT_DISABLE_AN_LLR)) { + if (link->an_rx_page[x] & (AN_OPT_LLR << AN_OPT_BASE_BIT)) + link->an_options |= AN_OPT_LLR; + if (link->an_rx_page[x] & (AN_OPT_ETHER_LLR << AN_OPT_BASE_BIT)) { + if (link->blattr.options & SBL_OPT_ENABLE_ETHER_LLR) + link->an_options |= AN_OPT_ETHER_LLR; + } + if (link->an_rx_page[x] & (AN_OPT_HPC_WITH_LLR << AN_OPT_BASE_BIT)) { + if (link->blattr.options & SBL_OPT_ENABLE_IFG_HPC_WITH_LLR) + link->an_options |= AN_OPT_HPC_WITH_LLR; + } + /* TODO: add IPV4 option */ + } + } + +} + +static int sbl_an_ability_match(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + u32 fec_ability; + u32 tech_ability; + u32 pause_ability; + u64 last_msg_code = 0; + bool expect_hpe = false; + int x = 0; + + sbl_dev_dbg(sbl->dev, "an %d: rx count = %d", port_num, link->an_rx_count); + + if (!sbl->is_hw) { + /* Netsim/Z1: No AN happening, so just force the response data to the + * tx data to ensure capability match + */ + link->an_rx_page[0] = link->an_tx_page[0]; + } + /* abilities */ + tech_ability = (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_A_MASK) + >> AN_CW_A_BASE_BIT; + fec_ability = (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_F_MASK) + >> AN_CW_F_BASE_BIT; + pause_ability = (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_C_MASK) + >> AN_CW_C_BASE_BIT; + sbl_dev_dbg(sbl->dev, "an %d: tech 0x%x, fec 0x%x, pause %d", port_num, + tech_ability, fec_ability, pause_ability); + + /* FEC mode (we only do RS) */ + if (fec_ability & AN_CW_F_25G_RS_REQ) { + /* ignore for now - set in config */ + } else if (fec_ability & AN_CW_F_25G_BASER_REQ) { + sbl_dev_warn(sbl->dev, "an %d: cannot do fec mode baser", + port_num); + } else { + sbl_dev_dbg(sbl->dev, "an %d: no matching fec mode (partner fec 0x%x)", + port_num, fec_ability); + } + + /* TODO pause */ + + /* link mode (speed) */ + if (tech_ability & AN_CW_A_200GBASE_CR4) { + link->link_mode = SBL_LINK_MODE_BS_200G; + } else if (tech_ability & AN_CW_A_100GBASE_CR4) { + link->link_mode = SBL_LINK_MODE_BJ_100G; + } else if (tech_ability & AN_CW_A_100GBASE_CR2) { + link->link_mode = SBL_LINK_MODE_CD_100G; + } else if (tech_ability & AN_CW_A_50GBASE_KR) { + link->link_mode = SBL_LINK_MODE_CD_50G; + } else { + sbl_dev_err(sbl->dev, "an %d: no matching mode (partner ability 0x%x)", + port_num, tech_ability); + return -ENOENT; + } + + /* look at next page(s) */ + link->lp_subtype = SBL_LP_SUBTYPE_UNKNOWN; + for (x = 1; x < link->an_rx_count; ++x) { + + /* do message page */ + if (link->an_rx_page[x] & AN_NP_MP_MASK) { + last_msg_code = link->an_rx_page[x] & AN_NP_MSG_MASK; + sbl_dev_dbg(sbl->dev, "an %d: msg_code = %llu", port_num, last_msg_code); + switch (last_msg_code) { + case AN_NP_CODE_NULL_MSG: + case AN_NP_CODE_OUI_MSG: + /* nothing to do */ + break; + case AN_NP_CODE_OUI_EXTENDED_MSG: + switch (link->an_rx_page[x] & AN_NP_OUI_MASK) { + case AN_NP_OUI_HPE: + /* have HPE OUI message - expect an unformatted page next */ + expect_hpe = true; + break; + default: + sbl_dev_dbg(sbl->dev, "an %d: unknown OUI = 0x%llX", port_num, (link->an_rx_page[x] & AN_NP_OUI_MASK) >> AN_NP_OUI_BASE_BIT); + expect_hpe = false; + break; + } + break; + default: + sbl_dev_dbg(sbl->dev, "an %d: unknown msg_code = 0x%llX", port_num, last_msg_code); + break; + } + /* go on to next page */ + continue; + } + + /* do unformatted page */ + switch (last_msg_code) { + case AN_NP_CODE_OUI_EXTENDED_MSG: + if (!expect_hpe) + break; + + /* got a HPE OUI message - check the unformatted page and set options */ + switch (link->an_rx_page[x] & AN_NP_OUI_VER_MASK) { + case AN_NP_OUI_VER_0_1: + sbl_an_save_llr_options(link, x); + /* save away cassini version here */ + link->lp_subtype = ((link->an_rx_page[x] >> AN_LP_SUBTYPE_BASE_BIT) & AN_LP_SUBTYPE_MASK); + sbl_dev_dbg(sbl->dev, "an %d: HPE an_options = 0x%X, lp_subtype = %d", port_num, link->an_options, link->lp_subtype); + break; + default: + sbl_dev_dbg(sbl->dev, "an %d: unknown OUI version 0x%llX", port_num, link->an_rx_page[x] & AN_NP_OUI_VER_MASK); + break; + } + expect_hpe = false; + break; + default: + sbl_dev_dbg(sbl->dev, "an %d: unknown ufp = 0x%llX", port_num, link->an_rx_page[x]); + break; + } + } + + return 0; +} + +static void sbl_an_update_timeout(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + unsigned int new_timeout = 0; + + if (link->blattr.options & SBL_OPT_AUTONEG_TIMEOUT_SSHOT) { + /* use slingshot timeouts - which are longer than the IEEE defaults */ + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + new_timeout = SBL_LINK_AUTONEG_TIMEOUT_SSHOT_200; + break; + case SBL_LINK_MODE_BJ_100G: + case SBL_LINK_MODE_CD_100G: + new_timeout = SBL_LINK_AUTONEG_TIMEOUT_SSHOT_100; + break; + case SBL_LINK_MODE_CD_50G: + new_timeout = SBL_LINK_AUTONEG_TIMEOUT_SSHOT_50; + break; + default: + sbl_dev_warn(sbl->dev, "an %d: bad mode in update_timeout", + port_num); + } + } + + if (link->blattr.options & SBL_OPT_AUTONEG_TIMEOUT_IEEE) { + /* use IEEE timeouts */ + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + new_timeout = SBL_LINK_AUTONEG_TIMEOUT_IEEE_200; + break; + case SBL_LINK_MODE_BJ_100G: + case SBL_LINK_MODE_CD_100G: + new_timeout = SBL_LINK_AUTONEG_TIMEOUT_IEEE_100; + break; + case SBL_LINK_MODE_CD_50G: + new_timeout = SBL_LINK_AUTONEG_TIMEOUT_IEEE_50; + break; + default: + sbl_dev_warn(sbl->dev, "an %d: bad mode in update_timeout", + port_num); + } + } + + if (new_timeout) { + sbl_dev_dbg(sbl->dev, "an %d: update start timeout to %d ms", + port_num, new_timeout); + link->an_timeout_active = true; + sbl_link_update_start_timeout(sbl, port_num, new_timeout); + } +} + +/* If there is no common mode between this port and our link partner + * and if the lp advertises 100KR4 and we advertise 100CR4 then add 100CR4 + * to the lp's abilities. + * + * This is necessary because recent Mellanox software no longer advertises + * 100CR even though it seems to be able to tune in this mode. Adding 100CR4 + * to the lp abilities means that we will resolve 100CR4 and start using this + * mode. + */ +#define SBL_AN_KR4_BIT (1ULL << (AN_CW_A_BASE_BIT + AN_CW_A_100GBASE_KR4_BIT)) +#define SBL_AN_CR4_BIT (1ULL << (AN_CW_A_BASE_BIT + AN_CW_A_100GBASE_CR4_BIT)) + +static bool sbl_an_100cr4_fixup(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + /* check no match */ + if (link->an_tx_page[0] & link->an_rx_page[0] & AN_CW_A_MASK) + return false; + + /* check we want cr4 */ + if (!(link->an_tx_page[0] & SBL_AN_CR4_BIT)) + return false; + + /* check lp wants kr4 */ + if (!(link->an_rx_page[0] & SBL_AN_KR4_BIT)) + return false; + + /* add cr4 to lp abilities */ + link->an_rx_page[0] |= SBL_AN_CR4_BIT; + + /* fixup applied */ + sbl_dev_info(sbl->dev, "an %d: 100cr4 fixup applied", port_num); + return true; +} + +int sbl_link_autoneg(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link; + u32 base = SBL_PML_BASE(port_num); + u32 random; + u64 cfg_pcs_reg; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + /* set starting point */ + link = sbl->link + port_num; + link->an_options = 0; + link->link_mode = link->blattr.link_mode; + + switch (link->blattr.pec.an_mode) { + + case SBL_AN_MODE_OFF: + sbl_dev_dbg(sbl->dev, "an %d: AN off", port_num); + /* nothing more to do here */ + return 0; + + case SBL_AN_MODE_ON: + case SBL_AN_MODE_FIXED: + /* ok */ + break; + + default: + sbl_dev_err(sbl->dev, "an %d: invalid AN mode (%s)", port_num, + sbl_an_mode_str(link->blattr.pec.an_mode)); + return -EINVAL; + } + + /* we can never be in loopback mode as the nonce will be the same! */ + if (link->blattr.loopback_mode != SBL_LOOPBACK_MODE_OFF) { + sbl_dev_err(sbl->dev, "an %d: cannot autoneg in loopback mode", port_num); + return -ENOTUNIQ; + } + + /* pcs must not be enabled and autoneg must be off */ + cfg_pcs_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); + if (SBL_PML_CFG_PCS_PCS_ENABLE_GET(cfg_pcs_reg)) { + sbl_dev_err(sbl->dev, "an %d: pcs is enabled", port_num); + err = -EBUSY; + goto out; + } + if (SBL_PML_CFG_PCS_ENABLE_AUTO_NEG_GET(cfg_pcs_reg)) { + sbl_dev_err(sbl->dev, "an %d: autoneg already enabled", port_num); + err = -EBUSY; + goto out; + } + + /* sort out the data to send/receive */ + err = sbl_an_setup_tx_pages(sbl, port_num); + if (err) { + sbl_dev_err(sbl->dev, "an %d, page setup failed [%d]", port_num, err); + goto out; + } + + /* setup */ + err = sbl_pml_install_intr_handler(sbl, port_num, SBL_AUTONEG_ERR_FLGS); + if (err) + goto out; + link->an_100cr4_fixup_applied = false; + + /* try to negotiate */ + link->an_try_count = 0; + while (1) { + + link->an_try_count++; + + /* keep trying until up timeout expires or cancelled */ + if (sbl_start_timeout(sbl, port_num)) { + err = -ETIMEDOUT; + break; + } + if (sbl_base_link_start_cancelled(sbl, port_num)) { + err = -ECANCELED; + break; + } + + /* setup the serdes and pml */ + err = sbl_an_serdes_start(sbl, port_num); + if (err) + break; + usleep_range(1000, 2000); + + err = sbl_an_pml_setup(sbl, port_num); + if (err) + break; + + /* try to exchange pages */ + err = sbl_an_exchange(sbl, port_num); + if (err == -EPROTO) { + /* we have received an unexpected base page + * The lp must have restarted autoneg and we should too + * + * However we know that some Mellanox cards will do this if there is no + * ability match - particularly only supporting 100KR4 not 100CR4 + * See if we can try to fix this up + */ + if (link->blattr.options & SBL_OPT_AUTONEG_100CR4_FIXUP) { + if (sbl_an_100cr4_fixup(sbl, port_num)) { + /* fixup applied */ + link->an_100cr4_fixup_applied = true; + /* carry on to ability match */ + } else { + /* fixup failed - retry exchange */ + sbl_an_serdes_stop(sbl, port_num); + continue; + } + } else { + sbl_an_serdes_stop(sbl, port_num); + continue; + } + } else if (err == -ETIME) { + /* we have timed out */ + if (link->an_try_count < link->blattr.pec.an_max_retry) { + /* retry */ + sbl_an_serdes_stop(sbl, port_num); + /* Random delay of 1-5 before retry */ + get_random_bytes(&random, sizeof(random)); + random = 1u + (random % 5u); + msleep(random); + continue; + } else { + /* give up */ + break; + } + } else if (err) { + /* other error */ + sbl_dev_dbg(sbl->dev, "an %d: exchange failed [%d] (sm_state %s)", + port_num, err, sbl_an_get_sm_state(sbl, port_num)); + break; + } + + /* pages have been exchanged - try to resolve the mode etc */ + err = sbl_an_ability_match(sbl, port_num); + if (err) { + /* no match - try again (in case they change) */ + sbl_an_serdes_stop(sbl, port_num); + continue; + } + + /* see if we need to update the start timeout */ + sbl_an_update_timeout(sbl, port_num); + + /* we know the lp is there */ + link->lp_detected = true; + break; + } + + /* cleanup */ + if (link->sstate == SBL_SERDES_STATUS_AUTONEG) + sbl_an_serdes_stop(sbl, port_num); + sbl_pml_disable_intr_handler(sbl, port_num, SBL_AUTONEG_ERR_FLGS); + sbl_pml_remove_intr_handler(sbl, port_num); + +out: + sbl_pml_err_flgs_clear_all(sbl, port_num); + sbl_link_info_clear(sbl, port_num, SBL_LINK_INFO_PCS_ANEG); + + return err; +} + +void sbl_an_send_next_page(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 cfg_pcs_autoneg_reg; + + cfg_pcs_autoneg_reg = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET); + cfg_pcs_autoneg_reg = SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_LOADED_UPDATE(cfg_pcs_autoneg_reg, 1ULL); + sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET, cfg_pcs_autoneg_reg); +} + +bool sbl_an_next_is_complete(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 sts_autoneg_next_reg; + + sts_autoneg_next_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET); + + return SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_COMPLETE_GET(sts_autoneg_next_reg); +} + +bool sbl_an_is_next_page(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 sts_autoneg_next_reg; + u64 state; + bool value; + + sts_autoneg_next_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET); + + state = SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_STATE_GET(sts_autoneg_next_reg); + + value = (state == SBL_PML_AUTONEG_STATE_COMPLETE_ACK) || + (state == SBL_PML_AUTONEG_STATE_AN_GOOD_CHECK); + + value = value && (SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_ABILITY_GET(sts_autoneg_next_reg)) && + (!SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_BASE_PAGE_GET(sts_autoneg_next_reg)); + + return value; +} + +void sbl_an_setup_next_page(struct sbl_inst *sbl, int port_num, int page_idx) +{ + struct sbl_link *link = sbl->link + port_num; + u32 base = SBL_PML_BASE(port_num); + + sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET, + link->an_tx_page[page_idx]); + sbl_dev_dbg(sbl->dev, "an %d: tx next page: 0x%llx", port_num, + link->an_tx_page[page_idx]); + sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET); +} + +void sbl_an_setup_null_page(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 null_page; + + /* Construct a null message page as in 802.3-2015: 28.2.3.4.1 & annex 28C */ + null_page = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET); + null_page ^= AN_NP_T_MASK; /* toggle bit 11 */ + null_page &= ~AN_NP_NP_MASK; /* clear next page bit */ + null_page |= AN_NP_MP_MASK; /* set this is msg page */ + null_page &= ~AN_NP_MSG_MASK; /* clear msg code */ + null_page |= AN_NP_CODE_NULL_MSG; /* set null msg code */ + null_page &= ~AN_NP_UCF_MASK; /* clear unformatted code field code */ + sbl_write64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET, null_page); + sbl_dev_dbg(sbl->dev, "an %d: tx null next page: 0x%llx", port_num, + null_page); + sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET); +} + +/* setup to detect complete or page received error flags become set */ +int sbl_an_hw_wait_prepare(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + unsigned long flags; + int err; + + spin_lock_irqsave(&link->lock, flags); + if (link->start_cancelled) { + spin_unlock_irqrestore(&link->lock, flags); + return -ECANCELED; + } + reinit_completion(&link->an_hw_change); + spin_unlock_irqrestore(&link->lock, flags); + + sbl_pml_err_flgs_clear(sbl, port_num, SBL_AUTONEG_ERR_FLGS); + + err = sbl_pml_enable_intr_handler(sbl, port_num, SBL_AUTONEG_ERR_FLGS); + if (err) { + sbl_dev_err(sbl->dev, "an %d: intr enable failed [%d]", port_num, err); + return err; + } + + return 0; +} + +const char *sbl_an_get_sm_state(struct sbl_inst *sbl, int port_num) +{ + u32 base = SBL_PML_BASE(port_num); + u64 sts_pcs_autoneg_reg; + u64 sm_state; + + sts_pcs_autoneg_reg = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET); + sm_state = SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_STATE_GET(sts_pcs_autoneg_reg); + + return sbl_an_state_str(sm_state); +} + +/** + * sbl_get_an_pages() - Get an pages + * @sbl: A slingshot base link device instance + * @port_num: port number + * @count: number of pages + * @pages: Pointer to max rx pages + * + * Get number of an pages 'count' and rx pages 'pages' + * by reading 'an_rx_count' and 'an_rx_page' + * + * Return: 0 on success, negative error code on failure + */ +int sbl_get_an_pages(struct sbl_inst *sbl, int port_num, int *count, u64 *pages) +{ + struct sbl_link *link; + int i; + int err; + + err = sbl_validate_instance(sbl); + if (err) + return err; + + err = sbl_validate_port_num(sbl, port_num); + if (err) + return err; + + if (!count || !pages) + return -EINVAL; + + link = sbl->link + port_num; + + /* check we did autoneg successfully */ + if ((link->blattr.pec.an_mode != SBL_AN_MODE_ON) && + (link->blattr.pec.an_mode != SBL_AN_MODE_FIXED)) { + sbl_dev_info(sbl->dev, "%d: not autoneg mode - no pages to get", port_num); + return -ENODATA; + } + if (!link->an_rx_count) { + sbl_dev_info(sbl->dev, "%d: no an pages to get", port_num); + return -ENODATA; + } + + if (link->an_rx_count > *count) { + /* insufficient space + * update size to say how much we need and return + */ + *count = link->an_rx_count; + return -ENOSPC; + } + + for (i = 0; i < link->an_rx_count; ++i) + *pages++ = link->an_rx_page[i]; + *count = link->an_rx_count; + + return 0; +} +EXPORT_SYMBOL(sbl_get_an_pages); + +void sbl_an_dump_state(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + u32 base = SBL_PML_BASE(port_num); + u64 val64; + int i; + + val64 = sbl_read64(sbl, base | SBL_PML_ERR_FLG_OFFSET); + sbl_dev_dbg(sbl->dev, "an %d: dump: err flgs 0x%llx", port_num, val64); + + val64 = sbl_read64(sbl, base | SBL_PML_CFG_PCS_OFFSET); + sbl_dev_dbg(sbl->dev, "an %d: dump: cfg pcs 0x%llx", port_num, val64); + + val64 = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_OFFSET); + sbl_dev_dbg(sbl->dev, "an %d: dump: cfg pcs an 0x%llx", port_num, val64); + + val64 = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_OFFSET); + sbl_dev_dbg(sbl->dev, "an %d: dump: sts an base pg 0x%llx", port_num, val64); + + sbl_dev_dbg(sbl->dev, "%d base, complete (%lld), state (%lld) (%s), received (%lld) , base page (%lld), lp (%lld), lp_base (%lld) ", + port_num, + SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_COMPLETE_GET(val64), + SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_STATE_GET(val64), + sbl_an_get_sm_state(sbl, port_num), + SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_PAGE_RECEIVED_GET(val64), + SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_BASE_PAGE_GET(val64), + SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_ABILITY_GET(val64), + SBL_PML_STS_PCS_AUTONEG_BASE_PAGE_LP_BASE_PAGE_GET(val64)); + + val64 = sbl_read64(sbl, base | SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET); + + sbl_dev_dbg(sbl->dev, "%d next, complete (%lld), state (%lld) (%s), received (%lld) , base page (%lld), lp (%lld), lp_next (%lld) ", + port_num, + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_COMPLETE_GET(val64), + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_STATE_GET(val64), + sbl_an_get_sm_state(sbl, port_num), + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_PAGE_RECEIVED_GET(val64), + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_BASE_PAGE_GET(val64), + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_ABILITY_GET(val64), + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_NEXT_PAGE_GET(val64)); + + sbl_dev_dbg(sbl->dev, "an %d: dump: sts an next pg 0x%llx", port_num, val64); + + for (i = 0; i < link->an_tx_count; ++i) { + sbl_dev_dbg(sbl->dev, "an %d: dump: tx pg %d: 0x%llx", port_num, + i, link->an_tx_page[i]); + } + for (i = 0; i < link->an_rx_count; ++i) { + sbl_dev_dbg(sbl->dev, "an %d: dump: rx pg %d: 0x%llx", port_num, + i, link->an_rx_page[i]); + } +}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c
new file mode 100644
index 000000000000..e9536283c47a
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link_an_nic.c@@ -0,0 +1,111 @@ +// 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/random.h> +#include <linux/delay.h> + +#include <linux/hpe/sbl/sbl.h> +#include <linux/hpe/sbl/sbl_an.h> + +#include <linux/hpe/sbl/sbl_pml.h> + +#include "sbl_pml_fn.h" +#include "sbl_link.h" +#include "sbl_serdes.h" +#include "sbl_constants.h" +#include "sbl_serdes_map.h" +#include "sbl_internal.h" + +int sbl_an_page_exchange(struct sbl_inst *sbl, int port_num, unsigned long remaining_jiffies) +{ + struct sbl_link *link = sbl->link + port_num; + int xcng_count = 1; + u64 sts_autoneg_next_reg; + u32 base = SBL_PML_BASE(port_num); + int err; + + /* -- Interrupt based next page exchange sequence -- */ + do { + /* Too many Rx pages ?*/ + if (xcng_count >= SBL_AN_MAX_RX_PAGES) { + sbl_dev_err_ratelimited(sbl->dev, "an %d: rx next page: too many pages %d", + port_num, xcng_count); + sbl_an_dump_state(sbl, port_num); + return -EPROTO; + } + + sbl_dev_dbg(sbl->dev, "an %d: next page %d: start", port_num, xcng_count); + + if (sbl_base_link_start_cancelled(sbl, port_num)) + return -ECANCELED; + if (sbl_start_timeout(sbl, port_num)) + return -ETIMEDOUT; + + /* setup the next page (real or null) */ + if (xcng_count < link->an_tx_count) + sbl_an_setup_next_page(sbl, port_num, xcng_count); + else + sbl_an_setup_null_page(sbl, port_num); + + /* setup and enable interrupt */ + err = sbl_an_hw_wait_prepare(sbl, port_num); + if (err) + return err; + + /* send the next page */ + sbl_an_send_next_page(sbl, port_num); + + /* wait for next page interrupt */ + remaining_jiffies = wait_for_completion_timeout(&link->an_hw_change, remaining_jiffies); + if (sbl_base_link_start_cancelled(sbl, port_num)) + return -ECANCELED; + if (!remaining_jiffies) { + sbl_dev_err(sbl->dev, "an %d: next page %d: exchange timeout", port_num, xcng_count); + sbl_an_dump_state(sbl, port_num); + return -ETIME; + } + + /* check if received page is a next page */ + if (!sbl_an_is_next_page(sbl, port_num)) { + sbl_dev_dbg_ratelimited(sbl->dev, "an %d: next page %d: missing next page indication, resend next-page", + port_num, xcng_count); + sbl_an_dump_state(sbl, port_num); + continue; + + } + + /* put the received page in the buffer if indicated */ + if (link->an_rx_page[xcng_count - 1] & AN_NP_NP_MASK) { + sts_autoneg_next_reg = sbl_read64(sbl, base | + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_OFFSET); + link->an_rx_page[xcng_count] = + SBL_PML_STS_PCS_AUTONEG_NEXT_PAGE_LP_NEXT_PAGE_GET(sts_autoneg_next_reg); + sbl_dev_dbg(sbl->dev, "an %d: rx next page: 0x%llx", + port_num, link->an_rx_page[xcng_count]); + link->an_rx_count++; + } + + /* go to the additional next page */ + xcng_count++; + + /* check to see if the exchange is done */ + } while (!sbl_an_next_is_complete(sbl, port_num)); + + return 0; +} + +void sbl_an_version_read(struct sbl_inst *sbl, int port_num) +{ + struct sbl_link *link = sbl->link + port_num; + + /* cassini version here */ + /* TODO: Implement dynamic version detection from HW device tree or runtime detection. + * Currently hardcoded to CASSINI_V1. Should query actual hardware version + * during autonegotiation setup to properly report link partner capability. + */ + link->an_tx_page[2] |= ((SBL_LP_SUBTYPE_CASSINI_V1 & AN_LP_SUBTYPE_MASK) << AN_LP_SUBTYPE_BASE_BIT); +}
diff --git a/drivers/net/ethernet/hpe/sbl/sbl_link_nic.c b/drivers/net/ethernet/hpe/sbl/sbl_link_nic.c
new file mode 100644
index 000000000000..2c0be275d019
--- /dev/null
+++ b/drivers/net/ethernet/hpe/sbl/sbl_link_nic.c@@ -0,0 +1,196 @@ +// 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/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" + +/** + * sbl_link_get_fec_thresholds() - Get fec thresholds + * @sbl: A slingshot base link device instance + * @port_num: port number + * @ucw_bad: Pointer to an int where output is stored(ucw threshold value) + * @ccw_bad: Pointer to an int where output is stored(ccw threshold value) + * @fecl_warn: Pointer to an int where output is stored(warn threshold value) + * + * Get threshold values based on link mode used. + * @ucw_bad, @ucw_bad and @fecl_warn holds the values and + * returns through provided pointers to the caller. + * + * Return the failure and warning thresholds for fec metrics + * monitoring + * + * The ccw values used here corresponds to a BER of 1.e-4 + * The ucw values correspond to a little less than 1e-10 + * The single lane fec warning threshold is the ccw rate divided by + * the number of fec lanes in use for the mode. + * + * + * 200G LINK (BS) (CD) + * 212500000000 bits/second over a 4-lane link @ 200G + * 39062500 cw/s (5440 bits/codeword) + * + * (Post)Pre-FEC BER + * 21250000.000000 (un)corrected cw/s => 1e-04 BER (1 bad bit per codeword) + * 2125000.000000 (un)corrected cw/s => 1e-05 BER + * 212500.000000 (un)corrected cw/s => 1e-06 BER + * 21250.000000 (un)corrected cw/s => 1e-07 BER + * 2125.000000 (un)corrected cw/s => 1e-08 BER + * 212.500000 (un)corrected cw/s => 1e-09 BER + * 21.250000 (un)corrected cw/s => 1e-10 BER + * 2.125000 (un)corrected cw/s => 1e-11 BER + * 0.212500 (un)corrected cw/s => 1e-12 BER + * 0.021250 (un)corrected cw/s => 1e-13 BER + * 0.002125 (un)corrected cw/s => 1e-13 BER + * + * 100G LINK (BJ) + * 103125000000 bits/second over a 4-lane link @ 100G + * 19531250 cw/s (5280 bits/codeword) + * + * (Post) Pre-FEC BER + * 1031250.000000 (un)corrected cw/s => 1e-05 BER (1 bad bit per codeword) + * 103125.000000 (un)corrected cw/s => 1e-06 BER + * 10312.500000 (un)corrected cw/s => 1e-07 BER + * 1031.250000 (un)corrected cw/s => 1e-08 BER + * 103.125000 (un)corrected cw/s => 1e-09 BER + * 10.312500 (un)corrected cw/s => 1e-10 BER + * 1.031250 (un)corrected cw/s => 1e-11 BER + * 0.103125 (un)corrected cw/s => 1e-12 BER + * + * Cassini uses the old + * sbl_link_get_fec_thresholds() + * Rosetta uses the newer + * sbl_link_get_ucw_thresh_ieee() + * sbl_link_get_ucw_thresh_hpe() + * sbl_link_get_ccw_thresh_ieee() + * sbl_link_get_ccw_thresh_hpe() + * + * Return: 0 on success, negative error code on failure + */ +int sbl_link_get_fec_thresholds(struct sbl_inst *sbl, int port_num, + int *ucw_bad, int *ccw_bad, int *fecl_warn) +{ + struct sbl_link *link = sbl->link + port_num; + int ucw; + int ccw; + int warn; + + if (link->sstate != SBL_SERDES_STATUS_RUNNING) + return -ENAVAIL; + + switch (link->link_mode) { + case SBL_LINK_MODE_BS_200G: + ucw = 21; + ccw = 21250000; + warn = ccw / 8; + break; + case SBL_LINK_MODE_BJ_100G: + ucw = 11; + ccw = 10312500; + warn = ccw / 8; + break; + case SBL_LINK_MODE_CD_100G: + ucw = 11; + ccw = 10625000; + warn = ccw / 4; + break; + case SBL_LINK_MODE_CD_50G: + ucw = 5; + ccw = 5312500; + warn = ccw / 2; + break; + default: + sbl_dev_err(sbl->dev, "%d: bad mode for fec thresh", port_num); + return -ENAVAIL; + } + + if (ucw_bad) + *ucw_bad = ucw; + if (ccw_bad) + *ccw_bad = ccw; + if (fecl_warn) + *fecl_warn = warn; + + return 0; +} +EXPORT_SYMBOL(sbl_link_get_fec_thresholds); + +#ifdef CONFIG_SYSFS +/** + * sbl_base_link_llr_sysfs_sprint() - Report LLR on/off 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 checks the state info flags of LLR. + * Based on this flag, it formats the string indicating + * whether LLR is on/off and writes it into provided buffer + * + * Context: Process context, Acquires lock and release link->lock <spin_lock> + * + * Return: Number of characters written, at most @size - 1 + */ +int sbl_base_link_llr_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 (READ_ONCE(link->link_info) & SBL_LINK_INFO_LLR_RUN) + s += scnprintf(buf + s, size - s, "on"); + else + s += scnprintf(buf + s, size - s, "off"); + + spin_unlock(&link->lock); + + return s; +} +EXPORT_SYMBOL(sbl_base_link_llr_sysfs_sprint); + +/** + * sbl_base_link_loopback_sysfs_sprint() - Report loopback mode 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 gets the loopback mode status and + * formats the string into the provided buffer + * + * Context: Process context, Acquires lock and release link->lock <spin_lock> + * + * Return: Number of characters written, at most @size - 1 + */ +int sbl_base_link_loopback_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); + + s += scnprintf(buf + s, size - s, sbl_loopback_mode_str(link->loopback_mode)); + + spin_unlock(&link->lock); + + return s; +} +EXPORT_SYMBOL(sbl_base_link_loopback_sysfs_sprint); +#endif
--
2.56.0