[PATCH net-next 2/3] net: phy: restore the interrupt after a generic-driver bind cycle
From: Aleksei Sviridkin <hidden>
Date: 2026-08-22 15:53:12
Also in:
lkml
Subsystem:
ethernet phy library, networking drivers, the rest · Maintainers:
Andrew Lunn, Heiner Kallweit, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
fwnode_mdiobus_phy_device_register() resolves the interrupt declared for a PHY once, at MDIO bus registration. If no specific driver is available when the PHY is attached, the generic driver binds and phy_probe() parks the device in polling mode, since the generic driver has no interrupt callbacks. phy_detach() releases the generic driver so a specific driver can bind later, but nothing brings the interrupt back: the firmware node is never re-read after bus registration, so the specific driver attaches with irq == PHY_POLL, phy_request_interrupt() is never reached, and the PHY is polled for the rest of the uptime with nothing in the logs but the "irq=POLL" attach line. A DSA switch probing before the rootfs is mounted produces exactly that cycle for a PHY whose driver is a module: the generic driver binds and fails validation during switch setup, and the real driver binds at ifup. Observed on an MT7981B board with an Airoha EN8811H on an MT7531 port: the device tree declares the INT_B line, yet the attach says irq=POLL and the interrupt is never claimed. Save the interrupt when the generic driver binds and give it back when that driver is released. The restore runs before the device becomes bindable again, so a concurrently arriving specific driver cannot observe or overwrite the intermediate state. Only the value the generic-driver cycle took is restored. A PHY already parked in polling mode before that cycle, by a failed phy_request_interrupt() or by a driver that chose PHY_POLL in its own probe, had PHY_POLL saved, so the restore is skipped. The PHY_F_NO_IRQ and no-interrupt-support checks in phy_attach_direct() still apply to whichever driver binds next. Signed-off-by: Aleksei Sviridkin <redacted> --- Testing MT7981B board, mt7530 switch, Airoha EN8811H whose INT_B line is in the device tree, driver in a module on the rootfs, together with the next patch: the attach line reports irq=15 instead of irq=POLL, the EINT is claimed, its counter advances on link changes forced from the link partner, and there is no interrupt storm. The SoC's internal PHY on the same board, which has no interrupt in its bus table, keeps irq=POLL through the same boot, so the save-restore pair does not resurrect an interrupt the device never had. Consistent across reboots. Hardware testing was done on 6.18 with this exact shape of the change; on net-next the files are compile-tested. The saved value uses zero as "nothing saved"; no registration path produces a valid interrupt number of zero, and non-positive values are never restored. drivers/net/phy/phy_device.c | 13 +++++++++++++ include/linux/phy.h | 6 ++++++ 2 files changed, 19 insertions(+)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 94b2e85e0..6047dce61 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c@@ -1780,6 +1780,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, else d->driver = &genphy_driver.mdiodrv.driver; + phydev->genphy_saved_irq = phydev->irq; phydev->is_genphy_driven = 1; }
@@ -1897,6 +1898,9 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, error_module_put: module_put(d->driver->owner); phydev->is_genphy_driven = 0; + if (phydev->genphy_saved_irq > 0 && phydev->irq == PHY_POLL) + phydev->irq = phydev->genphy_saved_irq; + phydev->genphy_saved_irq = 0; d->driver = NULL; error_put_device: put_device(d);
@@ -1965,6 +1969,15 @@ void phy_detach(struct phy_device *phydev) * real driver could be loaded */ if (phydev->is_genphy_driven) { + /* Give back the interrupt phy_probe() parked when the generic + * driver bound, before the device becomes bindable again. A + * PHY that was in polling mode for any other reason had + * PHY_POLL saved, and the restore is skipped. + */ + if (phydev->genphy_saved_irq > 0 && phydev->irq == PHY_POLL) + phydev->irq = phydev->genphy_saved_irq; + phydev->genphy_saved_irq = 0; + device_release_driver(&phydev->mdio.dev); phydev->is_genphy_driven = 0; }
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 5f8d65868..43e20b19e 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h@@ -591,6 +591,10 @@ struct phy_oatc14_sqi_capability { * - Bits [31:24] are reserved for defining generic * PHY driver behavior. * @irq: IRQ number of the PHY's interrupt (-1 if none) + * @genphy_saved_irq: value of @irq before the generic driver bound, given + * back when that driver is released; zero outside a + * generic bind cycle, and non-positive values are + * never restored * @phylink: Pointer to phylink instance for this PHY * @sfp_bus_attached: Flag indicating whether the SFP bus has been attached * @sfp_bus: SFP bus attached to this PHY's fiber port
@@ -762,6 +766,8 @@ struct phy_device { */ int irq; + int genphy_saved_irq; + /* private data pointer */ /* For use by PHYs to maintain extra state */ void *priv;
--
2.43.0