PM: Do not hold dpm_list_mtx while disabling/enabling nonboot CPUs
We shouldn't hold dpm_list_mtx while executing [disable|enable]_nonboot_cpus(), because theoretically this may lead to a deadlock as shown by the following example (provided by Johannes Berg): CPU 3 CPU 2 CPU 1 suspend/hibernate something: rtnl_lock() device_pm_lock() -> mutex_lock(&dpm_list_mtx) mutex_lock(&dpm_list_mtx) linkwatch_work -> rtnl_lock() disable_nonboot_cpus() -> flush CPU 3 workqueue Fortunately, device drivers are supposed to stop any activities that might lead to the registration of new device objects way before disable_nonboot_cpus() is called, so it shouldn't be necessary to hold dpm_list_mtx over the entire late part of device suspend and early part of device resume. Thus, during the late suspend and the early resume of devices acquire dpm_list_mtx only when dpm_list is going to be traversed and release it right after that. This patch is reported to fix the regressions tracked as http://bugzilla.kernel.org/show_bug.cgi?id=13245. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Alan Stern <stern@rowland.harvard.edu> Reported-by: Miles Lane <miles.lane@gmail.com> Tested-by: Ming Lei <tom.leiming@gmail.com>
This commit is contained in:
parent
59a3759d0f
commit
32bdfac546
|
@ -357,6 +357,7 @@ static void dpm_power_up(pm_message_t state)
|
||||||
{
|
{
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
|
|
||||||
|
mutex_lock(&dpm_list_mtx);
|
||||||
list_for_each_entry(dev, &dpm_list, power.entry)
|
list_for_each_entry(dev, &dpm_list, power.entry)
|
||||||
if (dev->power.status > DPM_OFF) {
|
if (dev->power.status > DPM_OFF) {
|
||||||
int error;
|
int error;
|
||||||
|
@ -366,6 +367,7 @@ static void dpm_power_up(pm_message_t state)
|
||||||
if (error)
|
if (error)
|
||||||
pm_dev_err(dev, state, " early", error);
|
pm_dev_err(dev, state, " early", error);
|
||||||
}
|
}
|
||||||
|
mutex_unlock(&dpm_list_mtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -614,6 +616,7 @@ int device_power_down(pm_message_t state)
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
|
||||||
suspend_device_irqs();
|
suspend_device_irqs();
|
||||||
|
mutex_lock(&dpm_list_mtx);
|
||||||
list_for_each_entry_reverse(dev, &dpm_list, power.entry) {
|
list_for_each_entry_reverse(dev, &dpm_list, power.entry) {
|
||||||
error = suspend_device_noirq(dev, state);
|
error = suspend_device_noirq(dev, state);
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -622,6 +625,7 @@ int device_power_down(pm_message_t state)
|
||||||
}
|
}
|
||||||
dev->power.status = DPM_OFF_IRQ;
|
dev->power.status = DPM_OFF_IRQ;
|
||||||
}
|
}
|
||||||
|
mutex_unlock(&dpm_list_mtx);
|
||||||
if (error)
|
if (error)
|
||||||
device_power_up(resume_event(state));
|
device_power_up(resume_event(state));
|
||||||
return error;
|
return error;
|
||||||
|
|
|
@ -1451,7 +1451,6 @@ int kernel_kexec(void)
|
||||||
error = device_suspend(PMSG_FREEZE);
|
error = device_suspend(PMSG_FREEZE);
|
||||||
if (error)
|
if (error)
|
||||||
goto Resume_console;
|
goto Resume_console;
|
||||||
device_pm_lock();
|
|
||||||
/* At this point, device_suspend() has been called,
|
/* At this point, device_suspend() has been called,
|
||||||
* but *not* device_power_down(). We *must*
|
* but *not* device_power_down(). We *must*
|
||||||
* device_power_down() now. Otherwise, drivers for
|
* device_power_down() now. Otherwise, drivers for
|
||||||
|
@ -1489,7 +1488,6 @@ int kernel_kexec(void)
|
||||||
enable_nonboot_cpus();
|
enable_nonboot_cpus();
|
||||||
device_power_up(PMSG_RESTORE);
|
device_power_up(PMSG_RESTORE);
|
||||||
Resume_devices:
|
Resume_devices:
|
||||||
device_pm_unlock();
|
|
||||||
device_resume(PMSG_RESTORE);
|
device_resume(PMSG_RESTORE);
|
||||||
Resume_console:
|
Resume_console:
|
||||||
resume_console();
|
resume_console();
|
||||||
|
|
|
@ -215,8 +215,6 @@ static int create_image(int platform_mode)
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
device_pm_lock();
|
|
||||||
|
|
||||||
/* At this point, device_suspend() has been called, but *not*
|
/* At this point, device_suspend() has been called, but *not*
|
||||||
* device_power_down(). We *must* call device_power_down() now.
|
* device_power_down(). We *must* call device_power_down() now.
|
||||||
* Otherwise, drivers for some devices (e.g. interrupt controllers)
|
* Otherwise, drivers for some devices (e.g. interrupt controllers)
|
||||||
|
@ -227,7 +225,7 @@ static int create_image(int platform_mode)
|
||||||
if (error) {
|
if (error) {
|
||||||
printk(KERN_ERR "PM: Some devices failed to power down, "
|
printk(KERN_ERR "PM: Some devices failed to power down, "
|
||||||
"aborting hibernation\n");
|
"aborting hibernation\n");
|
||||||
goto Unlock;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = platform_pre_snapshot(platform_mode);
|
error = platform_pre_snapshot(platform_mode);
|
||||||
|
@ -280,9 +278,6 @@ static int create_image(int platform_mode)
|
||||||
device_power_up(in_suspend ?
|
device_power_up(in_suspend ?
|
||||||
(error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE);
|
(error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE);
|
||||||
|
|
||||||
Unlock:
|
|
||||||
device_pm_unlock();
|
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,13 +339,11 @@ static int resume_target_kernel(bool platform_mode)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
device_pm_lock();
|
|
||||||
|
|
||||||
error = device_power_down(PMSG_QUIESCE);
|
error = device_power_down(PMSG_QUIESCE);
|
||||||
if (error) {
|
if (error) {
|
||||||
printk(KERN_ERR "PM: Some devices failed to power down, "
|
printk(KERN_ERR "PM: Some devices failed to power down, "
|
||||||
"aborting resume\n");
|
"aborting resume\n");
|
||||||
goto Unlock;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = platform_pre_restore(platform_mode);
|
error = platform_pre_restore(platform_mode);
|
||||||
|
@ -403,9 +396,6 @@ static int resume_target_kernel(bool platform_mode)
|
||||||
|
|
||||||
device_power_up(PMSG_RECOVER);
|
device_power_up(PMSG_RECOVER);
|
||||||
|
|
||||||
Unlock:
|
|
||||||
device_pm_unlock();
|
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,11 +454,9 @@ int hibernation_platform_enter(void)
|
||||||
goto Resume_devices;
|
goto Resume_devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
device_pm_lock();
|
|
||||||
|
|
||||||
error = device_power_down(PMSG_HIBERNATE);
|
error = device_power_down(PMSG_HIBERNATE);
|
||||||
if (error)
|
if (error)
|
||||||
goto Unlock;
|
goto Resume_devices;
|
||||||
|
|
||||||
error = hibernation_ops->prepare();
|
error = hibernation_ops->prepare();
|
||||||
if (error)
|
if (error)
|
||||||
|
@ -493,9 +481,6 @@ int hibernation_platform_enter(void)
|
||||||
|
|
||||||
device_power_up(PMSG_RESTORE);
|
device_power_up(PMSG_RESTORE);
|
||||||
|
|
||||||
Unlock:
|
|
||||||
device_pm_unlock();
|
|
||||||
|
|
||||||
Resume_devices:
|
Resume_devices:
|
||||||
entering_platform_hibernation = false;
|
entering_platform_hibernation = false;
|
||||||
device_resume(PMSG_RESTORE);
|
device_resume(PMSG_RESTORE);
|
||||||
|
|
|
@ -289,12 +289,10 @@ static int suspend_enter(suspend_state_t state)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
device_pm_lock();
|
|
||||||
|
|
||||||
if (suspend_ops->prepare) {
|
if (suspend_ops->prepare) {
|
||||||
error = suspend_ops->prepare();
|
error = suspend_ops->prepare();
|
||||||
if (error)
|
if (error)
|
||||||
goto Done;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = device_power_down(PMSG_SUSPEND);
|
error = device_power_down(PMSG_SUSPEND);
|
||||||
|
@ -343,9 +341,6 @@ static int suspend_enter(suspend_state_t state)
|
||||||
if (suspend_ops->finish)
|
if (suspend_ops->finish)
|
||||||
suspend_ops->finish();
|
suspend_ops->finish();
|
||||||
|
|
||||||
Done:
|
|
||||||
device_pm_unlock();
|
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue