Commit 775b64d2 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki Committed by Greg Kroah-Hartman

PM: Acquire device locks on suspend

This patch reorganizes the way suspend and resume notifications are
sent to drivers.  The major changes are that now the PM core acquires
every device semaphore before calling the methods, and calls to
device_add() during suspends will fail, while calls to device_del()
during suspends will block.

It also provides a way to safely remove a suspended device with the
help of the PM core, by using the device_pm_schedule_removal() callback
introduced specifically for this purpose, and updates two drivers (msr
and cpuid) that need to use it.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 7a83d456
...@@ -157,15 +157,15 @@ static int __cpuinit cpuid_class_cpu_callback(struct notifier_block *nfb, ...@@ -157,15 +157,15 @@ static int __cpuinit cpuid_class_cpu_callback(struct notifier_block *nfb,
switch (action) { switch (action) {
case CPU_UP_PREPARE: case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
err = cpuid_device_create(cpu); err = cpuid_device_create(cpu);
break; break;
case CPU_UP_CANCELED: case CPU_UP_CANCELED:
case CPU_UP_CANCELED_FROZEN:
case CPU_DEAD: case CPU_DEAD:
case CPU_DEAD_FROZEN:
cpuid_device_destroy(cpu); cpuid_device_destroy(cpu);
break; break;
case CPU_UP_CANCELED_FROZEN:
destroy_suspended_device(cpuid_class, MKDEV(CPUID_MAJOR, cpu));
break;
} }
return err ? NOTIFY_BAD : NOTIFY_OK; return err ? NOTIFY_BAD : NOTIFY_OK;
} }
......
...@@ -155,15 +155,15 @@ static int __cpuinit msr_class_cpu_callback(struct notifier_block *nfb, ...@@ -155,15 +155,15 @@ static int __cpuinit msr_class_cpu_callback(struct notifier_block *nfb,
switch (action) { switch (action) {
case CPU_UP_PREPARE: case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
err = msr_device_create(cpu); err = msr_device_create(cpu);
break; break;
case CPU_UP_CANCELED: case CPU_UP_CANCELED:
case CPU_UP_CANCELED_FROZEN:
case CPU_DEAD: case CPU_DEAD:
case CPU_DEAD_FROZEN:
msr_device_destroy(cpu); msr_device_destroy(cpu);
break; break;
case CPU_UP_CANCELED_FROZEN:
destroy_suspended_device(msr_class, MKDEV(MSR_MAJOR, cpu));
break;
} }
return err ? NOTIFY_BAD : NOTIFY_OK; return err ? NOTIFY_BAD : NOTIFY_OK;
} }
......
...@@ -726,11 +726,20 @@ int device_add(struct device *dev) ...@@ -726,11 +726,20 @@ int device_add(struct device *dev)
{ {
struct device *parent = NULL; struct device *parent = NULL;
struct class_interface *class_intf; struct class_interface *class_intf;
int error = -EINVAL; int error;
error = pm_sleep_lock();
if (error) {
dev_warn(dev, "Suspicious %s during suspend\n", __FUNCTION__);
dump_stack();
return error;
}
dev = get_device(dev); dev = get_device(dev);
if (!dev || !strlen(dev->bus_id)) if (!dev || !strlen(dev->bus_id)) {
error = -EINVAL;
goto Error; goto Error;
}
pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id); pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);
...@@ -795,6 +804,7 @@ int device_add(struct device *dev) ...@@ -795,6 +804,7 @@ int device_add(struct device *dev)
} }
Done: Done:
put_device(dev); put_device(dev);
pm_sleep_unlock();
return error; return error;
BusError: BusError:
device_pm_remove(dev); device_pm_remove(dev);
...@@ -905,6 +915,7 @@ void device_del(struct device * dev) ...@@ -905,6 +915,7 @@ void device_del(struct device * dev)
struct device * parent = dev->parent; struct device * parent = dev->parent;
struct class_interface *class_intf; struct class_interface *class_intf;
device_pm_remove(dev);
if (parent) if (parent)
klist_del(&dev->knode_parent); klist_del(&dev->knode_parent);
if (MAJOR(dev->devt)) if (MAJOR(dev->devt))
...@@ -981,7 +992,6 @@ void device_del(struct device * dev) ...@@ -981,7 +992,6 @@ void device_del(struct device * dev)
if (dev->bus) if (dev->bus)
blocking_notifier_call_chain(&dev->bus->bus_notifier, blocking_notifier_call_chain(&dev->bus->bus_notifier,
BUS_NOTIFY_DEL_DEVICE, dev); BUS_NOTIFY_DEL_DEVICE, dev);
device_pm_remove(dev);
kobject_uevent(&dev->kobj, KOBJ_REMOVE); kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj); kobject_del(&dev->kobj);
if (parent) if (parent)
...@@ -1156,14 +1166,11 @@ struct device *device_create(struct class *class, struct device *parent, ...@@ -1156,14 +1166,11 @@ struct device *device_create(struct class *class, struct device *parent,
EXPORT_SYMBOL_GPL(device_create); EXPORT_SYMBOL_GPL(device_create);
/** /**
* device_destroy - removes a device that was created with device_create() * find_device - finds a device that was created with device_create()
* @class: pointer to the struct class that this device was registered with * @class: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered * @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/ */
void device_destroy(struct class *class, dev_t devt) static struct device *find_device(struct class *class, dev_t devt)
{ {
struct device *dev = NULL; struct device *dev = NULL;
struct device *dev_tmp; struct device *dev_tmp;
...@@ -1176,12 +1183,54 @@ void device_destroy(struct class *class, dev_t devt) ...@@ -1176,12 +1183,54 @@ void device_destroy(struct class *class, dev_t devt)
} }
} }
up(&class->sem); up(&class->sem);
return dev;
}
/**
* device_destroy - removes a device that was created with device_create()
* @class: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/
void device_destroy(struct class *class, dev_t devt)
{
struct device *dev;
dev = find_device(class, devt);
if (dev) if (dev)
device_unregister(dev); device_unregister(dev);
} }
EXPORT_SYMBOL_GPL(device_destroy); EXPORT_SYMBOL_GPL(device_destroy);
#ifdef CONFIG_PM_SLEEP
/**
* destroy_suspended_device - asks the PM core to remove a suspended device
* @class: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call notifies the PM core of the necessity to unregister a suspended
* device created with a call to device_create() (devices cannot be
* unregistered directly while suspended, since the PM core holds their
* semaphores at that time).
*
* It can only be called within the scope of a system sleep transition. In
* practice this means it has to be directly or indirectly invoked either by
* a suspend or resume method, or by the PM core (e.g. via
* disable_nonboot_cpus() or enable_nonboot_cpus()).
*/
void destroy_suspended_device(struct class *class, dev_t devt)
{
struct device *dev;
dev = find_device(class, devt);
if (dev)
device_pm_schedule_removal(dev);
}
EXPORT_SYMBOL_GPL(destroy_suspended_device);
#endif /* CONFIG_PM_SLEEP */
/** /**
* device_rename - renames a device * device_rename - renames a device
* @dev: the pointer to the struct device to be renamed * @dev: the pointer to the struct device to be renamed
......
This diff is collapsed.
...@@ -20,6 +20,9 @@ static inline struct device *to_device(struct list_head *entry) ...@@ -20,6 +20,9 @@ static inline struct device *to_device(struct list_head *entry)
extern void device_pm_add(struct device *); extern void device_pm_add(struct device *);
extern void device_pm_remove(struct device *); extern void device_pm_remove(struct device *);
extern void device_pm_schedule_removal(struct device *);
extern int pm_sleep_lock(void);
extern void pm_sleep_unlock(void);
#else /* CONFIG_PM_SLEEP */ #else /* CONFIG_PM_SLEEP */
...@@ -32,6 +35,15 @@ static inline void device_pm_remove(struct device *dev) ...@@ -32,6 +35,15 @@ static inline void device_pm_remove(struct device *dev)
{ {
} }
static inline int pm_sleep_lock(void)
{
return 0;
}
static inline void pm_sleep_unlock(void)
{
}
#endif #endif
#ifdef CONFIG_PM #ifdef CONFIG_PM
......
...@@ -521,6 +521,14 @@ extern struct device *device_create(struct class *cls, struct device *parent, ...@@ -521,6 +521,14 @@ extern struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, const char *fmt, ...) dev_t devt, const char *fmt, ...)
__attribute__((format(printf,4,5))); __attribute__((format(printf,4,5)));
extern void device_destroy(struct class *cls, dev_t devt); extern void device_destroy(struct class *cls, dev_t devt);
#ifdef CONFIG_PM_SLEEP
extern void destroy_suspended_device(struct class *cls, dev_t devt);
#else /* !CONFIG_PM_SLEEP */
static inline void destroy_suspended_device(struct class *cls, dev_t devt)
{
device_destroy(cls, devt);
}
#endif /* !CONFIG_PM_SLEEP */
/* /*
* Platform "fixup" functions - allow the platform to have their say * Platform "fixup" functions - allow the platform to have their say
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment