From: sashiko-bot@kernel.org
To: "Doug Ledford" <doug.ledford@hpe.com>,
"Slingshot Open Source Team"
<slingshot-os-team@groups.ext.hpe.com>,
"Nathan Chancellor" <nathan@kernel.org>,
"Nicolas Schier" <nsc@kernel.org>,
"Richard Cochran" <richardcochran@gmail.com>,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@kernel.org>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Sumit Semwal" <sumit.semwal@linaro.org>,
"Christian König" <christian.koenig@amd.com>,
"Guenter Roeck" <linux@roeck-us.net>
Cc: linux-kbuild@vger.kernel.org,
"Mamila Santosh" <santosh-kumar.mamila@hpe.com>
Subject: Re: [PATCH 06/23] net: hpe: sbl: add link state management
Date: Sat, 31 Oct 2026 21:02:07 +0000 [thread overview]
Message-ID: <sashiko-outbox-192138@kernel.org> (raw)
In-Reply-To: <20261030-cxi-netdev-submission-v1-6-8c2c391cc283@hpe.com>
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [High] net: hpe: sbl: broken non-next-page handling in sbl_an_page_exchange()
- [High] net: hpe: sbl: inconsistent IRQ locking on link->lock
- [Medium] net: hpe: sbl: broken PCS and SerDes error unwind in sbl_base_link_start()
- [Medium] net: hpe: sbl: missing PML recovery cancel in sbl_link_fault_monitor_start()
- [Medium] net: hpe: sbl: all-port tuning invalidation in sbl_base_link_try_start_fail_cleanup()
- [Medium] net: hpe: sbl: stale DOWN state in sbl_base_link_config() without media
- [Medium] net: hpe: sbl: lost SerDes reset error in sbl_base_link_reset()
- [Medium] net: hpe: sbl: inconsistent link->lock synchronization on link state and attrs
- [Medium] net: hpe: sbl: unchecked sbl_an_serdes_stop() in sbl_link_autoneg()
- [Low] net: hpe: sbl: unlocked read of link->blerr in sbl_base_link_stop()
- [Low] net: hpe: sbl: missing %s format in sbl_base_link_loopback_sysfs_sprint()
--
commit 6a4ede20cdb63d19f2857dcbfd73ffcd9b42af61
Author: Doug Ledford <doug.ledford@hpe.com>
net: hpe: sbl: add link state management
This commit adds the Slingshot base link state machine, including link
bring-up, auto-negotiation, and NIC-specific link operations tying the
PML and SerDes layers together.
> 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 0000000000000..706fa9ba9676c
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/sbl/sbl_link.c
> @@ -0,0 +1,1739 @@
[ ... ]
> +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;
> +}
[Severity: Medium]
In sbl_link_fault_monitor_start(), sbl_pml_recovery_enable() sets
link->pml_recovery.enabled = true before installing and enabling the fault
interrupt handler. Once sbl_pml_enable_intr_handler() succeeds, a fault
interrupt can fire in sbl_pml_hdlr() -> sbl_pml_recovery_monitor() and arm
link->pml_recovery.timer.
If sbl_pml_install_intr_handler(), sbl_pml_enable_intr_handler(), or the
subsequent sbl_pml_err_flgs_test() check fails, the error unwind labels
(out_disable, out_uninstall, and out) do not call sbl_pml_recovery_cancel(),
nor does sbl_link_fault_monitor_stop(). If SBL_DEBUG_INHIBIT_CLEANUP is set
when sbl_base_link_start() unwinds, or if sbl_link_fault_monitor_stop()
fails in sbl_base_link_stop(), sbl_pml_link_down() and
sbl_pml_recovery_cancel() are skipped, leaving PML recovery enabled and any
armed pml_recovery.timer running.
Should sbl_link_fault_monitor_start() cancel PML recovery on its error
unwind paths (and sbl_link_fault_monitor_stop() do the same), and should the
unreachable return err statement after goto out_uninstall be removed?
[ ... ]
> +int sbl_base_link_config(struct sbl_inst *sbl, int port_num, struct sbl_base_link_attr *blattr)
> +{
> + struct sbl_link *link;
> + int err;
[ ... ]
> + 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;
> +}
[Severity: Medium]
If a port without configured media (link->mconfigured == false) is first
configured in SBL_LOOPBACK_MODE_LOCAL, sbl_base_link_config() transitions
link->blstate to SBL_BASE_LINK_STATUS_DOWN.
If sbl_base_link_config() is later called to reconfigure the port to a
non-local loopback mode while link->mconfigured is still false, neither
branch is taken and link->blstate remains SBL_BASE_LINK_STATUS_DOWN instead
of reverting to SBL_BASE_LINK_STATUS_UNCONFIGURED, causing subsequent
sbl_base_link_start() calls to fail in sbl_media_validate_config() with
-EUCLEAN.
Should there be an else branch setting link->blstate to
SBL_BASE_LINK_STATUS_UNCONFIGURED when neither local loopback nor media is
configured?
[ ... ]
> +int sbl_base_link_start(struct sbl_inst *sbl, int 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;
[Severity: Medium]
Can sbl_base_link_config() and sbl_base_link_dump_attr() race with
sbl_base_link_start(), sbl_base_link_reset(), and
sbl_base_link_try_start_fail_cleanup() on link->blstate, link->blconfigured,
link->blattr, and link->start_cancelled?
sbl_base_link_config() only acquires spin_lock(&link->lock) (not
link->busy_mtx) and relies on link->blstate being SBL_BASE_LINK_STATUS_DOWN
or SBL_BASE_LINK_STATUS_UNCONFIGURED to serialize updates to link->blattr
and link->blconfigured.
However, sbl_base_link_start(), sbl_base_link_reset(), and
sbl_base_link_try_start_fail_cleanup() read and write link->blstate,
link->blerr, link->blconfigured, and link->start_cancelled under
link->busy_mtx without holding link->lock. For example, in
sbl_base_link_reset():
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;
setting link->blstate to SBL_BASE_LINK_STATUS_UNCONFIGURED before clearing
link->blconfigured allows a concurrent sbl_base_link_config() to observe
SBL_BASE_LINK_STATUS_UNCONFIGURED, copy new attributes, and set
link->blconfigured = true, only for sbl_base_link_reset() to overwrite
link->blconfigured = false. Similarly, sbl_base_link_start() validates
link->blattr before setting link->blstate = SBL_BASE_LINK_STATUS_STARTING
without holding link->lock, and sbl_base_link_dump_attr() reads
link->blconfigured and link->blattr without acquiring link->lock.
Should state and attribute accesses across these functions be consistently
serialized with link->lock (or link->busy_mtx)?
[ ... ]
> + /* 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;
> + }
[ ... ]
> + /* 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:
[Severity: Medium]
Does the error unwind in sbl_base_link_start() leave the PCS enabled in
hardware on failure?
First, sbl_pml_pcs_am_start() configures and enables the PCS (setting
PCS_ENABLE in SBL_PML_CFG_PCS_OFFSET and forcing TX remote fault in
SBL_PML_DBG_PCS_OFFSET). If sbl_base_link_lp_detect() or sbl_serdes_start()
subsequently fails, control jumps to out, and if sbl_pml_start() fails,
control jumps to out_serdes. Because sbl_pml_start() only stops the MAC and
disables PCS alignment on error, jumping to out or out_serdes skips out_pcs
(sbl_pml_link_down()) and leaves the PCS enabled.
Second, when a later step jumps to out_fm or out_pcs, out_pcs calls
sbl_pml_link_down() before out_serdes calls sbl_serdes_stop(). At that point
link->sstate is still SBL_SERDES_STATUS_RUNNING, so on optical media
sbl_pml_link_down() -> sbl_pml_pcs_stop() hits:
if ((link->sstate != SBL_SERDES_STATUS_DOWN) &&
(link->mattr.media == SBL_LINK_MEDIA_OPTICAL)) {
sbl_dev_err(sbl->dev, "%d: not stopping pcs because serdes is running",
port_num);
return;
}
and returns without stopping the PCS. By contrast, sbl_base_link_stop(),
sbl_base_link_reset(), and sbl_base_link_try_start_fail_cleanup() stop the
SerDes before calling sbl_pml_link_down().
Should out_serdes run before out_pcs, and should failures after
sbl_pml_pcs_am_start() unwind through out_pcs?
[ ... ]
> +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);
[ ... ]
> +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;
> +}
[Severity: High]
Can mixing spin_lock() and spin_lock_irqsave() on link->lock cause a
deadlock?
Several callers acquire link->lock with spin_lock_irqsave(), including
sbl_base_link_start(), sbl_ignore_save_tuning_param(),
sbl_enable_opt_lane_degrade(), sbl_disable_pml_recovery(),
sbl_an_hw_wait_prepare(), and sbl_fec_monitor_stop(). Other callers acquire
link->lock with plain spin_lock(), including sbl_base_link_config(),
sbl_base_link_enable_start(), sbl_base_link_cancel_start(),
sbl_base_link_start_cancelled(), sbl_base_link_stop(),
sbl_base_link_sysfs_sprint(), sbl_base_link_llr_sysfs_sprint(), and
sbl_base_link_loopback_sysfs_sprint().
When the TIMER_IRQSAFE callback sbl_pml_recovery_monitor_fallback_timer()
fails, it invokes:
sbl_pml_recovery_monitor_fallback_timer()
-> sbl_pml_link_down_async_alert()
-> sbl_async_alert()
-> cass_sbl_async_alert()
-> cass_sbl_link_down_async_handler()
-> cass_link_async_down()
-> cass_lmon_request_down()
-> sbl_base_link_cancel_start()
which acquires link->lock from IRQ-disabled timer context. If that timer
fires on a CPU where a process-context caller holds link->lock via plain
spin_lock() (with interrupts enabled), the CPU will deadlock on link->lock.
Similarly, sbl_set_degraded_flag(), sbl_clear_degraded_flag(), and
sbl_get_degraded_flag() use plain spin_lock(&link->is_degraded_lock) while
sbl_pml_hdlr() accesses link->is_degraded in interrupt context.
Should all acquisitions of link->lock (and link->is_degraded_lock) use
spin_lock_irqsave() and spin_unlock_irqrestore()?
[ ... ]
> +int sbl_base_link_stop(struct sbl_inst *sbl, int port_num)
> +{
[ ... ]
> +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;
> +}
[Severity: Low]
In sbl_base_link_stop(), link->blerr is read for the return value after both
link->lock and link->busy_mtx have been unlocked, so a concurrent caller
acquiring link->busy_mtx can modify link->blerr before sbl_base_link_stop()
returns. Should this return err instead of link->blerr?
[ ... ]
> +int sbl_base_link_reset(struct sbl_inst *sbl, int 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;
> +}
[Severity: Medium]
In sbl_base_link_reset(), if sbl_serdes_reset() fails, it sets link->sstate
to SBL_SERDES_STATUS_ERROR and returns a negative error code, but that err
value is immediately overwritten by sbl_pml_link_down() (which always
returns 0) and sbl_base_link_reset() unconditionally returns 0 with
link->blerr = 0.
Should sbl_base_link_reset() preserve and return the error from
sbl_serdes_reset() (or sbl_pml_link_down()) as documented in its function
comment?
[ ... ]
> +void sbl_base_link_try_start_fail_cleanup(struct sbl_inst *sbl, int 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;
[Severity: Medium]
sbl_base_link_try_start_fail_cleanup() cleans up a single port_num while
holding only that port's link->busy_mtx, but calls
sbl_serdes_invalidate_all_tuning_params(sbl), which invalidates the saved
tuning parameters for every port on the SBL instance.
Should this call sbl_serdes_invalidate_tuning_params(sbl, port_num) instead,
matching sbl_base_link_start() and sbl_pml_start()?
[ ... ]
> 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 0000000000000..aecadbceb593c
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/sbl/sbl_link_an.c
> @@ -0,0 +1,1010 @@
[ ... ]
> +int sbl_link_autoneg(struct sbl_inst *sbl, int port_num)
> +{
[ ... ]
> + /* 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;
> +}
[Severity: Medium]
When autonegotiation succeeds in sbl_link_autoneg(), err is 0 and cleanup
calls sbl_an_serdes_stop(sbl, port_num) without checking its return value
(and likewise ignores the return value on the retry paths inside the loop).
If sbl_an_serdes_stop() fails in sbl_serdes_stop_internal(), it sets
link->sstate = SBL_SERDES_STATUS_ERROR, yet sbl_link_autoneg() still returns
0. sbl_base_link_start() then enables PCS alignment markers and calls
sbl_serdes_start(), which fails with -EUCLEAN because link->sstate is not
SBL_SERDES_STATUS_DOWN, leaving link->blerr set to -EUCLEAN (which
sbl_base_link_try_start_fail_cleanup() will not clear).
Should sbl_link_autoneg() check the return value of sbl_an_serdes_stop() and
propagate any error?
[ ... ]
> 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 0000000000000..e9536283c47a1
> --- /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 */
[ ... ]
> +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));
[Severity: High]
What happens in sbl_an_page_exchange() when sbl_an_is_next_page() returns
false (for example, when the link partner restarts autonegotiation and sends
an unexpected base page during next-page exchange)?
First, sbl_link_autoneg() checks for err == -EPROTO from sbl_an_exchange()
with the comment:
/* 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
*/
However, when sbl_an_is_next_page() is false, sbl_an_page_exchange() does a
continue without incrementing xcng_count or returning -EPROTO, so
xcng_count >= SBL_AN_MAX_RX_PAGES is never reached and the loop instead runs
until remaining_jiffies expires and returns -ETIME, making the -EPROTO
recovery and sbl_an_100cr4_fixup() path in sbl_link_autoneg() unreachable.
Second, because continue jumps to the while (!sbl_an_next_is_complete())
check, if the complete bit is set, sbl_an_page_exchange() exits the loop and
returns 0 without recording the received page.
Third, when the loop repeats for the same xcng_count and
xcng_count >= link->an_tx_count, sbl_an_setup_null_page() is called again:
null_page = sbl_read64(sbl, base | SBL_PML_CFG_PCS_AUTONEG_NEXT_PAGE_OFFSET);
null_page ^= AN_NP_T_MASK; /* toggle bit 11 */
which flips the IEEE 802.3 Next Page toggle bit (AN_NP_T_MASK) while
resending the same null page.
Should sbl_an_page_exchange() return -EPROTO when an unexpected base page is
received during next-page exchange (or avoid toggling AN_NP_T_MASK and
checking sbl_an_next_is_complete() when resending)?
[ ... ]
> 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 0000000000000..2c0be275d0198
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/sbl/sbl_link_nic.c
> @@ -0,0 +1,196 @@
[ ... ]
> +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;
> +}
[Severity: Low]
In sbl_base_link_loopback_sysfs_sprint(), passing the non-literal return
value of sbl_loopback_mode_str() directly as the format string to
scnprintf() triggers a -Wformat-security compiler warning. Should this use a
"%s" format string?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030-cxi-netdev-submission-v1-0-8c2c391cc283@hpe.com?part=6
next prev parent reply other threads:[~2026-10-31 21:02 UTC|newest]
Thread overview: 48+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 20:58 [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE Slingshot NIC drivers Doug Ledford
2026-10-30 20:58 ` [PATCH 01/23] net: hpe: ss1: add the Cassini headers Doug Ledford
2026-10-31 13:58 ` Andrew Lunn
2026-10-31 19:34 ` Doug Ledford
2026-10-30 20:58 ` [PATCH 02/23] net: hpe: sbl: add the Slingshot Base Link headers Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:58 ` [PATCH 03/23] net: hpe: sbl: add the core instance and configuration layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 04/23] net: hpe: sbl: add the SerDes layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 05/23] net: hpe: sbl: add the PML link layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 06/23] net: hpe: sbl: add link state management Doug Ledford
2026-10-31 21:02 ` sashiko-bot [this message]
2026-10-30 20:59 ` [PATCH 07/23] net: hpe: sbl: add the SBM serial bus layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 08/23] net: hpe: sbl: add media, FEC and counter support Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 09/23] net: hpe: sl: add the Slingshot Link headers Doug Ledford
2026-10-30 20:59 ` [PATCH 10/23] net: hpe: sl: add the module entry points Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 11/23] net: hpe: sl: add the core link state machines Doug Ledford
2026-10-30 20:59 ` [PATCH 12/23] net: hpe: sl: add the control layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 13/23] net: hpe: sl: add media and cable handling Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 14/23] net: hpe: sl: add the sysfs interface Doug Ledford
2026-10-30 20:59 ` [PATCH 15/23] net: hpe: sl: add the NIC binding and logging helpers Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 16/23] net: hpe: ss1: add the Cassini core device layer Doug Ledford
2026-10-30 20:59 ` [PATCH 17/23] net: hpe: ss1: add the datapath and memory translation layers Doug Ledford
2026-10-30 20:59 ` [PATCH 18/23] net: hpe: ss1: add the link layer glue Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 19/23] net: hpe: ss1: add SR-IOV support Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 20/23] net: hpe: ss1: add the CXI core interfaces Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 21/23] net: hpe: ss1: add resource groups, QoS and services Doug Ledford
2026-10-30 20:59 ` [PATCH 22/23] net: hpe: ss1: add the Ethernet personality Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 23/23] net: hpe: wire up the Slingshot NIC drivers Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 22:11 ` [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE " Jakub Kicinski
2026-10-31 20:17 ` Andrew Lunn
2026-11-02 1:02 ` Doug Ledford
2026-11-02 1:35 ` Andrew Lunn
2026-11-02 3:45 ` Doug Ledford
2026-11-02 3:01 ` Guenter Roeck
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-192138@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=christian.koenig@amd.com \
--cc=davem@davemloft.net \
--cc=doug.ledford@hpe.com \
--cc=edumazet@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=nathan@kernel.org \
--cc=nsc@kernel.org \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=santosh-kumar.mamila@hpe.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=slingshot-os-team@groups.ext.hpe.com \
--cc=sumit.semwal@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox