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 and phy_interrupt() runs the PHY driver's handler at any point of the sleep transition. If the MAC driver powers the MDIO bus down, the MDIO access in that handler can stall the CPU. This happens on both sides: - suspend: the MAC's suspend callback stops the PHY, the link goes down, and the handler runs while the MAC powers down; - resume: resume_device_irqs() re-enables a pending interrupt before the MAC resumes. On Helios64 (dwmac-rk, RTL8211F with its interrupt line described in DT) both hang the board: the handler reads INSR while the GMAC clocks are off, after rk_gmac_suspend() or before rk_gmac_resume(). 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. As for other PHYs, an interrupt in that window now signals a wakeup event if Wake-on-LAN is enabled or the device may wake the system. MAC drivers that close the interface in suspend free the interrupt before the PHY resumes, so phy_free_interrupt() re-enables a line left disabled by a postponed interrupt if other handlers still share it. Tested on Helios64 with 7.3, s2idle without Wake-on-LAN, GMAC clocks off in suspend: 10 of 10 cycles got through suspend, 4 of them then hit a separate PCIe error on resume. The same kernel without these changes hung or reset after the link went down in 10 of 10. Fixes: 1758bde2e4aa ("net: phy: Don't trigger state machine while in suspend") Signed-off-by: Igor Velkov --- drivers/net/phy/phy.c | 9 ++++++++ drivers/net/phy/phy_device.c | 44 ++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index fce9bc7be330..00aa48e807a5 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1508,6 +1508,15 @@ void phy_free_interrupt(struct phy_device *phydev) { phy_disable_interrupts(phydev); free_irq(phydev->irq, phydev); + + /* Undo the disable_irq_nosync() of a postponed interrupt if the line + * is still shared; otherwise the next irq_startup() resets the depth. + */ + if (phydev->irq_rerun) { + phydev->irq_rerun = 0; + if (irq_has_action(phydev->irq)) + enable_irq(phydev->irq); + } } EXPORT_SYMBOL(phy_free_interrupt); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 5d4508bcfbe9..e2807e08e436 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -336,18 +336,20 @@ 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. + * This holds when the MAC manages PHY PM too: an interrupt can still + * fire while the MAC has the PHY or the MDIO bus powered down. */ 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 @@ -364,13 +366,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; @@ -393,19 +415,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