[linux-pm] [PATCH 2/2] PM: fix async resume following suspend failure

Rafael J. Wysocki rjw at sisk.pl
Sat Jun 18 10:50:50 PDT 2011


On Friday, June 17, 2011, Alan Stern wrote:
> The PM core doesn't handle suspend failures correctly when it comes to
> asynchronously suspended devices.  These devices are moved onto the
> dpm_suspended_list as soon as the corresponding async thread is
> started up, and they remain on the list even if they fail to suspend
> or the sleep transition is cancelled before they get suspended.  As a
> result, when the PM core unwinds the transition, it tries to resume
> the devices even though they were never suspended.
> 
> This patch (as1474) fixes the problem by adding a new "is_suspended"
> flag to dev_pm_info.  Devices are resumed only if the flag is set.
> 
> Signed-off-by: Alan Stern <stern at rowland.harvard.edu>
> 
> ---
> 
> Rafael, you may want these two patches to go into the stable kernels.  
> I'll leave that decision to you.

I would, but please see below.

>  drivers/base/power/main.c |    8 +++++---
>  include/linux/pm.h        |    1 +
>  2 files changed, 6 insertions(+), 3 deletions(-)
> 
> Index: usb-3.0/include/linux/pm.h
> ===================================================================
> --- usb-3.0.orig/include/linux/pm.h
> +++ usb-3.0/include/linux/pm.h
> @@ -426,6 +426,7 @@ struct dev_pm_info {
>  	unsigned int		can_wakeup:1;
>  	unsigned int		async_suspend:1;
>  	bool			is_prepared:1;	/* Owned by the PM core */
> +	bool			is_suspended:1;	/* Ditto */
>  	spinlock_t		lock;
>  #ifdef CONFIG_PM_SLEEP
>  	struct list_head	entry;
> Index: usb-3.0/drivers/base/power/main.c
> ===================================================================
> --- usb-3.0.orig/drivers/base/power/main.c
> +++ usb-3.0/drivers/base/power/main.c
> @@ -57,7 +57,7 @@ static int async_error;
>   */
>  void device_pm_init(struct device *dev)
>  {
> -	dev->power.is_prepared = false;
> +	dev->power.is_prepared = dev->power.is_suspended = false;
>  	init_completion(&dev->power.completion);
>  	complete_all(&dev->power.completion);
>  	dev->power.wakeup = NULL;
> @@ -552,6 +552,7 @@ static int device_resume(struct device *
>  	}
>  
>   End:
> +	dev->power.is_suspended = false;
>  	device_unlock(dev);
>  	complete_all(&dev->power.completion);
>  
> @@ -596,7 +597,7 @@ void dpm_resume(pm_message_t state)
>  
>  	list_for_each_entry(dev, &dpm_suspended_list, power.entry) {
>  		INIT_COMPLETION(dev->power.completion);
> -		if (is_async(dev)) {
> +		if (is_async(dev) && dev->power.is_suspended) {

If we check dev->power.is_suspended here, we won't complete the
device's power.completion, which is necessary if the device is someone's
parent.  Moreover, I think we should clear the device's is_prepared
flage at this point.

>  			get_device(dev);
>  			async_schedule(async_resume, dev);
>  		}
> @@ -605,7 +606,7 @@ void dpm_resume(pm_message_t state)
>  	while (!list_empty(&dpm_suspended_list)) {
>  		dev = to_device(dpm_suspended_list.next);
>  		get_device(dev);
> -		if (!is_async(dev)) {
> +		if (!is_async(dev) && dev->power.is_suspended) {
>  			int error;

Same comments apply here, so I think it will be better to check
power.is_suspended in device_resume(), like in the appended patch.

>  
>  			mutex_unlock(&dpm_list_mtx);
> @@ -881,6 +882,7 @@ static int __device_suspend(struct devic
>  	}
>  
>   End:
> +	dev->power.is_suspended = !error;
>  	device_unlock(dev);
>  	complete_all(&dev->power.completion);

This change doesn't seem to be correct too, because error is 0 if
async_error is true, but the device won't be suspended in that case
too.

Thanks,
Rafael


---

 drivers/base/power/main.c |   15 ++++++++++++---
 include/linux/pm.h        |    1 +
 2 files changed, 13 insertions(+), 3 deletions(-)

Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -426,6 +426,7 @@ struct dev_pm_info {
 	unsigned int		can_wakeup:1;
 	unsigned int		async_suspend:1;
 	bool			is_prepared:1;	/* Owned by the PM core */
+	bool			is_suspended:1;	/* Ditto */
 	spinlock_t		lock;
 #ifdef CONFIG_PM_SLEEP
 	struct list_head	entry;
Index: linux-2.6/drivers/base/power/main.c
===================================================================
--- linux-2.6.orig/drivers/base/power/main.c
+++ linux-2.6/drivers/base/power/main.c
@@ -57,7 +57,7 @@ static int async_error;
  */
 void device_pm_init(struct device *dev)
 {
-	dev->power.is_prepared = false;
+	dev->power.is_prepared = dev->power.is_suspended = false;
 	init_completion(&dev->power.completion);
 	complete_all(&dev->power.completion);
 	dev->power.wakeup = NULL;
@@ -517,6 +517,9 @@ static int device_resume(struct device *
 	 */
 	dev->power.is_prepared = false;
 
+	if (!dev->power.is_suspended)
+		goto Unlock;
+
 	if (dev->pm_domain) {
 		pm_dev_dbg(dev, state, "power domain ");
 		error = pm_op(dev, &dev->pm_domain->ops, state);
@@ -552,6 +555,9 @@ static int device_resume(struct device *
 	}
 
  End:
+	dev->power.is_suspended = false;
+
+ Unlock:
 	device_unlock(dev);
 	complete_all(&dev->power.completion);
 
@@ -839,11 +845,11 @@ static int __device_suspend(struct devic
 	device_lock(dev);
 
 	if (async_error)
-		goto End;
+		goto Unlock;
 
 	if (pm_wakeup_pending()) {
 		async_error = -EBUSY;
-		goto End;
+		goto Unlock;
 	}
 
 	if (dev->pm_domain) {
@@ -881,6 +887,9 @@ static int __device_suspend(struct devic
 	}
 
  End:
+	dev->power.is_suspended = !error;
+
+ Unlock:
 	device_unlock(dev);
 	complete_all(&dev->power.completion);
 


More information about the linux-pm mailing list