[PATCH net-next v2 2/2] net: phy: restore the interrupt after a generic-driver bind cycle

Aleksei Sviridkin <[email protected]>
Newsgroups org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <[email protected]>
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 <[email protected]>
---
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.