* [RFC PATCH] PM: sleep: Allow direct-complete for wakeup devices in suspend-to-idle
@ 2026-11-14 0:15 Paul Menzel
0 siblings, 0 replies; only message in thread
From: Paul Menzel @ 2026-11-14 0:15 UTC (permalink / raw)
To: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman,
Danilo Krummrich, Johan Hovold, Ulf Hansson
Cc: Paul Menzel, Bjorn Helgaas, Mika Westerberg, linux-pm,
driver-core, linux-pci, linux-usb, linux-kernel,
Rafael J. Wysocki
Since commit dc351d4c5f4f ("PM / core: Propagate dev->power.wakeup_path
when no callbacks"), device_suspend() clears direct_complete for every
device with wakeup enabled or in the wakeup path of a child. That commit
addressed a device without PM callbacks, where no ->prepare() callback has
looked at the device, but the check applies to all devices.
For PCI devices, ->prepare() already takes wakeup into account:
pci_pm_prepare() only returns 1 if pci_dev_need_resume() is false, which
compares device_may_wakeup() with the ACPI wakeup configuration the device
got for runtime PM (acpi_pci_need_resume()) and computes the target state
with pci_target_state(dev, device_may_wakeup(dev)), and it then adjusts
PME with pci_dev_adjust_pme(). For a device with wakeup enabled, this
decision is discarded right afterwards. As PCI bridges that can do D3
(pci_acpi_setup()), PCI USB host controllers (usb_hcd_pci_probe()) and
Thunderbolt NHIs (nhi_probe()) enable wakeup by default, direct-complete is
effectively limited to devices with wakeup disabled on x86 laptops. On a
Dell XPS 13 9310 (Tiger Lake) these runtime-suspended functions are
affected:
0000:00:07.0 pcieport D3cold wakeup=enabled
0000:00:07.2 pcieport D3cold wakeup=enabled
0000:00:0d.0 xhci_hcd D3cold wakeup=enabled
0000:00:0d.2 thunderbolt D3cold wakeup=enabled
0000:00:0d.3 thunderbolt D3cold wakeup=enabled
0000:00:14.0 xhci_hcd D3hot wakeup=enabled
There, every suspend-to-idle resume spends 231 ms of the noirq phase
powering up the empty Thunderbolt root ports 00:07.0 and 00:07.2 and the
NHIs 00:0d.2 and 00:0d.3 from D3cold (ACPI _ON of the TBT0/TBT1 power
resources ~125 ms, root port _PS0 ~108 ms), only to runtime-suspend them
again after the resume. That is more than a third of the kernel resume
(641 ms with the panel lit, 427 ms with it off, from the end of
machine_suspend to "PM: suspend exit"):
230.77 ms noirq bus pcieport 0000:00:07.2
227.40 ms noirq bus pcieport 0000:00:07.0
142.53 ms noirq bus thunderbolt 0000:00:0d.3
137.27 ms noirq bus thunderbolt 0000:00:0d.2
In suspend-to-idle no firmware is involved in the transition, and a device
left in runtime suspend keeps the wakeup configuration it got for runtime
PM. So allow direct-complete for wakeup devices there, and propagate
wakeup_path from a direct-completed device to its parent, as
device_suspend() does for devices that go through the callbacks.
Together with three more changes the Thunderbolt functions stay in D3cold,
and the noirq phase takes 33 ms instead of 232 ms with the panel off, and
34 ms instead of 231 ms with the panel lit (medians of 3-4 cycles).
Fixes: dc351d4c5f4f ("PM / core: Propagate dev->power.wakeup_path when no callbacks")
Signed-off-by: Paul Menzel <pmenzel@molgen.mpg.de>
---
Notes:
Other three changes:
- PCI/portdrv: no DPM_FLAG_NO_DIRECT_COMPLETE for ports on a root bus, as
the reason for it in commit 52be9464aa7e ("PCI/portdrv: Resume upon exit
from system suspend if left runtime suspended"), a parent going through
D3cold, does not apply to them,
- thunderbolt: nhi_complete() does not runtime-resume a direct-completed
NHI,
- ACPI: power: __acpi_power_on() resumes only dependents whose _PR0
resources are all on. Otherwise resuming the xHCI 00:0d.0 during system
suspend (xhci-pci does not set DPM_FLAG_SMART_SUSPEND) turns on the
shared D3C resource and runtime-resumes the root ports and NHIs, which
also need TBT0/TBT1.
This is an RFC, because the runtime wakeup configuration of a device can
differ from the one its driver sets up for system sleep. For example,
tb_switch_suspend() also arms wake on DisplayPort for runtime suspend, and
if user space disables wakeup for a Thunderbolt router, a direct-completed
NHI still wakes up the system when a device is connected. Maybe
subsystems should opt in with a flag instead.
An alternative is to keep the check for devices relying on
no_pm_callbacks, where nothing has looked at the wakeup settings, to drop
it for devices whose ->prepare() returned a positive value, and to
propagate wakeup_path from direct-completed devices in both cases. That
works for all sleep states, but does not help in the example above: the
Thunderbolt domain and host router devices (domain0, 0-0) below the NHI
have no PM callbacks, but wakeup enabled, so they still go through
device_suspend() and clear direct_complete of the NHI with
dpm_clear_superiors_direct_complete().
Tested on the Dell XPS 13 9310 with Linux 7.4-rc1 and the three changes
above (plus a fix that keeps the absent root ports TRP1/TRP3 from being
powered up at boot, without which TBT0/TBT1 never turn off): 17
suspend-to-idle cycles of 6-8 s with rtcwake and sleepgraph, no failures,
111 s in hardware sleep in total (/sys/power/suspend_stats/total_hw_sleep).
Hot-plugging during suspend was not tested.
Tool use (Documentation/process/generated-content.rst): the analysis, the
code change and this changelog were drafted by an LLM coding assistant
(Claude Opus 5.5 via Claude Code), in a session asked to "instrument S0ix
resume [on the Dell XPS 13 9310], and halve the time". It traced the
resume with sleepgraph, function_graph and kprobes/rpm tracepoints, built
the test kernels and ran the measurements on the laptop.
drivers/base/power/main.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 88a31e09e4f3f..f36c38d1585b0 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -1999,8 +1999,14 @@ static void device_suspend(struct device *dev, pm_message_t state, bool async)
if (dev->power.syscore)
goto Complete;
- /* Avoid direct_complete to let wakeup_path propagate. */
- if (device_may_wakeup(dev) || device_wakeup_path(dev))
+ /*
+ * Avoid direct_complete to let wakeup_path propagate. In
+ * suspend-to-idle, though, no firmware is involved and a device left in
+ * runtime suspend keeps the wakeup configuration it got for runtime PM,
+ * so allow direct_complete there and propagate wakeup_path below.
+ */
+ if ((device_may_wakeup(dev) || device_wakeup_path(dev)) &&
+ pm_suspend_target_state != PM_SUSPEND_TO_IDLE)
dev->power.direct_complete = false;
if (dev->power.direct_complete) {
@@ -2009,6 +2015,10 @@ static void device_suspend(struct device *dev, pm_message_t state, bool async)
if (pm_runtime_status_suspended(dev)) {
pm_dev_dbg(dev, state, "direct-complete ");
dev->power.is_suspended = true;
+ if (device_may_wakeup(dev))
+ dev->power.wakeup_path = true;
+
+ dpm_propagate_wakeup_to_parent(dev);
goto Complete;
}
base-commit: 5fcaa701fd2d61b003dff89bbb9dd29ed6123740
--
2.54.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-11-14 0:16 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-14 0:15 [RFC PATCH] PM: sleep: Allow direct-complete for wakeup devices in suspend-to-idle Paul Menzel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®