Commit ebd68841 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

PM: sleep: Update power.completion for all devices on errors

After commit aa7a9275 ("PM: sleep: Suspend async parents after
suspending children"), the following scenario is possible:

 1. Device A is async and it depends on device B that is sync.
 2. Async suspend is scheduled for A before the processing of B is
    started.
 3. A is waiting for B.
 4. In the meantime, an unrelated device fails to suspend and returns
    an error.
 5. The processing of B doesn't start at all and its power.completion is
    not updated.
 6. A is still waiting for B when async_synchronize_full() is called.
 7. Deadlock ensues.

To prevent this from happening, update power.completion for all devices
on errors in all suspend phases, but do not do it directly for devices
that are already being processed or are waiting for the processing to
start because in those cases it may be necessary to wait for the
processing to actually complete before updating power.completion for
the device.

Fixes: aa7a9275 ("PM: sleep: Suspend async parents after suspending children")
Fixes: 443046d1 ("PM: sleep: Make suspend of devices more asynchronous")
Closes: https://lore.kernel.org/linux-pm/e13740a0-88f3-4a6f-920f-15805071a7d6@linaro.org/


Reported-and-tested-by: default avatarTudor Ambarus <tudor.ambarus@linaro.org>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
Link: https://patch.msgid.link/6191258.lOV4Wx5bFT@rjwysocki.net
parent 228b9ded
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -1280,6 +1280,22 @@ static void dpm_async_suspend_parent(struct device *dev, async_func_t func)
		dpm_async_with_cleanup(dev->parent, func);
}

static void dpm_async_suspend_complete_all(struct list_head *device_list)
{
	struct device *dev;

	guard(mutex)(&async_wip_mtx);

	list_for_each_entry_reverse(dev, device_list, power.entry) {
		/*
		 * In case the device is being waited for and async processing
		 * has not started for it yet, let the waiters make progress.
		 */
		if (!dev->power.work_in_progress)
			complete_all(&dev->power.completion);
	}
}

/**
 * resume_event - Return a "resume" message for given "suspend" sleep state.
 * @sleep_state: PM message representing a sleep state.
@@ -1456,6 +1472,7 @@ static int dpm_noirq_suspend_devices(pm_message_t state)
		mutex_lock(&dpm_list_mtx);

		if (error || async_error) {
			dpm_async_suspend_complete_all(&dpm_late_early_list);
			/*
			 * Move all devices to the target list to resume them
			 * properly.
@@ -1658,6 +1675,7 @@ int dpm_suspend_late(pm_message_t state)
		mutex_lock(&dpm_list_mtx);

		if (error || async_error) {
			dpm_async_suspend_complete_all(&dpm_suspended_list);
			/*
			 * Move all devices to the target list to resume them
			 * properly.
@@ -1951,6 +1969,7 @@ int dpm_suspend(pm_message_t state)
		mutex_lock(&dpm_list_mtx);

		if (error || async_error) {
			dpm_async_suspend_complete_all(&dpm_prepared_list);
			/*
			 * Move all devices to the target list to resume them
			 * properly.