Skip to content

Commit c26dd98

Browse files
rafaeljwJarkko Sakkinen
authored andcommitted
PM: runtime: Avoid device usage count underflows
A PM-runtime device usage count underflow is potentially critical, because it may cause a device to be suspended when it is expected to be operational. It is also a programming problem that would be good to catch and warn about. For this reason, (1) make rpm_check_suspend_allowed() return an error when the device usage count is negative to prevent devices from being suspended in that case, (2) introduce rpm_drop_usage_count() that will detect device usage count underflows, warn about them and fix them up, and (3) use it to drop the usage count in a few places instead of atomic_dec_and_test(). Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
1 parent c2767d5 commit c26dd98

File tree

1 file changed

+37
-7
lines changed

1 file changed

+37
-7
lines changed

drivers/base/power/runtime.c

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ static int rpm_check_suspend_allowed(struct device *dev)
263263
retval = -EINVAL;
264264
else if (dev->power.disable_depth > 0)
265265
retval = -EACCES;
266-
else if (atomic_read(&dev->power.usage_count) > 0)
266+
else if (atomic_read(&dev->power.usage_count))
267267
retval = -EAGAIN;
268268
else if (!dev->power.ignore_children &&
269269
atomic_read(&dev->power.child_count))
@@ -1039,13 +1039,33 @@ int pm_schedule_suspend(struct device *dev, unsigned int delay)
10391039
}
10401040
EXPORT_SYMBOL_GPL(pm_schedule_suspend);
10411041

1042+
static int rpm_drop_usage_count(struct device *dev)
1043+
{
1044+
int ret;
1045+
1046+
ret = atomic_sub_return(1, &dev->power.usage_count);
1047+
if (ret >= 0)
1048+
return ret;
1049+
1050+
/*
1051+
* Because rpm_resume() does not check the usage counter, it will resume
1052+
* the device even if the usage counter is 0 or negative, so it is
1053+
* sufficient to increment the usage counter here to reverse the change
1054+
* made above.
1055+
*/
1056+
atomic_inc(&dev->power.usage_count);
1057+
dev_warn(dev, "Runtime PM usage count underflow!\n");
1058+
return -EINVAL;
1059+
}
1060+
10421061
/**
10431062
* __pm_runtime_idle - Entry point for runtime idle operations.
10441063
* @dev: Device to send idle notification for.
10451064
* @rpmflags: Flag bits.
10461065
*
10471066
* If the RPM_GET_PUT flag is set, decrement the device's usage count and
1048-
* return immediately if it is larger than zero. Then carry out an idle
1067+
* return immediately if it is larger than zero (if it becomes negative, log a
1068+
* warning, increment it, and return an error). Then carry out an idle
10491069
* notification, either synchronous or asynchronous.
10501070
*
10511071
* This routine may be called in atomic context if the RPM_ASYNC flag is set,
@@ -1057,7 +1077,10 @@ int __pm_runtime_idle(struct device *dev, int rpmflags)
10571077
int retval;
10581078

10591079
if (rpmflags & RPM_GET_PUT) {
1060-
if (!atomic_dec_and_test(&dev->power.usage_count)) {
1080+
retval = rpm_drop_usage_count(dev);
1081+
if (retval < 0) {
1082+
return retval;
1083+
} else if (retval > 0) {
10611084
trace_rpm_usage_rcuidle(dev, rpmflags);
10621085
return 0;
10631086
}
@@ -1079,7 +1102,8 @@ EXPORT_SYMBOL_GPL(__pm_runtime_idle);
10791102
* @rpmflags: Flag bits.
10801103
*
10811104
* If the RPM_GET_PUT flag is set, decrement the device's usage count and
1082-
* return immediately if it is larger than zero. Then carry out a suspend,
1105+
* return immediately if it is larger than zero (if it becomes negative, log a
1106+
* warning, increment it, and return an error). Then carry out a suspend,
10831107
* either synchronous or asynchronous.
10841108
*
10851109
* This routine may be called in atomic context if the RPM_ASYNC flag is set,
@@ -1091,7 +1115,10 @@ int __pm_runtime_suspend(struct device *dev, int rpmflags)
10911115
int retval;
10921116

10931117
if (rpmflags & RPM_GET_PUT) {
1094-
if (!atomic_dec_and_test(&dev->power.usage_count)) {
1118+
retval = rpm_drop_usage_count(dev);
1119+
if (retval < 0) {
1120+
return retval;
1121+
} else if (retval > 0) {
10951122
trace_rpm_usage_rcuidle(dev, rpmflags);
10961123
return 0;
10971124
}
@@ -1527,14 +1554,17 @@ EXPORT_SYMBOL_GPL(pm_runtime_forbid);
15271554
*/
15281555
void pm_runtime_allow(struct device *dev)
15291556
{
1557+
int ret;
1558+
15301559
spin_lock_irq(&dev->power.lock);
15311560
if (dev->power.runtime_auto)
15321561
goto out;
15331562

15341563
dev->power.runtime_auto = true;
1535-
if (atomic_dec_and_test(&dev->power.usage_count))
1564+
ret = rpm_drop_usage_count(dev);
1565+
if (ret == 0)
15361566
rpm_idle(dev, RPM_AUTO | RPM_ASYNC);
1537-
else
1567+
else if (ret > 0)
15381568
trace_rpm_usage_rcuidle(dev, RPM_AUTO | RPM_ASYNC);
15391569

15401570
out:

0 commit comments

Comments
 (0)