Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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: default avatarRafael J. Wysocki <rjw@sisk.pl>
Acked-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Reported-by: default avatarMiles Lane <miles.lane@gmail.com>
Tested-by: default avatarMing Lei <tom.leiming@gmail.com>
parent 59a3759d
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -357,6 +357,7 @@ static void dpm_power_up(pm_message_t state)
{
	struct device *dev;

	mutex_lock(&dpm_list_mtx);
	list_for_each_entry(dev, &dpm_list, power.entry)
		if (dev->power.status > DPM_OFF) {
			int error;
@@ -366,6 +367,7 @@ static void dpm_power_up(pm_message_t state)
			if (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;

	suspend_device_irqs();
	mutex_lock(&dpm_list_mtx);
	list_for_each_entry_reverse(dev, &dpm_list, power.entry) {
		error = suspend_device_noirq(dev, state);
		if (error) {
@@ -622,6 +625,7 @@ int device_power_down(pm_message_t state)
		}
		dev->power.status = DPM_OFF_IRQ;
	}
	mutex_unlock(&dpm_list_mtx);
	if (error)
		device_power_up(resume_event(state));
	return error;
+0 −2
Original line number Diff line number Diff line
@@ -1451,7 +1451,6 @@ int kernel_kexec(void)
		error = device_suspend(PMSG_FREEZE);
		if (error)
			goto Resume_console;
		device_pm_lock();
		/* At this point, device_suspend() has been called,
		 * but *not* device_power_down(). We *must*
		 * device_power_down() now.  Otherwise, drivers for
@@ -1489,7 +1488,6 @@ int kernel_kexec(void)
		enable_nonboot_cpus();
		device_power_up(PMSG_RESTORE);
 Resume_devices:
		device_pm_unlock();
		device_resume(PMSG_RESTORE);
 Resume_console:
		resume_console();
+3 −18
Original line number Diff line number Diff line
@@ -215,8 +215,6 @@ static int create_image(int platform_mode)
	if (error)
		return error;

	device_pm_lock();

	/* At this point, device_suspend() has been called, but *not*
	 * device_power_down(). We *must* call device_power_down() now.
	 * Otherwise, drivers for some devices (e.g. interrupt controllers)
@@ -227,7 +225,7 @@ static int create_image(int platform_mode)
	if (error) {
		printk(KERN_ERR "PM: Some devices failed to power down, "
			"aborting hibernation\n");
		goto Unlock;
		return error;
	}

	error = platform_pre_snapshot(platform_mode);
@@ -280,9 +278,6 @@ static int create_image(int platform_mode)
	device_power_up(in_suspend ?
		(error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE);

 Unlock:
	device_pm_unlock();

	return error;
}

@@ -344,13 +339,11 @@ static int resume_target_kernel(bool platform_mode)
{
	int error;

	device_pm_lock();

	error = device_power_down(PMSG_QUIESCE);
	if (error) {
		printk(KERN_ERR "PM: Some devices failed to power down, "
			"aborting resume\n");
		goto Unlock;
		return error;
	}

	error = platform_pre_restore(platform_mode);
@@ -403,9 +396,6 @@ static int resume_target_kernel(bool platform_mode)

	device_power_up(PMSG_RECOVER);

 Unlock:
	device_pm_unlock();

	return error;
}

@@ -464,11 +454,9 @@ int hibernation_platform_enter(void)
		goto Resume_devices;
	}

	device_pm_lock();

	error = device_power_down(PMSG_HIBERNATE);
	if (error)
		goto Unlock;
		goto Resume_devices;

	error = hibernation_ops->prepare();
	if (error)
@@ -493,9 +481,6 @@ int hibernation_platform_enter(void)

	device_power_up(PMSG_RESTORE);

 Unlock:
	device_pm_unlock();

 Resume_devices:
	entering_platform_hibernation = false;
	device_resume(PMSG_RESTORE);
+1 −6
Original line number Diff line number Diff line
@@ -289,12 +289,10 @@ static int suspend_enter(suspend_state_t state)
{
	int error;

	device_pm_lock();

	if (suspend_ops->prepare) {
		error = suspend_ops->prepare();
		if (error)
			goto Done;
			return error;
	}

	error = device_power_down(PMSG_SUSPEND);
@@ -343,9 +341,6 @@ static int suspend_enter(suspend_state_t state)
	if (suspend_ops->finish)
		suspend_ops->finish();

 Done:
	device_pm_unlock();

	return error;
}