diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2025-06-03 18:21:57 +0200 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2025-06-03 22:14:03 +0200 |
commit | 8887abccf8aa16795f23ef3a3b25650cb8aa804c (patch) | |
tree | 1ee17d3fed2cef8e7fdc24c8b13626186a4c9960 | |
parent | d46c4c839c20a599a0eb8d73708ce401f9c7d06d (diff) | |
download | linux-8887abccf8aa16795f23ef3a3b25650cb8aa804c.tar.gz |
PM: sleep: Add locking to dpm_async_resume_children()
Commit 0cbef962ce1f ("PM: sleep: Resume children after resuming the
parent") introduced a subtle concurrency issue that may lead to a kernel
crash if system suspend is aborted and may also slow down asynchronous
device resume otherwise.
Namely, the initial list walks in dpm_noirq_resume_devices(),
dpm_resume_early(), and dpm_resume() call dpm_clear_async_state() for
every device and attempt to asynchronously resume it if it has no
children (so it is a "root" device). The asynchronous resume of a
root device triggers an attempt to asynchronously resume its children
which may take place before calling dpm_clear_async_state() for them
due to the lack of synchronization between dpm_async_resume_children()
and the code calling dpm_clear_async_state(). If this happens, the
dpm_clear_async_state() that comes in late, will clear
power.work_in_progress for the given device after it has been set by
__dpm_async(), so the suspend callback will be allowed to run once
again for the same device during the same transition. This leads to
a whole range of interesting breakage.
Fortunately, if the suspend transition is not aborted, power.work_in_progress
is set by it for all devices, so dpm_async_resume_children() will not
schedule asynchronous resume for them until dpm_clear_async_state()
clears that flag, but this means missing an opportunity to start the
resume of those devices earlier.
Address the above issue by adding dpm_list_mtx locking to
dpm_async_resume_children(), so it will wait for the entire initial
list walk and the invocation of dpm_clear_async_state() for all devices
to be completed before scheduling any new asynchronous resume callbacks.
Fixes: 0cbef962ce1f ("PM: sleep: Resume children after resuming the parent")
Link: https://gitlab.freedesktop.org/drm/amd/-/issues/4280
Reported-and-tested-by: Chris Bainbridge <chris.bainbridge@gmail.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>
Link: https://patch.msgid.link/13779172.uLZWGnKmhe@rjwysocki.net
-rw-r--r-- | drivers/base/power/main.c | 7 |
1 files changed, 7 insertions, 0 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 9dd9859f97d2b7..77c7a99f087007 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -638,6 +638,13 @@ static int dpm_async_with_cleanup(struct device *dev, void *fn) static void dpm_async_resume_children(struct device *dev, async_func_t func) { /* + * Prevent racing with dpm_clear_async_state() during initial list + * walks in dpm_noirq_resume_devices(), dpm_resume_early(), and + * dpm_resume(). + */ + guard(mutex)(&dpm_list_mtx); + + /* * Start processing "async" children of the device unless it's been * started already for them. * |