[PATCH 1/1] usb: xhci-plat: keep IRQ masked while HCD is inaccessible across system sleep
From: Jiazi Liu
Date: Tue Oct 20 2026 - 07:14:39 EST
From: Jiazi Liu <liujiazi@xxxxxxxxxx>
xhci_suspend() clears HCD_FLAG_HW_ACCESSIBLE and halts the xHC, but
the interrupter stays enabled and a pending event is not
acknowledged. The PM core re-enables device IRQs in
dpm_resume_noirq(), before the resume callback runs xhci_resume().
In this window a halted xHC with a pending event keeps its
level-triggered IRQ asserted. usb_hcd_irq() returns IRQ_NONE because
the hardware is not accessible, so the IRQ is delivered again and
again until the IRQ core reports it as spurious and disables it:
irq 121: nobody cared (try booting with the "irqpoll" option)
dpm_resume_early+0x90/0x300
dpm_resume_start+0x2c/0x50
hibernation_snapshot+0x224/0x488
handlers:
[<...>] usb_hcd_irq
Disabling IRQ #121
This was seen when a hibernation snapshot is taken right after a
restore, as the restore leaves a port status change event pending.
Disable the IRQ in xhci_plat_suspend_common() and enable it again in
xhci_plat_resume_common() after xhci_resume(). Skip this if the host
is a wakeup source, as it needs the IRQ during suspend.
Signed-off-by: Jiazi Liu <liujiazi@xxxxxxxxxx>
---
drivers/usb/host/xhci-plat.c | 28 +++++++++++++++++++++++++---
1 file changed, 25 insertions(+), 3 deletions(-)
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 074d9c731639..74a56f18de3e 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -478,13 +478,22 @@ static int xhci_plat_suspend_common(struct device *dev)
ret = xhci_priv_suspend_quirk(hcd);
if (ret)
return ret;
+
+ /*
+ * Mask the IRQ while the HCD is not accessible. The PM core unmasks
+ * it before xhci_resume() runs, and a pending event on the halted
+ * xHC would cause an interrupt storm.
+ */
+ if (!device_may_wakeup(dev))
+ disable_irq(hcd->irq);
+
/*
* xhci_suspend() needs `do_wakeup` to know whether host is allowed
* to do wakeup during suspend.
*/
ret = xhci_suspend(xhci, device_may_wakeup(dev));
if (ret)
- return ret;
+ goto err_enable_irq;
if (!device_may_wakeup(dev) && (xhci->quirks & XHCI_SUSPEND_RESUME_CLKS)) {
clk_disable_unprepare(xhci->clk);
@@ -492,6 +501,12 @@ static int xhci_plat_suspend_common(struct device *dev)
}
return 0;
+
+err_enable_irq:
+ if (!device_may_wakeup(dev))
+ enable_irq(hcd->irq);
+
+ return ret;
}
static int xhci_plat_suspend(struct device *dev)
@@ -523,12 +538,12 @@ static int xhci_plat_resume_common(struct device *dev, bool power_lost)
if (!device_may_wakeup(dev) && (xhci->quirks & XHCI_SUSPEND_RESUME_CLKS)) {
ret = clk_prepare_enable(xhci->clk);
if (ret)
- return ret;
+ goto enable_irq;
ret = clk_prepare_enable(xhci->reg_clk);
if (ret) {
clk_disable_unprepare(xhci->clk);
- return ret;
+ goto enable_irq;
}
}
@@ -544,6 +559,10 @@ static int xhci_plat_resume_common(struct device *dev, bool power_lost)
if (ret)
goto disable_clks;
+ /* The HCD is accessible again, pair with disable_irq() in suspend. */
+ if (!device_may_wakeup(dev))
+ enable_irq(hcd->irq);
+
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
@@ -555,6 +574,9 @@ static int xhci_plat_resume_common(struct device *dev, bool power_lost)
clk_disable_unprepare(xhci->clk);
clk_disable_unprepare(xhci->reg_clk);
}
+enable_irq:
+ if (!device_may_wakeup(dev))
+ enable_irq(hcd->irq);
return ret;
}
--
2.54.0 (Apple Git-157)