[PATCH net v6 2/2] net: phy: restore the interrupt the bus gave a PHY

From: Aleksei Sviridkin

Date: Tue Sep 08 2026 - 11:54:32 EST


phy_probe() replaces phydev->irq with PHY_POLL when the driver that is
binding has no interrupt support, and nothing puts it back. A PHY whose
own driver is a module on a filesystem that is not mounted yet gets the
generic driver first, loses the number there, and polls for the rest of
the uptime once the real driver takes over.

Put it back from mdiobus->irq[], which is where the number came from:
phy_device_create() seeds phydev->irq out of that table, so the bus that
described the interrupt still holds it. Restore at the three points the
bind cycle can end, phy_remove(), phy_probe()'s own error exit and the
unwind in phy_attach_direct(), so a bind that is undone by any path
leaves the PHY as it was found.

Skip it while phy_link_change marks an attached consumer. That consumer
called phy_request_interrupt() on the PHY_POLL it saw and would free an
interrupt it never requested.

A bus whose driver writes only phydev->irq and never the table is not
covered, because the table then holds PHY_POLL and there is nothing to
give back; lan78xx, smsc95xx and sxgbe are in that position today and
registering the interrupt with the bus is theirs to do.

This needs commit e0d1c55501d3 ("net: phy: fix
phy_uses_state_machine()") to be of any use: without it the mark this
skips on is never cleared once a consumer has attached, and the restore
never runs.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@xxxxxx>
---
drivers/net/phy/phy_device.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 94b2e85e00a3..b55402569300 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1734,6 +1734,19 @@ static bool phy_drv_supports_irq(const struct phy_driver *phydrv)
return phydrv->config_intr && phydrv->handle_interrupt;
}

+/* Give back what phy_probe() took, from the bus that owns the number, but
+ * not while phy_link_change marks a consumer: it skipped
+ * phy_request_interrupt() on the value it saw, so phy_disconnect() would
+ * free an interrupt nobody requested.
+ */
+static void phy_restore_probe_irq(struct phy_device *phydev)
+{
+ if (phydev->phy_link_change || phydev->irq != PHY_POLL)
+ return;
+
+ 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 +1909,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,

error_module_put:
module_put(d->driver->owner);
+ phy_restore_probe_irq(phydev);
phydev->is_genphy_driven = 0;
d->driver = NULL;
error_put_device:
@@ -3820,6 +3834,8 @@ static int phy_probe(struct device *dev)
if (!phydev->is_on_sfp_module)
phy_led_triggers_unregister(phydev);

+ phy_restore_probe_irq(phydev);
+
/* Re-assert the reset signal on error */
phy_device_reset(phydev, 1);

@@ -3848,6 +3864,8 @@ static int phy_remove(struct device *dev)
if (phydev->drv && phydev->drv->remove)
phydev->drv->remove(phydev);

+ phy_restore_probe_irq(phydev);
+
/* Assert the reset signal */
phy_device_reset(phydev, 1);

--
2.53.0