From 03c10884c0e3aaaf2dbf6586df47f681b2a49b3f Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 1/8] net: phy: sfp: retry PHY probe when c45 identifiers are implausible RollBall SFP modules gate PHY register access for up to 25 seconds after insertion, returning 0xffff for all reads. get_phy_c45_ids() detects this (devices-in-package reads as all-ones) and returns -ENODEV, which the SFP state machine handles with its 25 x 1s retry loop (R_PHY_RETRY / phy_t_retry set by sfp_fixup_rollball()). Some of these modules (observed with an "OEM SFP-10G-T" containing a Broadcom BCM84891) leave that window before their identifier registers are stable, in two forms. Devices-in-package reads a plausible value while MII_PHYSID1/2 in all MMDs still read zero; or a RollBall command that times out mid-probe returns 0xffff for one 16-bit half of an identifier while the other half reads real data, giving a PMA ID of 0xffff5081 where the true value is 0x35905081. get_phy_c45_ids() has no validity check for either case, so a phydev is created with identifiers that no driver can match - phy_bus_match() only skips 0xffffffff. phy->drv is NULL, phylink_sfp_connect_phy() rejects the PHY, and sfp_add_phy() fails with -EINVAL: mtk_soc_eth 15100000.ethernet lsfp: PHY i2c:sfp2:11 (id 0x00000000) has no driver loaded sfp sfp2: sfp_add_phy failed: -EINVAL Unlike -ENODEV, this error path sends the state machine to SFP_S_FAIL, which is terminal until the module is physically re-seated. Validate the identifiers in sfp_sm_probe_phy(): an identifier is only plausible if it is non-zero and neither 16-bit half reads 0xffff. If no MMD carries a plausible identifier, treat the PHY as not ready and return -ENODEV so the existing retry logic applies. Also log the c45 identifiers when sfp_add_phy() fails, so a genuinely unsupported PHY model is diagnosable from dmesg. A legitimate PHY ID with a 16-bit half of exactly 0xffff is not known to exist; if one ever appears, it would only cause probe retries for that module, diagnosable via the retry log message. Signed-off-by: Mihai Ordean --- drivers/net/phy/sfp.c | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index e1358a015442..defa07b21da1 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -1938,6 +1938,44 @@ static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45) return PTR_ERR(phy); } + /* Some RollBall modules stop gating PHY register access (reads + * returning 0xffff) slightly before the PHY's identifier registers + * are stable. A probe landing in this window reads a plausible + * devices-in-package value but all-zero device identifiers, which + * no PHY driver can match. sfp_add_phy() then fails hard with + * -EINVAL and the state machine enters SFP_S_FAIL, requiring a + * physical re-plug. Treat all-zero identifiers as "PHY not ready" + * instead, so the existing R_PHY_RETRY/phy_t_retry logic applies. + */ + if (is_c45) { + bool ids_valid = false; + int i; + + /* Absent MMDs leave their identifier zero; a gated or + * timed-out RollBall access reads 0xffff, so an identifier + * with either 16-bit half at 0xffff is corrupt. Require at + * least one fully plausible identifier before accepting + * the PHY. + */ + for (i = 1; i < ARRAY_SIZE(phy->c45_ids.device_ids); i++) { + u32 id = phy->c45_ids.device_ids[i]; + + if (id && (id & 0xffff) != 0xffff && + (id >> 16) != 0xffff) { + ids_valid = true; + break; + } + } + + if (!ids_valid) { + dev_info(sfp->dev, + "PHY identifiers unstable (devs_in_pkg 0x%08x), retrying\n", + phy->c45_ids.devices_in_package); + phy_device_free(phy); + return -ENODEV; + } + } + /* Mark this PHY as being on a SFP module */ phy->is_on_sfp_module = true; @@ -1951,6 +1989,13 @@ static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45) err = sfp_add_phy(sfp->sfp_bus, phy); if (err) { + if (is_c45) + dev_err(sfp->dev, + "PHY c45 ids: PMA 0x%08x PCS 0x%08x AN 0x%08x devs_in_pkg 0x%08x\n", + phy->c45_ids.device_ids[MDIO_MMD_PMAPMD], + phy->c45_ids.device_ids[MDIO_MMD_PCS], + phy->c45_ids.device_ids[MDIO_MMD_AN], + phy->c45_ids.devices_in_package); phy_device_remove(phy); phy_device_free(phy); dev_err(sfp->dev, "sfp_add_phy failed: %pe\n", ERR_PTR(err)); From cd0b12e5dce0a7583931572ab20cd703a5ba2acf Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 2/8] net: mdio: mdio-i2c: rollball: retry command on completion timeout The RollBall MDIO-over-I2C protocol issues a command and polls the command register for ROLLBALL_CMD_DONE, with a budget of 10 x 20 ms. When the poll times out, i2c_mii_read_rollball() returns 0xffff -- indistinguishable from register data. Some modules (observed: "OEM SFP-10G-T" with a Broadcom BCM84891) intermittently take longer than the ~200 ms budget to complete a command, well past the initial 25 s access-denial window. Each timed-out read then injects a spurious 0xffff word into whatever the caller is reading. During PHY probe this corrupts the C45 identifiers (e.g. devs_in_pkg 0xc000ffff, PMA ID 0xffff5081 where the true values are 0xc000009b and 0x35905081), so no PHY driver matches and sfp_add_phy() fails with -EINVAL, leaving the SFP state machine in SFP_S_FAIL until the module is re-seated. The same mechanism can corrupt reads and silently drop writes at runtime after a successful probe. Re-issue the command up to 3 times when the completion poll times out, in both the read and write paths. Re-issuing a read of the same devad/reg is idempotent, so a late completion of a previous attempt still yields data for the correct register. The 0xffff-on-timeout semantic is preserved once retries are exhausted, keeping the existing behaviour for the initial access-denial window (where get_phy_c45_ids() maps all-ones to -ENODEV and the SFP state machine retries the whole probe). Signed-off-by: Mihai Ordean --- drivers/net/mdio/mdio-i2c.c | 45 +++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/drivers/net/mdio/mdio-i2c.c b/drivers/net/mdio/mdio-i2c.c index ed20352a589a..3ea150b9a814 100644 --- a/drivers/net/mdio/mdio-i2c.c +++ b/drivers/net/mdio/mdio-i2c.c @@ -195,6 +195,11 @@ static int smbus_byte_mii_write_default_c22(struct mii_bus *bus, int phy_id, #define ROLLBALL_CMD_READ 0x02 #define ROLLBALL_CMD_DONE 0x04 +/* Number of times a command is re-issued if the module does not + * signal ROLLBALL_CMD_DONE within the polling budget. + */ +#define ROLLBALL_CMD_RETRIES 3 + #define SFP_PAGE_ROLLBALL_MDIO 3 static int __i2c_transfer_err(struct i2c_adapter *i2c, struct i2c_msg *msgs, @@ -362,7 +367,7 @@ static int i2c_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, int reg) { u8 buf[4], res[6]; - int bus_addr, ret; + int bus_addr, ret, i; u16 val; bus_addr = i2c_mii_phy_addr(phy_id); @@ -374,12 +379,23 @@ static int i2c_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, buf[2] = (reg >> 8) & 0xff; buf[3] = reg & 0xff; - ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_READ, buf, - sizeof(buf)); - if (ret < 0) - return ret; + /* Some modules (e.g. OEM SFP-10G-T with a BCM84891) occasionally + * take longer than the polling budget to execute a command. Since + * returning 0xffff for a timed-out read is indistinguishable from + * register data, retry the command a few times first; re-issuing + * the same read is idempotent. + */ + for (i = 0; i < ROLLBALL_CMD_RETRIES; i++) { + ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_READ, + buf, sizeof(buf)); + if (ret < 0) + return ret; + + ret = i2c_rollball_mii_poll(bus, bus_addr, res, sizeof(res)); + if (ret != -ETIMEDOUT) + break; + } - ret = i2c_rollball_mii_poll(bus, bus_addr, res, sizeof(res)); if (ret == -ETIMEDOUT) return 0xffff; else if (ret < 0) @@ -393,7 +409,7 @@ static int i2c_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, static int i2c_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad, int reg, u16 val) { - int bus_addr, ret; + int bus_addr, ret, i; u8 buf[6]; bus_addr = i2c_mii_phy_addr(phy_id); @@ -407,12 +423,17 @@ static int i2c_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad, buf[4] = val >> 8; buf[5] = val & 0xff; - ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_WRITE, buf, - sizeof(buf)); - if (ret < 0) - return ret; + for (i = 0; i < ROLLBALL_CMD_RETRIES; i++) { + ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_WRITE, + buf, sizeof(buf)); + if (ret < 0) + return ret; + + ret = i2c_rollball_mii_poll(bus, bus_addr, NULL, 0); + if (ret != -ETIMEDOUT) + break; + } - ret = i2c_rollball_mii_poll(bus, bus_addr, NULL, 0); if (ret < 0) return ret; From dc99f244606dfff25ca0d8c7361864d02c056f28 Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 3/8] net: mdio-i2c: rollball: idle-sleep backoff poll within a wall-clock budget i2c_rollball_mii_poll() slept msleep(20) before every read and capped the wait with an iteration count. Two problems were tangled in that one loop: - Load: the sleeps are TASK_UNINTERRUPTIBLE, so the entire time spent waiting on the module's MCU counts toward the load average with the CPU idle. On an OEM SFP-10G-T (BCM84891) the MCU was measured taking ~377 ms per register read at steady state (kprobe delta histogram, n=130, tight cluster 371-384 ms), so one read_status (8 MMD reads) occupies ~3 s of every ~4 s phylib poll cycle: 0.75 load per module, 1.5 with two modules, on an otherwise idle box. - Wake window: right after insertion the module's mailbox genuinely stalls for hundreds of ms, so the poll needs a generous budget there (this is what the previous iteration-count bump was really buying). Poll on usleep_range_idle() sleeps with exponential backoff (2-4 ms first, doubling to a 32-64 ms cap) bounded by a ~1 s wall-clock deadline. Fast completions return in a couple of ms instead of 20. Slow commands complete in a bounded number of iterations: a ~380 ms command costs ~15 status polls (each four I2C transfers for the page save/set/read/restore dance, plus an uninterruptible wait on the I2C controller) versus ~100+ with a flat 2-4 ms interval, keeping the residual D-state in the I2C core small. The cap is only reached ~30 ms into a command, so fast modules never pay it. Wake-window reads still get the same ~1 s budget as the previous iteration-count bump (combined with the command-retry logic still ~3 s worst case). usleep_range_idle() sleeps as TASK_IDLE, so the poll no longer inflates the load average while it waits on hardware with the CPU free. Supersedes the fixed iteration-count budget bump and the earlier flat-interval idle-poll rework. Signed-off-by: Mihai Ordean --- drivers/net/mdio/mdio-i2c.c | 43 +++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/drivers/net/mdio/mdio-i2c.c b/drivers/net/mdio/mdio-i2c.c index 3ea150b9a814..3afb6da3ca97 100644 --- a/drivers/net/mdio/mdio-i2c.c +++ b/drivers/net/mdio/mdio-i2c.c @@ -11,6 +11,7 @@ * of their settings. */ #include +#include #include #include #include @@ -195,6 +196,21 @@ static int smbus_byte_mii_write_default_c22(struct mii_bus *bus, int phy_id, #define ROLLBALL_CMD_READ 0x02 #define ROLLBALL_CMD_DONE 0x04 +/* Wall-clock budget for i2c_rollball_mii_poll(): generous enough to + * ride out the module's post-insertion wake window, during which the + * mailbox can stall for hundreds of ms. + */ +#define ROLLBALL_POLL_TIMEOUT_MS 1000 + +/* Completion-poll backoff: first sleep is short so modules that answer + * in a couple of ms return promptly; the interval then doubles up to + * the cap so modules that take tens of ms per command are not hammered + * with status reads (each poll iteration costs four I2C transactions + * for the page save/set/read/restore dance). + */ +#define ROLLBALL_POLL_SLEEP_MIN_US 2000 +#define ROLLBALL_POLL_SLEEP_MAX_US 32000 + /* Number of times a command is re-issued if the module does not * signal ROLLBALL_CMD_DONE within the polling budget. */ @@ -303,7 +319,9 @@ static int i2c_rollball_mii_poll(struct mii_bus *bus, int bus_addr, u8 *buf, struct i2c_adapter *i2c = bus->priv; struct i2c_msg msgs[2]; u8 cmd_addr, tmp, *res; - int i, ret; + unsigned long deadline; + unsigned long sleep_us; + int ret; cmd_addr = ROLLBALL_CMD_ADDR; @@ -320,12 +338,22 @@ static int i2c_rollball_mii_poll(struct mii_bus *bus, int bus_addr, u8 *buf, msgs[1].len = len; msgs[1].buf = res; - /* By experiment it takes up to 70 ms to access a register for these - * SFPs. Sleep 20ms between iterations and try 10 times. + /* Some of these SFPs answer within a couple of ms once running, + * others take tens of ms per command, and any of them can stall + * for hundreds of ms while the module is still waking after + * insertion. Poll with exponential backoff: the first iteration + * sleeps only a couple of ms so a fast module returns promptly + * instead of paying a fixed 20 ms per register access, while the + * backoff keeps the number of status-poll I2C transactions for a + * slow module close to the fixed-interval scheme's. The whole + * loop is bounded by a generous wall-clock budget for the wake + * window. Sleep as TASK_IDLE (usleep_range_idle): the CPU is free + * while we wait, so the poll must not inflate the load average. */ - i = 10; + deadline = jiffies + msecs_to_jiffies(ROLLBALL_POLL_TIMEOUT_MS); + sleep_us = ROLLBALL_POLL_SLEEP_MIN_US; do { - msleep(20); + usleep_range_idle(sleep_us, sleep_us * 2); ret = i2c_transfer_rollball(i2c, msgs, ARRAY_SIZE(msgs)); if (ret) @@ -333,7 +361,10 @@ static int i2c_rollball_mii_poll(struct mii_bus *bus, int bus_addr, u8 *buf, if (*res == ROLLBALL_CMD_DONE) return 0; - } while (i-- > 0); + + sleep_us = min_t(unsigned long, sleep_us * 2, + ROLLBALL_POLL_SLEEP_MAX_US); + } while (time_before(jiffies, deadline)); dev_dbg(&bus->dev, "poll timed out\n"); From a9c27139ea717b945e4a118d0e8f813ec16d989a Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 4/8] net: phy: bcm84881: accept BCM84891 attached over 10GBASE-R/SGMII/2500BASE-X bcm8489x_config_init() rejects any attachment other than USXGMII with -ENODEV, reflecting the board-soldered configuration it was written for. The BCM84891 is however also the PHY inside various RollBall "OEM SFP-10G-T" copper SFP+ modules, where phylink selects 10GBASE-R as the host interface (with SGMII/2500BASE-X rate switching), exactly as for the BCM84881. In that configuration a successful PHY probe still fails at attach time: sfp sfp2: PHY c45 ids: PMA 0x35905081 PCS ... devs_in_pkg ... sfp sfp2: sfp_add_phy failed: -ENODEV with the -ENODEV originating from phy_init_hw() -> bcm8489x_config_init(). Accept the same interface trio as the BCM84881 in addition to USXGMII, and populate possible_interfaces with all four so phylink can rate-switch on copper linkdowns. The LPOWER clearing is kept unconditional; it is harmless on module-hosted PHYs. Signed-off-by: Mihai Ordean --- drivers/net/phy/bcm84881.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c index 2ae70dcf82ec..6c62c5dc01f7 100644 --- a/drivers/net/phy/bcm84881.c +++ b/drivers/net/phy/bcm84881.c @@ -83,10 +83,24 @@ static int bcm84881_config_init(struct phy_device *phydev) static int bcm8489x_config_init(struct phy_device *phydev) { + bcm84881_fill_possible_interfaces(phydev); __set_bit(PHY_INTERFACE_MODE_USXGMII, phydev->possible_interfaces); - if (phydev->interface != PHY_INTERFACE_MODE_USXGMII) + /* The BCM84891 is found both soldered down and attached over + * USXGMII, and inside SFP+ copper modules (e.g. various + * "OEM SFP-10G-T" RollBall modules), where the host-side + * interface is 10GBASE-R with SGMII/2500BASE-X rate switching, + * as with the BCM84881. Accept both attachments. + */ + switch (phydev->interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_2500BASEX: + case PHY_INTERFACE_MODE_10GBASER: + case PHY_INTERFACE_MODE_USXGMII: + break; + default: return -ENODEV; + } /* MDIO_CTRL1_LPOWER is set at boot on the tested platform. Does not * recur on ifdown/ifup, cable events, or link-partner advertisement From 406a5394bbd536af4dd200a271c3f3f20681cda4 Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 5/8] net: phy: bcm84881: implement config_inband The bcm84881 driver declares .inband_caps, unconditionally returning LINK_INBAND_DISABLE, but does not implement .config_inband. When a BCM84881-family PHY is used with a phylink MAC in inband mode (e.g. any SFP module PHY, or managed = "in-band-status" in DT), phylink's negotiation resolves to outband operation and sets phy_ib_mode = LINK_INBAND_DISABLE, then calls phy_config_inband() to apply it. With no .config_inband method this returns -EOPNOTSUPP, which phylink_major_config() treats as a fatal configuration error: mtk_soc_eth 15100000.ethernet lsfp: phy_config_inband: -EOPNOTSUPP major_config_failed then forces the link down in phylink_resolve() and holds it down across reconfiguration attempts. The result is a permanently dead link with a perfectly negotiated copper side (observed with a BCM84891-based "OEM SFP-10G-T" module on mt7988: copper AN completes at 10G, carrier never comes up). Implement config_inband: the PHY does not generate inband signalling in any mode, so it is permanently in the "disabled" state and a request to disable inband succeeds as a no-op. Any other request cannot be satisfied and returns -EINVAL (phylink will not request one, given the capabilities this driver reports). Signed-off-by: Mihai Ordean --- drivers/net/phy/bcm84881.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c index 6c62c5dc01f7..391f32d25a0b 100644 --- a/drivers/net/phy/bcm84881.c +++ b/drivers/net/phy/bcm84881.c @@ -430,12 +430,30 @@ static unsigned int bcm84881_inband_caps(struct phy_device *phydev, return LINK_INBAND_DISABLE; } +static int bcm84881_config_inband(struct phy_device *phydev, + unsigned int modes) +{ + /* This PHY does not generate inband signalling in any mode (see + * bcm84881_inband_caps()); inband is permanently disabled in + * hardware. A request to disable inband therefore requires no + * action, but must succeed: phylink treats any error from + * phy_config_inband() (including -EOPNOTSUPP from a missing + * config_inband method) as a major configuration failure which + * forces and holds the link down. + */ + if (modes == LINK_INBAND_DISABLE) + return 0; + + return -EINVAL; +} + static struct phy_driver bcm84881_drivers[] = { { .phy_id = 0xae025150, .phy_id_mask = 0xfffffff0, .name = "Broadcom BCM84881", .inband_caps = bcm84881_inband_caps, + .config_inband = bcm84881_config_inband, .config_init = bcm84881_config_init, .probe = bcm84881_probe, .get_features = bcm84881_get_features, @@ -446,6 +464,7 @@ static struct phy_driver bcm84881_drivers[] = { PHY_ID_MATCH_MODEL(0x35905080), .name = "Broadcom BCM84891", .inband_caps = bcm84881_inband_caps, + .config_inband = bcm84881_config_inband, .config_init = bcm8489x_config_init, .probe = bcm84881_probe, .get_features = bcm84881_get_features, @@ -460,6 +479,7 @@ static struct phy_driver bcm84881_drivers[] = { PHY_ID_MATCH_MODEL(0x359050a0), .name = "Broadcom BCM84892", .inband_caps = bcm84881_inband_caps, + .config_inband = bcm84881_config_inband, .config_init = bcm8489x_config_init, .probe = bcm84881_probe, .get_features = bcm84881_get_features, From 469eabbf7fcaaf739bace3c52ed9e5c026f38667 Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 6/8] net: phy: bcm84881: skip re-reading negotiated parameters while link is up bcm84881_read_status() reads 8 MMD registers on every poll: three to determine link/autoneg state, and five (LPA base page, 10GBT status, C22 STAT1000, mdix, host interface mode) to resolve the negotiated parameters. The latter five cannot change while the link is up: any change requires a renegotiation, which is observable in the first three reads as a link drop, an autoneg restart or autoneg-complete deasserting. On modules where every MDIO access goes through a RollBall MDIO-over-I2C mailbox this matters a lot: an OEM SFP-10G-T (BCM84891) was measured taking ~377 ms per register read at steady state, so one read_status occupies ~3 s -- all of it holding phydev->lock. With the MAC PCS in polled mode (pcs->poll, e.g. mtk_eth USXGMII), phylink's 1 Hz PCS poll then blocks in phylink_resolve() on phy->lock for up to 3 s in TASK_UNINTERRUPTIBLE on every tick, inflating the load average by ~0.6 per port and delaying ethtool/link operations, while the effective link poll rate drops to 0.25 Hz. Cache the resolved state: when the previous poll left the link up and autoneg complete, and the three state reads show it still is, return with the previously resolved parameters intact. Steady state drops from 8 reads to 3 (~1.1 s on the module above), and any renegotiation still invalidates the cache on the very next poll. Signed-off-by: Mihai Ordean --- drivers/net/phy/bcm84881.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c index 391f32d25a0b..a8caf425275d 100644 --- a/drivers/net/phy/bcm84881.c +++ b/drivers/net/phy/bcm84881.c @@ -317,9 +317,19 @@ static int bcm84881_aneg_done(struct phy_device *phydev) static int bcm84881_read_status(struct phy_device *phydev) { + bool was_resolved; unsigned int mode; int bmsr, val; + /* Whether the previous poll left a fully resolved link whose + * negotiated parameters (lp_advertising, speed, duplex, pause, + * mdix, interface) are still valid. They can only change through + * a renegotiation, which is observable below as a link drop, an + * autoneg restart or autoneg-complete deasserting. + */ + was_resolved = phydev->link && phydev->autoneg_complete && + phydev->speed != SPEED_UNKNOWN; + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); if (val < 0) return val; @@ -344,6 +354,15 @@ static int bcm84881_read_status(struct phy_device *phydev) if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) phydev->link = false; + /* On some modules every MDIO access is expensive (RollBall + * MDIO-over-I2C mailbox, tens to hundreds of ms per register). + * If the link was already up and resolved on the previous poll + * and still is, the negotiated parameters cannot have changed: + * skip re-reading them and keep the cached values. + */ + if (was_resolved && phydev->link && phydev->autoneg_complete) + return 0; + linkmode_zero(phydev->lp_advertising); phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; From 9ba63e8a0515fa646fecfc63f244a4cd09de84d0 Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 7/8] net: phy: bcm84881: single latched-status read per poll while link is up With the negotiated-parameter cache in place, bcm84881_read_status() still performs three MMD reads per poll (AN CTRL1, AN STAT1, C22 shadow BMSR) just to confirm an unchanged link. Apply the same polling-mode logic genphy_c45_read_link() uses: the AN link status bit is latched low, so in polling mode a single read is authoritative -- reading 1 means the link has been continuously up since the last poll, and autoneg-complete deasserts across any renegotiation. When the previous poll left the link up and resolved, read AN STAT1 alone and return if it still shows link up and autoneg complete. If the fast read shows anything else, fall through to the full evaluation, reusing the already-read value: the latched status has been consumed, and re-reading the register would miss a momentary link drop that the pre-existing code reports. An autoneg restart issued between polls drops the latched link status, so the CTRL1 restart check in the full path is still reached in that case. On an OEM SFP-10G-T (BCM84891, RollBall MDIO-over-I2C, ~377 ms per register read) this takes the steady-state poll from three reads (~1.1 s) to one (~0.38 s), cutting the time phydev->lock is held per phylib poll cycle from ~53% to ~27% and correspondingly the time phylink_resolve() blocks on phy->lock under a polled PCS (pcs->poll). Signed-off-by: Mihai Ordean --- drivers/net/phy/bcm84881.c | 41 ++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c index a8caf425275d..a1f01f29a384 100644 --- a/drivers/net/phy/bcm84881.c +++ b/drivers/net/phy/bcm84881.c @@ -319,7 +319,7 @@ static int bcm84881_read_status(struct phy_device *phydev) { bool was_resolved; unsigned int mode; - int bmsr, val; + int bmsr, val, stat1; /* Whether the previous poll left a fully resolved link whose * negotiated parameters (lp_advertising, speed, duplex, pause, @@ -330,6 +330,33 @@ static int bcm84881_read_status(struct phy_device *phydev) was_resolved = phydev->link && phydev->autoneg_complete && phydev->speed != SPEED_UNKNOWN; + stat1 = -1; + + /* In polling mode with the link previously up and resolved, a + * single read of the AN status register is normally sufficient + * to confirm nothing changed: the link status bit is latched + * low, so a momentary drop or a renegotiation since the last + * poll reads as 0 even if the link has already come back, and + * autoneg-complete deasserts across a renegotiation. This + * mirrors the polling-mode single-read logic in + * genphy_c45_read_link(). + */ + if (phy_polling_mode(phydev) && was_resolved) { + stat1 = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); + if (stat1 < 0) + return stat1; + + if ((stat1 & MDIO_STAT1_LSTATUS) && + (stat1 & MDIO_AN_STAT1_COMPLETE)) + return 0; + + /* Something changed. The latched status has now been + * consumed, so the full evaluation below must reuse + * this value rather than re-read the register, which + * would miss a momentary link drop. + */ + } + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); if (val < 0) return val; @@ -339,17 +366,19 @@ static int bcm84881_read_status(struct phy_device *phydev) return 0; } - val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); - if (val < 0) - return val; + if (stat1 < 0) { + stat1 = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); + if (stat1 < 0) + return stat1; + } bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR); if (bmsr < 0) return bmsr; - phydev->autoneg_complete = !!(val & MDIO_AN_STAT1_COMPLETE) && + phydev->autoneg_complete = !!(stat1 & MDIO_AN_STAT1_COMPLETE) && !!(bmsr & BMSR_ANEGCOMPLETE); - phydev->link = !!(val & MDIO_STAT1_LSTATUS) && + phydev->link = !!(stat1 & MDIO_STAT1_LSTATUS) && !!(bmsr & BMSR_LSTATUS); if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) phydev->link = false; From 63f39558c046416349f612c764bc80db08aebe55 Mon Sep 17 00:00:00 2001 From: Mihai Ordean Date: Thu, 30 Jul 2026 13:36:34 +0000 Subject: [PATCH 8/8] net: phy: bcm84881: use static feature set for BCM8489x bcm84881_get_features() calls genphy_c45_pma_read_abilities(), which walks PMA STAT2/EXTABLE/10GBT ability registers to discover link mode support. For the BCM8489x family these are constants, but on SFP modules using the RollBall MDIO-over-I2C mailbox each register access was measured at ~377 ms (OEM SFP-10G-T, BCM84891), so ability discovery alone adds ~2 s to every PHY attach -- paid on every module insertion and every interface up on top of an already slow probe. Set the known feature set (100BASE-T half/full, 1000/2500/5000/ 10000BASE-T full, autoneg -- matching what ability discovery reports on this hardware) directly for the BCM84891/BCM84892 entries. EEE abilities may vary with firmware, so those are still read from the device. BCM84881 keeps the discovery path unchanged. Signed-off-by: Mihai Ordean --- drivers/net/phy/bcm84881.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c index a1f01f29a384..73ddfbb215c5 100644 --- a/drivers/net/phy/bcm84881.c +++ b/drivers/net/phy/bcm84881.c @@ -260,6 +260,32 @@ static int bcm84881_get_features(struct phy_device *phydev) return 0; } +static const int bcm8489x_features[] = { + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_100baseT_Half_BIT, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + ETHTOOL_LINK_MODE_10000baseT_Full_BIT, +}; + +static int bcm8489x_get_features(struct phy_device *phydev) +{ + /* The PMA/PMD abilities of this family are fixed and known, and + * on some modules every MDIO access is expensive (RollBall + * MDIO-over-I2C mailbox, tens to hundreds of ms per register), + * so do not spend half a dozen reads at attach time discovering + * constants. EEE abilities may vary with firmware; keep reading + * those. + */ + linkmode_set_bit_array(bcm8489x_features, + ARRAY_SIZE(bcm8489x_features), + phydev->supported); + + return genphy_c45_read_eee_abilities(phydev); +} + static int bcm84881_config_aneg(struct phy_device *phydev) { bool changed = false; @@ -515,7 +541,7 @@ static struct phy_driver bcm84881_drivers[] = { .config_inband = bcm84881_config_inband, .config_init = bcm8489x_config_init, .probe = bcm84881_probe, - .get_features = bcm84881_get_features, + .get_features = bcm8489x_get_features, .config_aneg = bcm84881_config_aneg, .aneg_done = bcm84881_aneg_done, .read_status = bcm84881_read_status, @@ -530,7 +556,7 @@ static struct phy_driver bcm84881_drivers[] = { .config_inband = bcm84881_config_inband, .config_init = bcm8489x_config_init, .probe = bcm84881_probe, - .get_features = bcm84881_get_features, + .get_features = bcm8489x_get_features, .config_aneg = bcm84881_config_aneg, .aneg_done = bcm84881_aneg_done, .read_status = bcm84881_read_status,