Skip to content

Commit

Permalink
PM / Domains: Fix potential deadlock while adding/removing subdomains
Browse files Browse the repository at this point in the history
We must preserve the same order of how we acquire and release the lock for
genpd, as otherwise we may encounter deadlocks.

The power on phase of a genpd starts by acquiring its lock. Then it walks
the hierarchy of its parent domains to be able to power on these first, as
per design of genpd.

From a locking perspective this means the locks of the parents becomes
acquired after the lock of the subdomain.

Let's fix pm_genpd_add|remove_subdomain() to maintain the same order of
acquiring/releasing the genpd lock as being applied in the power on/off
sequence.

Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
  • Loading branch information
Ulf Hansson authored and Rafael J. Wysocki committed Jan 27, 2016
1 parent 0106ef5 commit cdb300a
Showing 1 changed file with 6 additions and 8 deletions.
14 changes: 6 additions & 8 deletions drivers/base/power/domain.c
Original file line number Diff line number Diff line change
Expand Up @@ -1340,8 +1340,8 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd,
if (!link)
return -ENOMEM;

mutex_lock(&genpd->lock);
mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING);
mutex_lock(&subdomain->lock);
mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING);

if (genpd->status == GPD_STATE_POWER_OFF
&& subdomain->status != GPD_STATE_POWER_OFF) {
Expand All @@ -1364,8 +1364,8 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd,
genpd_sd_counter_inc(genpd);

out:
mutex_unlock(&subdomain->lock);
mutex_unlock(&genpd->lock);
mutex_unlock(&subdomain->lock);
if (ret)
kfree(link);
return ret;
Expand All @@ -1386,7 +1386,8 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain))
return -EINVAL;

mutex_lock(&genpd->lock);
mutex_lock(&subdomain->lock);
mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING);

if (!list_empty(&subdomain->slave_links) || subdomain->device_count) {
pr_warn("%s: unable to remove subdomain %s\n", genpd->name,
Expand All @@ -1399,22 +1400,19 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
if (link->slave != subdomain)
continue;

mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING);

list_del(&link->master_node);
list_del(&link->slave_node);
kfree(link);
if (subdomain->status != GPD_STATE_POWER_OFF)
genpd_sd_counter_dec(genpd);

mutex_unlock(&subdomain->lock);

ret = 0;
break;
}

out:
mutex_unlock(&genpd->lock);
mutex_unlock(&subdomain->lock);

return ret;
}
Expand Down

0 comments on commit cdb300a

Please sign in to comment.