PM-runtime: Fix __pm_runtime_set_status() race with runtime resume

Commit 4080ab0830 ("PM-runtime: Take suppliers into account in
__pm_runtime_set_status()") introduced a race condition that may
trigger if __pm_runtime_set_status() is used incorrectly (that is,
if it is called when PM-runtime is enabled for the target device
and working).

In that case, if the original PM-runtime status of the device is
RPM_SUSPENDED, a runtime resume of the device may occur after
__pm_runtime_set_status() has dropped its power.lock spinlock
and before deactivating its suppliers, so the suppliers may be
deactivated while the device is PM-runtime-active which may lead
to functional issues.

To avoid that, modify __pm_runtime_set_status() to check whether
or not PM-runtime is enabled for the device before activating its
suppliers (if the new status is RPM_ACTIVE) and either return an
error if that's the case or increment the device's disable_depth
counter to prevent PM-runtime from being enabled for it while
the remaining part of the function is running (disable_depth is
then decremented on the way out).

Fixes: 4080ab0830 ("PM-runtime: Take suppliers into account in __pm_runtime_set_status()")
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Rafael J. Wysocki 2019-02-12 13:04:12 +01:00 committed by Greg Kroah-Hartman
parent daaef255dc
commit c1567f813a
1 changed files with 18 additions and 6 deletions

View File

@ -1106,6 +1106,22 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status)
if (status != RPM_ACTIVE && status != RPM_SUSPENDED) if (status != RPM_ACTIVE && status != RPM_SUSPENDED)
return -EINVAL; return -EINVAL;
spin_lock_irq(&dev->power.lock);
/*
* Prevent PM-runtime from being enabled for the device or return an
* error if it is enabled already and working.
*/
if (dev->power.runtime_error || dev->power.disable_depth)
dev->power.disable_depth++;
else
error = -EAGAIN;
spin_unlock_irq(&dev->power.lock);
if (error)
return error;
/* /*
* If the new status is RPM_ACTIVE, the suppliers can be activated * If the new status is RPM_ACTIVE, the suppliers can be activated
* upfront regardless of the current status, because next time * upfront regardless of the current status, because next time
@ -1124,12 +1140,6 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status)
spin_lock_irq(&dev->power.lock); spin_lock_irq(&dev->power.lock);
if (!dev->power.runtime_error && !dev->power.disable_depth) {
status = dev->power.runtime_status;
error = -EAGAIN;
goto out;
}
if (dev->power.runtime_status == status || !parent) if (dev->power.runtime_status == status || !parent)
goto out_set; goto out_set;
@ -1182,6 +1192,8 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status)
device_links_read_unlock(idx); device_links_read_unlock(idx);
} }
pm_runtime_enable(dev);
return error; return error;
} }
EXPORT_SYMBOL_GPL(__pm_runtime_set_status); EXPORT_SYMBOL_GPL(__pm_runtime_set_status);