Skip to contentSkip to conversation

clk: Evict unregistered clks from parent caches

PATCH Superseded Stephen Boyd 4 participants 5 messages Aug 26, 2019 – Aug 28, 2019 Common clk

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore

Stephen Boyd

author maintainer
#0 lore raw

PATCH 1/1 clk: Evict unregistered clks from parent caches

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 commit ede77858473a ("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 commit da0f0b2c3ad2 ("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 da0f0b2c3ad2 ("clk: Correct lookup logic in clk_fetch_parent_index()")
Cc Bjorn Andersson <[email protected]>
Cc Sai Prakash Ranjan <[email protected]>
---
 drivers/clk/clk.c | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
drivers/clk/clk.c +30 -0
index c0990703ce54..f3982bfa39d6 100644
@@ -3737,6 +3737,34 @@ static const struct clk_ops clk_nodrv_ops = {
 	.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].core == target)
+			root->parents[i].core = 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: clock to unregister
@@ -3775,6 +3803,8 @@ void clk_unregister(struct clk *clk)
 			clk_core_set_parent_nolock(child, NULL);
 	}
 
+	clk_core_evict_parent_cache(clk->core);
+
 	hlist_del_init(&clk->core->child_node);
 
 	if (clk->core->prepare_count)
Signature · 1 line
Sent by a computer through tubes
Reviewed-by on v1 1/1
#1 lore raw
Quoted 25 lines · Stephen Boyd

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 commit ede77858473a ("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 commit da0f0b2c3ad2 ("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.

Quoted 4 lines

Based on a patch by Bjorn Andersson.

Fixes da0f0b2c3ad2 ("clk: Correct lookup logic in clk_fetch_parent_index()")
Cc Bjorn Andersson <[email protected]>

Thanks for writing this up.

Quoted 57 lines
Cc Sai Prakash Ranjan <[email protected]>
---
 drivers/clk/clk.c | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
drivers/clk/clk.c +30 -0
index c0990703ce54..f3982bfa39d6 100644
@@ -3737,6 +3737,34 @@ static const struct clk_ops clk_nodrv_ops = {
 	.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].core == target)
+			root->parents[i].core = 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: clock to unregister
@@ -3775,6 +3803,8 @@ void clk_unregister(struct clk *clk)
 			clk_core_set_parent_nolock(child, NULL);
 	}
 
+	clk_core_evict_parent_cache(clk->core);
+
 	hlist_del_init(&clk->core->child_node);
 
 	if (clk->core->prepare_count)
Signature · 1 line
Sent by a computer through tubes
Tested-by on v1 1/1
#2 lore raw
In reply to Stephen Boyd
Quoted 75 lines · Stephen Boyd

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 commit ede77858473a ("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 commit da0f0b2c3ad2 ("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 da0f0b2c3ad2 ("clk: Correct lookup logic in clk_fetch_parent_index()")
Cc Bjorn Andersson <[email protected]>
Cc Sai Prakash Ranjan <[email protected]>
---
 drivers/clk/clk.c | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
drivers/clk/clk.c +28 -0
index c0990703ce54..f3982bfa39d6 100644
@@ -3737,6 +3737,34 @@ static const struct clk_ops clk_nodrv_ops = {
 	.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].core == target)
+			root->parents[i].core = 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: clock to unregister

drivers/clk/clk.c void clk_unregister(struct clk *clk) · line 3810

Quoted 8 lines
drivers/clk/clk.c +2 -0
@@ -3775,6 +3803,8 @@ void clk_unregister(struct clk *clk)
 			clk_core_set_parent_nolock(child, NULL);
 	}
 
+	clk_core_evict_parent_cache(clk->core);
+
 	hlist_del_init(&clk->core->child_node);
 
 	if (clk->core->prepare_count)

Thanks Stephen. Tested this on cheza now and the issue is not seen anymore.

Signature · 2 lines
QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#3 lore raw
In reply to Stephen Boyd
I love your patch! Yet something to improve:

Hi Stephen,

I love your patch! Yet something to improve:

[auto build test ERROR on linus/master]
[cannot apply to v5.3-rc6 next-20190827]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url: https://github.com/0day-ci/linux/commits/Stephen-Boyd/clk-Evict-unregistered-clks-from-parent-caches/20190827-165138
config: mips-allnoconfig (attached as .config)
compiler: mips-linux-gcc (GCC) 7.4.0
reproduce:
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
GCC_VERSION=7.4.0 make.cross ARCH=mips

If you fix the issue, kindly add following tag

All errors (new ones prefixed by >>):

drivers/clk/clk.c: In function 'clk_core_evict_parent_cache':

>> drivers/clk/clk.c:3785:15: error: 'all_lists' undeclared (first use in this function); did you mean 'lists'?

for (lists = all_lists; *lists; lists++)
^~~~~~~~~
lists
drivers/clk/clk.c:3785:15: note: each undeclared identifier is reported only once for each function it appears in

vim +3785 drivers/clk/clk.c

3776
3777 /* Remove this clk from all parent caches */
3778 static void clk_core_evict_parent_cache(struct clk_core *core)
3779 {
3780 struct hlist_head **lists;
3781 struct clk_core *root;
3782
3783 lockdep_assert_held(&prepare_lock);
3784

> 3785		for (lists = all_lists; *lists; lists++)

3786 hlist_for_each_entry(root, *lists, child_node)
3787 clk_core_evict_parent_cache_subtree(root, core);
3788

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation

author maintainer
#4 lore raw

Quoting kbuild test robot (2019-08-27 03:28:35)

Quoted 20 lines

Hi Stephen,

I love your patch! Yet something to improve:

[auto build test ERROR on linus/master]
[cannot apply to v5.3-rc6 next-20190827]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url: https://github.com/0day-ci/linux/commits/Stephen-Boyd/clk-Evict-unregistered-clks-from-parent-caches/20190827-165138
config: mips-allnoconfig (attached as .config)
compiler: mips-linux-gcc (GCC) 7.4.0
reproduce:
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
GCC_VERSION=7.4.0 make.cross ARCH=mips

If you fix the issue, kindly add following tag

Quoted 8 lines

All errors (new ones prefixed by >>):

drivers/clk/clk.c: In function 'clk_core_evict_parent_cache':

Quoted 1 line
Quoted 1 line

drivers/clk/clk.c:3785:15: error: 'all_lists' undeclared (first use in this function); did you mean 'lists'?

for (lists = all_lists; *lists; lists++)
^~~~~~~~~
lists
drivers/clk/clk.c:3785:15: note: each undeclared identifier is reported only once for each function it appears in

Aha, thanks. all_lists is for debugfs it seems.