* [PATCH] usb: core: raise root-hub autosuspend delay to stop a busy-loop
@ 2026-10-28 4:18 Jianping Liu
2026-10-28 4:33 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Jianping Liu @ 2026-10-28 4:18 UTC (permalink / raw)
To: gregkh, dakr, shashank.mahadasyam, johan, hadess, frankjpliu,
guanyulin, gary, rafael.j.wysocki
Cc: linux-kernel, linux-usb
From: Jianping Liu <frankjpliu@tencent.com>
hub_probe() sets the hub autosuspend delay to 0. An idle hub is
therefore suspended at once, with no waiting time.
Suspending the root hub can fail. While a port reports over-current
or a connect change, xHCI refuses the bus suspend and returns -EBUSY.
usb_runtime_suspend() returns that error. The hub is resumed, and when
its status URB completes the same suspend runs again.
The PM core would normally leave a gap before that next attempt. It
starts the autosuspend timer only when the delay is still in the
future. A delay of 0 is already due, so no timer is started and there
is nothing to wait for. Suspend and resume repeat immediately, and one
CPU core stays at 100% until the port is no longer stuck.
If a root-hub runtime suspend fails while the delay is still 0, set
the delay to 1000 ms so the next attempt waits. If a later runtime
suspend succeeds and the delay is not 0, put it back to 0.
Signed-off-by: Jianping Liu <frankjpliu@tencent.com>
---
drivers/usb/core/driver.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index d3a61ebc6afb..30965d6ffd67 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -2008,6 +2008,20 @@ int usb_runtime_suspend(struct device *dev)
if (status == -EAGAIN || status == -EBUSY)
usb_mark_last_busy(udev);
+ /*
+ * Root hubs use 0ms delay, so a persistent -EBUSY retries with no
+ * wait and pins one CPU at 100%. Set autosuspend_delay to 1000ms
+ * on fail, then back to 0 ms on success.
+ */
+ if (!udev->parent) {
+ int delay = READ_ONCE(dev->power.autosuspend_delay);
+
+ if (unlikely(status != 0 && delay == 0))
+ pm_runtime_set_autosuspend_delay(dev, 1000);
+ if (unlikely(status == 0 && delay != 0))
+ pm_runtime_set_autosuspend_delay(dev, 0);
+ }
+
/*
* The PM core reacts badly unless the return code is 0,
* -EAGAIN, or -EBUSY, so always return -EBUSY on an error
--
2.43.7
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] usb: core: raise root-hub autosuspend delay to stop a busy-loop
2026-10-28 4:18 [PATCH] usb: core: raise root-hub autosuspend delay to stop a busy-loop Jianping Liu
@ 2026-10-28 4:33 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-28 4:33 UTC (permalink / raw)
To: Jianping Liu; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] usb: core: usb_runtime_suspend() clobbers root-hub autosuspend_delay
--
commit 60580cc9fa143b8fbfd60c5978507dea53f76384
Author: Jianping Liu <frankjpliu@tencent.com>
usb: core: raise root-hub autosuspend delay to stop a busy-loop
This commit updates usb_runtime_suspend() to set the root-hub autosuspend
delay to 1000 ms when runtime suspend fails while the delay is 0, and
resets any non-zero delay back to 0 when runtime suspend later succeeds.
> diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
> index d3a61ebc6afb0..30965d6ffd677 100644
> --- a/drivers/usb/core/driver.c
> +++ b/drivers/usb/core/driver.c
> @@ -2008,6 +2008,20 @@ int usb_runtime_suspend(struct device *dev)
> if (status == -EAGAIN || status == -EBUSY)
> usb_mark_last_busy(udev);
>
> + /*
> + * Root hubs use 0ms delay, so a persistent -EBUSY retries with no
> + * wait and pins one CPU at 100%. Set autosuspend_delay to 1000ms
> + * on fail, then back to 0 ms on success.
> + */
> + if (!udev->parent) {
> + int delay = READ_ONCE(dev->power.autosuspend_delay);
> +
> + if (unlikely(status != 0 && delay == 0))
> + pm_runtime_set_autosuspend_delay(dev, 1000);
> + if (unlikely(status == 0 && delay != 0))
> + pm_runtime_set_autosuspend_delay(dev, 0);
> + }
[Severity: Medium]
Can this unconditionally overwrite user-configured or driver-configured
dev->power.autosuspend_delay values on root hubs and drop the runtime PM
usage reference when racing with negative delay writes?
First, userspace can configure a custom positive delay on a root hub via
autosuspend_delay_ms_store() in drivers/base/power/sysfs.c or
autosuspend_store() in drivers/usb/core/sysfs.c, and child USB device
drivers may also adjust their parent hub's delay during probe as noted in
hub_probe() in drivers/usb/core/hub.c. Because usb_runtime_suspend() does
not track whether the non-zero delay was set by a prior suspend failure,
won't the first successful root-hub runtime suspend see status == 0 and
delay != 0 and reset the custom delay back to 0?
Conversely, if a transient suspend failure occurs while delay == 0 (for
instance, -EBUSY when a device connects to a port) and the newly connected
device keeps the root hub active afterward, dev->power.autosuspend_delay
remains left at 1000 ms in sysfs.
Second, __rpm_callback() in drivers/base/power/runtime.c releases
dev->power.lock while dev->power.runtime_status is RPM_SUSPENDING before
invoking usb_runtime_suspend(). If userspace concurrently writes a negative
delay to disable autosuspend via autosuspend_store() or
autosuspend_delay_ms_store(), pm_runtime_set_autosuspend_delay() calls
update_autosuspend() in drivers/base/power/runtime.c:
drivers/base/power/runtime.c:update_autosuspend() {
if (dev->power.use_autosuspend && delay < 0) {
/* If it used to be allowed then prevent it. */
if (!old_use || old_delay >= 0) {
atomic_inc(&dev->power.usage_count);
rpm_resume(dev, 0);
} else {
trace_rpm_usage(dev, 0);
}
}
/* Runtime suspend should be allowed now. */
else {
/* If it used to be prevented then allow it. */
if (old_use && old_delay < 0)
atomic_dec(&dev->power.usage_count);
/* Maybe we can autosuspend now. */
rpm_idle(dev, RPM_AUTO);
}
}
Inside rpm_resume(), because runtime_status is RPM_SUSPENDING, the caller
releases dev->power.lock and sleeps on dev->power.wait_queue waiting for
the suspend callback to complete.
When usb_suspend_both() finishes in usb_runtime_suspend():
1. If status == 0, READ_ONCE(dev->power.autosuspend_delay) sees the negative
delay (delay != 0) and calls pm_runtime_set_autosuspend_delay(dev, 0).
2. If status != 0 and READ_ONCE(dev->power.autosuspend_delay) read 0 just
before the concurrent writer set the negative delay, it calls
pm_runtime_set_autosuspend_delay(dev, 1000).
In both cases, wouldn't update_autosuspend() see old_use && old_delay < 0,
overwrite the negative delay with 0 or 1000, and execute
atomic_dec(&dev->power.usage_count), undoing the usage_count increment and
re-enabling autosuspend?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028041859.1061836-1-frankljpliu@gmail.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-28 4:33 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 4:18 [PATCH] usb: core: raise root-hub autosuspend delay to stop a busy-loop Jianping Liu
2026-10-28 4:33 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox