[PATCH net] net: phy: postpone PHY interrupts during sleep with MAC-managed PM
From: Igor Velkov
Date: Fri Oct 02 2026 - 00:37:57 EST
mdio_bus_phy_suspend() and mdio_bus_phy_resume() return early when the
MAC driver manages PHY PM, so the PHY never gets irq_suspended. A PHY
interrupt that wakes the system fires as soon as resume_device_irqs()
re-enables the line, before the MAC driver resumes, and phy_interrupt()
runs the PHY driver's handler at once. If the MDIO bus was powered down
in suspend, the MDIO access in that handler stalls the CPU.
On Helios64 (dwmac-rk, RTL8211F with an interrupt line, Wake-on-LAN in
the PHY) the wake interrupt reads INSR while the GMAC clocks are off,
and the board hangs within a few suspend cycles.
Set irq_suspended for these PHYs too. The PHY device is a child of its
MDIO bus, so mdio_bus_phy_resume() runs after the bus is back (with
stmmac, after the MAC) and replays the postponed interrupt then.
phy_suspend(), phy_resume() and the state machine stay with the MAC
driver.
Drop a pending rerun when the interrupt is freed: MAC drivers that close
the interface in suspend free and request it again before the PHY
resumes.
Tested on Helios64 with 7.3-rc5, the GMAC powered down in suspend and
Wake-on-LAN in the PHY: 20 of 20 magic-packet wakes. Without it the
same setup hung within 1 to 7 cycles in 14 of 16 runs, some on
instrumented builds; one run passed 25 cycles. ODROID-HC4
(dwmac-meson8b, RTL8211F with an interrupt line): 10 of 10.
Fixes: 1758bde2e4aa ("net: phy: Don't trigger state machine while in suspend")
Assisted-by: LLM
Signed-off-by: Igor Velkov <iav@xxxxxx>
---
This replaces the dwmac-rk change that kept the GMAC powered instead:
https://lore.kernel.org/r/20260930-dwmac-rk-phy-wol-v1-1-9fdc50bd9ae4@xxxxxx
Build-tested on net: allmodconfig and allyesconfig with W=1, no new
warnings.
drivers/net/phy/phy.c | 2 ++
drivers/net/phy/phy_device.c | 43 ++++++++++++++++++++++--------------
2 files changed, 28 insertions(+), 17 deletions(-)
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index fce9bc7be330..cd189ecee55e 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1507,6 +1507,8 @@ EXPORT_SYMBOL(phy_request_interrupt);
void phy_free_interrupt(struct phy_device *phydev)
{
phy_disable_interrupts(phydev);
+ /* A freed interrupt has nothing left to rerun on resume. */
+ phydev->irq_rerun = 0;
free_irq(phydev->irq, phydev);
}
EXPORT_SYMBOL(phy_free_interrupt);
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 94b2e85e00a3..948818964841 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -347,18 +347,19 @@ static __maybe_unused int mdio_bus_phy_suspend(struct device *dev)
{
struct phy_device *phydev = to_phy_device(dev);
- if (phydev->mac_managed_pm)
- return 0;
-
/* Wakeup interrupts may occur during the system sleep transition when
* the PHY is inaccessible. Set flag to postpone handling until the PHY
* has resumed. Wait for concurrent interrupt handler to complete.
+ * The MDIO bus may be powered down even when the MAC manages PHY PM.
*/
if (phy_interrupt_is_valid(phydev)) {
phydev->irq_suspended = 1;
synchronize_irq(phydev->irq);
}
+ if (phydev->mac_managed_pm)
+ return 0;
+
/* We must stop the state machine manually, otherwise it stops out of
* control, possibly with the phydev->lock held. Upon resume, netdev
* may call phy routines that try to grab the same lock, and that may
@@ -375,13 +376,33 @@ static __maybe_unused int mdio_bus_phy_suspend(struct device *dev)
return phy_suspend(phydev);
}
+static __maybe_unused void mdio_bus_phy_resume_irq(struct phy_device *phydev)
+{
+ if (!phy_interrupt_is_valid(phydev))
+ return;
+
+ phydev->irq_suspended = 0;
+ synchronize_irq(phydev->irq);
+
+ /* Rerun interrupts which were postponed by phy_interrupt()
+ * because they occurred during the system sleep transition.
+ */
+ if (phydev->irq_rerun) {
+ phydev->irq_rerun = 0;
+ enable_irq(phydev->irq);
+ irq_wake_thread(phydev->irq, phydev);
+ }
+}
+
static __maybe_unused int mdio_bus_phy_resume(struct device *dev)
{
struct phy_device *phydev = to_phy_device(dev);
int ret;
- if (phydev->mac_managed_pm)
+ if (phydev->mac_managed_pm) {
+ mdio_bus_phy_resume_irq(phydev);
return 0;
+ }
if (!phydev->suspended_by_mdio_bus)
goto no_resume;
@@ -404,19 +425,7 @@ static __maybe_unused int mdio_bus_phy_resume(struct device *dev)
if (ret < 0)
return ret;
no_resume:
- if (phy_interrupt_is_valid(phydev)) {
- phydev->irq_suspended = 0;
- synchronize_irq(phydev->irq);
-
- /* Rerun interrupts which were postponed by phy_interrupt()
- * because they occurred during the system sleep transition.
- */
- if (phydev->irq_rerun) {
- phydev->irq_rerun = 0;
- enable_irq(phydev->irq);
- irq_wake_thread(phydev->irq, phydev);
- }
- }
+ mdio_bus_phy_resume_irq(phydev);
if (phy_uses_state_machine(phydev))
phy_start_machine(phydev);
--
2.43.0