mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-14 12:37:32 +00:00
clk: Evict unregistered clks from parent caches
commitbdcf1dc253
upstream. We leave a dangling pointer in each clk_core::parents array that has an unregistered clk as a potential parent when that clk_core pointer is freed by clk{_hw}_unregister(). It is impossible for the true parent of a clk to be set with clk_set_parent() once the dangling pointer is left in the cache because we compare parent pointers in clk_fetch_parent_index() instead of checking for a matching clk name or clk_hw pointer. Before commitede7785847
("clk: Remove global clk traversal on fetch parent index"), we would check clk_hw pointers, which has a higher chance of being the same between registration and unregistration, but it can still be allocated and freed by the clk provider. In fact, this has been a long standing problem since commitda0f0b2c3a
("clk: Correct lookup logic in clk_fetch_parent_index()") where we stopped trying to compare clk names and skipped over entries in the cache that weren't NULL. There are good (performance) reasons to not do the global tree lookup in cases where the cache holds dangling pointers to parents that have been unregistered. Let's take the performance hit on the uncommon registration path instead. Loop through all the clk_core::parents arrays when a clk is unregistered and set the entry to NULL when the parent cache entry and clk being unregistered are the same pointer. This will fix this problem and avoid the overhead for the "normal" case. Based on a patch by Bjorn Andersson. Fixes:da0f0b2c3a
("clk: Correct lookup logic in clk_fetch_parent_index()") Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org> Tested-by: Sai Prakash Ranjan <saiprakash.ranjan@codeaurora.org> Signed-off-by: Stephen Boyd <sboyd@kernel.org> Link: https://lkml.kernel.org/r/20190828181959.204401-1-sboyd@kernel.org Tested-by: Naresh Kamboju <naresh.kamboju@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
877ed46861
commit
f114a36246
1 changed files with 41 additions and 11 deletions
|
@ -39,6 +39,17 @@ static HLIST_HEAD(clk_root_list);
|
||||||
static HLIST_HEAD(clk_orphan_list);
|
static HLIST_HEAD(clk_orphan_list);
|
||||||
static LIST_HEAD(clk_notifier_list);
|
static LIST_HEAD(clk_notifier_list);
|
||||||
|
|
||||||
|
static struct hlist_head *all_lists[] = {
|
||||||
|
&clk_root_list,
|
||||||
|
&clk_orphan_list,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct hlist_head *orphan_list[] = {
|
||||||
|
&clk_orphan_list,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
/*** private data structures ***/
|
/*** private data structures ***/
|
||||||
|
|
||||||
struct clk_core {
|
struct clk_core {
|
||||||
|
@ -1993,17 +2004,6 @@ static int inited = 0;
|
||||||
static DEFINE_MUTEX(clk_debug_lock);
|
static DEFINE_MUTEX(clk_debug_lock);
|
||||||
static HLIST_HEAD(clk_debug_list);
|
static HLIST_HEAD(clk_debug_list);
|
||||||
|
|
||||||
static struct hlist_head *all_lists[] = {
|
|
||||||
&clk_root_list,
|
|
||||||
&clk_orphan_list,
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct hlist_head *orphan_list[] = {
|
|
||||||
&clk_orphan_list,
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void clk_summary_show_one(struct seq_file *s, struct clk_core *c,
|
static void clk_summary_show_one(struct seq_file *s, struct clk_core *c,
|
||||||
int level)
|
int level)
|
||||||
{
|
{
|
||||||
|
@ -2735,6 +2735,34 @@ static const struct clk_ops clk_nodrv_ops = {
|
||||||
.set_parent = clk_nodrv_set_parent,
|
.set_parent = clk_nodrv_set_parent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void clk_core_evict_parent_cache_subtree(struct clk_core *root,
|
||||||
|
struct clk_core *target)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct clk_core *child;
|
||||||
|
|
||||||
|
for (i = 0; i < root->num_parents; i++)
|
||||||
|
if (root->parents[i] == target)
|
||||||
|
root->parents[i] = NULL;
|
||||||
|
|
||||||
|
hlist_for_each_entry(child, &root->children, child_node)
|
||||||
|
clk_core_evict_parent_cache_subtree(child, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove this clk from all parent caches */
|
||||||
|
static void clk_core_evict_parent_cache(struct clk_core *core)
|
||||||
|
{
|
||||||
|
struct hlist_head **lists;
|
||||||
|
struct clk_core *root;
|
||||||
|
|
||||||
|
lockdep_assert_held(&prepare_lock);
|
||||||
|
|
||||||
|
for (lists = all_lists; *lists; lists++)
|
||||||
|
hlist_for_each_entry(root, *lists, child_node)
|
||||||
|
clk_core_evict_parent_cache_subtree(root, core);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clk_unregister - unregister a currently registered clock
|
* clk_unregister - unregister a currently registered clock
|
||||||
* @clk: clock to unregister
|
* @clk: clock to unregister
|
||||||
|
@ -2773,6 +2801,8 @@ void clk_unregister(struct clk *clk)
|
||||||
clk_core_set_parent(child, NULL);
|
clk_core_set_parent(child, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clk_core_evict_parent_cache(clk->core);
|
||||||
|
|
||||||
hlist_del_init(&clk->core->child_node);
|
hlist_del_init(&clk->core->child_node);
|
||||||
|
|
||||||
if (clk->core->prepare_count)
|
if (clk->core->prepare_count)
|
||||||
|
|
Loading…
Reference in a new issue