diff options
Diffstat (limited to 'drivers/of')
-rw-r--r-- | drivers/of/Kconfig | 4 | ||||
-rw-r--r-- | drivers/of/Makefile | 1 | ||||
-rw-r--r-- | drivers/of/base.c | 3 | ||||
-rw-r--r-- | drivers/of/resolver.c | 336 | ||||
-rw-r--r-- | drivers/of/selftest.c | 120 | ||||
-rw-r--r-- | drivers/of/testcase-data/testcases.dts | 35 |
6 files changed, 490 insertions, 9 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 5160c4eb73c2..1a13f5b722c5 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -11,6 +11,7 @@ config OF_SELFTEST bool "Device Tree Runtime self tests" depends on OF_IRQ && OF_EARLY_FLATTREE select OF_DYNAMIC + select OF_RESOLVE help This option builds in test cases for the device tree infrastructure that are executed once at boot time, and the results dumped to the @@ -79,4 +80,7 @@ config OF_RESERVED_MEM help Helpers to allow for reservation of memory regions +config OF_RESOLVE + bool + endmenu # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index 2b6a7b129d10..ca9209ce50cd 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_OF_PCI) += of_pci.o obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o obj-$(CONFIG_OF_MTD) += of_mtd.o obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o +obj-$(CONFIG_OF_RESOLVE) += resolver.o CFLAGS_fdt.o = -I$(src)/../../scripts/dtc/libfdt CFLAGS_fdt_address.o = -I$(src)/../../scripts/dtc/libfdt diff --git a/drivers/of/base.c b/drivers/of/base.c index 293ed4b687ba..2305dc0382bc 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1021,6 +1021,9 @@ struct device_node *of_find_node_by_phandle(phandle handle) struct device_node *np; unsigned long flags; + if (!handle) + return NULL; + raw_spin_lock_irqsave(&devtree_lock, flags); for (np = of_allnodes; np; np = np->allnext) if (np->phandle == handle) diff --git a/drivers/of/resolver.c b/drivers/of/resolver.c new file mode 100644 index 000000000000..aed7959f800d --- /dev/null +++ b/drivers/of/resolver.c @@ -0,0 +1,336 @@ +/* + * Functions for dealing with DT resolution + * + * Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com> + * Copyright (C) 2012 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> + +/* illegal phandle value (set when unresolved) */ +#define OF_PHANDLE_ILLEGAL 0xdeadbeef + +/** + * Find a node with the give full name by recursively following any of + * the child node links. + */ +static struct device_node *__of_find_node_by_full_name(struct device_node *node, + const char *full_name) +{ + struct device_node *child, *found; + + if (node == NULL) + return NULL; + + /* check */ + if (of_node_cmp(node->full_name, full_name) == 0) + return node; + + for_each_child_of_node(node, child) { + found = __of_find_node_by_full_name(child, full_name); + if (found != NULL) + return found; + } + + return NULL; +} + +/* + * Find live tree's maximum phandle value. + */ +static phandle of_get_tree_max_phandle(void) +{ + struct device_node *node; + phandle phandle; + unsigned long flags; + + /* now search recursively */ + raw_spin_lock_irqsave(&devtree_lock, flags); + phandle = 0; + for_each_of_allnodes(node) { + if (node->phandle != OF_PHANDLE_ILLEGAL && + node->phandle > phandle) + phandle = node->phandle; + } + raw_spin_unlock_irqrestore(&devtree_lock, flags); + + return phandle; +} + +/* + * Adjust a subtree's phandle values by a given delta. + * Makes sure not to just adjust the device node's phandle value, + * but modify the phandle properties values as well. + */ +static void __of_adjust_tree_phandles(struct device_node *node, + int phandle_delta) +{ + struct device_node *child; + struct property *prop; + phandle phandle; + + /* first adjust the node's phandle direct value */ + if (node->phandle != 0 && node->phandle != OF_PHANDLE_ILLEGAL) + node->phandle += phandle_delta; + + /* now adjust phandle & linux,phandle values */ + for_each_property_of_node(node, prop) { + + /* only look for these two */ + if (of_prop_cmp(prop->name, "phandle") != 0 && + of_prop_cmp(prop->name, "linux,phandle") != 0) + continue; + + /* must be big enough */ + if (prop->length < 4) + continue; + + /* read phandle value */ + phandle = be32_to_cpup(prop->value); + if (phandle == OF_PHANDLE_ILLEGAL) /* unresolved */ + continue; + + /* adjust */ + *(uint32_t *)prop->value = cpu_to_be32(node->phandle); + } + + /* now do the children recursively */ + for_each_child_of_node(node, child) + __of_adjust_tree_phandles(child, phandle_delta); +} + +static int __of_adjust_phandle_ref(struct device_node *node, struct property *rprop, int value, bool is_delta) +{ + phandle phandle; + struct device_node *refnode; + struct property *sprop; + char *propval, *propcur, *propend, *nodestr, *propstr, *s; + int offset, propcurlen; + int err = 0; + + /* make a copy */ + propval = kmalloc(rprop->length, GFP_KERNEL); + if (!propval) { + pr_err("%s: Could not copy value of '%s'\n", + __func__, rprop->name); + return -ENOMEM; + } + memcpy(propval, rprop->value, rprop->length); + + propend = propval + rprop->length; + for (propcur = propval; propcur < propend; propcur += propcurlen + 1) { + propcurlen = strlen(propcur); + + nodestr = propcur; + s = strchr(propcur, ':'); + if (!s) { + pr_err("%s: Illegal symbol entry '%s' (1)\n", + __func__, propcur); + err = -EINVAL; + goto err_fail; + } + *s++ = '\0'; + + propstr = s; + s = strchr(s, ':'); + if (!s) { + pr_err("%s: Illegal symbol entry '%s' (2)\n", + __func__, (char *)rprop->value); + err = -EINVAL; + goto err_fail; + } + + *s++ = '\0'; + err = kstrtoint(s, 10, &offset); + if (err != 0) { + pr_err("%s: Could get offset '%s'\n", + __func__, (char *)rprop->value); + goto err_fail; + } + + /* look into the resolve node for the full path */ + refnode = __of_find_node_by_full_name(node, nodestr); + if (!refnode) { + pr_warn("%s: Could not find refnode '%s'\n", + __func__, (char *)rprop->value); + continue; + } + + /* now find the property */ + for_each_property_of_node(refnode, sprop) { + if (of_prop_cmp(sprop->name, propstr) == 0) + break; + } + + if (!sprop) { + pr_err("%s: Could not find property '%s'\n", + __func__, (char *)rprop->value); + err = -ENOENT; + goto err_fail; + } + + phandle = is_delta ? be32_to_cpup(sprop->value + offset) + value : value; + *(__be32 *)(sprop->value + offset) = cpu_to_be32(phandle); + } + +err_fail: + kfree(propval); + return err; +} + +/* + * Adjust the local phandle references by the given phandle delta. + * Assumes the existances of a __local_fixups__ node at the root + * of the tree. Does not take any devtree locks so make sure you + * call this on a tree which is at the detached state. + */ +static int __of_adjust_tree_phandle_references(struct device_node *node, + int phandle_delta) +{ + struct device_node *child; + struct property *rprop; + int err; + + /* locate the symbols & fixups nodes on resolve */ + for_each_child_of_node(node, child) + if (of_node_cmp(child->name, "__local_fixups__") == 0) + break; + + /* no local fixups */ + if (!child) + return 0; + + /* find the local fixups property */ + for_each_property_of_node(child, rprop) { + /* skip properties added automatically */ + if (of_prop_cmp(rprop->name, "name") == 0) + continue; + + err = __of_adjust_phandle_ref(node, rprop, phandle_delta, true); + if (err) + return err; + } + + return 0; +} + +/** + * of_resolve - Resolve the given node against the live tree. + * + * @resolve: Node to resolve + * + * Perform dynamic Device Tree resolution against the live tree + * to the given node to resolve. This depends on the live tree + * having a __symbols__ node, and the resolve node the __fixups__ & + * __local_fixups__ nodes (if needed). + * The result of the operation is a resolve node that it's contents + * are fit to be inserted or operate upon the live tree. + * Returns 0 on success or a negative error value on error. + */ +int of_resolve_phandles(struct device_node *resolve) +{ + struct device_node *child, *refnode; + struct device_node *root_sym, *resolve_sym, *resolve_fix; + struct property *rprop; + const char *refpath; + phandle phandle, phandle_delta; + int err; + + /* the resolve node must exist, and be detached */ + if (!resolve || !of_node_check_flag(resolve, OF_DETACHED)) + return -EINVAL; + + /* first we need to adjust the phandles */ + phandle_delta = of_get_tree_max_phandle() + 1; + __of_adjust_tree_phandles(resolve, phandle_delta); + err = __of_adjust_tree_phandle_references(resolve, phandle_delta); + if (err != 0) + return err; + + root_sym = NULL; + resolve_sym = NULL; + resolve_fix = NULL; + + /* this may fail (if no fixups are required) */ + root_sym = of_find_node_by_path("/__symbols__"); + + /* locate the symbols & fixups nodes on resolve */ + for_each_child_of_node(resolve, child) { + + if (!resolve_sym && + of_node_cmp(child->name, "__symbols__") == 0) + resolve_sym = child; + + if (!resolve_fix && + of_node_cmp(child->name, "__fixups__") == 0) + resolve_fix = child; + + /* both found, don't bother anymore */ + if (resolve_sym && resolve_fix) + break; + } + + /* we do allow for the case where no fixups are needed */ + if (!resolve_fix) { + err = 0; /* no error */ + goto out; + } + + /* we need to fixup, but no root symbols... */ + if (!root_sym) { + err = -EINVAL; + goto out; + } + + for_each_property_of_node(resolve_fix, rprop) { + + /* skip properties added automatically */ + if (of_prop_cmp(rprop->name, "name") == 0) + continue; + + err = of_property_read_string(root_sym, + rprop->name, &refpath); + if (err != 0) { + pr_err("%s: Could not find symbol '%s'\n", + __func__, rprop->name); + goto out; + } + + refnode = of_find_node_by_path(refpath); + if (!refnode) { + pr_err("%s: Could not find node by path '%s'\n", + __func__, refpath); + err = -ENOENT; + goto out; + } + + phandle = refnode->phandle; + of_node_put(refnode); + + pr_debug("%s: %s phandle is 0x%08x\n", + __func__, rprop->name, phandle); + + err = __of_adjust_phandle_ref(resolve, rprop, phandle, false); + if (err) + break; + } + +out: + /* NULL is handled by of_node_put as NOP */ + of_node_put(root_sym); + + return err; +} +EXPORT_SYMBOL_GPL(of_resolve_phandles); diff --git a/drivers/of/selftest.c b/drivers/of/selftest.c index a737cb5974de..78001270a598 100644 --- a/drivers/of/selftest.c +++ b/drivers/of/selftest.c @@ -7,6 +7,7 @@ #include <linux/clk.h> #include <linux/err.h> #include <linux/errno.h> +#include <linux/hashtable.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_fdt.h> @@ -24,7 +25,7 @@ static struct selftest_results { int failed; } selftest_results; -#define NO_OF_NODES 2 +#define NO_OF_NODES 3 static struct device_node *nodes[NO_OF_NODES]; static int last_node_index; static bool selftest_live_tree; @@ -145,6 +146,97 @@ static void __init of_selftest_dynamic(void) "Adding a large property should have passed\n"); } +static int __init of_selftest_check_node_linkage(struct device_node *np) +{ + struct device_node *child, *allnext_index = np; + int count = 0, rc; + + for_each_child_of_node(np, child) { + if (child->parent != np) { + pr_err("Child node %s links to wrong parent %s\n", + child->name, np->name); + return -EINVAL; + } + + while (allnext_index && allnext_index != child) + allnext_index = allnext_index->allnext; + if (allnext_index != child) { + pr_err("Node %s is ordered differently in sibling and allnode lists\n", + child->name); + return -EINVAL; + } + + rc = of_selftest_check_node_linkage(child); + if (rc < 0) + return rc; + count += rc; + } + + return count + 1; +} + +static void __init of_selftest_check_tree_linkage(void) +{ + struct device_node *np; + int allnode_count = 0, child_count; + + if (!of_allnodes) + return; + + for_each_of_allnodes(np) + allnode_count++; + child_count = of_selftest_check_node_linkage(of_allnodes); + + selftest(child_count > 0, "Device node data structure is corrupted\n"); + selftest(child_count == allnode_count, "allnodes list size (%i) doesn't match" + "sibling lists size (%i)\n", allnode_count, child_count); + pr_debug("allnodes list size (%i); sibling lists size (%i)\n", allnode_count, child_count); +} + +struct node_hash { + struct hlist_node node; + struct device_node *np; +}; + +static DEFINE_HASHTABLE(phandle_ht, 8); +static void __init of_selftest_check_phandles(void) +{ + struct device_node *np; + struct node_hash *nh; + struct hlist_node *tmp; + int i, dup_count = 0, phandle_count = 0; + + for_each_of_allnodes(np) { + if (!np->phandle) + continue; + + hash_for_each_possible(phandle_ht, nh, node, np->phandle) { + if (nh->np->phandle == np->phandle) { + pr_info("Duplicate phandle! %i used by %s and %s\n", + np->phandle, nh->np->full_name, np->full_name); + dup_count++; + break; + } + } + + nh = kzalloc(sizeof(*nh), GFP_KERNEL); + if (WARN_ON(!nh)) + return; + + nh->np = np; + hash_add(phandle_ht, &nh->node, np->phandle); + phandle_count++; + } + selftest(dup_count == 0, "Found %i duplicates in %i phandles\n", + dup_count, phandle_count); + + /* Clean up */ + hash_for_each_safe(phandle_ht, i, tmp, nh, node) { + hash_del(&nh->node); + kfree(nh); + } +} + static void __init of_selftest_parse_phandle_with_args(void) { struct device_node *np; @@ -637,6 +729,8 @@ static int attach_node_and_children(struct device_node *np) dup = np; while (dup) { + if (WARN_ON(last_node_index >= NO_OF_NODES)) + return -EINVAL; nodes[last_node_index++] = dup; dup = dup->sibling; } @@ -670,6 +764,7 @@ static int __init selftest_data_add(void) extern uint8_t __dtb_testcases_begin[]; extern uint8_t __dtb_testcases_end[]; const int size = __dtb_testcases_end - __dtb_testcases_begin; + int rc; if (!size) { pr_warn("%s: No testcase data to attach; not running tests\n", @@ -690,6 +785,12 @@ static int __init selftest_data_add(void) pr_warn("%s: No tree to attach; not running tests\n", __func__); return -ENODATA; } + of_node_set_flag(selftest_data_node, OF_DETACHED); + rc = of_resolve_phandles(selftest_data_node); + if (rc) { + pr_err("%s: Failed to resolve phandles (rc=%i)\n", __func__, rc); + return -EINVAL; + } if (!of_allnodes) { /* enabling flag for removing nodes */ @@ -717,10 +818,6 @@ static void detach_node_and_children(struct device_node *np) { while (np->child) detach_node_and_children(np->child); - - while (np->sibling) - detach_node_and_children(np->sibling); - of_detach_node(np); } @@ -749,8 +846,7 @@ static void selftest_data_remove(void) if (nodes[last_node_index]) { np = of_find_node_by_path(nodes[last_node_index]->full_name); if (strcmp(np->full_name, "/aliases") != 0) { - detach_node_and_children(np->child); - of_detach_node(np); + detach_node_and_children(np); } else { for_each_property_of_node(np, prop) { if (strcmp(prop->name, "testcase-alias") == 0) @@ -780,6 +876,8 @@ static int __init of_selftest(void) of_node_put(np); pr_info("start of selftest - you will see error messages\n"); + of_selftest_check_tree_linkage(); + of_selftest_check_phandles(); of_selftest_find_node_by_name(); of_selftest_dynamic(); of_selftest_parse_phandle_with_args(); @@ -790,12 +888,16 @@ static int __init of_selftest(void) of_selftest_parse_interrupts_extended(); of_selftest_match_node(); of_selftest_platform_populate(); - pr_info("end of selftest - %i passed, %i failed\n", - selftest_results.passed, selftest_results.failed); /* removing selftest data from live tree */ selftest_data_remove(); + /* Double check linkage after removing testcase data */ + of_selftest_check_tree_linkage(); + + pr_info("end of selftest - %i passed, %i failed\n", + selftest_results.passed, selftest_results.failed); + return 0; } late_initcall(of_selftest); diff --git a/drivers/of/testcase-data/testcases.dts b/drivers/of/testcase-data/testcases.dts index 219ef9324e9c..6994e15c24bf 100644 --- a/drivers/of/testcase-data/testcases.dts +++ b/drivers/of/testcase-data/testcases.dts @@ -13,3 +13,38 @@ #include "tests-interrupts.dtsi" #include "tests-match.dtsi" #include "tests-platform.dtsi" + +/* + * phandle fixup data - generated by dtc patches that aren't upstream. + * This data must be regenerated whenever phandle references are modified in + * the testdata tree. + * + * The format of this data may be subject to change. For the time being consider + * this a kernel-internal data format. + */ +/ { __local_fixups__ { + fixup = "/testcase-data/testcase-device2:interrupt-parent:0", + "/testcase-data/testcase-device1:interrupt-parent:0", + "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:60", + "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:52", + "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:44", + "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:36", + "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:24", + "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:8", + "/testcase-data/interrupts/interrupts-extended0:interrupts-extended:0", + "/testcase-data/interrupts/interrupts1:interrupt-parent:0", + "/testcase-data/interrupts/interrupts0:interrupt-parent:0", + "/testcase-data/interrupts/intmap1:interrupt-map:12", + "/testcase-data/interrupts/intmap0:interrupt-map:52", + "/testcase-data/interrupts/intmap0:interrupt-map:36", + "/testcase-data/interrupts/intmap0:interrupt-map:16", + "/testcase-data/interrupts/intmap0:interrupt-map:4", + "/testcase-data/phandle-tests/consumer-a:phandle-list-bad-args:12", + "/testcase-data/phandle-tests/consumer-a:phandle-list-bad-args:0", + "/testcase-data/phandle-tests/consumer-a:phandle-list:56", + "/testcase-data/phandle-tests/consumer-a:phandle-list:52", + "/testcase-data/phandle-tests/consumer-a:phandle-list:40", + "/testcase-data/phandle-tests/consumer-a:phandle-list:24", + "/testcase-data/phandle-tests/consumer-a:phandle-list:8", + "/testcase-data/phandle-tests/consumer-a:phandle-list:0"; +}; }; |