diff --git a/Documentation/power/runtime_pm.txt b/Documentation/power/runtime_pm.txt index 0e856088db7c..5336149f831b 100644 --- a/Documentation/power/runtime_pm.txt +++ b/Documentation/power/runtime_pm.txt @@ -789,6 +789,16 @@ will behave normally, not taking the autosuspend delay into account. Similarly, if the power.use_autosuspend field isn't set then the autosuspend helper functions will behave just like the non-autosuspend counterparts. +Under some circumstances a driver or subsystem may want to prevent a device +from autosuspending immediately, even though the usage counter is zero and the +autosuspend delay time has expired. If the ->runtime_suspend() callback +returns -EAGAIN or -EBUSY, and if the next autosuspend delay expiration time is +in the future (as it normally would be if the callback invoked +pm_runtime_mark_last_busy()), the PM core will automatically reschedule the +autosuspend. The ->runtime_suspend() callback can't do this rescheduling +itself because no suspend requests of any kind are accepted while the device is +suspending (i.e., while the callback is running). + The implementation is well suited for asynchronous use in interrupt contexts. However such use inevitably involves races, because the PM core can't synchronize ->runtime_suspend() callbacks with the arrival of I/O requests. diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 18ef87e525fa..124dbf60c9bf 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -293,6 +293,9 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) * the callback was running then carry it out, otherwise send an idle * notification for its parent (if the suspend succeeded and both * ignore_children of parent->power and irq_safe of dev->power are not set). + * If ->runtime_suspend failed with -EAGAIN or -EBUSY, and if the RPM_AUTO + * flag is set and the next autosuspend-delay expiration time is in the + * future, schedule another autosuspend attempt. * * This function must be called under dev->power.lock with interrupts disabled. */ @@ -413,10 +416,21 @@ static int rpm_suspend(struct device *dev, int rpmflags) if (retval) { __update_runtime_status(dev, RPM_ACTIVE); dev->power.deferred_resume = false; - if (retval == -EAGAIN || retval == -EBUSY) + if (retval == -EAGAIN || retval == -EBUSY) { dev->power.runtime_error = 0; - else + + /* + * If the callback routine failed an autosuspend, and + * if the last_busy time has been updated so that there + * is a new autosuspend expiration time, automatically + * reschedule another autosuspend. + */ + if ((rpmflags & RPM_AUTO) && + pm_runtime_autosuspend_expiration(dev) != 0) + goto repeat; + } else { pm_runtime_cancel_pending(dev); + } wake_up_all(&dev->power.wait_queue); goto out; }