Skip to content

Commit

Permalink
opp: Don't drop extra references to OPPs accidentally
Browse files Browse the repository at this point in the history
commit 606a5d4 upstream.

We are required to call dev_pm_opp_put() from outside of the
opp_table->lock as debugfs removal needs to happen lock-less to avoid
circular dependency issues.

commit cf1fac9 ("opp: Reduce the size of critical section in
_opp_kref_release()") tried to fix that introducing a new routine
_opp_get_next() which keeps returning OPPs that can be freed by the
callers and this routine shall be called without holding the
opp_table->lock.

Though the commit overlooked the fact that the OPPs can be referenced by
other users as well and this routine will end up dropping references
which were taken by other users and hence freeing the OPPs prematurely.

In effect, other users of the OPPs will end up having invalid pointers
at hand. We didn't see any crash reports earlier as the exact situation
never happened, though it is certainly possible.

We need a way to mark which OPPs are no longer referenced by the OPP
core, so we don't drop extra references to them accidentally.

This commit adds another OPP flag, "removed", which is used to track
this. And now we should never end up dropping extra references to the
OPPs.

Cc: v5.11+ <stable@vger.kernel.org> # v5.11+
Fixes: cf1fac9 ("opp: Reduce the size of critical section in _opp_kref_release()")
Signed-off-by: Beata Michalska <beata.michalska@arm.com>
[ Viresh: Almost rewrote entire patch, added new "removed" field,
	  rewrote commit log and added the correct Fixes tag. ]
Co-developed-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
bea-arm authored and gregkh committed Mar 17, 2021
1 parent 569c90d commit 614b31a
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 23 deletions.
48 changes: 25 additions & 23 deletions drivers/opp/core.c
Expand Up @@ -1335,7 +1335,11 @@ static struct dev_pm_opp *_opp_get_next(struct opp_table *opp_table,

mutex_lock(&opp_table->lock);
list_for_each_entry(temp, &opp_table->opp_list, node) {
if (dynamic == temp->dynamic) {
/*
* Refcount must be dropped only once for each OPP by OPP core,
* do that with help of "removed" flag.
*/
if (!temp->removed && dynamic == temp->dynamic) {
opp = temp;
break;
}
Expand All @@ -1345,10 +1349,27 @@ static struct dev_pm_opp *_opp_get_next(struct opp_table *opp_table,
return opp;
}

bool _opp_remove_all_static(struct opp_table *opp_table)
/*
* Can't call dev_pm_opp_put() from under the lock as debugfs removal needs to
* happen lock less to avoid circular dependency issues. This routine must be
* called without the opp_table->lock held.
*/
static void _opp_remove_all(struct opp_table *opp_table, bool dynamic)
{
struct dev_pm_opp *opp;

while ((opp = _opp_get_next(opp_table, dynamic))) {
opp->removed = true;
dev_pm_opp_put(opp);

/* Drop the references taken by dev_pm_opp_add() */
if (dynamic)
dev_pm_opp_put_opp_table(opp_table);
}
}

bool _opp_remove_all_static(struct opp_table *opp_table)
{
mutex_lock(&opp_table->lock);

if (!opp_table->parsed_static_opps) {
Expand All @@ -1363,13 +1384,7 @@ bool _opp_remove_all_static(struct opp_table *opp_table)

mutex_unlock(&opp_table->lock);

/*
* Can't remove the OPP from under the lock, debugfs removal needs to
* happen lock less to avoid circular dependency issues.
*/
while ((opp = _opp_get_next(opp_table, false)))
dev_pm_opp_put(opp);

_opp_remove_all(opp_table, false);
return true;
}

Expand All @@ -1382,25 +1397,12 @@ bool _opp_remove_all_static(struct opp_table *opp_table)
void dev_pm_opp_remove_all_dynamic(struct device *dev)
{
struct opp_table *opp_table;
struct dev_pm_opp *opp;
int count = 0;

opp_table = _find_opp_table(dev);
if (IS_ERR(opp_table))
return;

/*
* Can't remove the OPP from under the lock, debugfs removal needs to
* happen lock less to avoid circular dependency issues.
*/
while ((opp = _opp_get_next(opp_table, true))) {
dev_pm_opp_put(opp);
count++;
}

/* Drop the references taken by dev_pm_opp_add() */
while (count--)
dev_pm_opp_put_opp_table(opp_table);
_opp_remove_all(opp_table, true);

/* Drop the reference taken by _find_opp_table() */
dev_pm_opp_put_opp_table(opp_table);
Expand Down
2 changes: 2 additions & 0 deletions drivers/opp/opp.h
Expand Up @@ -56,6 +56,7 @@ extern struct list_head opp_tables;
* @dynamic: not-created from static DT entries.
* @turbo: true if turbo (boost) OPP
* @suspend: true if suspend OPP
* @removed: flag indicating that OPP's reference is dropped by OPP core.
* @pstate: Device's power domain's performance state.
* @rate: Frequency in hertz
* @level: Performance level
Expand All @@ -78,6 +79,7 @@ struct dev_pm_opp {
bool dynamic;
bool turbo;
bool suspend;
bool removed;
unsigned int pstate;
unsigned long rate;
unsigned int level;
Expand Down

0 comments on commit 614b31a

Please sign in to comment.