Message ID | ef43f01ac06976b2aa2b17266d307bb1a4f7e6f9.1604294460.git.viresh.kumar@linaro.org |
---|---|
State | Accepted |
Commit | ef43f01ac06976b2aa2b17266d307bb1a4f7e6f9 |
Headers | show |
Series | [1/3] opp: Always add entries in dev_list with opp_table->lock held | expand |
On 02-11-20, 10:51, Viresh Kumar wrote: > diff --git a/drivers/opp/core.c b/drivers/opp/core.c > +/* > + * We need to make sure that the OPP table for a device doesn't get added twice, > + * if this routine gets called in parallel with the same device pointer. > + * > + * The simplest way to enforce that is to perform everything (find existing > + * table and if not found, create a new one) under the opp_table_lock, so only > + * one creator gets access to the same. But that expands the critical section > + * under the lock and may end up causing circular dependencies with frameworks > + * like debugfs, interconnect or clock framework as they may be direct or > + * indirect users of OPP core. > + * > + * And for that reason we have to go for a bit tricky implementation here, which > + * uses the opp_tables_busy flag to indicate if another creator is in the middle > + * of adding an OPP table and others should wait for it to finish. > + */ > static struct opp_table *_opp_get_opp_table(struct device *dev, int index) > { > struct opp_table *opp_table; > > - /* Hold our table modification lock here */ > +again: > mutex_lock(&opp_table_lock); > > opp_table = _find_opp_table_unlocked(dev); > if (!IS_ERR(opp_table)) > goto unlock; > > + /* > + * The opp_tables list or an OPP table's dev_list is getting updated by > + * another user, wait for it to finish. > + */ > + if (unlikely(opp_tables_busy)) { > + mutex_unlock(&opp_table_lock); > + cpu_relax(); > + goto again; > + } > + > + opp_tables_busy = true; > opp_table = _managed_opp(dev, index); > + > + /* Drop the lock to reduce the size of critical section */ > + mutex_unlock(&opp_table_lock); > + > if (opp_table) { > if (!_add_opp_dev(dev, opp_table)) { > dev_pm_opp_put_opp_table(opp_table); > opp_table = ERR_PTR(-ENOMEM); > } > - goto unlock; > + > + mutex_lock(&opp_table_lock); > + } else { > + opp_table = _allocate_opp_table(dev, index); > + > + mutex_lock(&opp_table_lock); > + if (!IS_ERR(opp_table)) > + list_add(&opp_table->node, &opp_tables); > } > > - opp_table = _allocate_opp_table(dev, index); > + opp_tables_busy = false; And here is a fix that will be merged with this patch while applying. It is required as _allocate_opp_table() (which calls _find_table_of_opp_np()) isn't called with the opp_table_lock anymore. diff --git a/drivers/opp/of.c b/drivers/opp/of.c index c718092757d9..6b7f0066942d 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -112,8 +112,6 @@ static struct opp_table *_find_table_of_opp_np(struct device_node *opp_np) struct opp_table *opp_table; struct device_node *opp_table_np; - lockdep_assert_held(&opp_table_lock); - opp_table_np = of_get_parent(opp_np); if (!opp_table_np) goto err; @@ -121,12 +119,15 @@ static struct opp_table *_find_table_of_opp_np(struct device_node *opp_np) /* It is safe to put the node now as all we need now is its address */ of_node_put(opp_table_np); + mutex_lock(&opp_table_lock); list_for_each_entry(opp_table, &opp_tables, node) { if (opp_table_np == opp_table->np) { _get_opp_table_kref(opp_table); + mutex_unlock(&opp_table_lock); return opp_table; } } + mutex_unlock(&opp_table_lock); err: return ERR_PTR(-ENODEV);
diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 0e0a5269dc82..84035ab8bb31 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1036,8 +1036,8 @@ static void _remove_opp_dev(struct opp_device *opp_dev, kfree(opp_dev); } -static struct opp_device *_add_opp_dev_unlocked(const struct device *dev, - struct opp_table *opp_table) +struct opp_device *_add_opp_dev(const struct device *dev, + struct opp_table *opp_table) { struct opp_device *opp_dev; @@ -1048,7 +1048,9 @@ static struct opp_device *_add_opp_dev_unlocked(const struct device *dev, /* Initialize opp-dev */ opp_dev->dev = dev; + mutex_lock(&opp_table->lock); list_add(&opp_dev->node, &opp_table->dev_list); + mutex_unlock(&opp_table->lock); /* Create debugfs entries for the opp_table */ opp_debug_register(opp_dev, opp_table); @@ -1056,18 +1058,6 @@ static struct opp_device *_add_opp_dev_unlocked(const struct device *dev, return opp_dev; } -struct opp_device *_add_opp_dev(const struct device *dev, - struct opp_table *opp_table) -{ - struct opp_device *opp_dev; - - mutex_lock(&opp_table->lock); - opp_dev = _add_opp_dev_unlocked(dev, opp_table); - mutex_unlock(&opp_table->lock); - - return opp_dev; -} - static struct opp_table *_allocate_opp_table(struct device *dev, int index) { struct opp_table *opp_table; @@ -1148,7 +1138,7 @@ static struct opp_table *_opp_get_opp_table(struct device *dev, int index) opp_table = _managed_opp(dev, index); if (opp_table) { - if (!_add_opp_dev_unlocked(dev, opp_table)) { + if (!_add_opp_dev(dev, opp_table)) { dev_pm_opp_put_opp_table(opp_table); opp_table = ERR_PTR(-ENOMEM); }
The readers of dev_list expect the updates to it to take place from within the opp_table->lock and this is missing in the case where the dev_list is updated for already managed OPPs. Fix that by calling _add_opp_dev() from there and remove the now unused _add_opp_dev_unlocked() callback. While at it, also reduce the length of the critical section in _add_opp_dev(). Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> --- drivers/opp/core.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-)