summaryrefslogtreecommitdiffstats
path: root/drivers/of
diff options
context:
space:
mode:
authorFrank Rowand <frank.rowand@sony.com>2018-12-18 20:40:03 +0100
committerRob Herring <robh@kernel.org>2018-12-21 19:42:36 +0100
commit5801169a2ed20003f771acecf3ac00574cf10a38 (patch)
tree56bcdc99f2d669033eea3c4308e52419a44ddc7e /drivers/of
parentof: of_node_get()/of_node_put() nodes held in phandle cache (diff)
downloadlinux-5801169a2ed20003f771acecf3ac00574cf10a38.tar.xz
linux-5801169a2ed20003f771acecf3ac00574cf10a38.zip
of: __of_detach_node() - remove node from phandle cache
Non-overlay dynamic devicetree node removal may leave the node in the phandle cache. Subsequent calls to of_find_node_by_phandle() will incorrectly find the stale entry. Remove the node from the cache. Add paranoia checks in of_find_node_by_phandle() as a second level of defense (do not return cached node if detached, do not add node to cache if detached). Fixes: 0b3ce78e90fc ("of: cache phandle nodes to reduce cost of of_find_node_by_phandle()") Reported-by: Michael Bringmann <mwb@linux.vnet.ibm.com> Cc: stable@vger.kernel.org # v4.17+ Signed-off-by: Frank Rowand <frank.rowand@sony.com> Signed-off-by: Rob Herring <robh@kernel.org>
Diffstat (limited to 'drivers/of')
-rw-r--r--drivers/of/base.c31
-rw-r--r--drivers/of/dynamic.c3
-rw-r--r--drivers/of/of_private.h4
3 files changed, 37 insertions, 1 deletions
diff --git a/drivers/of/base.c b/drivers/of/base.c
index 4da1c688995b..5226e898476e 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -169,6 +169,28 @@ int of_free_phandle_cache(void)
late_initcall_sync(of_free_phandle_cache);
#endif
+/*
+ * Caller must hold devtree_lock.
+ */
+void __of_free_phandle_cache_entry(phandle handle)
+{
+ phandle masked_handle;
+ struct device_node *np;
+
+ if (!handle)
+ return;
+
+ masked_handle = handle & phandle_cache_mask;
+
+ if (phandle_cache) {
+ np = phandle_cache[masked_handle];
+ if (np && handle == np->phandle) {
+ of_node_put(np);
+ phandle_cache[masked_handle] = NULL;
+ }
+ }
+}
+
void of_populate_phandle_cache(void)
{
unsigned long flags;
@@ -1214,11 +1236,18 @@ struct device_node *of_find_node_by_phandle(phandle handle)
if (phandle_cache[masked_handle] &&
handle == phandle_cache[masked_handle]->phandle)
np = phandle_cache[masked_handle];
+ if (np && of_node_check_flag(np, OF_DETACHED)) {
+ WARN_ON(1); /* did not uncache np on node removal */
+ of_node_put(np);
+ phandle_cache[masked_handle] = NULL;
+ np = NULL;
+ }
}
if (!np) {
for_each_of_allnodes(np)
- if (np->phandle == handle) {
+ if (np->phandle == handle &&
+ !of_node_check_flag(np, OF_DETACHED)) {
if (phandle_cache) {
/* will put when removed from cache */
of_node_get(np);
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c
index b4e5b90cb314..a09c1c3cf831 100644
--- a/drivers/of/dynamic.c
+++ b/drivers/of/dynamic.c
@@ -277,6 +277,9 @@ void __of_detach_node(struct device_node *np)
}
of_node_set_flag(np, OF_DETACHED);
+
+ /* race with of_find_node_by_phandle() prevented by devtree_lock */
+ __of_free_phandle_cache_entry(np->phandle);
}
/**
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h
index 5d1567025358..24786818e32e 100644
--- a/drivers/of/of_private.h
+++ b/drivers/of/of_private.h
@@ -84,6 +84,10 @@ static inline void __of_detach_node_sysfs(struct device_node *np) {}
int of_resolve_phandles(struct device_node *tree);
#endif
+#if defined(CONFIG_OF_DYNAMIC)
+void __of_free_phandle_cache_entry(phandle handle);
+#endif
+
#if defined(CONFIG_OF_OVERLAY)
void of_overlay_mutex_lock(void);
void of_overlay_mutex_unlock(void);