[PATCH net v5 2/2] net: phy: restore the interrupt phy_probe() replaced with PHY_POLL
From: Aleksei Sviridkin <hidden>
Date: 2026-09-06 17:46:49
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
phy_probe() sets phydev->irq to PHY_POLL when the driver it is binding
has no interrupt callbacks, and nothing puts the number back. The
driver that binds afterwards therefore starts polled, and unless its
consumer installs the interrupt again the PHY stays that way for the
rest of the uptime, with no trace beyond an informational irq=POLL
beside that driver's name. A DSA switch that connects its user ports
before the rootfs holding the PHY driver module is mounted hits this on
every boot.
Save the number where it is taken away and put it back in phy_remove(),
in phy_probe()'s own error path, which the driver core does not follow
with a remove, and in phy_attach_direct()'s unwind of a generic bind
that failed after its probe succeeded. Only a probe that took a number
away arms the restore, and phy_link_change suppresses it when a
consumer holds the PHY, since that consumer skipped requesting an
interrupt on the value it saw. phy_detach() clears phy_link_change
before it releases the driver, so the case above still restores.
That last part arrived in commit e0d1c55501d3 ("net: phy: fix
phy_uses_state_machine()"). Without it the mark is never cleared once a
consumer has attached, so the restore this patch exists for never fires;
a backport needs that commit first.
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.")
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <redacted>
---
drivers/net/phy/phy_device.c | 23 ++++++++++++++++++++++-
include/linux/phy.h | 3 +++
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 94b2e85e00a3..ba117147721a 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c@@ -770,6 +770,7 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id, mdiodev->device_remove = phy_mdio_device_remove; mdiodev->reset_state = -1; + dev->irq_saved = PHY_POLL; dev->speed = SPEED_UNKNOWN; dev->duplex = DUPLEX_UNKNOWN; dev->pause = false;
@@ -1734,6 +1735,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, 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_saved == PHY_POLL) + return; + + phydev->irq = phydev->irq_saved; + phydev->irq_saved = PHY_POLL; +} + /** * phy_attach_direct - attach a network device to a given PHY device pointer * @dev: network device to attach
@@ -1896,6 +1910,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:
@@ -3694,8 +3709,10 @@ static int phy_probe(struct device *dev) /* Disable the interrupt if the PHY doesn't support it * but the interrupt is still a valid one */ - if (!phy_drv_supports_irq(phydrv) && phy_interrupt_is_valid(phydev)) + if (!phy_drv_supports_irq(phydrv) && phy_interrupt_is_valid(phydev)) { + phydev->irq_saved = phydev->irq; phydev->irq = PHY_POLL; + } if (phydrv->flags & PHY_IS_INTERNAL) phydev->is_internal = true;
@@ -3820,6 +3837,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 +3867,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);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 5f8d65868e0f..d492417572a0 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h@@ -591,6 +591,8 @@ 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) + * @irq_saved: @irq as it was before phy_probe() replaced it with PHY_POLL, + * or PHY_POLL when there is nothing to restore * @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
@@ -761,6 +763,7 @@ struct phy_device { * -1 means no interrupt */ int irq; + int irq_saved; /* private data pointer */ /* For use by PHYs to maintain extra state */
--
2.53.0