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 04/23] net: hpe: sbl: add the SerDes layer
Date: Sat, 31 Oct 2026 21:02:05 +0000 [thread overview]
Message-ID: <sashiko-outbox-192136@kernel.org> (raw)
In-Reply-To: <20261030-cxi-netdev-submission-v1-4-8c2c391cc283@hpe.com>
Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [High] net: hpe: sbl: out-of-bounds ring indexing and broken flash on NIC1
- [High] net: hpe: sbl: lock order inversion in sbl_spico_reset()
- [High] net: hpe: sbl: unbounded retry loop in sbl_serdes_tuning() on eye failure
- [Medium] net: hpe: sbl: leak of tps on error in sbl_check_serdes_tuning_params()
- [Medium] net: hpe: sbl: unlocked clear and valid magic in sbl_serdes_reset()
- [Medium] net: hpe: sbl: corrupted tuning_params left valid on save error
- [Medium] net: hpe: sbl: missing SBL_SC_MAX_INTR check on num_intr in config
- [Medium] net: hpe: sbl: stale values.num_intr and uninit result in sbl_serdes_config()
- [Medium] net: hpe: sbl: 16-bit pointer cast of uint in sbl_serdes_get_fw_vers()
- [Medium] net: hpe: sbl: sbl_port_dfe_tune_start() tunes TX-only SerDes lanes
- [Medium] net: hpe: sbl: SPICO_INT_SBMS_FW_STS used as SBus reg addr in flash_safe()
- [Low] net: hpe: sbl: commit message describes absent file and ops table
- [Low] net: hpe: sbl: pointer-sign mismatches in sbl_serdes_fn.c
--
commit 8fdddc2fa6224d724654d04f766be5b1dd3e769a
Author: Doug Ledford <doug.ledford@hpe.com>
net: hpe: sbl: add the SerDes layer
This commit adds the SerDes layer implementation in sbl_serdes.c and
sbl_serdes_fn.c to manage SerDes firmware loading, per-lane configuration,
tuning, and status reporting.
> net: hpe: sbl: add the SerDes layer
>
> Add the SerDes layer: per-lane configuration, the autogenerated lane
> map, and the function-pointer table that lets the PML layer drive the
> SerDes without knowing which SerDes core is present.
>
> sbl_serdes_map_autogen.h is generated from the hardware lane
> description and is kept in generated form, so style checkers will flag
> it for line length and macro layout.
>
> 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 (45), BLOCK_COMMENT_STYLE (36), ALLOC_WITH_SIZEOF
> (2), ENOTSUPP (1), LONG_LINE_COMMENT (1), LONG_LINE_STRING (1),
> TYPO_SPELLING (1)
> - CHECK: UNNECESSARY_PARENTHESES (37), BRACES (13), ALLOC_SIZEOF_STRUCT
> (2), PARENTHESIS_ALIGNMENT (2), COMPARISON_TO_NULL (1), LINE_SPACING
> (1)
>
> Co-developed-by: Mamila Santosh <santosh-kumar.mamila@hpe.com>
> Signed-off-by: Mamila Santosh <santosh-kumar.mamila@hpe.com>
> Signed-off-by: Doug Ledford <doug.ledford@hpe.com>
[Severity: Low]
Does the commit message match the contents of this patch?
The changelog states that this patch adds sbl_serdes_map_autogen.h and a
function-pointer table for driving the SerDes layer, but this commit only
adds drivers/net/ethernet/hpe/sbl/sbl_serdes.c and
drivers/net/ethernet/hpe/sbl/sbl_serdes_fn.c with direct C functions.
No file named sbl_serdes_map_autogen.h is present in the tree (the lane map
headers include/uapi/ethernet/sbl_serdes_map.h and
include/uapi/ethernet/sbl_serdes_map_init.h were added in commit
16e18859cf46 ("net: hpe: sbl: Add header files for SBL")).
> diff --git a/drivers/net/ethernet/hpe/sbl/sbl_serdes.c b/drivers/net/ethernet/hpe/sbl/sbl_serdes.c
> new file mode 100644
> index 0000000000000..4db5bb1a0bb91
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/sbl/sbl_serdes.c
[ ... ]
> @@ -382,56 +382,56 @@ int sbl_serdes_reset(struct sbl_inst *sbl, int port_num)
> link = sbl->link + port_num;
> link->sstate = SBL_SERDES_STATUS_RESETTING;
>
> /* optionally clear any saved tuning params */
> if (link->blattr.options & SBL_OPT_RESET_CLEAR_PARAMS) {
> link->tuning_params.tp_state_hash0 = 0;
> link->tuning_params.tp_state_hash1 = 0;
> for (serdes = 0; serdes < sbl->switch_info->num_serdes; ++serdes) {
> memset(link->tuning_params.params[serdes].ctle, 0,
> sizeof(u16) * NUM_CTLE_PARAMS);
> memset(link->tuning_params.params[serdes].ffe, 0,
> sizeof(u16) * NUM_FFE_PARAMS);
> memset(link->tuning_params.params[serdes].dfe, 0,
> sizeof(u16) * NUM_DFE_PARAMS);
> memset(link->tuning_params.params[serdes].rxvs, 0,
> sizeof(u16) * NUM_RXVS_PARAMS);
> memset(link->tuning_params.params[serdes].rxvc, 0,
> sizeof(u16) * NUM_RXVC_PARAMS);
> memset(link->tuning_params.params[serdes].rsdo, 0,
> sizeof(u16) * NUM_RSDO_PARAMS);
> memset(link->tuning_params.params[serdes].rsdc, 0,
> sizeof(u16) * NUM_RSDC_PARAMS);
> memset(link->tuning_params.params[serdes].rsto, 0,
> sizeof(u16) * NUM_RSTO_PARAMS);
> memset(link->tuning_params.params[serdes].rstc, 0,
> sizeof(u16) * NUM_RSTC_PARAMS);
> memset(link->tuning_params.params[serdes].eh, 0,
> sizeof(u16) * NUM_EH_PARAMS);
> memset(link->tuning_params.params[serdes].gtp, 0,
> sizeof(u16) * NUM_GTP_PARAMS);
> memset(link->tuning_params.params[serdes].dccd, 0,
> sizeof(u16) * NUM_DCCD_PARAMS);
> memset(link->tuning_params.params[serdes].p4lv, 0,
> sizeof(u16) * NUM_P4LV_PARAMS);
> memset(link->tuning_params.params[serdes].afec, 0,
> sizeof(u16) * NUM_AFEC_PARAMS);
> }
> }
[Severity: Medium]
Can clearing link->tuning_params in sbl_serdes_reset() when
SBL_OPT_RESET_CLEAR_PARAMS is set race with concurrent readers or writers
and leave magic set to SBL_TUNING_PARAM_MAGIC?
Other accessors of link->tuning_params (sbl_serdes_get_tuning_params(),
sbl_serdes_set_tuning_params(), sbl_serdes_invalidate_tuning_params(), and
sbl_serdes_tuning()) serialize access under link->tuning_params_mtx. Here,
link->tuning_params is modified without holding link->tuning_params_mtx
(and in sbl_serdes_set_tuning_params(), tp_state_hash0 and tp_state_hash1
are also read for sbl_dev_dbg() after unlocking link->tuning_params_mtx).
In addition, sbl_serdes_reset() does not clear link->tuning_params.magic = 0
like sbl_serdes_invalidate_tuning_params() does. Because magic remains
SBL_TUNING_PARAM_MAGIC, a subsequent call to sbl_serdes_get_tuning_params()
will copy out the zeroed tuning_params structure instead of returning
-ENODATA.
> diff --git a/drivers/net/ethernet/hpe/sbl/sbl_serdes_fn.c b/drivers/net/ethernet/hpe/sbl/sbl_serdes_fn.c
> new file mode 100644
> index 0000000000000..f085d5c41da13
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/sbl/sbl_serdes_fn.c
[ ... ]
> +void sbl_serdes_get_fw_vers(struct sbl_inst *sbl, int port_num, int serdes,
> + uint *fw_rev, uint *fw_build)
> +{
> + if (sbl_serdes_spico_int(sbl, port_num, serdes, SPICO_INT_CM4_REV_ID,
> + SPICO_INT_DATA_NONE, (uint16_t *)fw_rev,
> + SPICO_INT_RETURN_RESULT)) {
> + /* Failure expected when Spico is in reset */
> + dev_dbg(sbl->dev, "p%ds%d: Failed to read firmware rev!", port_num, serdes);
> + *fw_rev = 0x0;
> + }
> + if (sbl_serdes_spico_int(sbl, port_num, serdes, SPICO_INT_CM4_BUILD_ID,
> + SPICO_INT_DATA_NONE, (uint16_t *)fw_build,
> + SPICO_INT_RETURN_RESULT)) {
> + /* Failure expected when Spico is in reset */
> + dev_dbg(sbl->dev, "p%ds%d: Failed to read firmware build!", port_num, serdes);
> + *fw_build = 0x0;
> + }
> +}
[Severity: Medium]
Does casting uint *fw_rev and uint *fw_build to uint16_t * in
sbl_serdes_get_fw_vers() leave the upper 16 bits uninitialized on success
and write to the wrong halfword on big-endian architectures?
sbl_serdes_spico_int() only writes a 16-bit u16 value through its result
pointer. When sbl_serdes_spico_int() succeeds, the other 16 bits of the
32-bit uint pointed to by fw_rev and fw_build are never written by
sbl_serdes_get_fw_vers() (also note that in sbl_sbm_fw_sysfs_sprint() in
sbl_serdes.c, uint fw_rev is declared without initialization alongside
fw_build = 0).
[ ... ]
> +/* Parse the vers string into rev and build */
> +static int sbl_parse_version_string(struct sbl_inst *sbl, const char *fw_fname,
> + int *fw_rev, int *fw_build)
> +{
> + char rev_str[SBL_MAX_STR_LEN];
> + char build_str[SBL_MAX_STR_LEN];
> + char *p;
> + int err;
> +
> + p = strstr(fw_fname, ".");
> + if (p == NULL) {
> + sbl_dev_err(sbl->dev, "Bad firmware file name: %s", fw_fname);
> + return -EINVAL;
> + }
> +
> + if (strlen(p) < strlen(".0x0000_0000")) {
> + sbl_dev_err(sbl->dev, "Bad firmware file name: %s", fw_fname);
> + return -EINVAL;
> + }
> +
> + memcpy(rev_str, &(p[3]), SBL_FW_REV_LEN);
> + rev_str[SBL_FW_REV_LEN] = '\n';
> + rev_str[SBL_FW_REV_LEN + 1] = '\0';
> + memcpy(build_str, &(p[8]), SBL_FW_BUILD_LEN);
> + build_str[SBL_FW_BUILD_LEN] = '\n';
> + build_str[SBL_FW_BUILD_LEN + 1] = '\0';
> +
> + err = kstrtou32(rev_str, 16, fw_rev);
> + if (err) {
> + sbl_dev_err(sbl->dev, "Failed to convert %s to an integer [%d]", rev_str, err);
> + return err;
> + }
> +
> + err = kstrtou32(build_str, 16, fw_build);
> + if (err) {
> + sbl_dev_err(sbl->dev, "Failed to convert %s to an integer [%d]", build_str, err);
> + return err;
> + }
> + return 0;
> +}
[Severity: Low]
Will passing signed int *fw_rev and int *fw_build to kstrtou32() here, and
passing signed __s16 * parameter array elements (ctle, ffe, dfe, rxvs, rxvc,
rsdo, rsdc, rsto, rstc, eh, gtp, dccd, p4lv, afec) from struct
sbl_serdes_params to the u16 * result parameter of sbl_serdes_spico_int() in
sbl_get_serdes_tuning_params(), trigger -Wpointer-sign compiler warnings?
> +
> +void sbl_sbm_get_fw_vers(struct sbl_inst *sbl, int sbus_ring, uint *fw_rev, uint *fw_build)
> +{
> + uint sbus_addr = SBUS_ADDR(sbus_ring, SBUS_BCAST_SBM_SPICO);
> +
> + /* SBUS Critical Section */
> + mutex_lock(&sbl->sbus_ring_mtx[sbus_ring]);
> +
> + if (sbl_sbm_spico_int(sbl, sbus_addr, SPICO_INT_SBMS_REV_ID,
> + SPICO_INT_DATA_NONE, fw_rev)) {
> + /* Failure expected when Spico is in reset */
> + dev_dbg(sbl->dev, "sbm%d: Failed to read firmware rev from 0x%x", sbus_ring, sbus_addr);
> + *fw_rev = 0x0;
> + }
> + if (sbl_sbm_spico_int(sbl, sbus_addr, SPICO_INT_SBMS_BUILD_ID,
> + SPICO_INT_DATA_NONE, fw_build)) {
> + /* Failure expected when Spico is in reset */
> + dev_dbg(sbl->dev, "sbm%d: Failed to read firmware build from 0x%x", sbus_ring, sbus_addr);
> + *fw_build = 0x0;
> + }
> + mutex_unlock(&sbl->sbus_ring_mtx[sbus_ring]);
> +}
> +
> +int sbl_sbm_firmware_flash(struct sbl_inst *sbl)
> +{
> + int err;
> +
> + err = sbl_sbm_firmware_flash_ring(sbl, 0,
> + sbl->switch_info->num_sbus_rings - 1, false);
> + if (err)
> + /* sending port 0 in event as this event doesn't apply to any specific port */
> + sbl_async_alert(sbl, 0, SBL_ASYNC_ALERT_SBM_FW_LOAD_FAILURE, NULL, 0);
> +
> + return err;
> +}
> +
> +int sbl_sbm_firmware_flash_ring(struct sbl_inst *sbl, int first_ring,
> + int last_ring, bool force)
> +{
> + bool flash_needed, fw_requested = false;
> + int sbus_ring;
> + int err;
> + int fw_rev, fw_build;
> + const struct firmware *fw = NULL;
> +
> + if ((last_ring < first_ring) || (first_ring < 0) ||
> + (last_ring > sbl->switch_info->num_sbus_rings - 1)) {
> + sbl_dev_err(sbl->dev, "Invalid rings specified first:%d last:%d",
> + first_ring, last_ring);
> + return -EINVAL;
> + }
[ ... ]
> +static void sbl_serdes_firmware_validation(struct sbl_inst *sbl, const struct firmware *fw,
> + u32 sbus_addr, u32 result, u32 sbus_ring)
> +{
> + int rc, i;
> + u8 data;
> + bool corruption_found = false;
> +
> + for (i = 0; i < fw->size; ++i) {
> + if (i % 2 == 0) {
> + rc = sbl_sbus_wr(sbl, sbus_addr,
> + SPICO_SBR_ADDR_IMEM, i / 2);
> + if (rc)
> + break;
> + rc = sbl_sbus_rd(sbl, sbus_addr,
> + SPICO_SBR_ADDR_RDATA,
> + &result);
> + if (rc)
> + break;
> + data = (result & 0xff00) >> 8;
> + } else {
> + data = result & 0xff;
> + }
> + if (data != fw->data[i]) {
> + corruption_found = true;
> + sbl_dev_warn(sbl->dev, "0x%x: Act 0x%4.4x Exp 0x%4.4x", i, data, fw->data[i]);
> + }
> + }
> + rc = sbl_sbus_wr(sbl, sbus_addr, SPICO_SBR_ADDR_CTL,
> + SPICO_SBR_DATA_IMEM_CNTL_DIS);
> + if (rc)
> + sbl_dev_err(sbl->dev, "SBM Imem rd disable failed [%d]", rc);
> + if (corruption_found)
> + sbl_dev_err(sbl->dev, "r%d: SBM FW corruption found", sbus_ring);
> + else
> + sbl_dev_info(sbl->dev, "r%d: No SBM FW corruption found", sbus_ring);
> +}
> +
> +int sbl_serdes_firmware_flash_safe(struct sbl_inst *sbl, int port_num,
> + bool force)
> +{
> + int rc;
> + u32 sbus_addr, crc_result, sbus_ring;
> + u32 curr_fw_rev = 0, curr_fw_build = 0;
> + u32 result;
> + const struct firmware *fw = NULL;
> + int fw_rev, fw_build;
> + int serdes = 0;
> +
> + if (!sbl->is_hw)
> + return 0;
> +
> + if (port_num == SBL_ALL_PORTS)
> + return -ENOTSUPP;
> +
> + sbus_ring = sbl->switch_info->ports[port_num].serdes[serdes].sbus_ring;
> +
> + /* First, try the FW flash */
> + rc = sbl_serdes_firmware_flash(sbl, port_num, force);
> + if (rc)
> + goto sbm_fw_reload;
> +
> + /* Now, validate the SerDes FW - this also validates
> + * SPICO interrupts are working correctly.
> + */
> + rc = sbl_parse_version_string(sbl, sbl->iattr.serdes_fw_fname,
> + &fw_rev, &fw_build);
> + if (rc) {
> + sbl_dev_err(sbl->dev,
> + "Failed to parse version string %s [%d]",
> + sbl->iattr.sbm_fw_fname, rc);
> + return rc;
> + }
[ ... ]
> + sbm_fw_reload:
> + if (sbl_debug_option(sbl, port_num, SBL_DEBUG_INHIBIT_RELOAD_SBM_FW)) {
> + sbl_dev_warn(sbl->dev, "p%d: SBus Master FW reload inhibited", port_num);
> + return rc;
> + }
> +
> + /* We may trigger a sbus master FW reload from multiple ports at the
> + * same time. Ensure we only actually reload the firmware once per
> + * sbus ring
> + */
> + sbl->reload_sbm_fw[sbus_ring] = true;
> + mutex_lock(SBM_FW_MTX(sbl, sbus_ring));
> + if (!sbl->reload_sbm_fw[sbus_ring]) {
> + sbl_dev_info(sbl->dev, "r%d: Sbus master FW reload no longer needed", sbus_ring);
> + } else {
[ ... ]
> + /* FW Status */
> + rc = sbl_sbus_rd(sbl, sbus_addr, SPICO_INT_SBMS_FW_STS,
> + &result);
> + if (rc) {
> + sbl_dev_err(sbl->dev,
> + "p%d(0x%x): FW status read failed (%d)!",
> + port_num, sbus_addr, rc);
> + } else {
> + sbl_dev_info(sbl->dev, "p%d(0x%x): FW status: 0x%x",
> + port_num, sbus_addr, result);
> + }
[Severity: Medium]
Should the FW status query in sbl_serdes_firmware_flash_safe() call
sbl_sbm_spico_int() instead of sbl_sbus_rd()?
SPICO_INT_SBMS_FW_STS (0x20) is defined as an SBus Master SPICO interrupt
code alongside SPICO_INT_SBMS_REV_ID, SPICO_INT_SBMS_BUILD_ID, and
SPICO_INT_SBMS_DO_CRC, whereas valid SBus receiver register addresses
(SPICO_SBR_ADDR_*) are 0x0..0x1b and 0xff. Passing SPICO_INT_SBMS_FW_STS to
sbl_sbus_rd() reads register 0x20 instead of issuing the SPICO interrupt.
Also in this recovery path:
1. In sbl_serdes_firmware_validation(), if sbl_sbus_wr() or sbl_sbus_rd()
fails mid-loop and breaks out early while corruption_found is false, it
still logs "No SBM FW corruption found".
2. Earlier in sbl_serdes_firmware_flash_safe(), when
sbl_parse_version_string() fails on sbl->iattr.serdes_fw_fname, the
error message logs sbl->iattr.sbm_fw_fname instead.
> +
> + rc = request_firmware(&fw, sbl->iattr.sbm_fw_fname, sbl->dev);
> + if (rc != 0) {
> + sbl_dev_err(sbl->dev, "firmware request failed [%d]", rc);
> + } else {
> + sbl_dev_info(sbl->dev,
> + "p%d(0x%x): Checking SBM FW for corruption...",
> + port_num, sbus_addr);
> + rc = sbl_sbus_wr(sbl, sbus_addr, SPICO_SBR_ADDR_CTL,
> + SPICO_SBR_DATA_IMEM_CNTL_EN_RD);
> + if (rc)
> + sbl_dev_err(sbl->dev, "SBM Imem rd enable failed [%d]", rc);
> + else
> + sbl_serdes_firmware_validation(sbl, fw, sbus_addr, result, sbus_ring);
> +
> + release_firmware(fw);
> + }
> +
> + mutex_unlock(SBUS_RING_MTX(sbl, sbus_ring));
> +
> + /* Now, try reloading the sbus master FW */
> + rc = sbl_sbm_firmware_flash_ring(sbl, sbus_ring, sbus_ring, true);
> + if (rc)
> + sbl_dev_err(sbl->dev, "r%d: SBM FW flash failed (%d)!", sbus_ring, rc);
> + else
> + sbl_dev_info(sbl->dev, "r%d: SBM FW flash succeeded", sbus_ring);
> +
> + sbl->reload_sbm_fw[sbus_ring] = false;
> + }
> + mutex_unlock(SBM_FW_MTX(sbl, sbus_ring));
[Severity: High]
Does indexing by hardware sbus_ring cause out-of-bounds array accesses and
failed firmware flashing on Sawtooth NIC1?
In sbl_new_instance() (drivers/net/ethernet/hpe/sbl/sbl_inst.c),
sbl->sbus_ring_mtx, sbl->sbm_fw_mtx, sbl->reload_sbm_fw, and
sbl->sbm_fw_reload_count are allocated with sbl->switch_info->num_sbus_rings
(1) element, so only index 0 is valid. However, on Sawtooth NIC1
(cas_nic1_switch_info / SBL_CASSINI_NIC1_SW_INFO_INITIALIZER),
num_sbus_rings is 1 while ports[0].serdes[0..3].sbus_ring is 1.
When running on NIC1:
1. sbl_serdes_firmware_flash_safe() reads sbus_ring = 1 from
sbl->switch_info->ports[port_num].serdes[serdes].sbus_ring and accesses
sbl->reload_sbm_fw[1] out of bounds on the 1-element array (and also
writes sbl->reload_sbm_fw[sbus_ring] = true before locking SBM_FW_MTX and
clears it to false even when sbl_sbm_firmware_flash_ring() fails).
2. sbl_serdes_firmware_flash_safe() calls
sbl_sbm_firmware_flash_ring(sbl, 1, 1, true), which fails the check
last_ring > sbl->switch_info->num_sbus_rings - 1 (1 > 0) and returns
-EINVAL.
3. sbl_sbm_firm_upload() increments sbl->sbm_fw_reload_count[sbus_ring] out
of bounds when sbus_ring is 1, and sbl_sbm_get_fw_vers() indexes
&sbl->sbus_ring_mtx[sbus_ring] directly instead of using
SBUS_RING_MTX(sbl, sbus_ring).
4. Functions that loop from 0 to num_sbus_rings - 1, including
sbl_sbm_firmware_flash(), sbl_serdes_firm_upload() when port_num ==
SBL_ALL_PORTS, and sbl_apply_sbus_divider(), only iterate ring 0 and
never address NIC1's hardware ring 1 (and in sbl_serdes_firmware_flash(),
looping sr over mutex_lock(SBM_FW_MTX(sbl, sr)) would self-deadlock if
num_sbus_rings > 1 because SBM_FW_MTX ignores sr).
[ ... ]
> +static int sbl_get_serdes_tuning_params(struct sbl_inst *sbl, int port_num,
> + struct sbl_tuning_params *tps)
> +{
> + int i, serdes, err;
> +
> + if (!tps)
> + return -EINVAL;
> +
> + tps->magic = SBL_TUNING_PARAM_MAGIC;
> + tps->version = SBL_TUNING_PARAM_VERSION;
[ ... ]
> +int sbl_check_serdes_tuning_params(struct sbl_inst *sbl, int port_num)
> +{
> + int i, serdes, err;
> + struct sbl_tuning_params *tps;
> + int min, max;
> + int num_oob_params;
> + int max_oob_params = SBL_MAX_OOB_SERDES_PARAMS;
> +
> + tps = kzalloc(sizeof(struct sbl_tuning_params), GFP_KERNEL);
> + if (!tps)
> + return -ENOMEM;
[ ... ]
> + if (num_oob_params > max_oob_params) {
> + sbl_dev_err(sbl->dev, "p%ds%d: Too many tuning params out of bounds(%d, max %d)!",
> + port_num, serdes, num_oob_params, max_oob_params);
> + return -EDQUOT;
> + } else if (num_oob_params > 0) {
> + sbl_dev_info(sbl->dev, "p%ds%d: Some tuning params out of bounds(%d, max %d)",
> + port_num, serdes, num_oob_params, max_oob_params);
> + }
> + }
> +
> + kfree(tps);
> + return 0;
> +}
[Severity: Medium]
Does sbl_check_serdes_tuning_params() leak tps when returning -EDQUOT?
When num_oob_params > max_oob_params, the function returns -EDQUOT directly
without freeing tps allocated at the start of
sbl_check_serdes_tuning_params().
> +
> +int sbl_save_serdes_tuning_params(struct sbl_inst *sbl, int port_num)
> +{
> + int err;
> +
> + sbl->link[port_num].tuning_params.tp_state_hash0 =
> + sbl_get_tp_hash0(sbl, port_num);
> + sbl->link[port_num].tuning_params.tp_state_hash1 =
> + sbl_get_tp_hash1(sbl, port_num);
> + sbl_dev_dbg(sbl->dev, "Updated hash0 to 0x%llx, hash1 to 0x%llx",
> + sbl->link[port_num].tuning_params.tp_state_hash0,
> + sbl->link[port_num].tuning_params.tp_state_hash1);
> +
> + err = sbl_get_serdes_tuning_params(sbl, port_num,
> + &sbl->link[port_num].tuning_params);
> +
> + return err;
> +}
[Severity: Medium]
What happens to link->tuning_params if sbl_get_serdes_tuning_params() fails
partway through when called from sbl_save_serdes_tuning_params()?
sbl_save_serdes_tuning_params() writes tp_state_hash0 and tp_state_hash1
in-place, and sbl_get_serdes_tuning_params() sets tps->magic =
SBL_TUNING_PARAM_MAGIC and tps->version = SBL_TUNING_PARAM_VERSION before
reading the per-lane parameters from hardware.
If a SPICO read or the sbl_validate_serdes_fw_vers() check fails after lane
0's CTLE/FFE/DFE values have been written, sbl_serdes_tuning() logs a
warning and continues without invalidating link->tuning_params. That leaves
partially populated tuning parameters marked with valid magic and matching
state hashes, so sbl_serdes_get_tuning_params() will return corrupted data
and sbl_is_retune() will treat the incomplete parameters as valid on the
next link start.
[ ... ]
> +int sbl_serdes_dfe_tune_wait(struct sbl_inst *sbl, int port_num)
> +{
[ ... ]
> + if (!in_progress_mask)
> + break;
> +
> + msleep(link->blattr.dfe_poll_interval);
> + } while (time_is_after_jiffies(last_jiffy) &&
> + !sbl_start_timeout(sbl, port_num) &&
> + !sbl_base_link_start_cancelled(sbl, port_num));
> +
> + if (serdes_mask == tuned_mask)
> + return 0;
> +
> + if (sbl_base_link_start_cancelled(sbl, port_num))
> + return -ECANCELED;
> +
> + if (sbl_start_timeout(sbl, port_num)) {
> + sbl_dev_dbg(sbl->dev, "p%d: start timeout waiting for DFE to tune to complete\n",
> + port_num);
> + return -ETIMEDOUT;
> + }
> +
> + /* just didnt finish */
> + sbl_dev_dbg(sbl->dev, "p%d serdes_mask:0x%x: Timeout waiting for DFE to complete (timeout:%ds)",
> + port_num, serdes_mask, link->blattr.dfe_timeout);
> + return -ETIME;
> +}
> +
> +int sbl_port_dfe_tune_start(struct sbl_inst *sbl, int port_num, bool is_retune)
> +{
> + int err, serdes;
> +
> + sbl_dev_dbg(sbl->dev, "p%d: DFE tune retune:%d", port_num, is_retune);
> +
> + /* DFE Tune */
> + for (serdes = 0; serdes < sbl->switch_info->num_serdes; ++serdes) {
> + if (!(rx_serdes_required_for_link_mode(sbl, port_num, serdes) ||
> + tx_serdes_required_for_link_mode(sbl, port_num, serdes)))
> + continue;
> + err = sbl_serdes_dfe_tune_start(sbl, port_num, serdes,
> + is_retune);
> + if (err) {
> + sbl_dev_err(sbl->dev, "p%d: Failed to start DFE tune!",
> + port_num);
> + return err;
> + }
> + }
> +
> + return 0;
> +}
[Severity: Medium]
Should sbl_port_dfe_tune_start() only check
rx_serdes_required_for_link_mode() instead of
(rx_serdes_required_for_link_mode() || tx_serdes_required_for_link_mode())?
In SBL_LINK_MODE_CD_50G and SBL_LINK_MODE_CD_100G,
tx_serdes_required_for_link_mode() is true for physical lane 0 (and active
TX lanes) even when rx_serdes_required_for_link_mode() is false and
sbl_serdes_config() disabled the receiver on that lane. All other RX DFE and
calibration functions (sbl_serdes_dfe_tune_wait(), sbl_port_check_eyes(),
sbl_check_serdes_tuning_params(), sbl_port_start_pcal(), and
sbl_port_stop_pcal()) skip lanes where !rx_serdes_required_for_link_mode(),
so starting RX DFE calibration on TX-only lanes here tunes disabled
receivers that sbl_serdes_dfe_tune_wait() never waits on.
[ ... ]
> +int sbl_serdes_config(struct sbl_inst *sbl, int port_num, bool allow_an)
> +{
[ ... ]
> + /* Set port config values */
> + if (!allow_an) {
> + for (serdes = 0; serdes < sbl->switch_info->num_serdes; ++serdes) {
> + if (!(rx_serdes_required_for_link_mode(sbl, port_num, serdes) ||
> + tx_serdes_required_for_link_mode(sbl, port_num, serdes)))
> + continue;
> + use_default_tx_eq = false;
> + use_default_gs = false;
> + err = sbl_get_serdes_config_values(sbl, port_num,
> + serdes,
> + &values);
> + if (err) {
> + sbl_dev_warn(sbl->dev,
> + "p%ds%d: Unable to read config list!",
> + port_num, serdes);
> + use_default_tx_eq = true;
> + use_default_gs = true;
> + }
[ ... ]
> + if (values.num_intr) {
> + sbl_dev_dbg(sbl->dev,
> + "p%ds%d: Applying %d interrupts",
> + port_num, serdes, values.num_intr);
> + }
> + for (j = 0; j < values.num_intr; ++j) {
> + sbl_dev_dbg(sbl->dev,
> + "p%d: Applying interrupt 0x%x with data 0x%x",
> + port_num, values.intr_val[j],
> + values.data_val[j]);
> + err = sbl_serdes_spico_int(sbl, port_num,
> + serdes, values.intr_val[j],
> + values.data_val[j], &result,
> + SPICO_INT_RETURN_RESULT);
> + if (err) {
> + sbl_dev_warn(sbl->dev,
> + "p%ds%d: interrupt 0x%x data 0x%x failed!",
> + port_num, serdes, values.intr_val[j],
> + values.data_val[j]);
> + }
> + if (result != values.intr_val[j]) {
> + sbl_dev_dbg(sbl->dev,
> + "p%ds%d: interrupt:0x%x data:0x%x result:0x%x != code:0x%x. This is okay in some cases.",
> + port_num, serdes, values.intr_val[j], values.data_val[j], result, values.intr_val[j]);
> + }
> + }
> + }
> + }
[Severity: Medium]
Can this loop in sbl_serdes_config() overflow values.intr_val[] and
values.data_val[]?
In struct sbl_sc_values, intr_val and data_val are sized to SBL_SC_MAX_INTR
(16), but neither sbl_serdes_add_config() in sbl_serdes.c nor
sbl_serdes_config() validates that num_intr <= SBL_SC_MAX_INTR.
[Severity: Medium]
Also in sbl_serdes_config(), what happens if sbl_get_serdes_config_values()
succeeds on an earlier lane with values.num_intr > 0 and then fails on a
subsequent lane?
Because struct sbl_sc_values values is declared outside the per-SerDes loop
and the error path only sets use_default_tx_eq = true and
use_default_gs = true without resetting values.num_intr = 0, the previous
lane's SPICO interrupts will be re-applied to the current lane.
In addition, u16 result is uninitialized at declaration; if
sbl_serdes_spico_int() fails and returns an error without writing *result,
the check if (result != values.intr_val[j]) still reads the uninitialized or
stale value of result.
[ ... ]
> +int sbl_serdes_tuning(struct sbl_inst *sbl, int port_num)
> +{
[ ... ]
> + sbl_link_tune_zero_total_timespec(sbl, port_num);
> + link->dfe_tune_count = -1;
> + while (true) {
> + link->dfe_tune_count++;
> + link->serr = sbl_port_dfe_tune(sbl, port_num, is_retune);
> +
> + switch (link->serr) {
> + case 0:
> + sbl_dev_dbg(sbl->dev, "p%d: dfe %stune good\n", port_num,
> + is_retune ? "re" : "");
[ ... ]
> + case -ELNRNG: /* tuning params out of range */
> + if (link->tune_param_oob_count >= SBL_MAX_TUNE_PARAM_OOB_FAILS) {
> + sbl_dev_info(sbl->dev, "%d: too many params oob fails\n", port_num);
> + /* give up and return error
> + The calling framework should reset the spico to delete any
> + applied tuning params so we can do a full tune */
> + goto out;
> + }
> + fallthrough;
> +
> + case -ETIME: /* tuning did not complete */
> + case -ECHRNG: /* eye heights bad */
> +
> + /* try to tune again */
> +
> + break;
> +
> + case -ECANCELED:
> + sbl_dev_dbg(sbl->dev, "p%d: %stune cancelled\n", port_num,
> + is_retune ? "re" : "");
> + goto out;
> +
> + case -ETIMEDOUT:
> + sbl_dev_dbg(sbl->dev, "p%d: %stune timed out\n", port_num,
> + is_retune ? "re" : "");
> + goto out;
> +
> + default:
> + sbl_dev_dbg(sbl->dev, "p%d: %stune failed [%d]\n", port_num,
> + is_retune ? "re" : "", link->serr);
> + goto out;
> + }
> + }
[Severity: High]
Can this while (true) loop in sbl_serdes_tuning() spin indefinitely when
sbl_port_check_eyes() repeatedly returns -ECHRNG?
In sbl_serdes_dfe_tune_wait(), as soon as all active RX lanes complete DFE
calibration, the loop breaks at if (!in_progress_mask) break and immediately
returns 0 at if (serdes_mask == tuned_mask) return 0 before checking
sbl_base_link_start_cancelled() or sbl_start_timeout().
If sbl_port_check_eyes() in sbl_port_dfe_tune_wait() then returns -ECHRNG,
sbl_serdes_tuning() retries in while (true) without ever checking
sbl_start_timeout() or sbl_base_link_start_cancelled().
> +
> +out:
> + sbl_dev_dbg(sbl->dev, "p%d: SerDes tuning complete", port_num);
> + return link->serr;
> +}
> +
> +int sbl_spico_reset(struct sbl_inst *sbl, int port_num)
> +{
> + int serdes;
> + int err = 0;
> + int pass;
> + u32 sbus_addr;
> + u32 sbus_ring;
> + u32 result[SBL_SERDES_LANES_PER_PORT];
> + unsigned long last_jiffy;
> +
> + sbl_dev_dbg(sbl->dev, "p%d: spico reset start", port_num);
> +
> + sbus_ring = sbl->switch_info->ports[port_num].serdes[0].sbus_ring;
> + if (mutex_is_locked(SBUS_RING_MTX(sbl, sbus_ring)))
> + sbl_dev_dbg(sbl->dev, "spico_reset: Sbus contention detected, sbus_ring_mtx[%d] locked", sbus_ring);
> +
> + /* SBUS Critical Section */
> + mutex_lock(SBUS_RING_MTX(sbl, sbus_ring));
> +
> + /* Issue the reset */
> + for (serdes = 0; serdes < sbl->switch_info->num_serdes; ++serdes) {
> + if (!(rx_serdes_required_for_link_mode(sbl, port_num, serdes) ||
> + tx_serdes_required_for_link_mode(sbl, port_num, serdes)))
> + continue;
> + err = sbl_serdes_spico_int(sbl, port_num, serdes,
> + SPICO_INT_CM4_PROC_RESET,
> + SPICO_INT_DATA_PROC_RESET, NULL,
> + SPICO_INT_IGNORE_RESULT);
[Severity: High]
Does sbl_spico_reset() invert the lock ordering between
SBUS_RING_MTX(sbl, sbus_ring) and sbl->link[port_num].serdes_mtx?
In sbl_serdes_soft_reset() and sbl_serdes_firm_upload(),
sbl->link[port_num].serdes_mtx is acquired first, followed by
SBUS_RING_MTX(sbl, sbus_ring).
Here in sbl_spico_reset(), SBUS_RING_MTX(sbl, sbus_ring) is acquired first,
and then sbl_serdes_spico_int() -> sbl_sbm_pml_serdes_op() ->
sbl_pml_serdes_op() acquires sbl->link[port_num].serdes_mtx while holding
SBUS_RING_MTX(sbl, sbus_ring), creating an ABBA deadlock risk between the
two mutexes.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030-cxi-netdev-submission-v1-0-8c2c391cc283@hpe.com?part=4
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 [this message]
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
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-192136@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