PM: Acquire device locks on suspend
Subsystems (7)
PATCH 1/1 PM: Acquire device locks on suspend
Greg, Andrew,
The appended patch is a replacement for gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch that deadlocked suspend and hibernation on some systems.
Please consider for applying.
Thanks,
Rafael
---
From: Alan Stern <[email protected]>, Rafael J. Wysocki <[email protected]>
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.
It also provides a way to safely remove a suspended device with the help of the PM core, by using the destroy_suspended_device() callback introduced specifically for this purpose, and updates two drivers (msr and cpuid) that need to do that.
--- arch/x86/kernel/cpuid.c | 6 arch/x86/kernel/msr.c | 6 drivers/base/core.c | 67 +++++- drivers/base/power/main.c | 452 ++++++++++++++++++++++++++------------------- drivers/base/power/power.h | 12 + include/linux/device.h | 8 6 files changed, 352 insertions(+), 199 deletions(-)
drivers/base/core.c +59 -8
@@ -726,11 +726,20 @@ int device_add(struct device *dev) {struct device *parent =NULL ;struct class_interface *class_intf; -int error = -EINVAL ; +int error; + + error =pm_sleep_lock (); +if (error) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); +return error; + } dev =get_device (dev); -if (!dev || !strlen (dev->bus_id)) - goto Error; +if (!dev || !strlen (dev->bus_id)) { + error = -EINVAL ; + goto Done; + }pr_debug ("DEV: registering device: ID = '%s'\n" , dev->bus_id);
@@ -795,6 +804,7 @@ int device_add(struct device *dev) } Done:put_device (dev); +pm_sleep_unlock ();return error; BusError:device_pm_remove (dev);
@@ -905,6 +915,13 @@ void device_del(struct device * dev)struct device * parent = dev->parent;struct class_interface *class_intf; +if (pm_sleep_lock ()) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); + }else { +pm_sleep_unlock (); + } +if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))
@@ -1156,14 +1173,11 @@ error: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 * @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_tmp;
@@ -1176,12 +1190,49 @@ void device_destroy(struct class *class, } }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)device_unregister (dev); }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 causes the PM core to release and 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). +*/ +void destroy_suspended_device (struct class *class,dev_t devt) +{ +struct device *dev; + + dev =find_device (class, devt); +if (dev) +device_pm_destroy_suspended (dev); +} +EXPORT_SYMBOL_GPL (destroy_suspended_device); +#endif /* CONFIG_PM_SLEEP */ +/** * device_rename - renames a device * @dev: the pointer to the struct device to be renamed
include/linux/device.h +8 -0
@@ -521,6 +521,14 @@ extern struct device *device_create(stru dev_t devt, const char *fmt, ...) __attribute__((format(printf,4,5)));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
drivers/base/power/power.h +12 -0
@@ -20,6 +20,9 @@ static inline struct device *to_device(sextern void device_pm_add (struct device *);extern void device_pm_remove (struct device *); +extern void device_pm_destroy_suspended (struct device *); +extern int pm_sleep_lock (void ); +extern void pm_sleep_unlock (void );#else /* CONFIG_PM_SLEEP */
@@ -32,6 +35,15 @@ static inline void device_pm_remove(stru { } +static inline int pm_sleep_lock (void ) +{ +return 0 ; +} + +static inline void pm_sleep_unlock (void ) +{ +} +#endif #ifdef CONFIG_PM
drivers/base/power/main.c +267 -185
585 lines not on this page · raw message
arch/x86/kernel/msr.c +3 -3
@@ -155,15 +155,15 @@ static int __cpuinit msr_class_cpu_callbswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =msr_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :msr_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (msr_class,MKDEV (MSR_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
arch/x86/kernel/cpuid.c +3 -3
@@ -157,15 +157,15 @@ static int __cpuinit cpuid_class_cpu_calswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =cpuid_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :cpuid_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (cpuid_class,MKDEV (CPUID_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
Quoted 7 lines · Rafael J. Wysocki
Greg, Andrew,
The appended patch is a replacement for gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch that deadlocked suspend and hibernation on some systems.
Please consider for applying.
This warning message:
drivers/base/core.c void device_del(struct device * dev) · line 921
Quoted 8 lines
drivers/base/core.c +4
-0@@ -905,6 +915,13 @@ void device_del(struct device * dev)struct device * parent = dev->parent;struct class_interface *class_intf; +if (pm_sleep_lock ()) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); + }else {
will unavoidably be triggered by this code:
drivers/base/power/main.c void device_pm_remove(struct device *dev · line 97
Quoted 11 lines
drivers/base/power/main.c +11
-0+void device_pm_destroy_suspended (struct device *dev) +{ +pr_debug ("PM: Removing suspended device %s:%s\n" , + dev->bus ? dev->bus->name :"No Bus" , +kobject_name (&dev->kobj)); +mutex_lock (&dpm_list_mtx); +list_del_init (&dev->power.entry); +mutex_unlock (&dpm_list_mtx); +up (&dev->sem); +device_unregister (dev); +}
since the call to device_del() will occur while the pm_sleep_rwsem is still locked for writing. That's why I suggested not unregistering these devices until after everything else has been resumed and the rwsem has been dropped.
Another thing to watch out for: Just in case somebody ends up calling destroy_suspended_device(dev) from within dev's own resume method, you should interchange the resume_device() and the list_move_tail() calls in dpm_resume().
Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Greg, Andrew,
The appended patch is a replacement for gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch that deadlocked suspend and hibernation on some systems.
Please consider for applying.
This warning message:
drivers/base/core.c void device_del(struct device * dev) · line 921
Quoted 8 lines
drivers/base/core.c +4
-0@@ -905,6 +915,13 @@ void device_del(struct device * dev)struct device * parent = dev->parent;struct class_interface *class_intf; +if (pm_sleep_lock ()) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); + }else {
will unavoidably be triggered by this code:
drivers/base/power/main.c void device_pm_remove(struct device *dev · line 97
Quoted 11 lines
drivers/base/power/main.c +11
-0+void device_pm_destroy_suspended (struct device *dev) +{ +pr_debug ("PM: Removing suspended device %s:%s\n" , + dev->bus ? dev->bus->name :"No Bus" , +kobject_name (&dev->kobj)); +mutex_lock (&dpm_list_mtx); +list_del_init (&dev->power.entry); +mutex_unlock (&dpm_list_mtx); +up (&dev->sem); +device_unregister (dev); +}
since the call to device_del() will occur while the pm_sleep_rwsem is still locked for writing. That's why I suggested not unregistering these devices until after everything else has been resumed and the rwsem has been dropped.
Another thing to watch out for: Just in case somebody ends up calling destroy_suspended_device(dev) from within dev's own resume method, you should interchange the resume_device() and the list_move_tail() calls in dpm_resume().
Alan Stern
Quoted 16 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Greg, Andrew,
The appended patch is a replacement for gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch that deadlocked suspend and hibernation on some systems.
Please consider for applying.
This warning message:
Quoted 4 lines
drivers/base/core.c +0
-0@@ -905,6 +915,13 @@ void device_del(struct device * dev)struct device * parent = dev->parent;struct class_interface *class_intf;
Quoted 6 lines
drivers/base/core.c void device_del(struct device * dev) · line 921
Quoted 4 lines
drivers/base/core.c +4
-0+if (pm_sleep_lock ()) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); + }else {will unavoidably be triggered by this code:
Ah, my fault, sorry.
Quoted 8 lines
Quoted 8 lines
drivers/base/power/main.c +8
-0+void device_pm_destroy_suspended (struct device *dev) +{ +pr_debug ("PM: Removing suspended device %s:%s\n" , + dev->bus ? dev->bus->name :"No Bus" , +kobject_name (&dev->kobj)); +mutex_lock (&dpm_list_mtx); +list_del_init (&dev->power.entry); +mutex_unlock (&dpm_list_mtx);
Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev · line 97
Quoted 3 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); +device_unregister (dev); +}since the call to
device_del()will occur while thepm_sleep_rwsemis still locked for writing. That's why I suggested not unregistering these devices until after everything else has been resumed and the rwsem has been dropped.
Hmm, well. I'll go back to the previous version, then. Sorry for the mess.
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().
However, if we unregister them all at once after releasing pm_sleep_rwsem, that shouldn't be necessary, right?
Rafael
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?
It's still necessary, because destroy_suspended_device() still has to move the device from one list to another. You don't want it to end up on the dpm_locked list.
Alan Stern
Quoted 7 lines · Alan Stern
Quoted 5 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().
Quoted 6 lines
Quoted 2 lines
However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?It's still necessary, because
destroy_suspended_device()still has to move the device from one list to another. You don't want it to end up on thedpm_lockedlist.
Hmm. That means we'd have to do the same thing in dpm_power_up() in case someone calls destroy_suspended_device() from resume_device_early(dev).
Still, even doing that is not enough, since someone can call destroy_suspended_device() from a .suspend() routine and then the device will end up on a wrong list just as well.
Greetings,
Rafael
Quoted 11 lines · Rafael J. Wysocki
Quoted 10 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?
Quoted 6 lines
Quoted 3 lines
It's still necessary, because
destroy_suspended_device()still has to move the device from one list to another. You don't want it to end up on thedpm_lockedlist.Hmm. That means we'd have to do the same thing in
dpm_power_up()in case someone callsdestroy_suspended_device()fromresume_device_early(dev).
Yes.
Quoted 3 lines
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.
That should never happen. The whole idea of destroy_suspended_device() is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.
But it wouldn't hurt to mention in the kerneldoc that destroy_suspended_device() is meant to be called only during a system resume.
Alan Stern
Quoted 11 lines · Rafael J. Wysocki
Quoted 10 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?
Quoted 6 lines
Quoted 3 lines
It's still necessary, because
destroy_suspended_device()still has to move the device from one list to another. You don't want it to end up on thedpm_lockedlist.Hmm. That means we'd have to do the same thing in
dpm_power_up()in case someone callsdestroy_suspended_device()fromresume_device_early(dev).
Yes.
Quoted 3 lines
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.
That should never happen. The whole idea of destroy_suspended_device() is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.
But it wouldn't hurt to mention in the kerneldoc that destroy_suspended_device() is meant to be called only during a system resume.
Alan Stern
Quoted 32 lines · Alan Stern
Quoted 11 lines · Rafael J. Wysocki
Quoted 10 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?Quoted 6 lines
Quoted 3 lines
It's still necessary, because
destroy_suspended_device()still has to move the device from one list to another. You don't want it to end up on thedpm_lockedlist.Hmm. That means we'd have to do the same thing in
dpm_power_up()in case someone callsdestroy_suspended_device()fromresume_device_early(dev).Yes.
Quoted 3 lines
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.That should never happen. The whole idea of
destroy_suspended_device()is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.
Quoted 3 lines
But it wouldn't hurt to mention in the kerneldoc that
destroy_suspended_device()is meant to be called only during a system resume.
Hmm. Please have a look at the appended patch.
I have removed the warning from device_del() and used list_empty() to detect removed devices in the .suspend() routines. Is that viable?
Rafael
--- arch/x86/kernel/cpuid.c | 6 arch/x86/kernel/msr.c | 6 drivers/base/core.c | 67 +++++- drivers/base/power/main.c | 454 ++++++++++++++++++++++++++------------------- drivers/base/power/power.h | 12 + include/linux/device.h | 8 6 files changed, 354 insertions(+), 199 deletions(-)
drivers/base/core.c +52 -8
@@ -726,11 +726,20 @@ int device_add(struct device *dev) {struct device *parent =NULL ;struct class_interface *class_intf; -int error = -EINVAL ; +int error; + + error =pm_sleep_lock (); +if (error) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); +return error; + } dev =get_device (dev); -if (!dev || !strlen (dev->bus_id)) - goto Error; +if (!dev || !strlen (dev->bus_id)) { + error = -EINVAL ; + goto Done; + }pr_debug ("DEV: registering device: ID = '%s'\n" , dev->bus_id);
@@ -795,6 +804,7 @@ int device_add(struct device *dev) } Done:put_device (dev); +pm_sleep_unlock ();return error; BusError:device_pm_remove (dev);
@@ -1156,14 +1173,11 @@ error: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 * @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_tmp;
@@ -1176,12 +1190,49 @@ void device_destroy(struct class *class, } }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)device_unregister (dev); }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 causes the PM core to release and 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). +*/ +void destroy_suspended_device (struct class *class,dev_t devt) +{ +struct device *dev; + + dev =find_device (class, devt); +if (dev) +device_pm_destroy_suspended (dev); +} +EXPORT_SYMBOL_GPL (destroy_suspended_device); +#endif /* CONFIG_PM_SLEEP */ +/** * device_rename - renames a device * @dev: the pointer to the struct device to be renamed
include/linux/device.h +8 -0
@@ -521,6 +521,14 @@ extern struct device *device_create(stru dev_t devt, const char *fmt, ...) __attribute__((format(printf,4,5)));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
drivers/base/power/power.h +12 -0
@@ -20,6 +20,9 @@ static inline struct device *to_device(sextern void device_pm_add (struct device *);extern void device_pm_remove (struct device *); +extern void device_pm_destroy_suspended (struct device *); +extern int pm_sleep_lock (void ); +extern void pm_sleep_unlock (void );#else /* CONFIG_PM_SLEEP */
@@ -32,6 +35,15 @@ static inline void device_pm_remove(stru { } +static inline int pm_sleep_lock (void ) +{ +return 0 ; +} + +static inline void pm_sleep_unlock (void ) +{ +} +#endif #ifdef CONFIG_PM
drivers/base/power/main.c +269 -185
587 lines not on this page · raw message
arch/x86/kernel/msr.c +3 -3
@@ -155,15 +155,15 @@ static int __cpuinit msr_class_cpu_callbswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =msr_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :msr_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (msr_class,MKDEV (MSR_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
arch/x86/kernel/cpuid.c +3 -3
@@ -157,15 +157,15 @@ static int __cpuinit cpuid_class_cpu_calswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =cpuid_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :cpuid_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (cpuid_class,MKDEV (CPUID_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
Quoted 13 lines · Rafael J. Wysocki
Quoted 12 lines · Alan Stern
Quoted 3 lines · Rafael J. Wysocki
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.That should never happen. The whole idea of
destroy_suspended_device()is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.
Quoted 8 lines
Quoted 3 lines
But it wouldn't hurt to mention in the kerneldoc that
destroy_suspended_device()is meant to be called only during a system resume.Hmm. Please have a look at the appended patch.
I have removed the warning from
device_del()and usedlist_empty()to detect removed devices in the .suspend() routines. Is that viable?
It's not good.
The warning in device_del() is vital. It's what will tell people where the problem is when a deadlock occurs during system resume because some driver has mistakenly tried to unregister a device at the wrong time. It would have pointed immediately to the msr driver in the case of the bug Andrew found, for instance.
If you can figure out a way to disable the warning in device_del() for just the one device being unregistered by device_pm_destroy_suspended(), I suppose that would be okay.
Alan Stern
Quoted 13 lines · Rafael J. Wysocki
Quoted 12 lines · Alan Stern
Quoted 3 lines · Rafael J. Wysocki
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.That should never happen. The whole idea of
destroy_suspended_device()is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.
Quoted 8 lines
Quoted 3 lines
But it wouldn't hurt to mention in the kerneldoc that
destroy_suspended_device()is meant to be called only during a system resume.Hmm. Please have a look at the appended patch.
I have removed the warning from
device_del()and usedlist_empty()to detect removed devices in the .suspend() routines. Is that viable?
It's not good.
The warning in device_del() is vital. It's what will tell people where the problem is when a deadlock occurs during system resume because some driver has mistakenly tried to unregister a device at the wrong time. It would have pointed immediately to the msr driver in the case of the bug Andrew found, for instance.
If you can figure out a way to disable the warning in device_del() for just the one device being unregistered by device_pm_destroy_suspended(), I suppose that would be okay.
Alan Stern
Quoted 32 lines · Alan Stern
Quoted 13 lines · Rafael J. Wysocki
Quoted 12 lines · Alan Stern
Quoted 3 lines · Rafael J. Wysocki
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.That should never happen. The whole idea of
destroy_suspended_device()is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.Quoted 8 lines
Quoted 3 lines
But it wouldn't hurt to mention in the kerneldoc that
destroy_suspended_device()is meant to be called only during a system resume.Hmm. Please have a look at the appended patch.
I have removed the warning from
device_del()and usedlist_empty()to detect removed devices in the .suspend() routines. Is that viable?It's not good.
The warning in
device_del()is vital. It's what will tell people where the problem is when a deadlock occurs during system resume because some driver has mistakenly tried to unregister a device at the wrong time. It would have pointed immediately to the msr driver in the case of the bug Andrew found, for instance.
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),
Something like this, perhaps:
@@ -905,6 +915,18 @@ void device_del(struct device * dev)
struct device * parent = dev->parent; struct class_interface *class_intf;
+ if (down_trylock(&dev->sem)) {
+ if (pm_sleep_lock()) {
+ dev_warn(dev, "Illegal %s during suspend\n",
+ __FUNCTION__);
+ dump_stack();
+ } else {
+ pm_sleep_unlock();
+ }
+ } else {
+ up(&dev->sem);
+ }
+
if (parent)
klist_del(&dev->knode_parent);
if (MAJOR(dev->devt))
Quoted 1 line
I suppose that would be okay.
Rafael
Quoted 15 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +5
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack();
Review void device_del(struct device * dev) · line 932
Quoted 10 lines
(quoted diff) +7
-0+ } else { + pm_sleep_unlock(); + } + } else { + up(&dev->sem); + } + if (parent) klist_del(&dev->knode_parent); if (MAJOR(dev->devt))
Bizarre, but it should work. Be sure to include plenty of explanatory comments -- otherwise nobody will be able to figure it out! :-)
Alan Stern
Quoted 15 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +5
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack();
Review void device_del(struct device * dev) · line 932
Quoted 10 lines
(quoted diff) +7
-0+ } else { + pm_sleep_unlock(); + } + } else { + up(&dev->sem); + } + if (parent) klist_del(&dev->knode_parent); if (MAJOR(dev->devt))
Bizarre, but it should work. Be sure to include plenty of explanatory comments -- otherwise nobody will be able to figure it out! :-)
Alan Stern
Quoted 21 lines · Alan Stern
Quoted 19 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +9
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack(); + } else { + pm_sleep_unlock(); + } + } else {
Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev
Quoted 6 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); + } +if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))Bizarre, but it should work.
OK
Still, shouldn't we fail the removal of the device apart from giving the warning?
Quoted 1 line
Be sure to include plenty of explanatory comments
I will.
I think that code can be moved to its own function in drivers/base/power/main.c, btw.
Quoted 1 line
-- otherwise nobody will be able to figure it out! :-)
Well, I guess so. :-)
Thanks,
Rafael
Quoted 31 lines · Rafael J. Wysocki
Quoted 21 lines · Alan Stern
Quoted 19 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +9
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack(); + } else { + pm_sleep_unlock(); + } + } else {Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev
Quoted 6 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); + } +if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))Bizarre, but it should work.
Quoted 4 lines
OK
Still, shouldn't we fail the removal of the device apart from giving the warning?
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in device_pm_remove() if we allow all of the operations in device_del() preceding it to be performed.
Shouldn't we just take pm_sleep_rwsem in device_del() upfront and block on that if locked?
Rafael
Quoted 37 lines · Rafael J. Wysocki
Quoted 31 lines · Rafael J. Wysocki
Quoted 21 lines · Alan Stern
Quoted 19 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +9
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack(); + } else { + pm_sleep_unlock(); + } + } else {Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev
Quoted 6 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); + } +if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))Bizarre, but it should work.
Quoted 4 lines
OK
Still, shouldn't we fail the removal of the device apart from giving the warning?
Quoted 6 lines
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in
device_pm_remove()if we allow all of the operations indevice_del()preceding it to be performed.Shouldn't we just take
pm_sleep_rwsemindevice_del()upfront and block on that if locked?
Ugh, the $subject patch looks like a city of races. I'm struggling to close them all, but it's getting complicated.
I'll post the result in a new thread.
Thanks,
Rafael
Quoted 37 lines · Rafael J. Wysocki
Quoted 31 lines · Rafael J. Wysocki
Quoted 21 lines · Alan Stern
Quoted 19 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +9
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack(); + } else { + pm_sleep_unlock(); + } + } else {Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev
Quoted 6 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); + } +if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))Bizarre, but it should work.
Quoted 4 lines
OK
Still, shouldn't we fail the removal of the device apart from giving the warning?
Quoted 6 lines
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in
device_pm_remove()if we allow all of the operations indevice_del()preceding it to be performed.Shouldn't we just take
pm_sleep_rwsemindevice_del()upfront and block on that if locked?
Ugh, the $subject patch looks like a city of races. I'm struggling to close them all, but it's getting complicated.
I'll post the result in a new thread.
Thanks,
Rafael
Quoted 6 lines · Rafael J. Wysocki
Quoted 2 lines
Still, shouldn't we fail the removal of the device apart from giving the warning?
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in
device_pm_remove()if we allow all of the operations indevice_del()preceding it to be performed.
That's not the issue. We _don't_ allow all of the operations in device_del() preceding the call to device_pm_remove(). In particular, the call to the device's driver's remove method will deadlock because device_release_driver() always has to acquire dev->sem.
Quoted 2 lines
Shouldn't we just take
pm_sleep_rwsemindevice_del()upfront and block on that if locked?
No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.
Alan Stern
Quoted 6 lines · Rafael J. Wysocki
Quoted 2 lines
Still, shouldn't we fail the removal of the device apart from giving the warning?
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in
device_pm_remove()if we allow all of the operations indevice_del()preceding it to be performed.
That's not the issue. We _don't_ allow all of the operations in device_del() preceding the call to device_pm_remove(). In particular, the call to the device's driver's remove method will deadlock because device_release_driver() always has to acquire dev->sem.
Quoted 2 lines
Shouldn't we just take
pm_sleep_rwsemindevice_del()upfront and block on that if locked?
No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.
Alan Stern
Quoted 14 lines · Alan Stern
Quoted 6 lines · Rafael J. Wysocki
Quoted 2 lines
Still, shouldn't we fail the removal of the device apart from giving the warning?
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in
device_pm_remove()if we allow all of the operations indevice_del()preceding it to be performed.That's not the issue. We _don't_ allow all of the operations in
device_del()preceding the call todevice_pm_remove(). In particular, the call to the device's driver's remove method will deadlock becausedevice_release_driver()always has to acquiredev->sem.
Quoted 7 lines
Quoted 2 lines
Shouldn't we just take
pm_sleep_rwsemindevice_del()upfront and block on that if locked?No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call
device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.
Still, if we do that, we won't need to acquire dev->sem in device_pm_remove() any more. Apart from this, by acqiring pm_sleep_rwsem for reading in device_del() we can prevent a suspend from starting while the device is being removed.
Consider, for example, the scenario possible with the $subject patch:
- device_del() starts and notices pm_sleep_rwsem unlocked, so the warning is not printed
- it proceeds and everything before device_pm_remove() succeeds
- now, device_suspend() is called and locks dev->sem
- device_del() calls device_pm_remove() and blocks on that with the device partialy removed
I think we should prevent this from happening.
Rafael
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call
device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.Still, if we do that, we won't need to acquire
dev->semindevice_pm_remove()any more.
There's a window in lock_all_devices() when dpm_list_mtx isn't held. We don't want device_pm_remove() taking an already-locked device off the dpm_locked list at that time. So we do need to acquire dev->sem in device_pm_remove().
Quoted 4 lines
Apart from this, by acqiring
pm_sleep_rwsemfor reading indevice_del()we can prevent a suspend from starting while the device is being removed.
Quoted 8 lines
Consider, for example, the scenario possible with the $subject patch:
-device_del()starts and noticespm_sleep_rwsemunlocked, so the warning is not printed
- it proceeds and everything beforedevice_pm_remove()succeeds
- now,device_suspend()is called and locksdev->sem
-device_del()callsdevice_pm_remove()and blocks on that with the device partialy removed
I think we should prevent this from happening.
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
Alan Stern
Quoted 7 lines · Alan Stern
Quoted 5 lines · Rafael J. Wysocki
Quoted 4 lines
No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call
device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.
Quoted 7 lines
Quoted 2 lines
Still, if we do that, we won't need to acquire
dev->semindevice_pm_remove()any more.There's a window in
lock_all_devices()whendpm_list_mtxisn't held. We don't wantdevice_pm_remove()taking an already-locked device off thedpm_lockedlist at that time. So we do need to acquiredev->semindevice_pm_remove().
Not if pm_sleep_rwsem is held by device_del(), since in that case we won't reach lock_all_devices() (device_add() calls device_pm_remove() under pm_sleep_rwsem already).
Quoted 13 lines
Quoted 4 lines
Apart from this, by acqiring
pm_sleep_rwsemfor reading indevice_del()we can prevent a suspend from starting while the device is being removed.Quoted 8 lines
Consider, for example, the scenario possible with the $subject patch:
-device_del()starts and noticespm_sleep_rwsemunlocked, so the warning is not printed
- it proceeds and everything beforedevice_pm_remove()succeeds
- now,device_suspend()is called and locksdev->sem
-device_del()callsdevice_pm_remove()and blocks on that with the device partialy removed
I think we should prevent this from happening.
Quoted 2 lines
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
In that case, we'll attempt to call the device's .suspend() and .resume() routines, but we shouldn't do that, IMHO.
Rafael
Quoted 5 lines · Rafael J. Wysocki
Quoted 2 lines
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
In that case, we'll attempt to call the device's .suspend() and .resume() routines, but we shouldn't do that, IMHO.
I don't see anything wrong with that since the driver must be prepared to handle that even in the regular case, it's the only thing you can
guarantee: no more method calls after removal finishes. Am I totally misunderstanding things?
johannes
A message that is not in the archive (sent off-list or not yet received): 49505.::ffff:[email protected]
Quoted 5 lines · Johannes Berg
Quoted 3 lines · Rafael J. Wysocki
Quoted 2 lines
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
Quoted 7 lines
Quoted 2 lines
In that case, we'll attempt to call the device's .suspend() and .resume() routines, but we shouldn't do that, IMHO.
I don't see anything wrong with that since the driver must be prepared to handle that even in the regular case, it's the only thing you can
guarantee: no more method calls after removal finishes. Am I totally misunderstanding things?
Well, we are towards the end of device removal at this point, having called bus_remove_device(dev) for example, but still we've got it on dpm_active ...
This may not be technically wrong (ie. we should be able to recover from that), but it seems conceptually wrong and with pm_sleep_rwsem in place it can be avoided.
Rafael
Quoted 14 lines · Rafael J. Wysocki
Quoted 5 lines · Johannes Berg
Quoted 3 lines · Rafael J. Wysocki
Quoted 2 lines
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
Quoted 7 lines
Quoted 2 lines
In that case, we'll attempt to call the device's .suspend() and .resume() routines, but we shouldn't do that, IMHO.
I don't see anything wrong with that since the driver must be prepared to handle that even in the regular case, it's the only thing you can
guarantee: no more method calls after removal finishes. Am I totally misunderstanding things?
Quoted 6 lines
Well, we are towards the end of device removal at this point, having called
bus_remove_device(dev)for example, but still we've got it ondpm_active...This may not be technically wrong (ie. we should be able to recover from that), but it seems conceptually wrong and with
pm_sleep_rwsemin place it can be avoided.
No, it can't, without major complications.
Well, I think I'll just send a patch that should work most of the time ...
Rafael
Quoted 14 lines · Rafael J. Wysocki
Quoted 5 lines · Johannes Berg
Quoted 3 lines · Rafael J. Wysocki
Quoted 2 lines
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
Quoted 7 lines
Quoted 2 lines
In that case, we'll attempt to call the device's .suspend() and .resume() routines, but we shouldn't do that, IMHO.
I don't see anything wrong with that since the driver must be prepared to handle that even in the regular case, it's the only thing you can
guarantee: no more method calls after removal finishes. Am I totally misunderstanding things?
Quoted 6 lines
Well, we are towards the end of device removal at this point, having called
bus_remove_device(dev)for example, but still we've got it ondpm_active...This may not be technically wrong (ie. we should be able to recover from that), but it seems conceptually wrong and with
pm_sleep_rwsemin place it can be avoided.
No, it can't, without major complications.
Well, I think I'll just send a patch that should work most of the time ...
Rafael
Quoted 5 lines · Johannes Berg
Quoted 3 lines · Rafael J. Wysocki
Quoted 2 lines
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
Quoted 7 lines
Quoted 2 lines
In that case, we'll attempt to call the device's .suspend() and .resume() routines, but we shouldn't do that, IMHO.
I don't see anything wrong with that since the driver must be prepared to handle that even in the regular case, it's the only thing you can
guarantee: no more method calls after removal finishes. Am I totally misunderstanding things?
Well, we are towards the end of device removal at this point, having called bus_remove_device(dev) for example, but still we've got it on dpm_active ...
This may not be technically wrong (ie. we should be able to recover from that), but it seems conceptually wrong and with pm_sleep_rwsem in place it can be avoided.
Rafael
Let's try to summarize the main issues here:
1. We want the PM core to lock all devices during suspend and hibernation. This implies that registration and unregistration at such times can't work, because they need to lock the device sem in order to make probe and remove method calls.
2. Registration calls can be failed, with an error message in the system log. However unregistration calls cannot fail. They _can_ block until the system resumes, but if the unregistration call was made from within a suspend or resume method it will deadlock. This seems inescapable, but at least we should print an error in the log so the offending driver can be identified.
3. In response to 2, the PM core should have a special routine for unregistering devices while a suspend is in progress. Rafael proposed that the core should unlock the device to permit the call to go through. This seems dangerous to me; I would prefer to leave the locks in place and defer the unregistration until after the system is back up and the locks have all been dropped. This would avoid all sorts of locking, deadlock, and mutual exclusion problems.
(As a side note: destroy_suspended_device() has a rather limited interface anyway, since it can handle only devices that were created by create_device().)
4. Rafael pointed out that unregistration can occur concurrently with system suspend. When this happens we can end up trying to suspend a device which has already been through bus_remove_device(), because it hasn't yet been removed from the dpm_active list. He proposes we make unregistration block system suspend, just as registration does.
I don't see 4 as a real problem. Starting an unregistration before the suspend and finishing it afterward should be okay. Once a device has gone through bus_remove_device() it hasn't got a suspend method any more, so trying to suspend it won't do anything at all -- the tests in suspend_device() will all fail. Conversely, if bus_remove_device() hasn't run yet then we would end up calling the driver's suspend method before the device_del() call returns. As Johannes pointed out, this is a normal race that would exist anyway.
On the other hand, having unregistration block system suspend wouldn't actually be wrong. I simply don't think it is necessary. But note that making the two mutually exclusive would complicate Rafael's synchronous approach for destroy_suspended_device().
5. All the discussion about pm_sleep_rwsem and so on is implementation details. Once we have settled on the correct approach for 1-4, the implementation should be relatively easy.
Alan Stern
Let's try to summarize the main issues here:
1. We want the PM core to lock all devices during suspend and hibernation. This implies that registration and unregistration at such times can't work, because they need to lock the device sem in order to make probe and remove method calls.
2. Registration calls can be failed, with an error message in the system log. However unregistration calls cannot fail. They _can_ block until the system resumes, but if the unregistration call was made from within a suspend or resume method it will deadlock. This seems inescapable, but at least we should print an error in the log so the offending driver can be identified.
3. In response to 2, the PM core should have a special routine for unregistering devices while a suspend is in progress. Rafael proposed that the core should unlock the device to permit the call to go through. This seems dangerous to me; I would prefer to leave the locks in place and defer the unregistration until after the system is back up and the locks have all been dropped. This would avoid all sorts of locking, deadlock, and mutual exclusion problems.
(As a side note: destroy_suspended_device() has a rather limited interface anyway, since it can handle only devices that were created by create_device().)
4. Rafael pointed out that unregistration can occur concurrently with system suspend. When this happens we can end up trying to suspend a device which has already been through bus_remove_device(), because it hasn't yet been removed from the dpm_active list. He proposes we make unregistration block system suspend, just as registration does.
I don't see 4 as a real problem. Starting an unregistration before the suspend and finishing it afterward should be okay. Once a device has gone through bus_remove_device() it hasn't got a suspend method any more, so trying to suspend it won't do anything at all -- the tests in suspend_device() will all fail. Conversely, if bus_remove_device() hasn't run yet then we would end up calling the driver's suspend method before the device_del() call returns. As Johannes pointed out, this is a normal race that would exist anyway.
On the other hand, having unregistration block system suspend wouldn't actually be wrong. I simply don't think it is necessary. But note that making the two mutually exclusive would complicate Rafael's synchronous approach for destroy_suspended_device().
5. All the discussion about pm_sleep_rwsem and so on is implementation details. Once we have settled on the correct approach for 1-4, the implementation should be relatively easy.
Alan Stern
Quoted 43 lines · Alan Stern
Let's try to summarize the main issues here:
1. We want the PM core to lock all devices during suspend and hibernation. This implies that registration and unregistration at such times can't work, because they need to lock the device sem in order to make probe and remove method calls.
2. Registration calls can be failed, with an error message in the system log. However unregistration calls cannot fail. They _can_ block until the system resumes, but if the unregistration call was made from within a suspend or resume method it will deadlock. This seems inescapable, but at least we should print an error in the log so the offending driver can be identified.
3. In response to 2, the PM core should have a special routine for unregistering devices while a suspend is in progress. Rafael proposed that the core should unlock the device to permit the call to go through. This seems dangerous to me; I would prefer to leave the locks in place and defer the unregistration until after the system is back up and the locks have all been dropped. This would avoid all sorts of locking, deadlock, and mutual exclusion problems.
(As a side note:
destroy_suspended_device()has a rather limited interface anyway, since it can handle only devices that were created bycreate_device().)4. Rafael pointed out that unregistration can occur concurrently with system suspend. When this happens we can end up trying to suspend a device which has already been through
bus_remove_device(), because it hasn't yet been removed from thedpm_activelist. He proposes we make unregistration block system suspend, just as registration does.I don't see 4 as a real problem. Starting an unregistration before the suspend and finishing it afterward should be okay. Once a device has gone through
bus_remove_device()it hasn't got a suspend method any more, so trying to suspend it won't do anything at all -- the tests insuspend_device()will all fail. Conversely, ifbus_remove_device()hasn't run yet then we would end up calling the driver's suspend method before thedevice_del()call returns. As Johannes pointed out, this is a normal race that would exist anyway.
Quoted 8 lines
On the other hand, having unregistration block system suspend wouldn't actually be wrong. I simply don't think it is necessary. But note that making the two mutually exclusive would complicate Rafael's synchronous approach for
destroy_suspended_device().5. All the discussion about
pm_sleep_rwsemand so on is implementation details. Once we have settled on the correct approach for 1-4, the implementation should be relatively easy.
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
Thanks,
Rafael
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
First, note that the list manipulations in dpm_suspend(), device_power_down(), and so on aren't protected by dpm_list_mtx. So your patch could corrupt the list pointers. Are you assuming that no other threads can be running at this time?
Note also that device_pm_destroy_suspended() does up(&dev->sem), but it doesn't know whether or not dev->sem was locked to begin with.
Do you want to rule out the possibility of a driver's suspend or remove methods calling destroy_suspended_device() on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.
Alan Stern
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
First, note that the list manipulations in dpm_suspend(), device_power_down(), and so on aren't protected by dpm_list_mtx. So your patch could corrupt the list pointers. Are you assuming that no other threads can be running at this time?
Note also that device_pm_destroy_suspended() does up(&dev->sem), but it doesn't know whether or not dev->sem was locked to begin with.
Do you want to rule out the possibility of a driver's suspend or remove methods calling destroy_suspended_device() on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.
Alan Stern
Quoted 10 lines · Alan Stern
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
First, note that the list manipulations in
dpm_suspend(),device_power_down(), and so on aren't protected bydpm_list_mtx. So your patch could corrupt the list pointers.
Yes, they need the locking. I have overlooked that, mostly because the locking was removed by gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch too (because you assumed there woundn't be any need to remove a device during a suspend, right?).
Quoted 1 line
Are you assuming that no other threads can be running at this time?
No, I'm not.
Quoted 2 lines
Note also that
device_pm_destroy_suspended()doesup(&dev->sem), but it doesn't know whether or notdev->semwas locked to begin with.
Do you mean it might have been released already by another thread calling device_pm_destroy_suspended() on the same device?
Quoted 6 lines
Do you want to rule out the possibility of a driver's suspend or remove methods calling
destroy_suspended_device()on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.
Well, the asynchronous apprach has the problem that the device may end up on a wrong list when removed by one of the .suspend() callbacks (and I don't see how to avoid that without extra complexity). Perhaps that's something we can live with, though.
One more question: is there any particular reason not to call device_pm_remove() at the beginning of device_del()?
Rafael
Quoted 8 lines · Rafael J. Wysocki
Quoted 7 lines · Alan Stern
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
Quoted 8 lines
Quoted 3 lines
First, note that the list manipulations in
dpm_suspend(),device_power_down(), and so on aren't protected bydpm_list_mtx. So your patch could corrupt the list pointers.Yes, they need the locking. I have overlooked that, mostly because the locking was removed by gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch too (because you assumed there woundn't be any need to remove a device during a suspend, right?).
Right.
Quoted 9 lines
Quoted 1 line
Are you assuming that no other threads can be running at this time?
No, I'm not.
Quoted 2 lines
Note also that
device_pm_destroy_suspended()doesup(&dev->sem), but it doesn't know whether or notdev->semwas locked to begin with.Do you mean it might have been released already by another thread calling
device_pm_destroy_suspended()on the same device?
I was thinking that it might be called before lock_all_devices().
However let's ignore that possibility and simplify the discussion by assuming that destroy_suspended_device() is never called except by a suspend or resume method for that device or one of its ancestors. (This still leaves the possibility that it might get called by mistake during a runtime suspend or resume...)
Quoted 11 lines
Quoted 6 lines
Do you want to rule out the possibility of a driver's suspend or remove methods calling
destroy_suspended_device()on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.Well, the asynchronous apprach has the problem that the device may end up on a wrong list when removed by one of the .suspend() callbacks (and I don't see how to avoid that without extra complexity). Perhaps that's something we can live with, though.
The same problem affects the synchronous approach. We can fix it by having dpm_suspend() do the list_move() before calling suspend_device(). Then if the suspend fails move the device back.
Quoted 2 lines
One more question: is there any particular reason not to call
device_pm_remove()at the beginning ofdevice_del()?
I think it's done this way to avoid having a window where the device isn't on a PM list and is still owned by the bus and the driver. But if a suspend occurs during that window, it shouldn't matter that the device will be left unsuspended. After all, the same thing would have happened if the suspend occurred after bus_remove_device().
So no, there shouldn't be a problem with moving the call.
Alan Stern
Quoted 8 lines · Rafael J. Wysocki
Quoted 7 lines · Alan Stern
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
Quoted 8 lines
Quoted 3 lines
First, note that the list manipulations in
dpm_suspend(),device_power_down(), and so on aren't protected bydpm_list_mtx. So your patch could corrupt the list pointers.Yes, they need the locking. I have overlooked that, mostly because the locking was removed by gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch too (because you assumed there woundn't be any need to remove a device during a suspend, right?).
Right.
Quoted 9 lines
Quoted 1 line
Are you assuming that no other threads can be running at this time?
No, I'm not.
Quoted 2 lines
Note also that
device_pm_destroy_suspended()doesup(&dev->sem), but it doesn't know whether or notdev->semwas locked to begin with.Do you mean it might have been released already by another thread calling
device_pm_destroy_suspended()on the same device?
I was thinking that it might be called before lock_all_devices().
However let's ignore that possibility and simplify the discussion by assuming that destroy_suspended_device() is never called except by a suspend or resume method for that device or one of its ancestors. (This still leaves the possibility that it might get called by mistake during a runtime suspend or resume...)
Quoted 11 lines
Quoted 6 lines
Do you want to rule out the possibility of a driver's suspend or remove methods calling
destroy_suspended_device()on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.Well, the asynchronous apprach has the problem that the device may end up on a wrong list when removed by one of the .suspend() callbacks (and I don't see how to avoid that without extra complexity). Perhaps that's something we can live with, though.
The same problem affects the synchronous approach. We can fix it by having dpm_suspend() do the list_move() before calling suspend_device(). Then if the suspend fails move the device back.
Quoted 2 lines
One more question: is there any particular reason not to call
device_pm_remove()at the beginning ofdevice_del()?
I think it's done this way to avoid having a window where the device isn't on a PM list and is still owned by the bus and the driver. But if a suspend occurs during that window, it shouldn't matter that the device will be left unsuspended. After all, the same thing would have happened if the suspend occurred after bus_remove_device().
So no, there shouldn't be a problem with moving the call.
Alan Stern
Quoted 25 lines · Alan Stern
Quoted 8 lines · Rafael J. Wysocki
Quoted 7 lines · Alan Stern
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
Quoted 8 lines
Quoted 3 lines
First, note that the list manipulations in
dpm_suspend(),device_power_down(), and so on aren't protected bydpm_list_mtx. So your patch could corrupt the list pointers.Yes, they need the locking. I have overlooked that, mostly because the locking was removed by gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch too (because you assumed there woundn't be any need to remove a device during a suspend, right?).
Right.
Quoted 4 lines
Quoted 1 line
Are you assuming that no other threads can be running at this time?
No, I'm not.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
Note also that
device_pm_destroy_suspended()doesup(&dev->sem), but it doesn't know whether or notdev->semwas locked to begin with.Do you mean it might have been released already by another thread calling
device_pm_destroy_suspended()on the same device?I was thinking that it might be called before
lock_all_devices().
I've added pm_sleep_start_end_mtx and the locking dance in device_pm_destroy_suspended() specifically to prevent this from happening.
Quoted 3 lines
However let's ignore that possibility and simplify the discussion by assuming that
destroy_suspended_device()is never called except by a suspend or resume method for that device or one of its ancestors.
It may also be called by one of the CPU hotplug notifiers.
Quoted 10 lines
(This still leaves the possibility that it might get called by mistake during a runtime suspend or resume...)
Quoted 7 lines
Quoted 6 lines
Do you want to rule out the possibility of a driver's suspend or remove methods calling
destroy_suspended_device()on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.
Quoted 6 lines
Quoted 4 lines
Well, the asynchronous apprach has the problem that the device may end up on a wrong list when removed by one of the .suspend() callbacks (and I don't see how to avoid that without extra complexity). Perhaps that's something we can live with, though.
The same problem affects the synchronous approach.
No, it doesn't as of the $subject patch (the list_empty() tests should help).
Quoted 2 lines
We can fix it by having
dpm_suspend()do thelist_move()before callingsuspend_device(). Then if the suspend fails move the device back.
Yes, we can.
Quoted 10 lines
Quoted 2 lines
One more question: is there any particular reason not to call
device_pm_remove()at the beginning ofdevice_del()?I think it's done this way to avoid having a window where the device isn't on a PM list and is still owned by the bus and the driver. But if a suspend occurs during that window, it shouldn't matter that the device will be left unsuspended. After all, the same thing would have happened if the suspend occurred after
bus_remove_device().So no, there shouldn't be a problem with moving the call.
Okay, well, now I'm leaning towards the asynchronous approach.
I'll prepare a new patch and send it later today.
Thanks,
Rafael
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Quoted 2 lines
Do you mean it might have been released already by another thread calling
device_pm_destroy_suspended()on the same device?I was thinking that it might be called before
lock_all_devices().I've added
pm_sleep_start_end_mtxand the locking dance indevice_pm_destroy_suspended()specifically to prevent this from happening.
Yes, I see. What about the fact that device_suspend() locks pm_sleep_start_end_mtx first and pm_sleep_rwsem second, whereas device_pm_destroy_suspended() locks pm_sleep_start_end_mtx while holding pm_sleep_rwsem? That should produce a lockdep warning.
Quoted 5 lines
Quoted 3 lines
However let's ignore that possibility and simplify the discussion by assuming that
destroy_suspended_device()is never called except by a suspend or resume method for that device or one of its ancestors.It may also be called by one of the CPU hotplug notifiers.
This suggests another approach, simpler but not as general. So far all the problems we've seen have been associated with those CPU notifiers. Suppose the notifications about CPUs that failed to come back up were delayed until after the resume was complete? Drivers like msr would then have to check in their resume handler whether the CPU was actually up, but no other changes would be needed.
In this way we could fix the immediate problem. It wouldn't help with other sorts of devices that need to be unregistered during a suspend, though.
Quoted 3 lines
Okay, well, now I'm leaning towards the asynchronous approach.
I'll prepare a new patch and send it later today.
Okay.
Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Quoted 2 lines
Do you mean it might have been released already by another thread calling
device_pm_destroy_suspended()on the same device?I was thinking that it might be called before
lock_all_devices().I've added
pm_sleep_start_end_mtxand the locking dance indevice_pm_destroy_suspended()specifically to prevent this from happening.
Yes, I see. What about the fact that device_suspend() locks pm_sleep_start_end_mtx first and pm_sleep_rwsem second, whereas device_pm_destroy_suspended() locks pm_sleep_start_end_mtx while holding pm_sleep_rwsem? That should produce a lockdep warning.
Quoted 5 lines
Quoted 3 lines
However let's ignore that possibility and simplify the discussion by assuming that
destroy_suspended_device()is never called except by a suspend or resume method for that device or one of its ancestors.It may also be called by one of the CPU hotplug notifiers.
This suggests another approach, simpler but not as general. So far all the problems we've seen have been associated with those CPU notifiers. Suppose the notifications about CPUs that failed to come back up were delayed until after the resume was complete? Drivers like msr would then have to check in their resume handler whether the CPU was actually up, but no other changes would be needed.
In this way we could fix the immediate problem. It wouldn't help with other sorts of devices that need to be unregistered during a suspend, though.
Quoted 3 lines
Okay, well, now I'm leaning towards the asynchronous approach.
I'll prepare a new patch and send it later today.
Okay.
Alan Stern
Quoted 1 line · Alan Stern
On Mon, 7 Jan 2008, Rafael J. Wysocki wrote:
[--snip--]
Quoted 6 lines
Quoted 4 lines
Okay, well, now I'm leaning towards the asynchronous approach.
I'll prepare a new patch and send it later today.
Okay.
Appended is what I managed to put together today.
It probably still has some problems, but I'm not seeing them right now (too tired). At least, it doesn't break my system. ;-)
Please review.
Thanks,
Rafael
---
From: Alan Stern <[email protected]>, Rafael J. Wysocki <[email protected]>
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.
--- arch/x86/kernel/cpuid.c | 6 arch/x86/kernel/msr.c | 6 drivers/base/core.c | 60 ++++- drivers/base/power/main.c | 504 +++++++++++++++++++++++++++++---------------- drivers/base/power/power.h | 12 + include/linux/device.h | 8 6 files changed, 408 insertions(+), 188 deletions(-)
drivers/base/core.c +52 -8
@@ -726,11 +726,20 @@ int device_add(struct device *dev) {struct device *parent =NULL ;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); -if (!dev || !strlen (dev->bus_id)) +if (!dev || !strlen (dev->bus_id)) { + error = -EINVAL ; goto Error; + }pr_debug ("DEV: registering device: ID = '%s'\n" , dev->bus_id);
@@ -795,6 +804,7 @@ int device_add(struct device *dev) } Done:put_device (dev); +pm_sleep_unlock ();return error; BusError:device_pm_remove (dev);
@@ -905,6 +915,7 @@ void device_del(struct device * dev)struct device * parent = dev->parent;struct class_interface *class_intf; +device_pm_remove (dev);if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))
@@ -981,7 +992,6 @@ void device_del(struct device * dev)if (dev->bus)blocking_notifier_call_chain (&dev->bus->bus_notifier,BUS_NOTIFY_DEL_DEVICE , dev); -device_pm_remove (dev);kobject_uevent (&dev->kobj,KOBJ_REMOVE );kobject_del (&dev->kobj);if (parent)
@@ -1156,14 +1166,11 @@ error: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 * @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_tmp;
@@ -1176,12 +1183,49 @@ void device_destroy(struct class *class, } }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)device_unregister (dev); }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). +*/ +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 * @dev: the pointer to the struct device to be renamed
include/linux/device.h +8 -0
@@ -521,6 +521,14 @@ extern struct device *device_create(stru dev_t devt, const char *fmt, ...) __attribute__((format(printf,4,5)));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
drivers/base/power/power.h +12 -0
@@ -20,6 +20,9 @@ static inline struct device *to_device(sextern void device_pm_add (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 */
@@ -32,6 +35,15 @@ static inline void device_pm_remove(stru { } +static inline int pm_sleep_lock (void ) +{ +return 0 ; +} + +static inline void pm_sleep_unlock (void ) +{ +} +#endif #ifdef CONFIG_PM
drivers/base/power/main.c +330 -174
655 lines not on this page · raw message
arch/x86/kernel/msr.c +3 -3
@@ -155,15 +155,15 @@ static int __cpuinit msr_class_cpu_callbswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =msr_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :msr_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (msr_class,MKDEV (MSR_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
arch/x86/kernel/cpuid.c +3 -3
@@ -157,15 +157,15 @@ static int __cpuinit cpuid_class_cpu_calswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =cpuid_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :cpuid_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (cpuid_class,MKDEV (CPUID_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
Quoted 6 lines · Rafael J. Wysocki
Appended is what I managed to put together today.
It probably still has some problems, but I'm not seeing them right now (too tired). At least, it doesn't break my system. ;-)
Please review.
Okay, this seems to be better. I like the way the complicated tests are all localized in power/main.c.
In dpm_resume() you shouldn't need to use dpm_list_mtx at all, because the list_move_tail() comes before the resume_device(). It's the same as in dpm_power_up().
The same is true for dpm_suspend(). Once all the device have been locked, there shouldn't be any other tasks accessing the dpm lists. Hence there should be no need to protect the list.
Which reminds me, the kerneldoc for device_pm_schedule_removal() is inaccurate. The routine always just moves the device to dpm_destroy list for later processing.
Also, the kerneldoc for destroy_suspended_device() should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.
It looks good.
Alan Stern
Quoted 6 lines · Rafael J. Wysocki
Appended is what I managed to put together today.
It probably still has some problems, but I'm not seeing them right now (too tired). At least, it doesn't break my system. ;-)
Please review.
Okay, this seems to be better. I like the way the complicated tests are all localized in power/main.c.
In dpm_resume() you shouldn't need to use dpm_list_mtx at all, because the list_move_tail() comes before the resume_device(). It's the same as in dpm_power_up().
The same is true for dpm_suspend(). Once all the device have been locked, there shouldn't be any other tasks accessing the dpm lists. Hence there should be no need to protect the list.
Which reminds me, the kerneldoc for device_pm_schedule_removal() is inaccurate. The routine always just moves the device to dpm_destroy list for later processing.
Also, the kerneldoc for destroy_suspended_device() should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.
It looks good.
Alan Stern
Quoted 7 lines · Alan Stern
Quoted 5 lines · Rafael J. Wysocki
Appended is what I managed to put together today.
It probably still has some problems, but I'm not seeing them right now (too tired). At least, it doesn't break my system. ;-)
Quoted 8 lines
Quoted 1 line
Please review.
Okay, this seems to be better. I like the way the complicated tests are all localized in
power/main.c.In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().
Still, device_pm_schedule_removal() can (in theory) be called concurrently with dpm_resume() by another thread and this might corrupt the list without the locking.
Quoted 3 lines
The same is true for
dpm_suspend(). Once all the device have been locked, there shouldn't be any other tasks accessing the dpm lists. Hence there should be no need to protect the list.
Except for against theoretical races with device_pm_schedule_removal().
Quoted 3 lines
Which reminds me, the kerneldoc for
device_pm_schedule_removal()is inaccurate. The routine always just moves the device todpm_destroylist for later processing.
Correct.
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.
Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
Quoted 1 line
It looks good.
Thanks for the review.
I'll fix the comments and repost the patch from scratch for merging in a separate thread.
Greetings,
Rafael
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.
Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for destroy_suspended_device().
However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is: device_power_down(), dpm_power_up(), and even unregister_dropped_devices().
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a CPU_UP_CANCELED_FROZEN notification. Isn't it true that this notification is issued only as part of a system sleep transition? We wouldn't want to allow destroy_suspended_device() to be called when an arbitrary CPU hotplug notification occurs.
Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.
Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for destroy_suspended_device().
However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is: device_power_down(), dpm_power_up(), and even unregister_dropped_devices().
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a CPU_UP_CANCELED_FROZEN notification. Isn't it true that this notification is issued only as part of a system sleep transition? We wouldn't want to allow destroy_suspended_device() to be called when an arbitrary CPU hotplug notification occurs.
Alan Stern
Quoted 13 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for
destroy_suspended_device().
Quoted 6 lines
However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is:
device_power_down(),dpm_power_up(), and evenunregister_dropped_devices().
Except for those run on one CPU with interrupts disabled, I think.
Quoted 11 lines
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?
Yes, it is.
Quoted 2 lines
We wouldn't want to allow
destroy_suspended_device()to be called when an arbitrary CPU hotplug notification occurs.
Of course.
Greetings,
Rafael
Quoted 14 lines · Rafael J. Wysocki
Quoted 13 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for
destroy_suspended_device().
Quoted 8 lines
Quoted 6 lines
However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is:
device_power_down(),dpm_power_up(), and evenunregister_dropped_devices().Except for those run on one CPU with interrupts disabled, I think.
Not unregister_dropped_devices()!
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.
Quoted 7 lines
Quoted 5 lines
Quoted 1 line
Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?Yes, it is.
So it counts as being indirectly invoked by a resume method.
Alan Stern
Quoted 14 lines · Rafael J. Wysocki
Quoted 13 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for
destroy_suspended_device().
Quoted 8 lines
Quoted 6 lines
However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is:
device_power_down(),dpm_power_up(), and evenunregister_dropped_devices().Except for those run on one CPU with interrupts disabled, I think.
Not unregister_dropped_devices()!
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.
Quoted 7 lines
Quoted 5 lines
Quoted 1 line
Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?Yes, it is.
So it counts as being indirectly invoked by a resume method.
Alan Stern
Quoted 23 lines · Alan Stern
Quoted 21 lines · Rafael J. Wysocki
Quoted 19 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for
destroy_suspended_device().However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is:
device_power_down(),dpm_power_up(), and evenunregister_dropped_devices().
Quoted 3 lines
Quoted 1 line
Except for those run on one CPU with interrupts disabled, I think.
Not
unregister_dropped_devices()!
Sure, it will need locking around the check in while().
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?Yes, it is.
So it counts as being indirectly invoked by a resume method.
Rather, by the resume core. Technically, it's invoked by enable_nonboot_cpus(), which is not a resume method literally.
Greetings,
Rafael
Quoted 12 lines · Rafael J. Wysocki
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
Yes, it is.
So it counts as being indirectly invoked by a resume method.
Rather, by the resume core. Technically, it's invoked by
enable_nonboot_cpus(), which is not a resume method literally.
Okay, then the routine should only be called directly or indirectly from a suspend or resume method or from the suspend or resume core.
Alan Stern
Quoted 12 lines · Rafael J. Wysocki
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
Yes, it is.
So it counts as being indirectly invoked by a resume method.
Rather, by the resume core. Technically, it's invoked by
enable_nonboot_cpus(), which is not a resume method literally.
Okay, then the routine should only be called directly or indirectly from a suspend or resume method or from the suspend or resume core.
Alan Stern
Quoted 23 lines · Alan Stern
Quoted 21 lines · Rafael J. Wysocki
Quoted 19 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for
destroy_suspended_device().However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is:
device_power_down(),dpm_power_up(), and evenunregister_dropped_devices().
Quoted 3 lines
Quoted 1 line
Except for those run on one CPU with interrupts disabled, I think.
Not
unregister_dropped_devices()!
Sure, it will need locking around the check in while().
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?Yes, it is.
So it counts as being indirectly invoked by a resume method.
Rather, by the resume core. Technically, it's invoked by enable_nonboot_cpus(), which is not a resume method literally.
Greetings,
Rafael
Quoted 13 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 3 lines
In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().Still,
device_pm_schedule_removal()can (in theory) be called concurrently withdpm_resume()by another thread and this might corrupt the list without the locking.Any thread doing that would be in violation of the restrictions you're going to add to the kerneldoc for
destroy_suspended_device().
Quoted 6 lines
However the overhead for the locking isn't critical. There won't be any contention (if everything is working right) and it isn't a hot path anyway. So you can leave the extra locking in if you want. But then you should put it in all the routines where the lists get manipulated, not just some of them. That is:
device_power_down(),dpm_power_up(), and evenunregister_dropped_devices().
Except for those run on one CPU with interrupts disabled, I think.
Quoted 11 lines
Quoted 7 lines
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
In your patch the call is made in response to a
CPU_UP_CANCELED_FROZENnotification. Isn't it true that this notification is issued only as part of a system sleep transition?
Yes, it is.
Quoted 2 lines
We wouldn't want to allow
destroy_suspended_device()to be called when an arbitrary CPU hotplug notification occurs.
Of course.
Greetings,
Rafael
Quoted 7 lines · Alan Stern
Quoted 5 lines · Rafael J. Wysocki
Appended is what I managed to put together today.
It probably still has some problems, but I'm not seeing them right now (too tired). At least, it doesn't break my system. ;-)
Quoted 8 lines
Quoted 1 line
Please review.
Okay, this seems to be better. I like the way the complicated tests are all localized in
power/main.c.In
dpm_resume()you shouldn't need to usedpm_list_mtxat all, because thelist_move_tail()comes before theresume_device(). It's the same as indpm_power_up().
Still, device_pm_schedule_removal() can (in theory) be called concurrently with dpm_resume() by another thread and this might corrupt the list without the locking.
Quoted 3 lines
The same is true for
dpm_suspend(). Once all the device have been locked, there shouldn't be any other tasks accessing the dpm lists. Hence there should be no need to protect the list.
Except for against theoretical races with device_pm_schedule_removal().
Quoted 3 lines
Which reminds me, the kerneldoc for
device_pm_schedule_removal()is inaccurate. The routine always just moves the device todpm_destroylist for later processing.
Correct.
Quoted 5 lines
Also, the kerneldoc for
destroy_suspended_device()should contain an extra paragraph warning that the routine should never be called except within the scope of a system sleep transition. In practice this means it has to be directly or indirectly invoked by a suspend or resume method.
Or by a CPU hotplug notifier (that will be the majority of cases, IMO).
Quoted 1 line
It looks good.
Thanks for the review.
I'll fix the comments and repost the patch from scratch for merging in a separate thread.
Greetings,
Rafael
Quoted 1 line · Alan Stern
On Mon, 7 Jan 2008, Rafael J. Wysocki wrote:
[--snip--]
Quoted 6 lines
Quoted 4 lines
Okay, well, now I'm leaning towards the asynchronous approach.
I'll prepare a new patch and send it later today.
Okay.
Appended is what I managed to put together today.
It probably still has some problems, but I'm not seeing them right now (too tired). At least, it doesn't break my system. ;-)
Please review.
Thanks,
Rafael
---
From: Alan Stern <[email protected]>, Rafael J. Wysocki <[email protected]>
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.
--- arch/x86/kernel/cpuid.c | 6 arch/x86/kernel/msr.c | 6 drivers/base/core.c | 60 ++++- drivers/base/power/main.c | 504 +++++++++++++++++++++++++++++---------------- drivers/base/power/power.h | 12 + include/linux/device.h | 8 6 files changed, 408 insertions(+), 188 deletions(-)
drivers/base/core.c +52 -8
@@ -726,11 +726,20 @@ int device_add(struct device *dev) {struct device *parent =NULL ;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); -if (!dev || !strlen (dev->bus_id)) +if (!dev || !strlen (dev->bus_id)) { + error = -EINVAL ; goto Error; + }pr_debug ("DEV: registering device: ID = '%s'\n" , dev->bus_id);
@@ -795,6 +804,7 @@ int device_add(struct device *dev) } Done:put_device (dev); +pm_sleep_unlock ();return error; BusError:device_pm_remove (dev);
@@ -905,6 +915,7 @@ void device_del(struct device * dev)struct device * parent = dev->parent;struct class_interface *class_intf; +device_pm_remove (dev);if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))
@@ -981,7 +992,6 @@ void device_del(struct device * dev)if (dev->bus)blocking_notifier_call_chain (&dev->bus->bus_notifier,BUS_NOTIFY_DEL_DEVICE , dev); -device_pm_remove (dev);kobject_uevent (&dev->kobj,KOBJ_REMOVE );kobject_del (&dev->kobj);if (parent)
@@ -1156,14 +1166,11 @@ error: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 * @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_tmp;
@@ -1176,12 +1183,49 @@ void device_destroy(struct class *class, } }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)device_unregister (dev); }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). +*/ +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 * @dev: the pointer to the struct device to be renamed
include/linux/device.h +8 -0
@@ -521,6 +521,14 @@ extern struct device *device_create(stru dev_t devt, const char *fmt, ...) __attribute__((format(printf,4,5)));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
drivers/base/power/power.h +12 -0
@@ -20,6 +20,9 @@ static inline struct device *to_device(sextern void device_pm_add (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 */
@@ -32,6 +35,15 @@ static inline void device_pm_remove(stru { } +static inline int pm_sleep_lock (void ) +{ +return 0 ; +} + +static inline void pm_sleep_unlock (void ) +{ +} +#endif #ifdef CONFIG_PM
drivers/base/power/main.c +330 -174
655 lines not on this page · raw message
arch/x86/kernel/msr.c +3 -3
@@ -155,15 +155,15 @@ static int __cpuinit msr_class_cpu_callbswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =msr_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :msr_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (msr_class,MKDEV (MSR_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
arch/x86/kernel/cpuid.c +3 -3
@@ -157,15 +157,15 @@ static int __cpuinit cpuid_class_cpu_calswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =cpuid_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :cpuid_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (cpuid_class,MKDEV (CPUID_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
Quoted 25 lines · Alan Stern
Quoted 8 lines · Rafael J. Wysocki
Quoted 7 lines · Alan Stern
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
Quoted 8 lines
Quoted 3 lines
First, note that the list manipulations in
dpm_suspend(),device_power_down(), and so on aren't protected bydpm_list_mtx. So your patch could corrupt the list pointers.Yes, they need the locking. I have overlooked that, mostly because the locking was removed by gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch too (because you assumed there woundn't be any need to remove a device during a suspend, right?).
Right.
Quoted 4 lines
Quoted 1 line
Are you assuming that no other threads can be running at this time?
No, I'm not.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
Note also that
device_pm_destroy_suspended()doesup(&dev->sem), but it doesn't know whether or notdev->semwas locked to begin with.Do you mean it might have been released already by another thread calling
device_pm_destroy_suspended()on the same device?I was thinking that it might be called before
lock_all_devices().
I've added pm_sleep_start_end_mtx and the locking dance in device_pm_destroy_suspended() specifically to prevent this from happening.
Quoted 3 lines
However let's ignore that possibility and simplify the discussion by assuming that
destroy_suspended_device()is never called except by a suspend or resume method for that device or one of its ancestors.
It may also be called by one of the CPU hotplug notifiers.
Quoted 10 lines
(This still leaves the possibility that it might get called by mistake during a runtime suspend or resume...)
Quoted 7 lines
Quoted 6 lines
Do you want to rule out the possibility of a driver's suspend or remove methods calling
destroy_suspended_device()on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.
Quoted 6 lines
Quoted 4 lines
Well, the asynchronous apprach has the problem that the device may end up on a wrong list when removed by one of the .suspend() callbacks (and I don't see how to avoid that without extra complexity). Perhaps that's something we can live with, though.
The same problem affects the synchronous approach.
No, it doesn't as of the $subject patch (the list_empty() tests should help).
Quoted 2 lines
We can fix it by having
dpm_suspend()do thelist_move()before callingsuspend_device(). Then if the suspend fails move the device back.
Yes, we can.
Quoted 10 lines
Quoted 2 lines
One more question: is there any particular reason not to call
device_pm_remove()at the beginning ofdevice_del()?I think it's done this way to avoid having a window where the device isn't on a PM list and is still owned by the bus and the driver. But if a suspend occurs during that window, it shouldn't matter that the device will be left unsuspended. After all, the same thing would have happened if the suspend occurred after
bus_remove_device().So no, there shouldn't be a problem with moving the call.
Okay, well, now I'm leaning towards the asynchronous approach.
I'll prepare a new patch and send it later today.
Thanks,
Rafael
Quoted 10 lines · Alan Stern
Quoted 2 lines · Rafael J. Wysocki
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
It has some problems.
First, note that the list manipulations in
dpm_suspend(),device_power_down(), and so on aren't protected bydpm_list_mtx. So your patch could corrupt the list pointers.
Yes, they need the locking. I have overlooked that, mostly because the locking was removed by gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch too (because you assumed there woundn't be any need to remove a device during a suspend, right?).
Quoted 1 line
Are you assuming that no other threads can be running at this time?
No, I'm not.
Quoted 2 lines
Note also that
device_pm_destroy_suspended()doesup(&dev->sem), but it doesn't know whether or notdev->semwas locked to begin with.
Do you mean it might have been released already by another thread calling device_pm_destroy_suspended() on the same device?
Quoted 6 lines
Do you want to rule out the possibility of a driver's suspend or remove methods calling
destroy_suspended_device()on its own device? With your synchronous approach, this would mean that the suspend/resume method would indirectly end up calling the remove method. This is dangerous at best; with USB it would be a lockdep violation. With an asynchronous approach, on the other hand, this wouldn't be a problem.
Well, the asynchronous apprach has the problem that the device may end up on a wrong list when removed by one of the .suspend() callbacks (and I don't see how to avoid that without extra complexity). Perhaps that's something we can live with, though.
One more question: is there any particular reason not to call device_pm_remove() at the beginning of device_del()?
Rafael
Quoted 43 lines · Alan Stern
Let's try to summarize the main issues here:
1. We want the PM core to lock all devices during suspend and hibernation. This implies that registration and unregistration at such times can't work, because they need to lock the device sem in order to make probe and remove method calls.
2. Registration calls can be failed, with an error message in the system log. However unregistration calls cannot fail. They _can_ block until the system resumes, but if the unregistration call was made from within a suspend or resume method it will deadlock. This seems inescapable, but at least we should print an error in the log so the offending driver can be identified.
3. In response to 2, the PM core should have a special routine for unregistering devices while a suspend is in progress. Rafael proposed that the core should unlock the device to permit the call to go through. This seems dangerous to me; I would prefer to leave the locks in place and defer the unregistration until after the system is back up and the locks have all been dropped. This would avoid all sorts of locking, deadlock, and mutual exclusion problems.
(As a side note:
destroy_suspended_device()has a rather limited interface anyway, since it can handle only devices that were created bycreate_device().)4. Rafael pointed out that unregistration can occur concurrently with system suspend. When this happens we can end up trying to suspend a device which has already been through
bus_remove_device(), because it hasn't yet been removed from thedpm_activelist. He proposes we make unregistration block system suspend, just as registration does.I don't see 4 as a real problem. Starting an unregistration before the suspend and finishing it afterward should be okay. Once a device has gone through
bus_remove_device()it hasn't got a suspend method any more, so trying to suspend it won't do anything at all -- the tests insuspend_device()will all fail. Conversely, ifbus_remove_device()hasn't run yet then we would end up calling the driver's suspend method before thedevice_del()call returns. As Johannes pointed out, this is a normal race that would exist anyway.
Quoted 8 lines
On the other hand, having unregistration block system suspend wouldn't actually be wrong. I simply don't think it is necessary. But note that making the two mutually exclusive would complicate Rafael's synchronous approach for
destroy_suspended_device().5. All the discussion about
pm_sleep_rwsemand so on is implementation details. Once we have settled on the correct approach for 1-4, the implementation should be relatively easy.
Please see the patch at: http://lkml.org/lkml/2008/1/6/298 . It represents my current idea about how to do that.
Thanks,
Rafael
Quoted 7 lines · Alan Stern
Quoted 5 lines · Rafael J. Wysocki
Quoted 4 lines
No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call
device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.
Quoted 7 lines
Quoted 2 lines
Still, if we do that, we won't need to acquire
dev->semindevice_pm_remove()any more.There's a window in
lock_all_devices()whendpm_list_mtxisn't held. We don't wantdevice_pm_remove()taking an already-locked device off thedpm_lockedlist at that time. So we do need to acquiredev->semindevice_pm_remove().
Not if pm_sleep_rwsem is held by device_del(), since in that case we won't reach lock_all_devices() (device_add() calls device_pm_remove() under pm_sleep_rwsem already).
Quoted 13 lines
Quoted 4 lines
Apart from this, by acqiring
pm_sleep_rwsemfor reading indevice_del()we can prevent a suspend from starting while the device is being removed.Quoted 8 lines
Consider, for example, the scenario possible with the $subject patch:
-device_del()starts and noticespm_sleep_rwsemunlocked, so the warning is not printed
- it proceeds and everything beforedevice_pm_remove()succeeds
- now,device_suspend()is called and locksdev->sem
-device_del()callsdevice_pm_remove()and blocks on that with the device partialy removed
I think we should prevent this from happening.
Quoted 2 lines
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
In that case, we'll attempt to call the device's .suspend() and .resume() routines, but we shouldn't do that, IMHO.
Rafael
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call
device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.Still, if we do that, we won't need to acquire
dev->semindevice_pm_remove()any more.
There's a window in lock_all_devices() when dpm_list_mtx isn't held. We don't want device_pm_remove() taking an already-locked device off the dpm_locked list at that time. So we do need to acquire dev->sem in device_pm_remove().
Quoted 4 lines
Apart from this, by acqiring
pm_sleep_rwsemfor reading indevice_del()we can prevent a suspend from starting while the device is being removed.
Quoted 8 lines
Consider, for example, the scenario possible with the $subject patch:
-device_del()starts and noticespm_sleep_rwsemunlocked, so the warning is not printed
- it proceeds and everything beforedevice_pm_remove()succeeds
- now,device_suspend()is called and locksdev->sem
-device_del()callsdevice_pm_remove()and blocks on that with the device partialy removed
I think we should prevent this from happening.
I don't see anything wrong with it. All that will happen is that the removal will start before the suspend and finish after the resume.
Alan Stern
Quoted 14 lines · Alan Stern
Quoted 6 lines · Rafael J. Wysocki
Quoted 2 lines
Still, shouldn't we fail the removal of the device apart from giving the warning?
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in
device_pm_remove()if we allow all of the operations indevice_del()preceding it to be performed.That's not the issue. We _don't_ allow all of the operations in
device_del()preceding the call todevice_pm_remove(). In particular, the call to the device's driver's remove method will deadlock becausedevice_release_driver()always has to acquiredev->sem.
Quoted 7 lines
Quoted 2 lines
Shouldn't we just take
pm_sleep_rwsemindevice_del()upfront and block on that if locked?No -- the whole idea here is to print an error message in the system log if a driver's resume method tries to call
device_del(). Deadlock is unavoidable in this case, but at least we'll know which driver is guilty.
Still, if we do that, we won't need to acquire dev->sem in device_pm_remove() any more. Apart from this, by acqiring pm_sleep_rwsem for reading in device_del() we can prevent a suspend from starting while the device is being removed.
Consider, for example, the scenario possible with the $subject patch:
- device_del() starts and notices pm_sleep_rwsem unlocked, so the warning is not printed
- it proceeds and everything before device_pm_remove() succeeds
- now, device_suspend() is called and locks dev->sem
- device_del() calls device_pm_remove() and blocks on that with the device partialy removed
I think we should prevent this from happening.
Rafael
Quoted 31 lines · Rafael J. Wysocki
Quoted 21 lines · Alan Stern
Quoted 19 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +9
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack(); + } else { + pm_sleep_unlock(); + } + } else {Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev
Quoted 6 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); + } +if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))Bizarre, but it should work.
Quoted 4 lines
OK
Still, shouldn't we fail the removal of the device apart from giving the warning?
Actually, having thought about it a bit more, I don't see the point in preventing the removal of the device from the list in device_pm_remove() if we allow all of the operations in device_del() preceding it to be performed.
Shouldn't we just take pm_sleep_rwsem in device_del() upfront and block on that if locked?
Rafael
Quoted 4 lines · Rafael J. Wysocki
On Sunday, 6 of January 2008, Alan Stern wrote:
Still, shouldn't we fail the removal of the device apart from giving the warning?
We can't. device_del() can't fail -- it returns void. Besides, how can a driver hope to deal with an unregistration failure?
Alan Stern
Quoted 4 lines · Rafael J. Wysocki
On Sunday, 6 of January 2008, Alan Stern wrote:
Still, shouldn't we fail the removal of the device apart from giving the warning?
We can't. device_del() can't fail -- it returns void. Besides, how can a driver hope to deal with an unregistration failure?
Alan Stern
Quoted 9 lines · Alan Stern
Quoted 4 lines · Rafael J. Wysocki
On Sunday, 6 of January 2008, Alan Stern wrote:
Still, shouldn't we fail the removal of the device apart from giving the warning?
We can't.
device_del()can't fail -- it returns void. Besides, how can a driver hope to deal with an unregistration failure?
Well, right.
Still, our present approach doesn't seem to be correct overall. For example, I think we should prevent a suspend from happening while a device is being removed.
Rafael
Quoted 3 lines · Rafael J. Wysocki
Still, our present approach doesn't seem to be correct overall. For example, I think we should prevent a suspend from happening while a device is being removed.
We could, however I don't think it's dangerous to allow it. The two problems to avoid are (1) messing up the PM device list pointers, and
(2) calling a driver's suspend/resume methods while its remove method is running. (1) is handled by the pm_list_mutex and (2) is handled by locking dev->sem.
Alan Stern
Quoted 3 lines · Rafael J. Wysocki
Still, our present approach doesn't seem to be correct overall. For example, I think we should prevent a suspend from happening while a device is being removed.
We could, however I don't think it's dangerous to allow it. The two problems to avoid are (1) messing up the PM device list pointers, and
(2) calling a driver's suspend/resume methods while its remove method is running. (1) is handled by the pm_list_mutex and (2) is handled by locking dev->sem.
Alan Stern
Quoted 9 lines · Alan Stern
Quoted 4 lines · Rafael J. Wysocki
On Sunday, 6 of January 2008, Alan Stern wrote:
Still, shouldn't we fail the removal of the device apart from giving the warning?
We can't.
device_del()can't fail -- it returns void. Besides, how can a driver hope to deal with an unregistration failure?
Well, right.
Still, our present approach doesn't seem to be correct overall. For example, I think we should prevent a suspend from happening while a device is being removed.
Rafael
Quoted 21 lines · Alan Stern
Quoted 19 lines · Rafael J. Wysocki
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),Something like this, perhaps:
(quoted diff) +9
-0@@ -905,6 +915,18 @@ void device_del(struct device * dev) struct device * parent = dev->parent; struct class_interface *class_intf; + if (down_trylock(&dev->sem)) { + if (pm_sleep_lock()) { + dev_warn(dev, "Illegal %s during suspend\n", + __FUNCTION__); + dump_stack(); + } else { + pm_sleep_unlock(); + } + } else {
Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev
Quoted 6 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); + } +if (parent)klist_del (&dev->knode_parent);if (MAJOR (dev->devt))Bizarre, but it should work.
OK
Still, shouldn't we fail the removal of the device apart from giving the warning?
Quoted 1 line
Be sure to include plenty of explanatory comments
I will.
I think that code can be moved to its own function in drivers/base/power/main.c, btw.
Quoted 1 line
-- otherwise nobody will be able to figure it out! :-)
Well, I guess so. :-)
Thanks,
Rafael
Quoted 32 lines · Alan Stern
Quoted 13 lines · Rafael J. Wysocki
Quoted 12 lines · Alan Stern
Quoted 3 lines · Rafael J. Wysocki
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.That should never happen. The whole idea of
destroy_suspended_device()is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.Quoted 8 lines
Quoted 3 lines
But it wouldn't hurt to mention in the kerneldoc that
destroy_suspended_device()is meant to be called only during a system resume.Hmm. Please have a look at the appended patch.
I have removed the warning from
device_del()and usedlist_empty()to detect removed devices in the .suspend() routines. Is that viable?It's not good.
The warning in
device_del()is vital. It's what will tell people where the problem is when a deadlock occurs during system resume because some driver has mistakenly tried to unregister a device at the wrong time. It would have pointed immediately to the msr driver in the case of the bug Andrew found, for instance.
Quoted 3 lines
If you can figure out a way to disable the warning in
device_del()for just the one device being unregistered bydevice_pm_destroy_suspended(),
Something like this, perhaps:
@@ -905,6 +915,18 @@ void device_del(struct device * dev)
struct device * parent = dev->parent; struct class_interface *class_intf;
+ if (down_trylock(&dev->sem)) {
+ if (pm_sleep_lock()) {
+ dev_warn(dev, "Illegal %s during suspend\n",
+ __FUNCTION__);
+ dump_stack();
+ } else {
+ pm_sleep_unlock();
+ }
+ } else {
+ up(&dev->sem);
+ }
+
if (parent)
klist_del(&dev->knode_parent);
if (MAJOR(dev->devt))
Quoted 1 line
I suppose that would be okay.
Rafael
Quoted 32 lines · Alan Stern
Quoted 11 lines · Rafael J. Wysocki
Quoted 10 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?Quoted 6 lines
Quoted 3 lines
It's still necessary, because
destroy_suspended_device()still has to move the device from one list to another. You don't want it to end up on thedpm_lockedlist.Hmm. That means we'd have to do the same thing in
dpm_power_up()in case someone callsdestroy_suspended_device()fromresume_device_early(dev).Yes.
Quoted 3 lines
Still, even doing that is not enough, since someone can call
destroy_suspended_device()from a .suspend() routine and then the device will end up on a wrong list just as well.That should never happen. The whole idea of
destroy_suspended_device()is that the device couldn't be resumed and in fact should be unregistered because it is no longer working or no longer present. A suspend routine won't detect this sort of thing since it doesn't try to resume the device.
Quoted 3 lines
But it wouldn't hurt to mention in the kerneldoc that
destroy_suspended_device()is meant to be called only during a system resume.
Hmm. Please have a look at the appended patch.
I have removed the warning from device_del() and used list_empty() to detect removed devices in the .suspend() routines. Is that viable?
Rafael
--- arch/x86/kernel/cpuid.c | 6 arch/x86/kernel/msr.c | 6 drivers/base/core.c | 67 +++++- drivers/base/power/main.c | 454 ++++++++++++++++++++++++++------------------- drivers/base/power/power.h | 12 + include/linux/device.h | 8 6 files changed, 354 insertions(+), 199 deletions(-)
drivers/base/core.c +52 -8
@@ -726,11 +726,20 @@ int device_add(struct device *dev) {struct device *parent =NULL ;struct class_interface *class_intf; -int error = -EINVAL ; +int error; + + error =pm_sleep_lock (); +if (error) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); +return error; + } dev =get_device (dev); -if (!dev || !strlen (dev->bus_id)) - goto Error; +if (!dev || !strlen (dev->bus_id)) { + error = -EINVAL ; + goto Done; + }pr_debug ("DEV: registering device: ID = '%s'\n" , dev->bus_id);
@@ -795,6 +804,7 @@ int device_add(struct device *dev) } Done:put_device (dev); +pm_sleep_unlock ();return error; BusError:device_pm_remove (dev);
@@ -1156,14 +1173,11 @@ error: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 * @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_tmp;
@@ -1176,12 +1190,49 @@ void device_destroy(struct class *class, } }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)device_unregister (dev); }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 causes the PM core to release and 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). +*/ +void destroy_suspended_device (struct class *class,dev_t devt) +{ +struct device *dev; + + dev =find_device (class, devt); +if (dev) +device_pm_destroy_suspended (dev); +} +EXPORT_SYMBOL_GPL (destroy_suspended_device); +#endif /* CONFIG_PM_SLEEP */ +/** * device_rename - renames a device * @dev: the pointer to the struct device to be renamed
include/linux/device.h +8 -0
@@ -521,6 +521,14 @@ extern struct device *device_create(stru dev_t devt, const char *fmt, ...) __attribute__((format(printf,4,5)));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
drivers/base/power/power.h +12 -0
@@ -20,6 +20,9 @@ static inline struct device *to_device(sextern void device_pm_add (struct device *);extern void device_pm_remove (struct device *); +extern void device_pm_destroy_suspended (struct device *); +extern int pm_sleep_lock (void ); +extern void pm_sleep_unlock (void );#else /* CONFIG_PM_SLEEP */
@@ -32,6 +35,15 @@ static inline void device_pm_remove(stru { } +static inline int pm_sleep_lock (void ) +{ +return 0 ; +} + +static inline void pm_sleep_unlock (void ) +{ +} +#endif #ifdef CONFIG_PM
drivers/base/power/main.c +269 -185
587 lines not on this page · raw message
arch/x86/kernel/msr.c +3 -3
@@ -155,15 +155,15 @@ static int __cpuinit msr_class_cpu_callbswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =msr_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :msr_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (msr_class,MKDEV (MSR_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
arch/x86/kernel/cpuid.c +3 -3
@@ -157,15 +157,15 @@ static int __cpuinit cpuid_class_cpu_calswitch (action) {case CPU_UP_PREPARE : -case CPU_UP_PREPARE_FROZEN : err =cpuid_device_create (cpu);break ;case CPU_UP_CANCELED : -case CPU_UP_CANCELED_FROZEN :case CPU_DEAD : -case CPU_DEAD_FROZEN :cpuid_device_destroy (cpu);break ; +case CPU_UP_CANCELED_FROZEN : +destroy_suspended_device (cpuid_class,MKDEV (CPUID_MAJOR , cpu)); +break ; }return err ?NOTIFY_BAD :NOTIFY_OK ; }
Quoted 7 lines · Alan Stern
Quoted 5 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().
Quoted 6 lines
Quoted 2 lines
However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?It's still necessary, because
destroy_suspended_device()still has to move the device from one list to another. You don't want it to end up on thedpm_lockedlist.
Hmm. That means we'd have to do the same thing in dpm_power_up() in case someone calls destroy_suspended_device() from resume_device_early(dev).
Still, even doing that is not enough, since someone can call destroy_suspended_device() from a .suspend() routine and then the device will end up on a wrong list just as well.
Greetings,
Rafael
Quoted 7 lines · Rafael J. Wysocki
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().However, if we unregister them all at once after releasing
pm_sleep_rwsem, that shouldn't be necessary, right?
It's still necessary, because destroy_suspended_device() still has to move the device from one list to another. You don't want it to end up on the dpm_locked list.
Alan Stern
Quoted 16 lines · Alan Stern
Quoted 7 lines · Rafael J. Wysocki
Greg, Andrew,
The appended patch is a replacement for gregkh-driver-pm-acquire-device-locks-prior-to-suspending.patch that deadlocked suspend and hibernation on some systems.
Please consider for applying.
This warning message:
Quoted 4 lines
drivers/base/core.c +0
-0@@ -905,6 +915,13 @@ void device_del(struct device * dev)struct device * parent = dev->parent;struct class_interface *class_intf;
Quoted 6 lines
drivers/base/core.c void device_del(struct device * dev) · line 921
Quoted 4 lines
drivers/base/core.c +4
-0+if (pm_sleep_lock ()) { +dev_warn (dev,"Illegal %s during suspend\n" , __FUNCTION__); +dump_stack (); + }else {will unavoidably be triggered by this code:
Ah, my fault, sorry.
Quoted 8 lines
Quoted 8 lines
drivers/base/power/main.c +8
-0+void device_pm_destroy_suspended (struct device *dev) +{ +pr_debug ("PM: Removing suspended device %s:%s\n" , + dev->bus ? dev->bus->name :"No Bus" , +kobject_name (&dev->kobj)); +mutex_lock (&dpm_list_mtx); +list_del_init (&dev->power.entry); +mutex_unlock (&dpm_list_mtx);
Quoted 8 lines
drivers/base/power/main.c void device_pm_remove(struct device *dev · line 97
Quoted 3 lines
drivers/base/power/main.c +3
-0+up (&dev->sem); +device_unregister (dev); +}since the call to
device_del()will occur while thepm_sleep_rwsemis still locked for writing. That's why I suggested not unregistering these devices until after everything else has been resumed and the rwsem has been dropped.
Hmm, well. I'll go back to the previous version, then. Sorry for the mess.
Quoted 4 lines
Another thing to watch out for: Just in case somebody ends up calling
destroy_suspended_device(dev)from within dev's own resume method, you should interchange theresume_device()and thelist_move_tail()calls indpm_resume().
However, if we unregister them all at once after releasing pm_sleep_rwsem, that shouldn't be necessary, right?
Rafael