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:
Rafael J. Wysocki 2009-05-24 21:15:07 +02:00
parent 59a3759d0f
commit 32bdfac546
4 changed files with 8 additions and 26 deletions

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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;
} }