From 04a3879bb219b40a8e8da399f2d922c1a67c6f57 Mon Sep 17 00:00:00 2001 From: Paul Tretiakov Date: Wed, 1 Jul 2026 18:36:30 +0100 Subject: [PATCH 1/5] retry when sfp returns invalid caps --- drivers/net/phy/sfp.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 5788df991bedb..d6a8b2ae109bb 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -1946,6 +1946,18 @@ static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45) if (err) { phy_device_remove(phy); phy_device_free(phy); + if (err == -EINVAL) { + /* sfp_add_phy() returned -EINVAL, which typically means + * the PHY was found but its link capabilities are not yet + * available (e.g. firmware still initialising). Return + * -ENODEV so the caller retries the probe rather than + * entering a permanent failure state. + */ + dev_warn(sfp->dev, + "sfp_add_phy failed (%pe), will retry\n", + ERR_PTR(err)); + return -ENODEV; + } dev_err(sfp->dev, "sfp_add_phy failed: %pe\n", ERR_PTR(err)); return err; } From 0e985814247d6961b661e6b329174a257d3576e5 Mon Sep 17 00:00:00 2001 From: Paul Tretiakov Date: Wed, 1 Jul 2026 22:54:50 +0100 Subject: [PATCH 2/5] add more debug --- drivers/net/phy/phylink.c | 15 ++++++++++++--- drivers/net/phy/sfp.c | 9 +++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 9182443082158..62c7d8f9766eb 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2982,11 +2982,15 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, * link can be configured correctly. */ if (pl->sfp_bus) { - if (kset->base.autoneg == AUTONEG_ENABLE) + if (kset->base.autoneg == AUTONEG_ENABLE) { + phylink_info(pl, + "ethtool: selecting SFP interface for advertising %*pb\n", + __ETHTOOL_LINK_MODE_MASK_NBITS, + config.advertising); config.interface = phylink_sfp_select_interface(pl, config.advertising); - else + } else config.interface = phylink_sfp_select_interface_speed(pl, config.speed); @@ -3784,6 +3788,8 @@ static int phylink_sfp_module_start(void *upstream) /* If this SFP module has a PHY, start the PHY now. */ if (pl->phydev) { + phylink_info(pl, "SFP module start: PHY present, starting %s\n", + phydev_name(pl->phydev)); phy_start(pl->phydev); return 0; } @@ -3791,9 +3797,12 @@ static int phylink_sfp_module_start(void *upstream) /* If the module may have a PHY but we didn't detect one we * need to configure the MAC here. */ - if (!pl->sfp_may_have_phy) + if (!pl->sfp_may_have_phy) { + phylink_info(pl, "SFP module start: no PHY expected, optical already configured\n"); return 0; + } + phylink_info(pl, "SFP module start: no PHY found, falling back to optical config\n"); return phylink_sfp_config_optical(pl); } diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index d6a8b2ae109bb..9cdfa28c68df7 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -1994,10 +1994,15 @@ static void sfp_sm_link_check_los(struct sfp *sfp) else if (los_options == los_normal) los = !!(sfp->state & SFP_F_LOS); - if (los) + if (los) { + dev_info(sfp->dev, "LOS active (state=0x%x options=0x%04x), waiting for signal\n", + sfp->state & SFP_F_LOS, + be16_to_cpu(los_options)); sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); - else + } else { + dev_info(sfp->dev, "LOS clear, bringing link up\n"); sfp_sm_link_up(sfp); + } } static bool sfp_los_event_active(struct sfp *sfp, unsigned int event) From 777be6230cf2676ba20b8a0c2259a08d5e5a6fba Mon Sep 17 00:00:00 2001 From: Paul Tretiakov Date: Thu, 2 Jul 2026 13:01:13 +0100 Subject: [PATCH 3/5] fix return to userspace --- drivers/net/phy/phylink.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 62c7d8f9766eb..21fb33f933bfa 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2853,6 +2853,23 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, */ phylink_get_ksettings(&link_state, kset); break; + + default: + /* MLO_AN_PHY without a PHY device: the interface is not yet + * fully configured (e.g. waiting for an SFP module to be + * detected and its state machine to run). Return the current + * link_config state so that userspace sees a consistent + * picture and, crucially, does not observe autoneg=false and + * perform a read-modify-write that forces autoneg off before + * the SFP initialisation completes. + */ + if (!pl->phydev) { + phylink_dbg(pl, + "ksettings_get: pre-init state, returning link_config (interface=%s)\n", + phy_modes(pl->link_config.interface)); + phylink_get_ksettings(&pl->link_config, kset); + } + break; } return 0; From 64a78283a98d2cb5f44b3e64995cd711bb0a82d3 Mon Sep 17 00:00:00 2001 From: Paul Tretiakov Date: Wed, 8 Jul 2026 11:03:42 +0100 Subject: [PATCH 4/5] add more specific debug --- drivers/net/phy/phylink.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 21fb33f933bfa..7de0f40880605 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2864,7 +2864,7 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, * the SFP initialisation completes. */ if (!pl->phydev) { - phylink_dbg(pl, + phylink_info(pl, "ksettings_get: pre-init state, returning link_config (interface=%s)\n", phy_modes(pl->link_config.interface)); phylink_get_ksettings(&pl->link_config, kset); @@ -3049,6 +3049,11 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, if (pl->link_config.interface != config.interface) { /* The interface changed, e.g. 1000base-X <-> 2500base-X */ + phylink_info(pl, + "ethtool: interface change %s -> %s (link was %s)\n", + phy_modes(pl->link_config.interface), + phy_modes(config.interface), + pl->old_link_state ? "up" : "down"); /* We need to force the link down, then change the interface */ if (pl->old_link_state) { phylink_link_down(pl); From a949d1bcbfd747b9c7923ad9b917745d15ddac0d Mon Sep 17 00:00:00 2001 From: Paul Tretiakov Date: Wed, 8 Jul 2026 11:31:27 +0100 Subject: [PATCH 5/5] Remove excess logging --- drivers/net/phy/phylink.c | 22 ++++------------------ drivers/net/phy/sfp.c | 21 ++------------------- 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 7de0f40880605..b6775f96eec48 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2864,7 +2864,7 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, * the SFP initialisation completes. */ if (!pl->phydev) { - phylink_info(pl, + phylink_dbg(pl, "ksettings_get: pre-init state, returning link_config (interface=%s)\n", phy_modes(pl->link_config.interface)); phylink_get_ksettings(&pl->link_config, kset); @@ -2999,15 +2999,11 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, * link can be configured correctly. */ if (pl->sfp_bus) { - if (kset->base.autoneg == AUTONEG_ENABLE) { - phylink_info(pl, - "ethtool: selecting SFP interface for advertising %*pb\n", - __ETHTOOL_LINK_MODE_MASK_NBITS, - config.advertising); + if (kset->base.autoneg == AUTONEG_ENABLE) config.interface = phylink_sfp_select_interface(pl, config.advertising); - } else + else config.interface = phylink_sfp_select_interface_speed(pl, config.speed); @@ -3049,11 +3045,6 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, if (pl->link_config.interface != config.interface) { /* The interface changed, e.g. 1000base-X <-> 2500base-X */ - phylink_info(pl, - "ethtool: interface change %s -> %s (link was %s)\n", - phy_modes(pl->link_config.interface), - phy_modes(config.interface), - pl->old_link_state ? "up" : "down"); /* We need to force the link down, then change the interface */ if (pl->old_link_state) { phylink_link_down(pl); @@ -3810,8 +3801,6 @@ static int phylink_sfp_module_start(void *upstream) /* If this SFP module has a PHY, start the PHY now. */ if (pl->phydev) { - phylink_info(pl, "SFP module start: PHY present, starting %s\n", - phydev_name(pl->phydev)); phy_start(pl->phydev); return 0; } @@ -3819,12 +3808,9 @@ static int phylink_sfp_module_start(void *upstream) /* If the module may have a PHY but we didn't detect one we * need to configure the MAC here. */ - if (!pl->sfp_may_have_phy) { - phylink_info(pl, "SFP module start: no PHY expected, optical already configured\n"); + if (!pl->sfp_may_have_phy) return 0; - } - phylink_info(pl, "SFP module start: no PHY found, falling back to optical config\n"); return phylink_sfp_config_optical(pl); } diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 9cdfa28c68df7..5788df991bedb 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -1946,18 +1946,6 @@ static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45) if (err) { phy_device_remove(phy); phy_device_free(phy); - if (err == -EINVAL) { - /* sfp_add_phy() returned -EINVAL, which typically means - * the PHY was found but its link capabilities are not yet - * available (e.g. firmware still initialising). Return - * -ENODEV so the caller retries the probe rather than - * entering a permanent failure state. - */ - dev_warn(sfp->dev, - "sfp_add_phy failed (%pe), will retry\n", - ERR_PTR(err)); - return -ENODEV; - } dev_err(sfp->dev, "sfp_add_phy failed: %pe\n", ERR_PTR(err)); return err; } @@ -1994,15 +1982,10 @@ static void sfp_sm_link_check_los(struct sfp *sfp) else if (los_options == los_normal) los = !!(sfp->state & SFP_F_LOS); - if (los) { - dev_info(sfp->dev, "LOS active (state=0x%x options=0x%04x), waiting for signal\n", - sfp->state & SFP_F_LOS, - be16_to_cpu(los_options)); + if (los) sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); - } else { - dev_info(sfp->dev, "LOS clear, bringing link up\n"); + else sfp_sm_link_up(sfp); - } } static bool sfp_los_event_active(struct sfp *sfp, unsigned int event)