PM: Handle device registrations during suspend/resume
Modify the PM core to protect its data structures, specifically the dpm_active list, from being corrupted if a child of the currently suspending device is registered concurrently with its ->suspend() callback. In that case, since the new device (the child) is added to dpm_active after its parent, the PM core will attempt to suspend it after the parent, which is wrong. Introduce a new member of struct dev_pm_info, called 'sleeping', and use it to check if the parent of the device being added to dpm_active has been suspended, in which case the device registration fails. Also, use 'sleeping' for checking if the ordering of devices on dpm_active is correct. Introduce variable 'all_sleeping' that will be set to 'true' once all devices have been suspended and make new device registrations fail until 'all_sleeping' is reset to 'false', in order to avoid having unsuspended devices around while the system is going into a sleep state. Remove pm_sleep_rwsem which is not necessary any more. Special thanks to Alan Stern for discussions and suggestions that lead to the creation of this patch. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Pavel Machek <pavel@ucw.cz> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
6bcf19d02a
commit
58aca23226
@@ -54,7 +54,8 @@ static LIST_HEAD(dpm_destroy);
|
||||
|
||||
static DEFINE_MUTEX(dpm_list_mtx);
|
||||
|
||||
static DECLARE_RWSEM(pm_sleep_rwsem);
|
||||
/* 'true' if all devices have been suspended, protected by dpm_list_mtx */
|
||||
static bool all_sleeping;
|
||||
|
||||
int (*platform_enable_wakeup)(struct device *dev, int is_on);
|
||||
|
||||
@@ -62,14 +63,28 @@ int (*platform_enable_wakeup)(struct device *dev, int is_on);
|
||||
* device_pm_add - add a device to the list of active devices
|
||||
* @dev: Device to be added to the list
|
||||
*/
|
||||
void device_pm_add(struct device *dev)
|
||||
int device_pm_add(struct device *dev)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
pr_debug("PM: Adding info for %s:%s\n",
|
||||
dev->bus ? dev->bus->name : "No Bus",
|
||||
kobject_name(&dev->kobj));
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
list_add_tail(&dev->power.entry, &dpm_active);
|
||||
if ((dev->parent && dev->parent->power.sleeping) || all_sleeping) {
|
||||
if (dev->parent->power.sleeping)
|
||||
dev_warn(dev,
|
||||
"parent %s is sleeping, will not add\n",
|
||||
dev->parent->bus_id);
|
||||
else
|
||||
dev_warn(dev, "devices are sleeping, will not add\n");
|
||||
WARN_ON(true);
|
||||
error = -EBUSY;
|
||||
} else {
|
||||
list_add_tail(&dev->power.entry, &dpm_active);
|
||||
}
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,32 +122,6 @@ void device_pm_schedule_removal(struct device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_pm_schedule_removal);
|
||||
|
||||
/**
|
||||
* pm_sleep_lock - mutual exclusion for registration and suspend
|
||||
*
|
||||
* Returns 0 if no suspend is underway and device registration
|
||||
* may proceed, otherwise -EBUSY.
|
||||
*/
|
||||
int pm_sleep_lock(void)
|
||||
{
|
||||
if (down_read_trylock(&pm_sleep_rwsem))
|
||||
return 0;
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_sleep_unlock - mutual exclusion for registration and suspend
|
||||
*
|
||||
* This routine undoes the effect of device_pm_add_lock
|
||||
* when a device's registration is complete.
|
||||
*/
|
||||
void pm_sleep_unlock(void)
|
||||
{
|
||||
up_read(&pm_sleep_rwsem);
|
||||
}
|
||||
|
||||
|
||||
/*------------------------- Resume routines -------------------------*/
|
||||
|
||||
/**
|
||||
@@ -242,11 +231,13 @@ static int resume_device(struct device *dev)
|
||||
static void dpm_resume(void)
|
||||
{
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
all_sleeping = false;
|
||||
while(!list_empty(&dpm_off)) {
|
||||
struct list_head *entry = dpm_off.next;
|
||||
struct device *dev = to_device(entry);
|
||||
|
||||
list_move_tail(entry, &dpm_active);
|
||||
dev->power.sleeping = false;
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
resume_device(dev);
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
@@ -285,7 +276,6 @@ void device_resume(void)
|
||||
might_sleep();
|
||||
dpm_resume();
|
||||
unregister_dropped_devices();
|
||||
up_write(&pm_sleep_rwsem);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_resume);
|
||||
|
||||
@@ -421,6 +411,9 @@ static int dpm_suspend(pm_message_t state)
|
||||
struct list_head *entry = dpm_active.prev;
|
||||
struct device *dev = to_device(entry);
|
||||
|
||||
WARN_ON(dev->parent && dev->parent->power.sleeping);
|
||||
|
||||
dev->power.sleeping = true;
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
error = suspend_device(dev, state);
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
@@ -432,11 +425,14 @@ static int dpm_suspend(pm_message_t state)
|
||||
(error == -EAGAIN ?
|
||||
" (please convert to suspend_late)" :
|
||||
""));
|
||||
dev->power.sleeping = false;
|
||||
break;
|
||||
}
|
||||
if (!list_empty(&dev->power.entry))
|
||||
list_move(&dev->power.entry, &dpm_off);
|
||||
}
|
||||
if (!error)
|
||||
all_sleeping = true;
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
|
||||
return error;
|
||||
@@ -454,7 +450,6 @@ int device_suspend(pm_message_t state)
|
||||
int error;
|
||||
|
||||
might_sleep();
|
||||
down_write(&pm_sleep_rwsem);
|
||||
error = dpm_suspend(state);
|
||||
if (error)
|
||||
device_resume();
|
||||
|
Reference in New Issue
Block a user