Skip to content

Commit d1579e6

Browse files
committed
PM: runtime: Add safety net to supplier device release
Because refcount_dec_not_one() returns true if the target refcount becomes saturated, it is generally unsafe to use its return value as a loop termination condition, but that is what happens when a device link's supplier device is released during runtime PM suspend operations and on device link removal. To address this, introduce pm_runtime_release_supplier() to be used in the above cases which will check the supplier device's runtime PM usage counter in addition to the refcount_dec_not_one() return value, so the loop can be terminated in case the rpm_active refcount value becomes invalid, and update the code in question to use it as appropriate. This change is not expected to have any visible functional impact. Reported-by: Peter Zijlstra <[email protected]> Signed-off-by: Rafael J. Wysocki <[email protected]> Acked-by: Greg Kroah-Hartman <[email protected]> Acked-by: Peter Zijlstra (Intel) <[email protected]>
1 parent c24efa6 commit d1579e6

File tree

3 files changed

+34
-13
lines changed

3 files changed

+34
-13
lines changed

drivers/base/core.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,7 @@ static void device_link_release_fn(struct work_struct *work)
485485
/* Ensure that all references to the link object have been dropped. */
486486
device_link_synchronize_removal();
487487

488-
while (refcount_dec_not_one(&link->rpm_active))
489-
pm_runtime_put(link->supplier);
488+
pm_runtime_release_supplier(link, true);
490489

491490
put_device(link->consumer);
492491
put_device(link->supplier);

drivers/base/power/runtime.c

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -305,19 +305,40 @@ static int rpm_get_suppliers(struct device *dev)
305305
return 0;
306306
}
307307

308+
/**
309+
* pm_runtime_release_supplier - Drop references to device link's supplier.
310+
* @link: Target device link.
311+
* @check_idle: Whether or not to check if the supplier device is idle.
312+
*
313+
* Drop all runtime PM references associated with @link to its supplier device
314+
* and if @check_idle is set, check if that device is idle (and so it can be
315+
* suspended).
316+
*/
317+
void pm_runtime_release_supplier(struct device_link *link, bool check_idle)
318+
{
319+
struct device *supplier = link->supplier;
320+
321+
/*
322+
* The additional power.usage_count check is a safety net in case
323+
* the rpm_active refcount becomes saturated, in which case
324+
* refcount_dec_not_one() would return true forever, but it is not
325+
* strictly necessary.
326+
*/
327+
while (refcount_dec_not_one(&link->rpm_active) &&
328+
atomic_read(&supplier->power.usage_count) > 0)
329+
pm_runtime_put_noidle(supplier);
330+
331+
if (check_idle)
332+
pm_request_idle(supplier);
333+
}
334+
308335
static void __rpm_put_suppliers(struct device *dev, bool try_to_suspend)
309336
{
310337
struct device_link *link;
311338

312339
list_for_each_entry_rcu(link, &dev->links.suppliers, c_node,
313-
device_links_read_lock_held()) {
314-
315-
while (refcount_dec_not_one(&link->rpm_active))
316-
pm_runtime_put_noidle(link->supplier);
317-
318-
if (try_to_suspend)
319-
pm_request_idle(link->supplier);
320-
}
340+
device_links_read_lock_held())
341+
pm_runtime_release_supplier(link, try_to_suspend);
321342
}
322343

323344
static void rpm_put_suppliers(struct device *dev)
@@ -1777,9 +1798,7 @@ void pm_runtime_drop_link(struct device_link *link)
17771798
return;
17781799

17791800
pm_runtime_drop_link_count(link->consumer);
1780-
1781-
while (refcount_dec_not_one(&link->rpm_active))
1782-
pm_runtime_put(link->supplier);
1801+
pm_runtime_release_supplier(link, true);
17831802
}
17841803

17851804
static bool pm_runtime_need_not_resume(struct device *dev)

include/linux/pm_runtime.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ extern void pm_runtime_get_suppliers(struct device *dev);
5858
extern void pm_runtime_put_suppliers(struct device *dev);
5959
extern void pm_runtime_new_link(struct device *dev);
6060
extern void pm_runtime_drop_link(struct device_link *link);
61+
extern void pm_runtime_release_supplier(struct device_link *link, bool check_idle);
6162

6263
extern int devm_pm_runtime_enable(struct device *dev);
6364

@@ -283,6 +284,8 @@ static inline void pm_runtime_get_suppliers(struct device *dev) {}
283284
static inline void pm_runtime_put_suppliers(struct device *dev) {}
284285
static inline void pm_runtime_new_link(struct device *dev) {}
285286
static inline void pm_runtime_drop_link(struct device_link *link) {}
287+
static inline void pm_runtime_release_supplier(struct device_link *link,
288+
bool check_idle) {}
286289

287290
#endif /* !CONFIG_PM */
288291

0 commit comments

Comments
 (0)