* [PATCH net-next v2 0/2] net: fix a stale phylink PHY pointer and a lost PHY interrupt @ 2026-08-24 2:40 Aleksei Sviridkin 2026-08-24 2:40 ` [PATCH net-next v2 1/2] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin 2026-08-24 2:40 ` [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin 0 siblings, 2 replies; 6+ messages in thread From: Aleksei Sviridkin @ 2026-08-24 2:40 UTC (permalink / raw) To: Andrew Lunn, Heiner Kallweit, Russell King Cc: Aleksei Sviridkin, Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel Two independent fixes, both found while chasing a PHY whose driver is a module on a rootfs that is not mounted yet when a DSA switch probes. Neither one depends on that setup, and neither depends on the other. Patch 1: phylink_bringup_phy() records the PHY in pl->phydev before its last fallible step, so a failure there leaves a pointer to a PHY the caller has already detached. A later phylink_disconnect_phy() detaches it a second time and drops references the first detach already released. Patch 2: a PHY that goes through a generic-driver bind cycle comes out of it in polling mode for good. The specific driver that binds afterwards never sees the interrupt the firmware node declared. Tested on an MT7981B board: an Airoha EN8811H on an MT7531 switch port, its interrupt declared in the device tree, its driver a module on the rootfs. lan4 attaches with irq=15 rather than irq=POLL, the line is claimed as mt-eint 0 in /proc/interrupts, and its counter goes 1 -> 3 -> 5 across two forced aneg restarts, matching the link dropping and coming back each time, and holding steady in between. wan, whose internal PHY has no interrupt in the device tree, still attaches with irq=POLL: that is the observation which says the bus table cannot hand back an interrupt the device never had. Patch 2's other exit, the one in phy_attach_direct(), needs a generic probe to fail and is compile-tested only. --- Changes in v2: - patch 1: the unwind now shares a helper with phylink_disconnect_phy() instead of repeating its inner block (Andrew Lunn), and the patch gained a Fixes: tag - patch 2: the interrupt comes back from the MDIO bus interrupt table, so the new phy_device member, its kernel-doc and the save site in phy_attach_direct() are gone (Andrew Lunn) - dropped "net: dsa: connect a late-arriving PHY at ifup": binding the MAC to the PHY from open is the wrong layer - v1: https://lore.kernel.org/netdev/20260822155259.87146-1-f@lex.la/ Aleksei Sviridkin (2): net: phylink: unwind the PHY binding when bringup fails late net: phy: restore the interrupt after a generic-driver bind cycle drivers/net/phy/phy_device.c | 14 ++++++++++++++ drivers/net/phy/phylink.c | 29 ++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 9 deletions(-) -- 2.55.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v2 1/2] net: phylink: unwind the PHY binding when bringup fails late 2026-08-24 2:40 [PATCH net-next v2 0/2] net: fix a stale phylink PHY pointer and a lost PHY interrupt Aleksei Sviridkin @ 2026-08-24 2:40 ` Aleksei Sviridkin 2026-08-27 18:04 ` Andrew Lunn 2026-08-24 2:40 ` [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin 1 sibling, 1 reply; 6+ messages in thread From: Aleksei Sviridkin @ 2026-08-24 2:40 UTC (permalink / raw) To: Andrew Lunn, Heiner Kallweit, Russell King Cc: Aleksei Sviridkin, Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel phylink_bringup_phy() records the PHY in pl->phydev before its last fallible step: on a MAC whose phylink ops implement LPI, phy_eee_rx_clock_stop() can fail with a real MDIO error. The callers unwind with phy_detach(), which knows nothing about pl->phydev, so a pointer to a PHY that is no longer attached outlives the failed connect. What that costs depends on how the caller got here. phylink_connect_phy() and the SFP path go through phylink_attach_phy(), which refuses to attach while pl->phydev is set and turns a transient MDIO error into a permanent -EBUSY. phylink_fwnode_phy_connect() has no such check, so a later connect overwrites the stale pointer and hides the problem. A disconnect does not: phylink_disconnect_phy() hands that pointer to phy_disconnect(), and the second phy_detach() on the same PHY drops references the first one already released. Clear the binding on the failure path. This is the same operation phylink_disconnect_phy() performs, so both now share a helper. The PHY-side fields are left to phy_detach(), which every caller already runs on this path. Fixes: 03abf2a7c654 ("net: phylink: add EEE management") Signed-off-by: Aleksei Sviridkin <f@lex.la> --- drivers/net/phy/phylink.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 5b8e956902fb..a55e4a64028f 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2083,6 +2083,18 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy, return phylink_validate(pl, supported, state); } +/* Disassociate @phy from @pl. Caller must hold pl->phydev_mutex. */ +static void phylink_clear_phydev(struct phylink *pl, struct phy_device *phy) +{ + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->phydev = NULL; + pl->phy_enable_tx_lpi = false; + pl->mac_tx_clk_stop = false; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); +} + static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, phy_interface_t interface) { @@ -2197,6 +2209,12 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, if (ret == 0 && phy_interrupt_is_valid(phy)) phy_request_interrupt(phy); + if (ret) { + mutex_lock(&pl->phydev_mutex); + phylink_clear_phydev(pl, phy); + mutex_unlock(&pl->phydev_mutex); + } + return ret; } @@ -2347,15 +2365,8 @@ void phylink_disconnect_phy(struct phylink *pl) mutex_lock(&pl->phydev_mutex); phy = pl->phydev; - if (phy) { - mutex_lock(&phy->lock); - mutex_lock(&pl->state_mutex); - pl->phydev = NULL; - pl->phy_enable_tx_lpi = false; - pl->mac_tx_clk_stop = false; - mutex_unlock(&pl->state_mutex); - mutex_unlock(&phy->lock); - } + if (phy) + phylink_clear_phydev(pl, phy); mutex_unlock(&pl->phydev_mutex); if (phy) { -- 2.55.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v2 1/2] net: phylink: unwind the PHY binding when bringup fails late 2026-08-24 2:40 ` [PATCH net-next v2 1/2] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin @ 2026-08-27 18:04 ` Andrew Lunn 0 siblings, 0 replies; 6+ messages in thread From: Andrew Lunn @ 2026-08-27 18:04 UTC (permalink / raw) To: Aleksei Sviridkin Cc: Heiner Kallweit, Russell King, Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel On Mon, Aug 24, 2026 at 05:40:27AM +0300, Aleksei Sviridkin wrote: > phylink_bringup_phy() records the PHY in pl->phydev before its last > fallible step: on a MAC whose phylink ops implement LPI, > phy_eee_rx_clock_stop() can fail with a real MDIO error. The callers > unwind with phy_detach(), which knows nothing about pl->phydev, so a > pointer to a PHY that is no longer attached outlives the failed > connect. > > What that costs depends on how the caller got here. > phylink_connect_phy() and the SFP path go through > phylink_attach_phy(), which refuses to attach while pl->phydev is set > and turns a transient MDIO error into a permanent -EBUSY. > phylink_fwnode_phy_connect() has no such check, so a later connect > overwrites the stale pointer and hides the problem. A disconnect does > not: phylink_disconnect_phy() hands that pointer to phy_disconnect(), > and the second phy_detach() on the same PHY drops references the first > one already released. > > Clear the binding on the failure path. This is the same operation > phylink_disconnect_phy() performs, so both now share a helper. The > PHY-side fields are left to phy_detach(), which every caller already > runs on this path. > > Fixes: 03abf2a7c654 ("net: phylink: add EEE management") > Signed-off-by: Aleksei Sviridkin <f@lex.la> This should be for net, not net-next. Reviewed-by: Andrew Lunn <andrew@lunn.ch> Andrew ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle 2026-08-24 2:40 [PATCH net-next v2 0/2] net: fix a stale phylink PHY pointer and a lost PHY interrupt Aleksei Sviridkin 2026-08-24 2:40 ` [PATCH net-next v2 1/2] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin @ 2026-08-24 2:40 ` Aleksei Sviridkin 2026-08-27 13:28 ` Paolo Abeni 2026-08-27 18:09 ` Andrew Lunn 1 sibling, 2 replies; 6+ messages in thread From: Aleksei Sviridkin @ 2026-08-24 2:40 UTC (permalink / raw) To: Andrew Lunn, Heiner Kallweit, Russell King Cc: Aleksei Sviridkin, Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel A PHY with no specific driver available at attach time gets the generic one, and phy_probe() parks it in polling mode because that driver has no interrupt callbacks. phy_detach() releases the generic driver so a real one can bind later, but the interrupt is not given back, and a generic probe that fails leaves the PHY the same way without reaching phy_detach() at all. The specific driver then attaches with irq == PHY_POLL and the PHY stays polled for the rest of the uptime, with no warning on that path. Take it back from the MDIO bus interrupt table on both exits from the cycle: the cycle does not touch that table, so whatever the bus recorded there still stands. A bus that never filled it has nothing to give back and its PHY stays polled. Restore only where the cycle left PHY_POLL, so that an interrupt mode installed on the attached PHY afterwards is not reset. Signed-off-by: Aleksei Sviridkin <f@lex.la> --- The cycle is easy to hit on a DSA switch that probes before the rootfs is mounted: the switch connects its user ports during setup, the PHY driver is still a module on that rootfs, so the generic driver binds and is released again when the connect fails. The real driver binds at ifup and gets irq == PHY_POLL. Both exits from the cycle need the restore: phy_detach() for a generic driver that bound and is being released, and phy_attach_direct()'s error_module_put label for a generic probe that failed, which never calls phy_detach() at all. What the table covers and what it does not. Nothing writes mii_bus->irq[] after the bus is registered except stmmac_mdio.c and mlxbf_gige_main.c, and both assign the same value to phydev->irq in the same breath, so a PHY whose interrupt came from firmware is fixed here. A PHY handed its interrupt by its MAC driver is not: lan78xx and smsc95xx write phydev->irq after registration and leave the table alone, so they keep polling after a generic cycle exactly as they do today, and the set of drivers that write only phydev->irq is larger than those two. Which raises a question I would rather ask than settle alone: if bus->irq[] is the per-address registry for a bus, should those drivers be mirroring into it the way mlxbf_gige and stmmac already do? If that is the intent I am happy to send it as a follow-up. What the guard distinguishes and what it does not. It preserves an interrupt mode installed on the attached PHY after connect, so PHY_MAC_INTERRUPT from genet, tsnep or bcmasp survives the detach. It cannot tell phy_probe()'s parking from the other ways phydev->irq reaches PHY_POLL, so a PHY parked by PHY_F_NO_IRQ, by a failed phy_request_interrupt(), or by a MAC taking the phy.rst advice to set PHY_POLL, is restored here as well. That is harmless for the drivers that do so today: phy_attach_direct() applies PHY_F_NO_IRQ again on the next attach, a failed request is simply retried, and every MAC that forces PHY_POLL does so in the same function that connects the PHY, so a restored value is overwritten before anything can act on it. The guard is not only tidiness. Without it a PHY that a MAC had put in PHY_MAC_INTERRUPT mode would get a real interrupt number back at detach, the next phy_connect_direct() would request it, and phy_disconnect() would then skip phy_free_interrupt(), because the MAC overwrites phydev->irq again right after connect and phy_interrupt_is_valid() is false by the time the interrupt would be freed. That asymmetry between phy_connect_direct() and phy_disconnect() is not new, and it bites any such MAC whose PHY has an interrupt to request; the guard keeps a generic-driver cycle from walking a PHY into it. The same interrupt is also lost on a plain sysfs unbind and rebind of a PHY driver, and this patch does not cover that. A restore in phy_remove() would cover both paths, but is_genphy_driven is what keeps the intent narrow here, so I would rather not widen the fix on a guess. No Fixes: tag on this one. The behaviour predates what I can bisect in this tree; if someone can name the commit I will add it. drivers/net/phy/phy_device.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 94b2e85e00a3..8c93d35e1c94 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1734,6 +1734,18 @@ static bool phy_drv_supports_irq(const struct phy_driver *phydrv) return phydrv->config_intr && phydrv->handle_interrupt; } +/* Give back the interrupt phy_probe() parked when a driver with no interrupt + * callbacks bound. The bind cycle does not touch the bus interrupt table, so + * whatever the bus recorded there still stands; a bus that never filled it has + * nothing to give back. Only the parking is undone: any other value the PHY + * carries was put there by someone else. + */ +static void phy_restore_genphy_irq(struct phy_device *phydev) +{ + if (phydev->irq == PHY_POLL) + phydev->irq = phydev->mdio.bus->irq[phydev->mdio.addr]; +} + /** * phy_attach_direct - attach a network device to a given PHY device pointer * @dev: network device to attach @@ -1896,6 +1908,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, error_module_put: module_put(d->driver->owner); + phy_restore_genphy_irq(phydev); phydev->is_genphy_driven = 0; d->driver = NULL; error_put_device: @@ -1965,6 +1978,7 @@ void phy_detach(struct phy_device *phydev) * real driver could be loaded */ if (phydev->is_genphy_driven) { + phy_restore_genphy_irq(phydev); device_release_driver(&phydev->mdio.dev); phydev->is_genphy_driven = 0; } -- 2.55.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle 2026-08-24 2:40 ` [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin @ 2026-08-27 13:28 ` Paolo Abeni 2026-08-27 18:09 ` Andrew Lunn 1 sibling, 0 replies; 6+ messages in thread From: Paolo Abeni @ 2026-08-27 13:28 UTC (permalink / raw) To: Aleksei Sviridkin, Andrew Lunn, Heiner Kallweit, Russell King Cc: Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski, Simon Horman, netdev, linux-kernel On 8/24/26 4:40 AM, Aleksei Sviridkin wrote: > A PHY with no specific driver available at attach time gets the generic > one, and phy_probe() parks it in polling mode because that driver has > no interrupt callbacks. phy_detach() releases the generic driver so a > real one can bind later, but the interrupt is not given back, and a > generic probe that fails leaves the PHY the same way without reaching > phy_detach() at all. The specific driver then attaches with > irq == PHY_POLL and the PHY stays polled for the rest of the uptime, > with no warning on that path. > > Take it back from the MDIO bus interrupt table on both exits from the > cycle: the cycle does not touch that table, so whatever the bus > recorded there still stands. A bus that never filled it has nothing to > give back and its PHY stays polled. Restore only where the cycle left > PHY_POLL, so that an interrupt mode installed on the attached PHY > afterwards is not reset. > > Signed-off-by: Aleksei Sviridkin <f@lex.la> > --- > The cycle is easy to hit on a DSA switch that probes before the rootfs > is mounted: the switch connects its user ports during setup, the PHY > driver is still a module on that rootfs, so the generic driver binds > and is released again when the connect fails. The real driver binds at > ifup and gets irq == PHY_POLL. > > Both exits from the cycle need the restore: phy_detach() for a generic > driver that bound and is being released, and phy_attach_direct()'s > error_module_put label for a generic probe that failed, which never > calls phy_detach() at all. > > What the table covers and what it does not. Nothing writes > mii_bus->irq[] after the bus is registered except stmmac_mdio.c and > mlxbf_gige_main.c, and both assign the same value to phydev->irq in the > same breath, so a PHY whose interrupt came from firmware is fixed here. > A PHY handed its interrupt by its MAC driver is not: lan78xx and > smsc95xx write phydev->irq after registration and leave the table > alone, so they keep polling after a generic cycle exactly as they do > today, and the set of drivers that write only phydev->irq is larger > than those two. Which raises a question I would rather ask than settle > alone: if bus->irq[] is the per-address registry for a bus, should > those drivers be mirroring into it the way mlxbf_gige and stmmac > already do? If that is the intent I am happy to send it as a follow-up. > > What the guard distinguishes and what it does not. It preserves an > interrupt mode installed on the attached PHY after connect, so > PHY_MAC_INTERRUPT from genet, tsnep or bcmasp survives the detach. It > cannot tell phy_probe()'s parking from the other ways phydev->irq > reaches PHY_POLL, so a PHY parked by PHY_F_NO_IRQ, by a failed > phy_request_interrupt(), or by a MAC taking the phy.rst advice to set > PHY_POLL, is restored here as well. That is harmless for the drivers > that do so today: phy_attach_direct() applies PHY_F_NO_IRQ again on the > next attach, a failed request is simply retried, and every MAC that > forces PHY_POLL does so in the same function that connects the PHY, so > a restored value is overwritten before anything can act on it. > > The guard is not only tidiness. Without it a PHY that a MAC had put in > PHY_MAC_INTERRUPT mode would get a real interrupt number back at > detach, the next phy_connect_direct() would request it, and > phy_disconnect() would then skip phy_free_interrupt(), because the MAC > overwrites phydev->irq again right after connect and > phy_interrupt_is_valid() is false by the time the interrupt would be > freed. That asymmetry between phy_connect_direct() and > phy_disconnect() is not new, and it bites any such MAC whose PHY has an > interrupt to request; the guard keeps a generic-driver cycle from > walking a PHY into it. > > The same interrupt is also lost on a plain sysfs unbind and rebind of a > PHY driver, and this patch does not cover that. A restore in > phy_remove() would cover both paths, but is_genphy_driven is what keeps > the intent narrow here, so I would rather not widen the fix on a guess. > > No Fixes: tag on this one. The behaviour predates what I can bisect in > this tree; if someone can name the commit I will add it. If the root cause predates git history, the fixes tag should be: Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Otherwise the oldest commit that still reproduces the issue would do. It looks like out sashiko instance did not pick this series, and this is net material. Please re-submit with the correct target tree and fixes tag. Please also try to condense the above writing in a much shorter text, or point to prior discussion. /P ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle 2026-08-24 2:40 ` [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin 2026-08-27 13:28 ` Paolo Abeni @ 2026-08-27 18:09 ` Andrew Lunn 1 sibling, 0 replies; 6+ messages in thread From: Andrew Lunn @ 2026-08-27 18:09 UTC (permalink / raw) To: Aleksei Sviridkin Cc: Heiner Kallweit, Russell King, Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel > Take it back from the MDIO bus interrupt table on both exits from the > cycle: the cycle does not touch that table, so whatever the bus > recorded there still stands. A bus that never filled it has nothing to > give back and its PHY stays polled. Bit of an odd phrase. It might be better to explain that mdiobus_alloc() defaults to filling bus->irq[] for polling. > +static void phy_restore_genphy_irq(struct phy_device *phydev) > +{ > + if (phydev->irq == PHY_POLL) > + phydev->irq = phydev->mdio.bus->irq[phydev->mdio.addr]; Why conditional? Maybe it is buried deep in that wall of text? Andrew ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-27 18:10 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-24 2:40 [PATCH net-next v2 0/2] net: fix a stale phylink PHY pointer and a lost PHY interrupt Aleksei Sviridkin 2026-08-24 2:40 ` [PATCH net-next v2 1/2] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin 2026-08-27 18:04 ` Andrew Lunn 2026-08-24 2:40 ` [PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin 2026-08-27 13:28 ` Paolo Abeni 2026-08-27 18:09 ` Andrew Lunn
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox