Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cdb300a0 authored by Ulf Hansson's avatar Ulf Hansson Committed by Rafael J. Wysocki
Browse files

PM / Domains: Fix potential deadlock while adding/removing subdomains



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: default avatarUlf Hansson <ulf.hansson@linaro.org>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent 0106ef51
Loading
Loading
Loading
Loading
+6 −8
Original line number Original line Diff line number Diff line
@@ -1340,8 +1340,8 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd,
	if (!link)
	if (!link)
		return -ENOMEM;
		return -ENOMEM;


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


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


 out:
 out:
	mutex_unlock(&subdomain->lock);
	mutex_unlock(&genpd->lock);
	mutex_unlock(&genpd->lock);
	mutex_unlock(&subdomain->lock);
	if (ret)
	if (ret)
		kfree(link);
		kfree(link);
	return ret;
	return ret;
@@ -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))
	if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain))
		return -EINVAL;
		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) {
	if (!list_empty(&subdomain->slave_links) || subdomain->device_count) {
		pr_warn("%s: unable to remove subdomain %s\n", genpd->name,
		pr_warn("%s: unable to remove subdomain %s\n", genpd->name,
@@ -1399,22 +1400,19 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
		if (link->slave != subdomain)
		if (link->slave != subdomain)
			continue;
			continue;


		mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING);

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


		mutex_unlock(&subdomain->lock);

		ret = 0;
		ret = 0;
		break;
		break;
	}
	}


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


	return ret;
	return ret;
}
}