diff options
Diffstat (limited to 'drivers/of')
-rw-r--r-- | drivers/of/Kconfig | 1 | ||||
-rw-r--r-- | drivers/of/Makefile | 4 | ||||
-rw-r--r-- | drivers/of/address.c | 24 | ||||
-rw-r--r-- | drivers/of/base.c | 123 | ||||
-rw-r--r-- | drivers/of/fdt.c | 458 | ||||
-rw-r--r-- | drivers/of/fdt_address.c | 241 | ||||
-rw-r--r-- | drivers/of/irq.c | 22 | ||||
-rw-r--r-- | drivers/of/of_pci_irq.c | 8 | ||||
-rw-r--r-- | drivers/of/of_reserved_mem.c | 6 | ||||
-rw-r--r-- | drivers/of/platform.c | 150 | ||||
-rw-r--r-- | drivers/of/selftest.c | 80 | ||||
-rw-r--r-- | drivers/of/testcase-data/testcases.dtsi | 1 | ||||
-rw-r--r-- | drivers/of/testcase-data/tests-phandle.dtsi | 6 | ||||
-rw-r--r-- | drivers/of/testcase-data/tests-platform.dtsi | 35 |
14 files changed, 770 insertions, 389 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 889005fa4d04..2dcb0541012d 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -20,6 +20,7 @@ config OF_SELFTEST config OF_FLATTREE bool select DTC + select LIBFDT config OF_EARLY_FLATTREE bool diff --git a/drivers/of/Makefile b/drivers/of/Makefile index ed9660adad77..099b1fb00af4 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -1,5 +1,6 @@ obj-y = base.o device.o platform.o obj-$(CONFIG_OF_FLATTREE) += fdt.o +obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o obj-$(CONFIG_OF_PROMTREE) += pdt.o obj-$(CONFIG_OF_ADDRESS) += address.o obj-$(CONFIG_OF_IRQ) += irq.o @@ -10,3 +11,6 @@ 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 + +CFLAGS_fdt.o = -I$(src)/../../scripts/dtc/libfdt +CFLAGS_fdt_address.o = -I$(src)/../../scripts/dtc/libfdt diff --git a/drivers/of/address.c b/drivers/of/address.c index d244b2859aac..5edfcb0da37d 100644 --- a/drivers/of/address.c +++ b/drivers/of/address.c @@ -498,8 +498,7 @@ static u64 __of_translate_address(struct device_node *dev, /* Count address cells & copy address locally */ bus->count_cells(dev, &na, &ns); if (!OF_CHECK_COUNTS(na, ns)) { - printk(KERN_ERR "prom_parse: Bad cell count for %s\n", - of_node_full_name(dev)); + pr_debug("OF: Bad cell count for %s\n", of_node_full_name(dev)); goto bail; } memcpy(addr, in_addr, na * 4); @@ -564,25 +563,6 @@ u64 of_translate_dma_address(struct device_node *dev, const __be32 *in_addr) } EXPORT_SYMBOL(of_translate_dma_address); -bool of_can_translate_address(struct device_node *dev) -{ - struct device_node *parent; - struct of_bus *bus; - int na, ns; - - parent = of_get_parent(dev); - if (parent == NULL) - return false; - - bus = of_match_bus(parent); - bus->count_cells(dev, &na, &ns); - - of_node_put(parent); - - return OF_CHECK_COUNTS(na, ns); -} -EXPORT_SYMBOL(of_can_translate_address); - const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags) { @@ -830,4 +810,4 @@ bool of_dma_is_coherent(struct device_node *np) of_node_put(node); return false; } -EXPORT_SYMBOL_GPL(of_dma_is_coherent);
\ No newline at end of file +EXPORT_SYMBOL_GPL(of_dma_is_coherent); diff --git a/drivers/of/base.c b/drivers/of/base.c index 32e969d95319..8368d96ae7b4 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -695,6 +695,25 @@ struct device_node *of_get_next_parent(struct device_node *node) } EXPORT_SYMBOL(of_get_next_parent); +static struct device_node *__of_get_next_child(const struct device_node *node, + struct device_node *prev) +{ + struct device_node *next; + + if (!node) + return NULL; + + next = prev ? prev->sibling : node->child; + for (; next; next = next->sibling) + if (of_node_get(next)) + break; + of_node_put(prev); + return next; +} +#define __for_each_child_of_node(parent, child) \ + for (child = __of_get_next_child(parent, NULL); child != NULL; \ + child = __of_get_next_child(parent, child)) + /** * of_get_next_child - Iterate a node childs * @node: parent node @@ -710,11 +729,7 @@ struct device_node *of_get_next_child(const struct device_node *node, unsigned long flags; raw_spin_lock_irqsave(&devtree_lock, flags); - next = prev ? prev->sibling : node->child; - for (; next; next = next->sibling) - if (of_node_get(next)) - break; - of_node_put(prev); + next = __of_get_next_child(node, prev); raw_spin_unlock_irqrestore(&devtree_lock, flags); return next; } @@ -734,6 +749,9 @@ struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *next; unsigned long flags; + if (!node) + return NULL; + raw_spin_lock_irqsave(&devtree_lock, flags); next = prev ? prev->sibling : node->child; for (; next; next = next->sibling) { @@ -771,23 +789,78 @@ struct device_node *of_get_child_by_name(const struct device_node *node, } EXPORT_SYMBOL(of_get_child_by_name); +static struct device_node *__of_find_node_by_path(struct device_node *parent, + const char *path) +{ + struct device_node *child; + int len = strchrnul(path, '/') - path; + + if (!len) + return NULL; + + __for_each_child_of_node(parent, child) { + const char *name = strrchr(child->full_name, '/'); + if (WARN(!name, "malformed device_node %s\n", child->full_name)) + continue; + name++; + if (strncmp(path, name, len) == 0 && (strlen(name) == len)) + return child; + } + return NULL; +} + /** * of_find_node_by_path - Find a node matching a full OF path - * @path: The full path to match + * @path: Either the full path to match, or if the path does not + * start with '/', the name of a property of the /aliases + * node (an alias). In the case of an alias, the node + * matching the alias' value will be returned. + * + * Valid paths: + * /foo/bar Full path + * foo Valid alias + * foo/bar Valid alias + relative path * * Returns a node pointer with refcount incremented, use * of_node_put() on it when done. */ struct device_node *of_find_node_by_path(const char *path) { - struct device_node *np = of_allnodes; + struct device_node *np = NULL; + struct property *pp; unsigned long flags; + if (strcmp(path, "/") == 0) + return of_node_get(of_allnodes); + + /* The path could begin with an alias */ + if (*path != '/') { + char *p = strchrnul(path, '/'); + int len = p - path; + + /* of_aliases must not be NULL */ + if (!of_aliases) + return NULL; + + for_each_property_of_node(of_aliases, pp) { + if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) { + np = of_find_node_by_path(pp->value); + break; + } + } + if (!np) + return NULL; + path = p; + } + + /* Step down the tree matching path components */ raw_spin_lock_irqsave(&devtree_lock, flags); - for (; np; np = np->allnext) { - if (np->full_name && (of_node_cmp(np->full_name, path) == 0) - && of_node_get(np)) - break; + if (!np) + np = of_node_get(of_allnodes); + while (np && *path == '/') { + path++; /* Increment past '/' delimiter */ + np = __of_find_node_by_path(np, path); + path = strchrnul(path, '/'); } raw_spin_unlock_irqrestore(&devtree_lock, flags); return np; @@ -1800,7 +1873,7 @@ int of_update_property(struct device_node *np, struct property *newprop) { struct property **next, *oldprop; unsigned long flags; - int rc, found = 0; + int rc; rc = of_property_notify(OF_RECONFIG_UPDATE_PROPERTY, np, newprop); if (rc) @@ -1809,34 +1882,34 @@ int of_update_property(struct device_node *np, struct property *newprop) if (!newprop->name) return -EINVAL; - oldprop = of_find_property(np, newprop->name, NULL); - if (!oldprop) - return of_add_property(np, newprop); - raw_spin_lock_irqsave(&devtree_lock, flags); next = &np->properties; - while (*next) { + oldprop = __of_find_property(np, newprop->name, NULL); + if (!oldprop) { + /* add the new node */ + rc = __of_add_property(np, newprop); + } else while (*next) { + /* replace the node */ if (*next == oldprop) { - /* found the node */ newprop->next = oldprop->next; *next = newprop; oldprop->next = np->deadprops; np->deadprops = oldprop; - found = 1; break; } next = &(*next)->next; } raw_spin_unlock_irqrestore(&devtree_lock, flags); - if (!found) - return -ENODEV; + if (rc) + return rc; /* At early boot, bail out and defer setup to of_init() */ if (!of_kset) - return found ? 0 : -ENODEV; + return 0; /* Update the sysfs attribute */ - sysfs_remove_bin_file(&np->kobj, &oldprop->attr); + if (oldprop) + sysfs_remove_bin_file(&np->kobj, &oldprop->attr); __of_add_property_sysfs(np, newprop); return 0; @@ -2040,8 +2113,8 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align)) * @np: Pointer to the given device_node * @stem: Alias stem of the given device_node * - * The function travels the lookup table to get alias id for the given - * device_node and alias stem. It returns the alias id if find it. + * The function travels the lookup table to get the alias id for the given + * device_node and alias stem. It returns the alias id if found. */ int of_alias_get_id(struct device_node *np, const char *stem) { diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 7a2ef7bb8022..c4cddf0cd96d 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -12,7 +12,6 @@ #include <linux/kernel.h> #include <linux/initrd.h> #include <linux/memblock.h> -#include <linux/module.h> #include <linux/of.h> #include <linux/of_fdt.h> #include <linux/of_reserved_mem.h> @@ -20,62 +19,13 @@ #include <linux/string.h> #include <linux/errno.h> #include <linux/slab.h> +#include <linux/libfdt.h> +#include <linux/debugfs.h> +#include <linux/serial_core.h> #include <asm/setup.h> /* for COMMAND_LINE_SIZE */ -#ifdef CONFIG_PPC -#include <asm/machdep.h> -#endif /* CONFIG_PPC */ - #include <asm/page.h> -char *of_fdt_get_string(struct boot_param_header *blob, u32 offset) -{ - return ((char *)blob) + - be32_to_cpu(blob->off_dt_strings) + offset; -} - -/** - * of_fdt_get_property - Given a node in the given flat blob, return - * the property ptr - */ -void *of_fdt_get_property(struct boot_param_header *blob, - unsigned long node, const char *name, - unsigned long *size) -{ - unsigned long p = node; - - do { - u32 tag = be32_to_cpup((__be32 *)p); - u32 sz, noff; - const char *nstr; - - p += 4; - if (tag == OF_DT_NOP) - continue; - if (tag != OF_DT_PROP) - return NULL; - - sz = be32_to_cpup((__be32 *)p); - noff = be32_to_cpup((__be32 *)(p + 4)); - p += 8; - if (be32_to_cpu(blob->version) < 0x10) - p = ALIGN(p, sz >= 8 ? 8 : 4); - - nstr = of_fdt_get_string(blob, noff); - if (nstr == NULL) { - pr_warning("Can't find property index name !\n"); - return NULL; - } - if (strcmp(name, nstr) == 0) { - if (size) - *size = sz; - return (void *)p; - } - p += sz; - p = ALIGN(p, 4); - } while (1); -} - /** * of_fdt_is_compatible - Return true if given node from the given blob has * compat in its compatible list @@ -86,13 +36,14 @@ void *of_fdt_get_property(struct boot_param_header *blob, * On match, returns a non-zero value with smaller values returned for more * specific compatible values. */ -int of_fdt_is_compatible(struct boot_param_header *blob, +int of_fdt_is_compatible(const void *blob, unsigned long node, const char *compat) { const char *cp; - unsigned long cplen, l, score = 0; + int cplen; + unsigned long l, score = 0; - cp = of_fdt_get_property(blob, node, "compatible", &cplen); + cp = fdt_getprop(blob, node, "compatible", &cplen); if (cp == NULL) return 0; while (cplen > 0) { @@ -110,7 +61,7 @@ int of_fdt_is_compatible(struct boot_param_header *blob, /** * of_fdt_match - Return true if node matches a list of compatible values */ -int of_fdt_match(struct boot_param_header *blob, unsigned long node, +int of_fdt_match(const void *blob, unsigned long node, const char *const *compat) { unsigned int tmp, score = 0; @@ -149,30 +100,29 @@ static void *unflatten_dt_alloc(void **mem, unsigned long size, * @allnextpp: pointer to ->allnext from last allocated device_node * @fpsize: Size of the node path up at the current depth. */ -static void * unflatten_dt_node(struct boot_param_header *blob, +static void * unflatten_dt_node(void *blob, void *mem, - void **p, + int *poffset, struct device_node *dad, struct device_node ***allnextpp, unsigned long fpsize) { + const __be32 *p; struct device_node *np; struct property *pp, **prev_pp = NULL; - char *pathp; - u32 tag; + const char *pathp; unsigned int l, allocl; + static int depth = 0; + int old_depth; + int offset; int has_name = 0; int new_format = 0; - tag = be32_to_cpup(*p); - if (tag != OF_DT_BEGIN_NODE) { - pr_err("Weird tag at start of node: %x\n", tag); + pathp = fdt_get_name(blob, *poffset, &l); + if (!pathp) return mem; - } - *p += 4; - pathp = *p; - l = allocl = strlen(pathp) + 1; - *p = PTR_ALIGN(*p + l, 4); + + allocl = l++; /* version 0x10 has a more compact unit name here instead of the full * path. we accumulate the full path size using "fpsize", we'll rebuild @@ -190,7 +140,7 @@ static void * unflatten_dt_node(struct boot_param_header *blob, fpsize = 1; allocl = 2; l = 1; - *pathp = '\0'; + pathp = ""; } else { /* account for '/' and path size minus terminal 0 * already in 'l' @@ -237,32 +187,23 @@ static void * unflatten_dt_node(struct boot_param_header *blob, } } /* process properties */ - while (1) { - u32 sz, noff; - char *pname; - - tag = be32_to_cpup(*p); - if (tag == OF_DT_NOP) { - *p += 4; - continue; - } - if (tag != OF_DT_PROP) + for (offset = fdt_first_property_offset(blob, *poffset); + (offset >= 0); + (offset = fdt_next_property_offset(blob, offset))) { + const char *pname; + u32 sz; + + if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) { + offset = -FDT_ERR_INTERNAL; break; - *p += 4; - sz = be32_to_cpup(*p); - noff = be32_to_cpup(*p + 4); - *p += 8; - if (be32_to_cpu(blob->version) < 0x10) - *p = PTR_ALIGN(*p, sz >= 8 ? 8 : 4); - - pname = of_fdt_get_string(blob, noff); + } + if (pname == NULL) { pr_info("Can't find property name in list !\n"); break; } if (strcmp(pname, "name") == 0) has_name = 1; - l = strlen(pname) + 1; pp = unflatten_dt_alloc(&mem, sizeof(struct property), __alignof__(struct property)); if (allnextpp) { @@ -274,26 +215,25 @@ static void * unflatten_dt_node(struct boot_param_header *blob, if ((strcmp(pname, "phandle") == 0) || (strcmp(pname, "linux,phandle") == 0)) { if (np->phandle == 0) - np->phandle = be32_to_cpup((__be32*)*p); + np->phandle = be32_to_cpup(p); } /* And we process the "ibm,phandle" property * used in pSeries dynamic device tree * stuff */ if (strcmp(pname, "ibm,phandle") == 0) - np->phandle = be32_to_cpup((__be32 *)*p); - pp->name = pname; + np->phandle = be32_to_cpup(p); + pp->name = (char *)pname; pp->length = sz; - pp->value = *p; + pp->value = (__be32 *)p; *prev_pp = pp; prev_pp = &pp->next; } - *p = PTR_ALIGN((*p) + sz, 4); } /* with version 0x10 we may not have the name property, recreate * it here from the unit name if absent */ if (!has_name) { - char *p1 = pathp, *ps = pathp, *pa = NULL; + const char *p1 = pathp, *ps = pathp, *pa = NULL; int sz; while (*p1) { @@ -330,19 +270,18 @@ static void * unflatten_dt_node(struct boot_param_header *blob, if (!np->type) np->type = "<NULL>"; } - while (tag == OF_DT_BEGIN_NODE || tag == OF_DT_NOP) { - if (tag == OF_DT_NOP) - *p += 4; - else - mem = unflatten_dt_node(blob, mem, p, np, allnextpp, - fpsize); - tag = be32_to_cpup(*p); - } - if (tag != OF_DT_END_NODE) { - pr_err("Weird tag at end of node: %x\n", tag); - return mem; - } - *p += 4; + + old_depth = depth; + *poffset = fdt_next_node(blob, *poffset, &depth); + if (depth < 0) + depth = 0; + while (*poffset > 0 && depth > old_depth) + mem = unflatten_dt_node(blob, mem, poffset, np, allnextpp, + fpsize); + + if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND) + pr_err("unflatten: error %d processing FDT\n", *poffset); + return mem; } @@ -358,12 +297,13 @@ static void * unflatten_dt_node(struct boot_param_header *blob, * @dt_alloc: An allocator that provides a virtual address to memory * for the resulting tree */ -static void __unflatten_device_tree(struct boot_param_header *blob, +static void __unflatten_device_tree(void *blob, struct device_node **mynodes, void * (*dt_alloc)(u64 size, u64 align)) { unsigned long size; - void *start, *mem; + int start; + void *mem; struct device_node **allnextp = mynodes; pr_debug(" -> unflatten_device_tree()\n"); @@ -374,18 +314,18 @@ static void __unflatten_device_tree(struct boot_param_header *blob, } pr_debug("Unflattening device tree:\n"); - pr_debug("magic: %08x\n", be32_to_cpu(blob->magic)); - pr_debug("size: %08x\n", be32_to_cpu(blob->totalsize)); - pr_debug("version: %08x\n", be32_to_cpu(blob->version)); + pr_debug("magic: %08x\n", fdt_magic(blob)); + pr_debug("size: %08x\n", fdt_totalsize(blob)); + pr_debug("version: %08x\n", fdt_version(blob)); - if (be32_to_cpu(blob->magic) != OF_DT_HEADER) { + if (fdt_check_header(blob)) { pr_err("Invalid device tree blob header\n"); return; } /* First pass, scan for size */ - start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct); - size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0); + start = 0; + size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0); size = ALIGN(size, 4); pr_debug(" size is %lx, allocating...\n", size); @@ -399,10 +339,8 @@ static void __unflatten_device_tree(struct boot_param_header *blob, pr_debug(" unflattening %p...\n", mem); /* Second pass, do actual unflattening */ - start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct); + start = 0; unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0); - if (be32_to_cpup(start) != OF_DT_END) - pr_warning("Weird tag at end of tree: %08x\n", be32_to_cpup(start)); if (be32_to_cpup(mem + size) != 0xdeadbeef) pr_warning("End of tree marker overwritten: %08x\n", be32_to_cpup(mem + size)); @@ -427,9 +365,7 @@ static void *kernel_tree_alloc(u64 size, u64 align) void of_fdt_unflatten_tree(unsigned long *blob, struct device_node **mynodes) { - struct boot_param_header *device_tree = - (struct boot_param_header *)blob; - __unflatten_device_tree(device_tree, mynodes, &kernel_tree_alloc); + __unflatten_device_tree(blob, mynodes, &kernel_tree_alloc); } EXPORT_SYMBOL_GPL(of_fdt_unflatten_tree); @@ -437,7 +373,7 @@ EXPORT_SYMBOL_GPL(of_fdt_unflatten_tree); int __initdata dt_root_addr_cells; int __initdata dt_root_size_cells; -struct boot_param_header *initial_boot_params; +void *initial_boot_params; #ifdef CONFIG_OF_EARLY_FLATTREE @@ -449,8 +385,8 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, { int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); phys_addr_t base, size; - unsigned long len; - __be32 *prop; + int len; + const __be32 *prop; int nomap, first = 1; prop = of_get_flat_dt_prop(node, "reg", &len); @@ -493,7 +429,7 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, */ static int __init __reserved_mem_check_root(unsigned long node) { - __be32 *prop; + const __be32 *prop; prop = of_get_flat_dt_prop(node, "#size-cells", NULL); if (!prop || be32_to_cpup(prop) != dt_root_size_cells) @@ -557,9 +493,25 @@ static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname, */ void __init early_init_fdt_scan_reserved_mem(void) { + int n; + u64 base, size; + if (!initial_boot_params) return; + /* Reserve the dtb region */ + early_init_dt_reserve_memory_arch(__pa(initial_boot_params), + fdt_totalsize(initial_boot_params), + 0); + + /* Process header /memreserve/ fields */ + for (n = 0; ; n++) { + fdt_get_mem_rsv(initial_boot_params, n, &base, &size); + if (!size) + break; + early_init_dt_reserve_memory_arch(base, size, 0); + } + of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); fdt_init_reserved_mem(); } @@ -578,47 +530,19 @@ int __init of_scan_flat_dt(int (*it)(unsigned long node, void *data), void *data) { - unsigned long p = ((unsigned long)initial_boot_params) + - be32_to_cpu(initial_boot_params->off_dt_struct); - int rc = 0; - int depth = -1; - - do { - u32 tag = be32_to_cpup((__be32 *)p); - const char *pathp; - - p += 4; - if (tag == OF_DT_END_NODE) { - depth--; - continue; - } - if (tag == OF_DT_NOP) - continue; - if (tag == OF_DT_END) - break; - if (tag == OF_DT_PROP) { - u32 sz = be32_to_cpup((__be32 *)p); - p += 8; - if (be32_to_cpu(initial_boot_params->version) < 0x10) - p = ALIGN(p, sz >= 8 ? 8 : 4); - p += sz; - p = ALIGN(p, 4); - continue; - } - if (tag != OF_DT_BEGIN_NODE) { - pr_err("Invalid tag %x in flat device tree!\n", tag); - return -EINVAL; - } - depth++; - pathp = (char *)p; - p = ALIGN(p + strlen(pathp) + 1, 4); + const void *blob = initial_boot_params; + const char *pathp; + int offset, rc = 0, depth = -1; + + for (offset = fdt_next_node(blob, -1, &depth); + offset >= 0 && depth >= 0 && !rc; + offset = fdt_next_node(blob, offset, &depth)) { + + pathp = fdt_get_name(blob, offset, NULL); if (*pathp == '/') pathp = kbasename(pathp); - rc = it(p, pathp, depth, data); - if (rc != 0) - break; - } while (1); - + rc = it(offset, pathp, depth, data); + } return rc; } @@ -627,14 +551,15 @@ int __init of_scan_flat_dt(int (*it)(unsigned long node, */ unsigned long __init of_get_flat_dt_root(void) { - unsigned long p = ((unsigned long)initial_boot_params) + - be32_to_cpu(initial_boot_params->off_dt_struct); - - while (be32_to_cpup((__be32 *)p) == OF_DT_NOP) - p += 4; - BUG_ON(be32_to_cpup((__be32 *)p) != OF_DT_BEGIN_NODE); - p += 4; - return ALIGN(p + strlen((char *)p) + 1, 4); + return 0; +} + +/** + * of_get_flat_dt_size - Return the total size of the FDT + */ +int __init of_get_flat_dt_size(void) +{ + return fdt_totalsize(initial_boot_params); } /** @@ -643,10 +568,10 @@ unsigned long __init of_get_flat_dt_root(void) * This function can be used within scan_flattened_dt callback to get * access to properties */ -void *__init of_get_flat_dt_prop(unsigned long node, const char *name, - unsigned long *size) +const void *__init of_get_flat_dt_prop(unsigned long node, const char *name, + int *size) { - return of_fdt_get_property(initial_boot_params, node, name, size); + return fdt_getprop(initial_boot_params, node, name, size); } /** @@ -676,73 +601,6 @@ struct fdt_scan_status { void *data; }; -/** - * fdt_scan_node_by_path - iterator for of_scan_flat_dt_by_path function - */ -static int __init fdt_scan_node_by_path(unsigned long node, const char *uname, - int depth, void *data) -{ - struct fdt_scan_status *st = data; - - /* - * if scan at the requested fdt node has been completed, - * return -ENXIO to abort further scanning - */ - if (depth <= st->depth) - return -ENXIO; - - /* requested fdt node has been found, so call iterator function */ - if (st->found) - return st->iterator(node, uname, depth, st->data); - - /* check if scanning automata is entering next level of fdt nodes */ - if (depth == st->depth + 1 && - strncmp(st->name, uname, st->namelen) == 0 && - uname[st->namelen] == 0) { - st->depth += 1; - if (st->name[st->namelen] == 0) { - st->found = 1; - } else { - const char *next = st->name + st->namelen + 1; - st->name = next; - st->namelen = strcspn(next, "/"); - } - return 0; - } - - /* scan next fdt node */ - return 0; -} - -/** - * of_scan_flat_dt_by_path - scan flattened tree blob and call callback on each - * child of the given path. - * @path: path to start searching for children - * @it: callback function - * @data: context data pointer - * - * This function is used to scan the flattened device-tree starting from the - * node given by path. It is used to extract information (like reserved - * memory), which is required on ealy boot before we can unflatten the tree. - */ -int __init of_scan_flat_dt_by_path(const char *path, - int (*it)(unsigned long node, const char *name, int depth, void *data), - void *data) -{ - struct fdt_scan_status st = {path, 0, -1, 0, it, data}; - int ret = 0; - - if (initial_boot_params) - ret = of_scan_flat_dt(fdt_scan_node_by_path, &st); - - if (!st.found) - return -ENOENT; - else if (ret == -ENXIO) /* scan has been completed */ - return 0; - else - return ret; -} - const char * __init of_flat_dt_get_machine_name(void) { const char *name; @@ -782,7 +640,7 @@ const void * __init of_flat_dt_match_machine(const void *default_match, } if (!best_data) { const char *prop; - long size; + int size; pr_err("\n unrecognized device tree list:\n[ "); @@ -811,8 +669,8 @@ const void * __init of_flat_dt_match_machine(const void *default_match, static void __init early_init_dt_check_for_initrd(unsigned long node) { u64 start, end; - unsigned long len; - __be32 *prop; + int len; + const __be32 *prop; pr_debug("Looking for initrd properties... "); @@ -839,13 +697,68 @@ static inline void early_init_dt_check_for_initrd(unsigned long node) } #endif /* CONFIG_BLK_DEV_INITRD */ +#ifdef CONFIG_SERIAL_EARLYCON +extern struct of_device_id __earlycon_of_table[]; + +int __init early_init_dt_scan_chosen_serial(void) +{ + int offset; + const char *p; + int l; + const struct of_device_id *match = __earlycon_of_table; + const void *fdt = initial_boot_params; + + offset = fdt_path_offset(fdt, "/chosen"); + if (offset < 0) + offset = fdt_path_offset(fdt, "/chosen@0"); + if (offset < 0) + return -ENOENT; + + p = fdt_getprop(fdt, offset, "stdout-path", &l); + if (!p) + p = fdt_getprop(fdt, offset, "linux,stdout-path", &l); + if (!p || !l) + return -ENOENT; + + /* Get the node specified by stdout-path */ + offset = fdt_path_offset(fdt, p); + if (offset < 0) + return -ENODEV; + + while (match->compatible) { + unsigned long addr; + if (fdt_node_check_compatible(fdt, offset, match->compatible)) { + match++; + continue; + } + + addr = fdt_translate_address(fdt, offset); + if (!addr) + return -ENXIO; + + of_setup_earlycon(addr, match->data); + return 0; + } + return -ENODEV; +} + +static int __init setup_of_earlycon(char *buf) +{ + if (buf) + return 0; + + return early_init_dt_scan_chosen_serial(); +} +early_param("earlycon", setup_of_earlycon); +#endif + /** * early_init_dt_scan_root - fetch the top level address and size cells */ int __init early_init_dt_scan_root(unsigned long node, const char *uname, int depth, void *data) { - __be32 *prop; + const __be32 *prop; if (depth != 0) return 0; @@ -867,9 +780,9 @@ int __init early_init_dt_scan_root(unsigned long node, const char *uname, return 1; } -u64 __init dt_mem_next_cell(int s, __be32 **cellp) +u64 __init dt_mem_next_cell(int s, const __be32 **cellp) { - __be32 *p = *cellp; + const __be32 *p = *cellp; *cellp = p + s; return of_read_number(p, s); @@ -881,9 +794,9 @@ u64 __init dt_mem_next_cell(int s, __be32 **cellp) int __init early_init_dt_scan_memory(unsigned long node, const char *uname, int depth, void *data) { - char *type = of_get_flat_dt_prop(node, "device_type", NULL); - __be32 *reg, *endp; - unsigned long l; + const char *type = of_get_flat_dt_prop(node, "device_type", NULL); + const __be32 *reg, *endp; + int l; /* We are scanning "memory" nodes only */ if (type == NULL) { @@ -891,7 +804,7 @@ int __init early_init_dt_scan_memory(unsigned long node, const char *uname, * The longtrail doesn't have a device_type on the * /memory node, so look for the node called /memory@0. */ - if (depth != 1 || strcmp(uname, "memory@0") != 0) + if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "memory@0") != 0) return 0; } else if (strcmp(type, "memory") != 0) return 0; @@ -904,7 +817,7 @@ int __init early_init_dt_scan_memory(unsigned long node, const char *uname, endp = reg + (l / sizeof(__be32)); - pr_debug("memory scan node %s, reg size %ld, data: %x %x %x %x,\n", + pr_debug("memory scan node %s, reg size %d, data: %x %x %x %x,\n", uname, l, reg[0], reg[1], reg[2], reg[3]); while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) { @@ -927,8 +840,8 @@ int __init early_init_dt_scan_memory(unsigned long node, const char *uname, int __init early_init_dt_scan_chosen(unsigned long node, const char *uname, int depth, void *data) { - unsigned long l; - char *p; + int l; + const char *p; pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname); @@ -1003,8 +916,8 @@ void * __init __weak early_init_dt_alloc_memory_arch(u64 size, u64 align) int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base, phys_addr_t size, bool nomap) { - pr_err("Reserved memory not supported, ignoring range 0x%llx - 0x%llx%s\n", - base, size, nomap ? " (nomap)" : ""); + pr_err("Reserved memory not supported, ignoring range 0x%pa - 0x%pa%s\n", + &base, &size, nomap ? " (nomap)" : ""); return -ENOSYS; } #endif @@ -1018,7 +931,7 @@ bool __init early_init_dt_scan(void *params) initial_boot_params = params; /* check device tree validity */ - if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) { + if (fdt_check_header(params)) { initial_boot_params = NULL; return false; } @@ -1073,9 +986,9 @@ void __init unflatten_and_copy_device_tree(void) return; } - size = __be32_to_cpu(initial_boot_params->totalsize); + size = fdt_totalsize(initial_boot_params); dt = early_init_dt_alloc_memory_arch(size, - __alignof__(struct boot_param_header)); + roundup_pow_of_two(FDT_V17_SIZE)); if (dt) { memcpy(dt, initial_boot_params, size); @@ -1084,4 +997,27 @@ void __init unflatten_and_copy_device_tree(void) unflatten_device_tree(); } +#if defined(CONFIG_DEBUG_FS) && defined(DEBUG) +static struct debugfs_blob_wrapper flat_dt_blob; + +static int __init of_flat_dt_debugfs_export_fdt(void) +{ + struct dentry *d = debugfs_create_dir("device-tree", NULL); + + if (!d) + return -ENOENT; + + flat_dt_blob.data = initial_boot_params; + flat_dt_blob.size = fdt_totalsize(initial_boot_params); + + d = debugfs_create_blob("flat-device-tree", S_IFREG | S_IRUSR, + d, &flat_dt_blob); + if (!d) + return -ENOENT; + + return 0; +} +module_init(of_flat_dt_debugfs_export_fdt); +#endif + #endif /* CONFIG_OF_EARLY_FLATTREE */ diff --git a/drivers/of/fdt_address.c b/drivers/of/fdt_address.c new file mode 100644 index 000000000000..8d3dc6fbdb7a --- /dev/null +++ b/drivers/of/fdt_address.c @@ -0,0 +1,241 @@ +/* + * FDT Address translation based on u-boot fdt_support.c which in turn was + * based on the kernel unflattened DT address translation code. + * + * (C) Copyright 2007 + * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com + * + * Copyright 2010-2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + */ +#include <linux/kernel.h> +#include <linux/libfdt.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/sizes.h> + +/* Max address size we deal with */ +#define OF_MAX_ADDR_CELLS 4 +#define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \ + (ns) > 0) + +/* Debug utility */ +#ifdef DEBUG +static void __init of_dump_addr(const char *s, const __be32 *addr, int na) +{ + pr_debug("%s", s); + while(na--) + pr_cont(" %08x", *(addr++)); + pr_debug("\n"); +} +#else +static void __init of_dump_addr(const char *s, const __be32 *addr, int na) { } +#endif + +/* Callbacks for bus specific translators */ +struct of_bus { + void (*count_cells)(const void *blob, int parentoffset, + int *addrc, int *sizec); + u64 (*map)(__be32 *addr, const __be32 *range, + int na, int ns, int pna); + int (*translate)(__be32 *addr, u64 offset, int na); +}; + +/* Default translator (generic bus) */ +static void __init fdt_bus_default_count_cells(const void *blob, int parentoffset, + int *addrc, int *sizec) +{ + const __be32 *prop; + + if (addrc) { + prop = fdt_getprop(blob, parentoffset, "#address-cells", NULL); + if (prop) + *addrc = be32_to_cpup(prop); + else + *addrc = dt_root_addr_cells; + } + + if (sizec) { + prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL); + if (prop) + *sizec = be32_to_cpup(prop); + else + *sizec = dt_root_size_cells; + } +} + +static u64 __init fdt_bus_default_map(__be32 *addr, const __be32 *range, + int na, int ns, int pna) +{ + u64 cp, s, da; + + cp = of_read_number(range, na); + s = of_read_number(range + na + pna, ns); + da = of_read_number(addr, na); + + pr_debug("FDT: default map, cp=%llx, s=%llx, da=%llx\n", + cp, s, da); + + if (da < cp || da >= (cp + s)) + return OF_BAD_ADDR; + return da - cp; +} + +static int __init fdt_bus_default_translate(__be32 *addr, u64 offset, int na) +{ + u64 a = of_read_number(addr, na); + memset(addr, 0, na * 4); + a += offset; + if (na > 1) + addr[na - 2] = cpu_to_fdt32(a >> 32); + addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu); + + return 0; +} + +/* Array of bus specific translators */ +static const struct of_bus of_busses[] __initconst = { + /* Default */ + { + .count_cells = fdt_bus_default_count_cells, + .map = fdt_bus_default_map, + .translate = fdt_bus_default_translate, + }, +}; + +static int __init fdt_translate_one(const void *blob, int parent, + const struct of_bus *bus, + const struct of_bus *pbus, __be32 *addr, + int na, int ns, int pna, const char *rprop) +{ + const __be32 *ranges; + int rlen; + int rone; + u64 offset = OF_BAD_ADDR; + + ranges = fdt_getprop(blob, parent, rprop, &rlen); + if (!ranges) + return 1; + if (rlen == 0) { + offset = of_read_number(addr, na); + memset(addr, 0, pna * 4); + pr_debug("FDT: empty ranges, 1:1 translation\n"); + goto finish; + } + + pr_debug("FDT: walking ranges...\n"); + + /* Now walk through the ranges */ + rlen /= 4; + rone = na + pna + ns; + for (; rlen >= rone; rlen -= rone, ranges += rone) { + offset = bus->map(addr, ranges, na, ns, pna); + if (offset != OF_BAD_ADDR) + break; + } + if (offset == OF_BAD_ADDR) { + pr_debug("FDT: not found !\n"); + return 1; + } + memcpy(addr, ranges + na, 4 * pna); + + finish: + of_dump_addr("FDT: parent translation for:", addr, pna); + pr_debug("FDT: with offset: %llx\n", offset); + + /* Translate it into parent bus space */ + return pbus->translate(addr, offset, pna); +} + +/* + * Translate an address from the device-tree into a CPU physical address, + * this walks up the tree and applies the various bus mappings on the + * way. + * + * Note: We consider that crossing any level with #size-cells == 0 to mean + * that translation is impossible (that is we are not dealing with a value + * that can be mapped to a cpu physical address). This is not really specified + * that way, but this is traditionally the way IBM at least do things + */ +u64 __init fdt_translate_address(const void *blob, int node_offset) +{ + int parent, len; + const struct of_bus *bus, *pbus; + const __be32 *reg; + __be32 addr[OF_MAX_ADDR_CELLS]; + int na, ns, pna, pns; + u64 result = OF_BAD_ADDR; + + pr_debug("FDT: ** translation for device %s **\n", + fdt_get_name(blob, node_offset, NULL)); + + reg = fdt_getprop(blob, node_offset, "reg", &len); + if (!reg) { + pr_err("FDT: warning: device tree node '%s' has no address.\n", + fdt_get_name(blob, node_offset, NULL)); + goto bail; + } + + /* Get parent & match bus type */ + parent = fdt_parent_offset(blob, node_offset); + if (parent < 0) + goto bail; + bus = &of_busses[0]; + + /* Cound address cells & copy address locally */ + bus->count_cells(blob, parent, &na, &ns); + if (!OF_CHECK_COUNTS(na, ns)) { + pr_err("FDT: Bad cell count for %s\n", + fdt_get_name(blob, node_offset, NULL)); + goto bail; + } + memcpy(addr, reg, na * 4); + + pr_debug("FDT: bus (na=%d, ns=%d) on %s\n", + na, ns, fdt_get_name(blob, parent, NULL)); + of_dump_addr("OF: translating address:", addr, na); + + /* Translate */ + for (;;) { + /* Switch to parent bus */ + node_offset = parent; + parent = fdt_parent_offset(blob, node_offset); + + /* If root, we have finished */ + if (parent < 0) { + pr_debug("FDT: reached root node\n"); + result = of_read_number(addr, na); + break; + } + + /* Get new parent bus and counts */ + pbus = &of_busses[0]; + pbus->count_cells(blob, parent, &pna, &pns); + if (!OF_CHECK_COUNTS(pna, pns)) { + pr_err("FDT: Bad cell count for %s\n", + fdt_get_name(blob, node_offset, NULL)); + break; + } + + pr_debug("FDT: parent bus (na=%d, ns=%d) on %s\n", + pna, pns, fdt_get_name(blob, parent, NULL)); + + /* Apply bus translation */ + if (fdt_translate_one(blob, node_offset, bus, pbus, + addr, na, ns, pna, "ranges")) + break; + + /* Complete the move up one level */ + na = pna; + ns = pns; + bus = pbus; + + of_dump_addr("FDT: one level translation:", addr, na); + } + bail: + return result; +} diff --git a/drivers/of/irq.c b/drivers/of/irq.c index 5aeb89411350..3e06a699352d 100644 --- a/drivers/of/irq.c +++ b/drivers/of/irq.c @@ -406,6 +406,28 @@ int of_irq_get(struct device_node *dev, int index) } /** + * of_irq_get_byname - Decode a node's IRQ and return it as a Linux irq number + * @dev: pointer to device tree node + * @name: irq name + * + * Returns Linux irq number on success, or -EPROBE_DEFER if the irq domain + * is not yet created, or error code in case of any other failure. + */ +int of_irq_get_byname(struct device_node *dev, const char *name) +{ + int index; + + if (unlikely(!name)) + return -EINVAL; + + index = of_property_match_string(dev, "interrupt-names", name); + if (index < 0) + return index; + + return of_irq_get(dev, index); +} + +/** * of_irq_count - Count the number of IRQs a node uses * @dev: pointer to device tree node */ diff --git a/drivers/of/of_pci_irq.c b/drivers/of/of_pci_irq.c index 8736bc7676c5..1710d9dc7fc2 100644 --- a/drivers/of/of_pci_irq.c +++ b/drivers/of/of_pci_irq.c @@ -18,8 +18,6 @@ int of_irq_parse_pci(const struct pci_dev *pdev, struct of_phandle_args *out_irq { struct device_node *dn, *ppnode; struct pci_dev *ppdev; - u32 lspec; - __be32 lspec_be; __be32 laddr[3]; u8 pin; int rc; @@ -46,7 +44,6 @@ int of_irq_parse_pci(const struct pci_dev *pdev, struct of_phandle_args *out_irq return -ENODEV; /* Now we walk up the PCI tree */ - lspec = pin; for (;;) { /* Get the pci_dev of our parent */ ppdev = pdev->bus->self; @@ -80,14 +77,13 @@ int of_irq_parse_pci(const struct pci_dev *pdev, struct of_phandle_args *out_irq /* We can only get here if we hit a P2P bridge with no node, * let's do standard swizzling and try again */ - lspec = pci_swizzle_interrupt_pin(pdev, lspec); + pin = pci_swizzle_interrupt_pin(pdev, pin); pdev = ppdev; } out_irq->np = ppnode; out_irq->args_count = 1; - out_irq->args[0] = lspec; - lspec_be = cpu_to_be32(lspec); + out_irq->args[0] = pin; laddr[0] = cpu_to_be32((pdev->bus->number << 16) | (pdev->devfn << 8)); laddr[1] = laddr[2] = cpu_to_be32(0); return of_irq_parse_raw(laddr, out_irq); diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index daaaf935911d..632aae861375 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -95,8 +95,8 @@ static int __init __reserved_mem_alloc_size(unsigned long node, int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); phys_addr_t start = 0, end = 0; phys_addr_t base = 0, align = 0, size; - unsigned long len; - __be32 *prop; + int len; + const __be32 *prop; int nomap; int ret; @@ -188,7 +188,7 @@ static int __init __reserved_mem_init_node(struct reserved_mem *rmem) if (!of_flat_dt_is_compatible(rmem->fdt_node, compat)) continue; - if (initfn(rmem, rmem->fdt_node, rmem->name) == 0) { + if (initfn(rmem) == 0) { pr_info("Reserved memory: initialized node %s, compatible id %s\n", rmem->name, compat); return 0; diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 4e91583a3908..d90a02d89f51 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -51,10 +51,6 @@ struct platform_device *of_find_device_by_node(struct device_node *np) } EXPORT_SYMBOL(of_find_device_by_node); -#if defined(CONFIG_PPC_DCR) -#include <asm/dcr.h> -#endif - #ifdef CONFIG_OF_ADDRESS /* * The following routines scan a subtree and registers a device for @@ -68,66 +64,35 @@ EXPORT_SYMBOL(of_find_device_by_node); * of_device_make_bus_id - Use the device node data to assign a unique name * @dev: pointer to device structure that is linked to a device tree node * - * This routine will first try using either the dcr-reg or the reg property - * value to derive a unique name. As a last resort it will use the node - * name followed by a unique number. + * This routine will first try using the translated bus address to + * derive a unique name. If it cannot, then it will prepend names from + * parent nodes until a unique name can be derived. */ void of_device_make_bus_id(struct device *dev) { - static atomic_t bus_no_reg_magic; struct device_node *node = dev->of_node; const __be32 *reg; u64 addr; - const __be32 *addrp; - int magic; -#ifdef CONFIG_PPC_DCR - /* - * If it's a DCR based device, use 'd' for native DCRs - * and 'D' for MMIO DCRs. - */ - reg = of_get_property(node, "dcr-reg", NULL); - if (reg) { -#ifdef CONFIG_PPC_DCR_NATIVE - dev_set_name(dev, "d%x.%s", *reg, node->name); -#else /* CONFIG_PPC_DCR_NATIVE */ - u64 addr = of_translate_dcr_address(node, *reg, NULL); - if (addr != OF_BAD_ADDR) { - dev_set_name(dev, "D%llx.%s", - (unsigned long long)addr, node->name); + /* Construct the name, using parent nodes if necessary to ensure uniqueness */ + while (node->parent) { + /* + * If the address can be translated, then that is as much + * uniqueness as we need. Make it the first component and return + */ + reg = of_get_property(node, "reg", NULL); + if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) { + dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s", + (unsigned long long)addr, node->name, + dev_name(dev)); return; } -#endif /* !CONFIG_PPC_DCR_NATIVE */ - } -#endif /* CONFIG_PPC_DCR */ - /* - * For MMIO, get the physical address - */ - reg = of_get_property(node, "reg", NULL); - if (reg) { - if (of_can_translate_address(node)) { - addr = of_translate_address(node, reg); - } else { - addrp = of_get_address(node, 0, NULL, NULL); - if (addrp) - addr = of_read_number(addrp, 1); - else - addr = OF_BAD_ADDR; - } - if (addr != OF_BAD_ADDR) { - dev_set_name(dev, "%llx.%s", - (unsigned long long)addr, node->name); - return; - } + /* format arguments only used if dev_name() resolves to NULL */ + dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s", + strrchr(node->full_name, '/') + 1, dev_name(dev)); + node = node->parent; } - - /* - * No BusID, use the node name and add a globally incremented - * counter (and pray...) - */ - magic = atomic_add_return(1, &bus_no_reg_magic); - dev_set_name(dev, "%s.%d", node->name, magic - 1); } /** @@ -149,9 +114,8 @@ struct platform_device *of_device_alloc(struct device_node *np, return NULL; /* count the io and irq resources */ - if (of_can_translate_address(np)) - while (of_address_to_resource(np, num_reg, &temp_res) == 0) - num_reg++; + while (of_address_to_resource(np, num_reg, &temp_res) == 0) + num_reg++; num_irq = of_irq_count(np); /* Populate the resource table */ @@ -264,12 +228,13 @@ static struct platform_device *of_platform_device_create_pdata( { struct platform_device *dev; - if (!of_device_is_available(np)) + if (!of_device_is_available(np) || + of_node_test_and_set_flag(np, OF_POPULATED)) return NULL; dev = of_device_alloc(np, bus_id, parent); if (!dev) - return NULL; + goto err_clear_flag; of_dma_configure(dev); dev->dev.bus = &platform_bus_type; @@ -282,10 +247,14 @@ static struct platform_device *of_platform_device_create_pdata( if (of_device_add(dev) != 0) { platform_device_put(dev); - return NULL; + goto err_clear_flag; } return dev; + +err_clear_flag: + of_node_clear_flag(np, OF_POPULATED); + return NULL; } /** @@ -317,14 +286,15 @@ static struct amba_device *of_amba_device_create(struct device_node *node, pr_debug("Creating amba device %s\n", node->full_name); - if (!of_device_is_available(node)) + if (!of_device_is_available(node) || + of_node_test_and_set_flag(node, OF_POPULATED)) return NULL; dev = amba_device_alloc(NULL, 0, 0); if (!dev) { pr_err("%s(): amba_device_alloc() failed for %s\n", __func__, node->full_name); - return NULL; + goto err_clear_flag; } /* setup generic device info */ @@ -364,6 +334,8 @@ static struct amba_device *of_amba_device_create(struct device_node *node, err_free: amba_device_put(dev); +err_clear_flag: + of_node_clear_flag(node, OF_POPULATED); return NULL; } #else /* CONFIG_ARM_AMBA */ @@ -540,4 +512,60 @@ int of_platform_populate(struct device_node *root, return rc; } EXPORT_SYMBOL_GPL(of_platform_populate); + +static int of_platform_device_destroy(struct device *dev, void *data) +{ + bool *children_left = data; + + /* Do not touch devices not populated from the device tree */ + if (!dev->of_node || !of_node_check_flag(dev->of_node, OF_POPULATED)) { + *children_left = true; + return 0; + } + + /* Recurse, but don't touch this device if it has any children left */ + if (of_platform_depopulate(dev) != 0) { + *children_left = true; + return 0; + } + + if (dev->bus == &platform_bus_type) + platform_device_unregister(to_platform_device(dev)); +#ifdef CONFIG_ARM_AMBA + else if (dev->bus == &amba_bustype) + amba_device_unregister(to_amba_device(dev)); +#endif + else { + *children_left = true; + return 0; + } + + of_node_clear_flag(dev->of_node, OF_POPULATED); + + return 0; +} + +/** + * of_platform_depopulate() - Remove devices populated from device tree + * @parent: device which childred will be removed + * + * Complementary to of_platform_populate(), this function removes children + * of the given device (and, recurrently, their children) that have been + * created from their respective device tree nodes (and only those, + * leaving others - eg. manually created - unharmed). + * + * Returns 0 when all children devices have been removed or + * -EBUSY when some children remained. + */ +int of_platform_depopulate(struct device *parent) +{ + bool children_left = false; + + device_for_each_child(parent, &children_left, + of_platform_device_destroy); + + return children_left ? -EBUSY : 0; +} +EXPORT_SYMBOL_GPL(of_platform_depopulate); + #endif /* CONFIG_OF_ADDRESS */ diff --git a/drivers/of/selftest.c b/drivers/of/selftest.c index fe70b86bcffb..077314eebb95 100644 --- a/drivers/of/selftest.c +++ b/drivers/of/selftest.c @@ -31,6 +31,51 @@ static struct selftest_results { } \ } +static void __init of_selftest_find_node_by_name(void) +{ + struct device_node *np; + + np = of_find_node_by_path("/testcase-data"); + selftest(np && !strcmp("/testcase-data", np->full_name), + "find /testcase-data failed\n"); + of_node_put(np); + + /* Test if trailing '/' works */ + np = of_find_node_by_path("/testcase-data/"); + selftest(!np, "trailing '/' on /testcase-data/ should fail\n"); + + np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a"); + selftest(np && !strcmp("/testcase-data/phandle-tests/consumer-a", np->full_name), + "find /testcase-data/phandle-tests/consumer-a failed\n"); + of_node_put(np); + + np = of_find_node_by_path("testcase-alias"); + selftest(np && !strcmp("/testcase-data", np->full_name), + "find testcase-alias failed\n"); + of_node_put(np); + + /* Test if trailing '/' works on aliases */ + np = of_find_node_by_path("testcase-alias/"); + selftest(!np, "trailing '/' on testcase-alias/ should fail\n"); + + np = of_find_node_by_path("testcase-alias/phandle-tests/consumer-a"); + selftest(np && !strcmp("/testcase-data/phandle-tests/consumer-a", np->full_name), + "find testcase-alias/phandle-tests/consumer-a failed\n"); + of_node_put(np); + + np = of_find_node_by_path("/testcase-data/missing-path"); + selftest(!np, "non-existent path returned node %s\n", np->full_name); + of_node_put(np); + + np = of_find_node_by_path("missing-alias"); + selftest(!np, "non-existent alias returned node %s\n", np->full_name); + of_node_put(np); + + np = of_find_node_by_path("testcase-alias/missing-path"); + selftest(!np, "non-existent alias with relative path returned node %s\n", np->full_name); + of_node_put(np); +} + static void __init of_selftest_dynamic(void) { struct device_node *np; @@ -431,8 +476,12 @@ static void __init of_selftest_match_node(void) static void __init of_selftest_platform_populate(void) { int irq; - struct device_node *np; + struct device_node *np, *child; struct platform_device *pdev; + struct of_device_id match[] = { + { .compatible = "test-device", }, + {} + }; np = of_find_node_by_path("/testcase-data"); of_platform_populate(np, of_default_bus_match_table, NULL, NULL); @@ -440,22 +489,32 @@ static void __init of_selftest_platform_populate(void) /* Test that a missing irq domain returns -EPROBE_DEFER */ np = of_find_node_by_path("/testcase-data/testcase-device1"); pdev = of_find_device_by_node(np); - if (!pdev) - selftest(0, "device 1 creation failed\n"); + selftest(pdev, "device 1 creation failed\n"); + irq = platform_get_irq(pdev, 0); - if (irq != -EPROBE_DEFER) - selftest(0, "device deferred probe failed - %d\n", irq); + selftest(irq == -EPROBE_DEFER, "device deferred probe failed - %d\n", irq); /* Test that a parsing failure does not return -EPROBE_DEFER */ np = of_find_node_by_path("/testcase-data/testcase-device2"); pdev = of_find_device_by_node(np); - if (!pdev) - selftest(0, "device 2 creation failed\n"); + selftest(pdev, "device 2 creation failed\n"); irq = platform_get_irq(pdev, 0); - if (irq >= 0 || irq == -EPROBE_DEFER) - selftest(0, "device parsing error failed - %d\n", irq); + selftest(irq < 0 && irq != -EPROBE_DEFER, "device parsing error failed - %d\n", irq); - selftest(1, "passed"); + np = of_find_node_by_path("/testcase-data/platform-tests"); + if (!np) { + pr_err("No testcase data in device tree\n"); + return; + } + + for_each_child_of_node(np, child) { + struct device_node *grandchild; + of_platform_populate(child, match, NULL, NULL); + for_each_child_of_node(child, grandchild) + selftest(of_find_device_by_node(grandchild), + "Could not create device for node '%s'\n", + grandchild->name); + } } static int __init of_selftest(void) @@ -470,6 +529,7 @@ static int __init of_selftest(void) of_node_put(np); pr_info("start of selftest - you will see error messages\n"); + of_selftest_find_node_by_name(); of_selftest_dynamic(); of_selftest_parse_phandle_with_args(); of_selftest_property_match_string(); diff --git a/drivers/of/testcase-data/testcases.dtsi b/drivers/of/testcase-data/testcases.dtsi index 3a5b75a8e4d7..6d8d980ac858 100644 --- a/drivers/of/testcase-data/testcases.dtsi +++ b/drivers/of/testcase-data/testcases.dtsi @@ -1,3 +1,4 @@ #include "tests-phandle.dtsi" #include "tests-interrupts.dtsi" #include "tests-match.dtsi" +#include "tests-platform.dtsi" diff --git a/drivers/of/testcase-data/tests-phandle.dtsi b/drivers/of/testcase-data/tests-phandle.dtsi index 788a4c24b8f5..ce0fe083d406 100644 --- a/drivers/of/testcase-data/tests-phandle.dtsi +++ b/drivers/of/testcase-data/tests-phandle.dtsi @@ -1,6 +1,10 @@ / { - testcase-data { + aliases { + testcase-alias = &testcase; + }; + + testcase: testcase-data { security-password = "password"; duplicate-name = "duplicate"; duplicate-name { }; diff --git a/drivers/of/testcase-data/tests-platform.dtsi b/drivers/of/testcase-data/tests-platform.dtsi new file mode 100644 index 000000000000..eb20eeb2b062 --- /dev/null +++ b/drivers/of/testcase-data/tests-platform.dtsi @@ -0,0 +1,35 @@ + +/ { + testcase-data { + platform-tests { + #address-cells = <1>; + #size-cells = <0>; + + test-device@0 { + compatible = "test-device"; + reg = <0x0>; + + #address-cells = <1>; + #size-cells = <0>; + + dev@100 { + compatible = "test-sub-device"; + reg = <0x100>; + }; + }; + + test-device@1 { + compatible = "test-device"; + reg = <0x1>; + + #address-cells = <1>; + #size-cells = <0>; + + dev@100 { + compatible = "test-sub-device"; + reg = <0x100>; + }; + }; + }; + }; +}; |