A PHY with no specific driver available at attach time gets the generic one, and phy_probe() sets phydev->irq to PHY_POLL because that driver has no interrupt callbacks. Neither end of that bind cycle puts the value back: phy_detach() releases the generic driver so a real one can bind later, and a generic probe that fails never reaches phy_detach() at all. The specific driver that binds afterwards therefore starts with irq == PHY_POLL, and the PHY is polled for the rest of the uptime with no warning on that path. A DSA switch that connects its user ports before the rootfs holding the PHY driver module is mounted hits this on every boot. mdiobus_alloc() fills bus->irq[] with PHY_POLL for every address, and the bind cycle never writes to that table, so the entry still holds whatever the bus registered there. Restore phydev->irq from it on both exits, and only where the cycle left PHY_POLL. That guard preserves an interrupt mode a MAC installed on the attached PHY after connect, and it keeps a restored interrupt number out of the phy_connect_direct()/phy_disconnect() asymmetry, where such a MAC would have the interrupt requested and never freed. Fixes: 00db8189d984 ("This patch adds a PHY Abstraction Layer to the Linux Kernel, enabling ethernet drivers to remain as ignorant as is reasonable of the connected PHY's design and operation details.") Signed-off-by: Aleksei Sviridkin --- Both exits matter: 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(). Which buses and MAC drivers the bus interrupt table covers, which other paths to PHY_POLL the guard also restores and why none of them is harmed, and the sysfs unbind case this does not cover, are worked through under v2: https://lore.kernel.org/netdev/20260824024029.41310-3-f@lex.la/ 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..be4c35db8de9 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. mdiobus_alloc() defaults bus->irq[] to PHY_POLL and the + * bind cycle does not touch the table, so whatever the bus recorded there + * still stands. 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