diff options
Diffstat (limited to 'drivers/of')
-rw-r--r-- | drivers/of/Kconfig | 24 | ||||
-rw-r--r-- | drivers/of/Makefile | 2 | ||||
-rw-r--r-- | drivers/of/address.c | 42 | ||||
-rw-r--r-- | drivers/of/barebox.c | 27 | ||||
-rw-r--r-- | drivers/of/base.c | 965 | ||||
-rw-r--r-- | drivers/of/device.c | 21 | ||||
-rw-r--r-- | drivers/of/fdt.c | 343 | ||||
-rw-r--r-- | drivers/of/mem_generic.c | 6 | ||||
-rw-r--r-- | drivers/of/of_firmware.c | 133 | ||||
-rw-r--r-- | drivers/of/of_gpio.c | 81 | ||||
-rw-r--r-- | drivers/of/of_mtd.c | 7 | ||||
-rw-r--r-- | drivers/of/of_net.c | 82 | ||||
-rw-r--r-- | drivers/of/of_path.c | 113 | ||||
-rw-r--r-- | drivers/of/of_pci.c | 2 | ||||
-rw-r--r-- | drivers/of/overlay.c | 387 | ||||
-rw-r--r-- | drivers/of/partition.c | 144 | ||||
-rw-r--r-- | drivers/of/platform.c | 430 | ||||
-rw-r--r-- | drivers/of/reserved-mem.c | 87 | ||||
-rw-r--r-- | drivers/of/resolver.c | 10 |
19 files changed, 2351 insertions, 555 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 7436fc2de1..2791100a2d 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -1,27 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0-only config OFTREE select DTC bool config OFTREE_MEM_GENERIC depends on OFTREE - depends on PPC || ARM || EFI_BOOTUP || OPENRISC || SANDBOX || RISCV + depends on PPC || ARM || EFI_PAYLOAD || OPENRISC || SANDBOX || RISCV || KVX def_bool y config DTC bool +config OF + bool + config OFDEVICE select OFTREE + select OF select DTC bool "Enable probing of devices from the devicetree" +config FEATURE_CONTROLLER_FIXUP + bool "Fix up DT nodes gated by feature controller" + depends on FEATURE_CONTROLLER + default y + help + When specified, barebox feature controller drivers are consulted + prior to probing nodes to detect whether the device may not + be available (e.g. because support is fused out). + This option additionally fixes up the kernel device tree, + so it doesn't attempt probing these devices either. + If unsure, say y. + config OF_ADDRESS_PCI bool -config OF_NET - depends on NET - def_bool y - config OF_GPIO depends on GPIOLIB depends on OFDEVICE @@ -53,6 +66,7 @@ config OF_BAREBOX_ENV_IN_FS config OF_OVERLAY select OFTREE + select FIRMWARE bool "Devicetree overlays" help Overlays allow to patch the devicetree. Unlike Linux, Barebox does diff --git a/drivers/of/Makefile b/drivers/of/Makefile index b6847752d2..4785128bd9 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -1,9 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-y += address.o base.o fdt.o platform.o of_path.o device.o obj-$(CONFIG_OFTREE_MEM_GENERIC) += mem_generic.o obj-$(CONFIG_OF_GPIO) += of_gpio.o obj-$(CONFIG_OF_PCI) += of_pci.o obj-y += partition.o obj-y += of_net.o +obj-y += reserved-mem.o obj-$(CONFIG_MTD) += of_mtd.o obj-$(CONFIG_OF_BAREBOX_DRIVERS) += barebox.o obj-$(CONFIG_OF_OVERLAY) += overlay.o resolver.o of_firmware.o diff --git a/drivers/of/address.c b/drivers/of/address.c index f0a74ec37c..03868406e2 100644 --- a/drivers/of/address.c +++ b/drivers/of/address.c @@ -1,21 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * address.c - address related devicetree functions * * Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * based on Linux devicetree support - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> #include <of.h> @@ -136,8 +125,10 @@ static unsigned int of_bus_pci_get_flags(const __be32 *addr) case 0x01: flags |= IORESOURCE_IO; break; - case 0x02: /* 32 bits */ case 0x03: /* 64 bits */ + flags |= IORESOURCE_MEM_64; + /* fallthrough */ + case 0x02: /* 32 bits */ flags |= IORESOURCE_MEM; break; } @@ -310,10 +301,13 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus, * * As far as we know, this damage only exists on Apple machines, so * This code is only enabled on powerpc. --gcl + * + * This quirk also applies for 'dma-ranges' which frequently exist in + * child nodes without 'dma-ranges' in the parent nodes. --RobH */ ranges = of_get_property(parent, rprop, &rlen); #if !defined(CONFIG_PPC) - if (ranges == NULL) { + if (ranges == NULL && strcmp(rprop, "dma-ranges")) { pr_vdebug("OF: no ranges; cannot translate\n"); return 1; } @@ -368,7 +362,7 @@ static u64 __of_translate_address(struct device_node *dev, int na, ns, pna, pns; u64 result = OF_BAD_ADDR; - pr_vdebug("OF: ** translation for device %s **\n", dev->full_name); + pr_vdebug("OF: ** translation for device %pOF **\n", dev); /* Get parent & match bus type */ parent = of_get_parent(dev); @@ -379,14 +373,13 @@ 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)) { - pr_vdebug("prom_parse: Bad cell count for %s\n", - dev->full_name); + pr_vdebug("prom_parse: Bad cell count for %pOF\n", dev); return OF_BAD_ADDR; } memcpy(addr, in_addr, na * 4); - pr_vdebug("OF: bus is %s (na=%d, ns=%d) on %s\n", - bus->name, na, ns, parent->full_name); + pr_vdebug("OF: bus is %s (na=%d, ns=%d) on %pOF\n", + bus->name, na, ns, parent); of_dump_addr("OF: translating address:", addr, na); /* Translate */ @@ -406,13 +399,12 @@ static u64 __of_translate_address(struct device_node *dev, pbus = of_match_bus(parent); pbus->count_cells(dev, &pna, &pns); if (!OF_CHECK_COUNTS(pna, pns)) { - printk(KERN_ERR "prom_parse: Bad cell count for %s\n", - dev->full_name); + printk(KERN_ERR "prom_parse: Bad cell count for %pOF\n", dev); break; } - pr_vdebug("OF: parent bus is %s (na=%d, ns=%d) on %s\n", - pbus->name, pna, pns, parent->full_name); + pr_vdebug("OF: parent bus is %s (na=%d, ns=%d) on %pOF\n", + pbus->name, pna, pns, parent); /* Apply bus translation */ if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop)) @@ -679,8 +671,8 @@ int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *siz dmaaddr = of_read_number(ranges, naddr); *paddr = of_translate_dma_address(node, ranges + naddr); if (*paddr == OF_BAD_ADDR) { - pr_err("translation of DMA address(%llx) to CPU address failed node(%pOF)\n", - dmaaddr, np); + pr_err("translation of DMA address(%llx) to CPU address failed node(%s)\n", + dmaaddr, np->name); ret = -EINVAL; goto out; } diff --git a/drivers/of/barebox.c b/drivers/of/barebox.c index 64f483326d..560d9c0d15 100644 --- a/drivers/of/barebox.c +++ b/drivers/of/barebox.c @@ -1,20 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * barebox.c * * Copyright (c) 2013 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #include <common.h> @@ -22,7 +10,6 @@ #include <io.h> #include <of.h> #include <malloc.h> -#include <partition.h> #include <envfs.h> #include <fs.h> @@ -31,7 +18,7 @@ /* If dev describes a file on a fs, mount the fs and change devpath to * point to the file's path. Otherwise leave devpath alone. Does * nothing in env in a file support isn't enabled. */ -static int environment_check_mount(struct device_d *dev, char **devpath) +static int environment_check_mount(struct device *dev, char **devpath) { const char *filepath; int ret; @@ -39,7 +26,7 @@ static int environment_check_mount(struct device_d *dev, char **devpath) if (!IS_ENABLED(CONFIG_OF_BAREBOX_ENV_IN_FS)) return 0; - ret = of_property_read_string(dev->device_node, "file-path", &filepath); + ret = of_property_read_string(dev->of_node, "file-path", &filepath); if (ret == -EINVAL) { /* No file-path so just use device-path */ return 0; @@ -65,12 +52,13 @@ static int environment_check_mount(struct device_d *dev, char **devpath) return 0; } -static int environment_probe(struct device_d *dev) +static int environment_probe(struct device *dev) { char *path; int ret; - ret = of_find_path(dev->device_node, "device-path", &path, OF_FIND_PATH_FLAGS_BB); + ret = of_find_path(dev->of_node, "device-path", &path, + OF_FIND_PATH_FLAGS_BB); if (ret) return ret; @@ -92,8 +80,9 @@ static struct of_device_id environment_dt_ids[] = { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, environment_dt_ids); -static struct driver_d environment_driver = { +static struct driver environment_driver = { .name = "barebox-environment", .probe = environment_probe, .of_compatible = environment_dt_ids, diff --git a/drivers/of/base.c b/drivers/of/base.c index 9ede052274..f0d3574148 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1,23 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * base.c - basic devicetree functions * * Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * based on Linux devicetree support - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> +#include <deep-probe.h> #include <of.h> #include <of_address.h> #include <errno.h> @@ -26,12 +16,44 @@ #include <memory.h> #include <linux/sizes.h> #include <of_graph.h> +#include <string.h> +#include <libfile.h> +#include <linux/clk.h> #include <linux/ctype.h> -#include <linux/amba/bus.h> #include <linux/err.h> static struct device_node *root_node; +/** + * of_node_has_prefix - Test if a node name has a given prefix + * @np: The node name to test + * @prefix: The prefix to see if @np starts with + * + * Returns: + * * strlen(@prefix) if @np starts with @prefix + * * 0 if @np does not start with @prefix + */ +size_t of_node_has_prefix(const struct device_node *np, const char *prefix) +{ + return np ? str_has_prefix(kbasename(np->full_name), prefix) : 0; +} +EXPORT_SYMBOL(of_node_has_prefix); + +bool of_node_name_eq(const struct device_node *np, const char *name) +{ + const char *node_name; + size_t len; + + if (!np) + return false; + + node_name = kbasename(np->full_name); + len = strchrnul(node_name, '@') - node_name; + + return (strlen(name) == len) && (strncmp(node_name, name, len) == 0); +} +EXPORT_SYMBOL(of_node_name_eq); + /* * Iterate over all nodes of a tree. As a devicetree does not * have a dedicated list head, the start node (usually the root @@ -147,16 +169,40 @@ static void of_alias_add(struct alias_prop *ap, struct device_node *np, strncpy(ap->stem, stem, stem_len); ap->stem[stem_len] = 0; list_add_tail(&ap->link, &aliases_lookup); - pr_debug("adding DT alias:%s: stem=%s id=%i node=%s\n", - ap->alias, ap->stem, ap->id, np->full_name); + pr_debug("adding DT alias:%s: stem=%s id=%i node=%pOF\n", + ap->alias, ap->stem, ap->id, np); +} + +static struct device_node *of_alias_resolve(struct device_node *root, struct property *pp) +{ + /* Skip those we do not want to proceed */ + if (!of_prop_cmp(pp->name, "name") || + !of_prop_cmp(pp->name, "phandle") || + !of_prop_cmp(pp->name, "linux,phandle")) + return NULL; + + return of_find_node_by_path_from(root, of_property_get_value(pp)); +} + +static int of_alias_id_parse(const char *start, int *len) +{ + const char *end = start + strlen(start); + + /* walk the alias backwards to extract the id and work out + * the 'stem' string */ + while (isdigit(*(end-1)) && end > start) + end--; + + *len = end - start; + + return simple_strtol(end, NULL, 10); } /** * of_alias_scan - Scan all properties of 'aliases' node * * The function scans all the properties of 'aliases' node and populates - * the global lookup table with the properties. It returns the - * number of alias_prop found, or error code in error case. + * the global lookup table with the properties. */ void of_alias_scan(void) { @@ -177,28 +223,15 @@ void of_alias_scan(void) list_for_each_entry(pp, &of_aliases->properties, list) { const char *start = pp->name; - const char *end = start + strlen(start); struct device_node *np; struct alias_prop *ap; int id, len; - /* Skip those we do not want to proceed */ - if (!of_prop_cmp(pp->name, "name") || - !of_prop_cmp(pp->name, "phandle") || - !of_prop_cmp(pp->name, "linux,phandle")) - continue; - - np = of_find_node_by_path(of_property_get_value(pp)); + np = of_alias_resolve(root_node, pp); if (!np) continue; - /* walk the alias backwards to extract the id and work out - * the 'stem' string */ - while (isdigit(*(end-1)) && end > start) - end--; - len = end - start; - - id = simple_strtol(end, NULL, 10); + id = of_alias_id_parse(start, &len); if (id < 0) continue; @@ -237,6 +270,41 @@ int of_alias_get_id(struct device_node *np, const char *stem) } EXPORT_SYMBOL_GPL(of_alias_get_id); +int of_alias_get_id_from(struct device_node *root, struct device_node *np, + const char *stem) +{ + struct device_node *aliasnp, *entrynp; + struct property *pp; + + if (!root) + return of_alias_get_id(np, stem); + + aliasnp = of_find_node_by_path_from(root, "/aliases"); + if (!aliasnp) + return -ENODEV; + + for_each_property_of_node(aliasnp, pp) { + const char *start = pp->name; + int id, len; + + entrynp = of_alias_resolve(root_node, pp); + if (entrynp != np) + continue; + + id = of_alias_id_parse(start, &len); + if (id < 0) + continue; + + if (strncasecmp(start, stem, len)) + continue; + + return id; + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(of_alias_get_id_from); + const char *of_alias_get(struct device_node *np) { struct alias_prop *app; @@ -478,7 +546,9 @@ struct device_node *of_get_cpu_node(int cpu, unsigned int *thread) EXPORT_SYMBOL(of_get_cpu_node); /** Checks if the given "compat" string matches one of the strings in - * the device's "compatible" property + * the device's "compatible" property. Returns 0 on mismatch and a + * positive score on match with the maximum being OF_DEVICE_COMPATIBLE_MAX_SCORE, + * which is only returned if the first compatible matched. */ int of_device_is_compatible(const struct device_node *device, const char *compat) @@ -491,7 +561,7 @@ int of_device_is_compatible(const struct device_node *device, for (cp = of_prop_next_string(prop, NULL); cp; cp = of_prop_next_string(prop, cp), index++) { if (of_compat_cmp(cp, compat, strlen(compat)) == 0) { - score = INT_MAX/2 - (index << 2); + score = OF_DEVICE_COMPATIBLE_MAX_SCORE - (index << 2); break; } } @@ -501,6 +571,29 @@ int of_device_is_compatible(const struct device_node *device, EXPORT_SYMBOL(of_device_is_compatible); /** + * of_find_node_by_name_address - Find a node by its full name + * @from: The node to start searching from or NULL, the node + * you pass will not be searched, only the next one + * will; typically, you pass what the previous call + * returned. + * @name: The name string to match against + * + * Returns a pointer to the node found or NULL. + */ +struct device_node *of_find_node_by_name_address(struct device_node *from, + const char *name) +{ + struct device_node *np; + + of_tree_for_each_node_from(np, from) + if (np->name && !of_node_cmp(np->name, name)) + return np; + + return NULL; +} +EXPORT_SYMBOL(of_find_node_by_name_address); + +/** * of_find_node_by_name - Find a node by its "name" property * @from: The node to start searching from or NULL, the node * you pass will not be searched, only the next one @@ -516,7 +609,7 @@ struct device_node *of_find_node_by_name(struct device_node *from, struct device_node *np; of_tree_for_each_node_from(np, from) - if (np->name && !of_node_cmp(np->name, name)) + if (np->name && of_node_name_eq(np, name)) return np; return NULL; @@ -624,6 +717,9 @@ const struct of_device_id *of_match_node(const struct of_device_id *matches, if (score > best_score) { best_match = matches; best_score = score; + + if (score == OF_DEVICE_COMPATIBLE_MAX_SCORE) + break; } } @@ -664,11 +760,11 @@ struct device_node *of_find_matching_node_and_match(struct device_node *from, } EXPORT_SYMBOL(of_find_matching_node_and_match); -int of_match(struct device_d *dev, struct driver_d *drv) +int of_match(struct device *dev, struct driver *drv) { const struct of_device_id *id; - id = of_match_node(drv->of_compatible, dev->device_node); + id = of_match_node(drv->of_compatible, dev->of_node); if (!id) return 1; @@ -739,6 +835,38 @@ int of_property_read_u32_index(const struct device_node *np, EXPORT_SYMBOL_GPL(of_property_read_u32_index); /** + * of_property_count_elems_of_size - Count the number of elements in a property + * + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @elem_size: size of the individual element + * + * Search for a property in a device node and count the number of elements of + * size elem_size in it. Returns number of elements on sucess, -EINVAL if the + * property does not exist or its length does not match a multiple of elem_size + * and -ENODATA if the property does not have a value. + */ +int of_property_count_elems_of_size(const struct device_node *np, + const char *propname, int elem_size) +{ + struct property *prop = of_find_property(np, propname, NULL); + + if (!prop) + return -EINVAL; + if (!of_property_get_value(prop)) + return -ENODATA; + + if (prop->length % elem_size != 0) { + pr_err("size of %s in node %pOF is not a multiple of %d\n", + propname, np, elem_size); + return -EINVAL; + } + + return prop->length / elem_size; +} +EXPORT_SYMBOL_GPL(of_property_count_elems_of_size); + +/** * of_property_read_u8_array - Find and read an array of u8 from a property. * * @np: device node from which the property value is to be read. @@ -864,6 +992,43 @@ int of_property_read_u64(const struct device_node *np, const char *propname, EXPORT_SYMBOL_GPL(of_property_read_u64); /** + * of_property_read_u64_array - Find and read an array of 64 bit integers + * from a property. + * + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @out_value: pointer to return value, modified only if return value is 0. + * @sz: number of array elements to read + * + * Search for a property in a device node and read 64-bit value(s) from + * it. Returns 0 on success, -EINVAL if the property does not exist, + * -ENODATA if property does not have a value, and -EOVERFLOW if the + * property data isn't large enough. + * + * The out_value is modified only if a valid u64 value can be decoded. + */ +int of_property_read_variable_u64_array(const struct device_node *np, + const char *propname, u64 *out_values, + size_t sz) +{ + size_t count; + const __be32 *val = of_find_property_value_of_size(np, propname, + (sz * sizeof(*out_values))); + + if (IS_ERR(val)) + return PTR_ERR(val); + + count = sz; + while (count--) { + *out_values++ = of_read_number(val, 2); + val += 2; + } + + return sz; +} +EXPORT_SYMBOL_GPL(of_property_read_variable_u64_array); + +/** * of_property_read_string - Find and read a string from a property * @np: device node from which the property value is to be read. * @propname: name of the property to be searched. @@ -905,7 +1070,7 @@ EXPORT_SYMBOL_GPL(of_property_read_string); * This function searches a string list property and returns the index * of a specific string value. */ -int of_property_match_string(struct device_node *np, const char *propname, +int of_property_match_string(const struct device_node *np, const char *propname, const char *string) { struct property *prop = of_find_property(np, propname, NULL); @@ -934,7 +1099,7 @@ int of_property_match_string(struct device_node *np, const char *propname, } EXPORT_SYMBOL_GPL(of_property_match_string); -const __be32 *of_prop_next_u32(struct property *prop, const __be32 *cur, +const __be32 *of_prop_next_u32(const struct property *prop, const __be32 *cur, u32 *pu) { const void *curv = cur; @@ -960,7 +1125,7 @@ out_val: } EXPORT_SYMBOL_GPL(of_prop_next_u32); -const char *of_prop_next_string(struct property *prop, const char *cur) +const char *of_prop_next_string(const struct property *prop, const char *cur) { const void *curv = cur; const void *value; @@ -986,6 +1151,7 @@ EXPORT_SYMBOL_GPL(of_prop_next_string); * * @np: device node from which the property is to be set. * @propname: name of the property to be set. + * @value true to set, false to delete * * Search for a property in a device node and create or delete the property. * If the property already exists and write value is false, the property is @@ -1154,6 +1320,53 @@ int of_property_write_u64_array(struct device_node *np, } /** + * of_property_write_strings - Write strings to a property. If + * the property does not exist, it will be created and appended to the given + * device node. + * + * @np: device node to which the property value is to be written. + * @propname: name of the property to be written. + * @...: pointers to strings to write + * + * Search for a property in a device node and write a string to + * it. If the property does not exist, it will be created and appended to + * the device node. Returns 0 on success, -ENOMEM if the property or array + * of elements cannot be created, -EINVAL if no strings specified. + */ +int of_property_write_strings(struct device_node *np, + const char *propname, ...) +{ + const char *val; + char *buf = NULL, *next; + size_t len = 0; + va_list ap; + int ret = 0; + + va_start(ap, propname); + for (val = va_arg(ap, char *); val; val = va_arg(ap, char *)) + len += strlen(val) + 1; + va_end(ap); + + if (!len) + return -EINVAL; + + buf = malloc(len); + if (!buf) + return -ENOMEM; + + next = buf; + + va_start(ap, propname); + for (val = va_arg(ap, char *); val; val = va_arg(ap, char *)) + next = stpcpy(next, val) + 1; + va_end(ap); + + ret = of_set_property(np, propname, buf, len, 1); + free(buf); + return ret; +} + +/** * of_property_write_string - Write a string to a property. If * the property does not exist, it will be created and appended to the given * device node. @@ -1281,15 +1494,13 @@ static int __of_parse_phandle_with_args(const struct device_node *np, */ node = of_find_node_by_phandle(phandle); if (!node) { - pr_err("%s: could not find phandle\n", - np->full_name); + pr_err("%pOF: could not find phandle\n", np); goto err; } if (cells_name && of_property_read_u32(node, cells_name, &count)) { - pr_err("%s: could not get %s for %s\n", - np->full_name, cells_name, - node->full_name); + pr_err("%pOF: could not get %s for %pOF\n", + np, cells_name, node); goto err; } @@ -1298,8 +1509,7 @@ static int __of_parse_phandle_with_args(const struct device_node *np, * remaining property data length */ if (list + count > list_end) { - pr_err("%s: arguments longer than property\n", - np->full_name); + pr_err("%pOF: arguments longer than property\n", np); goto err; } } @@ -1521,6 +1731,9 @@ int of_modalias_node(struct device_node *node, char *modalias, int len) } EXPORT_SYMBOL_GPL(of_modalias_node); +static struct device_node *of_chosen; +static const char *of_model; + struct device_node *of_get_root_node(void) { return root_node; @@ -1533,11 +1746,59 @@ int of_set_root_node(struct device_node *node) root_node = node; + of_chosen = of_find_node_by_path("/chosen"); + of_property_read_string(root_node, "model", &of_model); + + if (of_model) + barebox_set_model(of_model); + of_alias_scan(); return 0; } +static int barebox_of_populate(void) +{ + if (IS_ENABLED(CONFIG_OFDEVICE) && deep_probe_is_supported()) + return of_probe(); + + return 0; +} +of_populate_initcall(barebox_of_populate); + +int barebox_register_of(struct device_node *root) +{ + if (root_node) + return -EBUSY; + + of_set_root_node(root); + of_fix_tree(root); + + if (IS_ENABLED(CONFIG_OFDEVICE)) { + of_clk_init(); + if (!deep_probe_is_supported()) + return of_probe(); + } + + return 0; +} + +int barebox_register_fdt(const void *dtb) +{ + struct device_node *root; + + if (root_node) + return -EBUSY; + + root = of_unflatten_dtb(dtb, INT_MAX); + if (IS_ERR(root)) { + pr_err("Cannot unflatten dtb: %pe\n", root); + return PTR_ERR(root); + } + + return barebox_register_of(root); +} + /** * of_device_is_available - check if a device is available for use * @@ -1681,6 +1942,31 @@ int of_get_available_child_count(const struct device_node *parent) EXPORT_SYMBOL(of_get_available_child_count); /** + * of_get_compatible_child - Find compatible child node + * @parent: parent node + * @compatible: compatible string + * + * Lookup child node whose compatible property contains the given compatible + * string. + * + * Returns a node pointer with refcount incremented, use of_node_put() on it + * when done; or NULL if not found. + */ +struct device_node *of_get_compatible_child(const struct device_node *parent, + const char *compatible) +{ + struct device_node *child; + + for_each_child_of_node(parent, child) { + if (of_device_is_compatible(child, compatible)) + return child; + } + + return NULL; +} +EXPORT_SYMBOL(of_get_compatible_child); + +/** * of_get_child_by_name - Find the child node by name for a given parent * @node: parent node * @name: child name to look for. @@ -1723,9 +2009,9 @@ int of_property_read_string_helper(const struct device_node *np, if (!prop) return -EINVAL; - if (!prop->value) + p = of_property_get_value(prop); + if (!p) return -ENODATA; - p = prop->value; end = p + prop->length; for (i = 0; p < end && (!out_strs || i < skip + sz); i++, p += l) { @@ -1739,68 +2025,83 @@ int of_property_read_string_helper(const struct device_node *np, return i <= 0 ? -ENODATA : i; } -static void __of_print_nodes(struct device_node *node, int indent, const char *prefix) +static void __of_print_property_prefixed(const struct property *p, int indent, + unsigned maxpropsize, const char *prefix) +{ + unsigned length; + + printf("%s%*s%s", prefix, indent * 8, "", p->name); + + length = min_t(unsigned, p->length, maxpropsize); + if (length) { + printf(" = "); + of_print_property(of_property_get_value(p), length); + } + if (length != p->length) + printf(" /* %u more bytes omitted */", p->length - length); + + printf(";\n"); +} + +static int __of_print_nodes(struct device_node *node, int indent, + unsigned maxpropsize, const char *prefix) { struct device_node *n; struct property *p; + int ret; if (!node) - return; + return 0; if (!prefix) prefix = ""; printf("%s%*s%s%s\n", prefix, indent * 8, "", node->name, node->name ? " {" : "{"); - list_for_each_entry(p, &node->properties, list) { - printf("%s%*s%s", prefix, (indent + 1) * 8, "", p->name); - if (p->length) { - printf(" = "); - of_print_property(of_property_get_value(p), p->length); - } - printf(";\n"); - } + list_for_each_entry(p, &node->properties, list) + __of_print_property_prefixed(p, indent + 1, maxpropsize, prefix); + + if (ctrlc()) + return -EINTR; list_for_each_entry(n, &node->children, parent_list) { - __of_print_nodes(n, indent + 1, prefix); + ret = __of_print_nodes(n, indent + 1, maxpropsize, prefix); + if (ret) + return ret; } printf("%s%*s};\n", prefix, indent * 8, ""); + return 0; } -void of_print_nodes(struct device_node *node, int indent) +void of_print_nodes(struct device_node *node, int indent, unsigned maxpropsize) { - __of_print_nodes(node, indent, NULL); + __of_print_nodes(node, indent, maxpropsize, NULL); } -static void __of_print_property(struct property *p, int indent) +static void __of_print_property(struct property *p, int indent, unsigned maxpropsize) { - int i; + __of_print_property_prefixed(p, indent, maxpropsize, ""); +} - for (i = 0; i < indent; i++) - printf("\t"); +void of_print_properties(struct device_node *node, unsigned maxpropsize) +{ + struct property *prop; - printf("%s", p->name); - if (p->length) { - printf(" = "); - of_print_property(of_property_get_value(p), p->length); - } - printf(";\n"); + list_for_each_entry(prop, &node->properties, list) + __of_print_property(prop, 0, maxpropsize); } static int __of_print_parents(struct device_node *node) { - int indent, i; + int indent; if (!node->parent) return 0; indent = __of_print_parents(node->parent); - for (i = 0; i < indent; i++) - printf("\t"); - - printf("%s {\n", node->name); + printf("%*s%s {\n", indent * 8, "", node->name); return indent + 1; } @@ -1841,57 +2142,76 @@ static void of_print_close(struct device_node *node, int *printed) * This function compares two device trees against each other and prints * a diff-like result. */ -void of_diff(struct device_node *a, struct device_node *b, int indent) +int of_diff(struct device_node *a, struct device_node *b, int indent) { struct property *ap, *bp; struct device_node *ca, *cb; - int printed = 0; + int printed = 0, diff = 0; + bool silent = indent < 0; list_for_each_entry(ap, &a->properties, list) { bp = of_find_property(b, ap->name, NULL); if (!bp) { + diff++; + if (silent) + continue; of_print_parents(a, &printed); printf("- "); - __of_print_property(ap, indent); + __of_print_property(ap, indent, ~0); continue; } if (ap->length != bp->length || memcmp(of_property_get_value(ap), of_property_get_value(bp), bp->length)) { + diff++; + if (silent) + continue; of_print_parents(a, &printed); printf("- "); - __of_print_property(ap, indent); + __of_print_property(ap, indent, ~0); printf("+ "); - __of_print_property(bp, indent); + __of_print_property(bp, indent, ~0); } } list_for_each_entry(bp, &b->properties, list) { ap = of_find_property(a, bp->name, NULL); if (!ap) { + diff++; + if (silent) + continue; of_print_parents(a, &printed); printf("+ "); - __of_print_property(bp, indent); + __of_print_property(bp, indent, ~0); } } for_each_child_of_node(a, ca) { cb = of_get_child_by_name(b, ca->name); if (cb) { - of_diff(ca, cb, indent + 1); + diff += of_diff(ca, cb, silent ? indent : indent + 1); } else { + diff++; + if (silent) + continue; of_print_parents(a, &printed); - __of_print_nodes(ca, indent, "-"); + __of_print_nodes(ca, indent, ~0, "- "); } } for_each_child_of_node(b, cb) { if (!of_get_child_by_name(a, cb->name)) { + diff++; + if (silent) + continue; of_print_parents(a, &printed); - __of_print_nodes(cb, indent, "+"); + __of_print_nodes(cb, indent, ~0, "+ "); } } - of_print_close(a, &printed); + if (!silent) + of_print_close(a, &printed); + + return diff; } struct device_node *of_new_node(struct device_node *parent, const char *name) @@ -1908,8 +2228,8 @@ struct device_node *of_new_node(struct device_node *parent, const char *name) if (parent) { node->name = xstrdup(name); - node->full_name = basprintf("%s/%s", - node->parent->full_name, name); + node->full_name = basprintf("%pOF/%s", + node->parent, name); list_add(&node->list, &parent->list); } else { node->name = xstrdup(""); @@ -1920,6 +2240,21 @@ struct device_node *of_new_node(struct device_node *parent, const char *name) return node; } +struct property *__of_new_property(struct device_node *node, const char *name, + void *data, int len) +{ + struct property *prop; + + prop = xzalloc(sizeof(*prop)); + prop->name = xstrdup(name); + prop->length = len; + prop->value = data; + + list_add_tail(&prop->list, &node->properties); + + return prop; +} + /** * of_new_property - Add a new property to a node * @node: device node to which the property is added @@ -1935,19 +2270,13 @@ struct device_node *of_new_node(struct device_node *parent, const char *name) struct property *of_new_property(struct device_node *node, const char *name, const void *data, int len) { - struct property *prop; - - prop = xzalloc(sizeof(*prop)); - prop->name = xstrdup(name); - prop->length = len; - prop->value = xzalloc(len); + char *buf; + buf = xzalloc(len); if (data) - memcpy(prop->value, data, len); + memcpy(buf, data, len); - list_add_tail(&prop->list, &node->properties); - - return prop; + return __of_new_property(node, name, buf, len); } /** @@ -1991,6 +2320,41 @@ void of_delete_property(struct property *pp) free(pp); } +struct property *of_rename_property(struct device_node *np, + const char *old_name, const char *new_name) +{ + struct property *pp; + + pp = of_find_property(np, old_name, NULL); + if (!pp) + return NULL; + + of_property_write_bool(np, new_name, false); + + free(pp->name); + pp->name = xstrdup(new_name); + return pp; +} + +struct property *of_copy_property(const struct device_node *src, + const char *propname, + struct device_node *dst) +{ + struct property *prop; + + prop = of_find_property(src, propname, NULL); + if (!prop) + return NULL; + + if (of_property_present(dst, propname)) + return ERR_PTR(-EEXIST); + + return of_new_property(dst, propname, + of_property_get_value(prop), prop->length); +} +EXPORT_SYMBOL_GPL(of_copy_property); + + /** * of_set_property - create a property for a given node * @node - the node @@ -2019,6 +2383,101 @@ int of_set_property(struct device_node *np, const char *name, const void *val, i return 0; } +int of_append_property(struct device_node *np, const char *name, const void *val, int len) +{ + struct property *pp; + int orig_len; + void *buf; + + if (!np) + return -ENOENT; + + pp = of_find_property(np, name, NULL); + if (!pp) { + of_new_property(np, name, val, len); + return 0; + } + + orig_len = pp->length; + buf = realloc(pp->value, orig_len + len); + if (!buf) + return -ENOMEM; + + memcpy(buf + orig_len, val, len); + + pp->value = buf; + pp->length += len; + + if (pp->value_const) { + memcpy(buf, pp->value_const, orig_len); + pp->value_const = NULL; + } + + return 0; +} + +int of_prepend_property(struct device_node *np, const char *name, const void *val, int len) +{ + struct property *pp; + const void *oldval; + void *buf; + int oldlen; + + pp = of_find_property(np, name, &oldlen); + if (!pp) { + of_new_property(np, name, val, len); + return 0; + } + + oldval = of_property_get_value(pp); + + buf = malloc(len + oldlen); + if (!buf) + return -ENOMEM; + + memcpy(buf, val, len); + memcpy(buf + len, oldval, oldlen); + + free(pp->value); + pp->value = buf; + pp->length = len + oldlen; + pp->value_const = NULL; + + return 0; +} + +int of_property_sprintf(struct device_node *np, + const char *propname, const char *fmt, ...) +{ + struct property *pp; + struct va_format vaf; + char *buf = NULL; + va_list args; + int len; + + if (!np) + return -ENOENT; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + len = asprintf(&buf, "%pV", &vaf); + va_end(args); + + if (len < 0) + return -ENOMEM; + + len++; /* trailing NUL */ + + pp = of_find_property(np, propname, NULL); + of_delete_property(pp); + + __of_new_property(np, propname, buf, len); + return len; +} + +static int mem_bank_num; + int of_add_memory(struct device_node *node, bool dump) { const char *device_type; @@ -2030,22 +2489,25 @@ int of_add_memory(struct device_node *node, bool dump) return -ENXIO; while (!of_address_to_resource(node, n, &res)) { - if (!resource_size(&res)) { - n++; + int err; + n++; + if (!resource_size(&res)) continue; - } - of_add_memory_bank(node, dump, n, + if (!of_device_is_available(node)) + continue; + + err = of_add_memory_bank(node, dump, mem_bank_num, res.start, resource_size(&res)); - n++; + if (err) + ret = err; + + mem_bank_num++; } - return 0; + return ret; } -static struct device_node *of_chosen; -static const char *of_model; - const char *of_get_model(void) { return of_model; @@ -2055,36 +2517,96 @@ const struct of_device_id of_default_bus_match_table[] = { { .compatible = "simple-bus", }, { + .compatible = "simple-pm-bus", + }, { .compatible = "simple-mfd", }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, of_default_bus_match_table); + +static int of_probe_memory(void) +{ + struct device_node *memory = root_node; + int ret = 0; + + if (!IS_ENABLED(CONFIG_OFDEVICE)) + return 0; + + /* Parse all available node with "memory" device_type */ + while (1) { + int err; + + memory = of_find_node_by_type(memory, "memory"); + if (!memory) + break; + + err = of_add_memory(memory, false); + if (err) + ret = err; + } + + return ret; +} +mem_initcall(of_probe_memory); + +static void of_platform_device_create_root(struct device_node *np) +{ + static struct device *dev; + int ret; + + if (dev) + return; + + dev = xzalloc(sizeof(*dev)); + dev->id = DEVICE_ID_SINGLE; + dev->of_node = np; + dev_set_name(dev, "machine"); + + ret = platform_device_register(dev); + if (ret) + free_device(dev); +} + +static const struct of_device_id reserved_mem_matches[] = { + { .compatible = "nvmem-rmem" }, + {} +}; +MODULE_DEVICE_TABLE(of, reserved_mem_matches); +/** + * of_probe - Probe unflattened device tree starting at of_get_root_node + * + * The function walks the device tree and creates devices as needed. + * With care, it can be called more than once, but if you really need that, + * consider first if deep probe would help instead. + */ int of_probe(void) { - struct device_node *memory, *firmware; + struct device_node *node; if(!root_node) return -ENODEV; - of_chosen = of_find_node_by_path("/chosen"); - of_property_read_string(root_node, "model", &of_model); - - if (of_model) - barebox_set_model(of_model); + /* + * We do this first thing, so board drivers can patch the device + * tree prior to device creation if needed. + */ + of_platform_device_create_root(root_node); - memory = of_find_node_by_path("/memory"); - if (!memory) - memory = of_find_node_by_type(root_node, "memory"); - if (memory) - of_add_memory(memory, false); + /* + * Handle certain compatibles explicitly, since we don't want to create + * platform_devices for every node in /reserved-memory with a + * "compatible", + */ + for_each_matching_node(node, reserved_mem_matches) + of_platform_device_create(node, NULL); - firmware = of_find_node_by_path("/firmware"); - if (firmware) - of_platform_populate(firmware, NULL, NULL); + node = of_find_node_by_path("/firmware"); + if (node) + of_platform_populate(node, NULL, NULL); - of_clk_init(root_node, NULL); of_platform_populate(root_node, of_default_bus_match_table, NULL); return 0; @@ -2134,31 +2656,51 @@ out: return dn; } -struct device_node *of_copy_node(struct device_node *parent, const struct device_node *other) +void of_merge_nodes(struct device_node *np, const struct device_node *other) { - struct device_node *np, *child; + struct device_node *child; struct property *pp; - np = of_new_node(parent, other->name); - list_for_each_entry(pp, &other->properties, list) of_new_property(np, pp->name, pp->value, pp->length); for_each_child_of_node(other, child) of_copy_node(np, child); +} + +struct device_node *of_copy_node(struct device_node *parent, const struct device_node *other) +{ + struct device_node *np; + + np = of_new_node(parent, other->name); + np->phandle = other->phandle; + + of_merge_nodes(np, other); return np; } +struct device_node *of_dup(const struct device_node *root) +{ + if (IS_ERR_OR_NULL(root)) + return ERR_CAST(root); + + return of_copy_node(NULL, root); +} + void of_delete_node(struct device_node *node) { struct device_node *n, *nt; struct property *p, *pt; - struct device_d *dev; if (!node) return; + if (node == root_node) { + pr_err("Won't delete root device node\n"); + return; + } + list_for_each_entry_safe(p, pt, &node->properties, list) of_delete_property(p); @@ -2170,47 +2712,72 @@ void of_delete_node(struct device_node *node) list_del(&node->list); } - dev = of_find_device_by_node(node); - if (dev) - dev->device_node = NULL; - free(node->name); free(node->full_name); free(node); - - if (node == root_node) - of_set_root_node(NULL); } -int of_device_is_stdout_path(struct device_d *dev) -{ +/* + * of_find_node_by_chosen - Find a node given a chosen property pointing at it + * @propname: the name of the property containing a path or alias + * The function will lookup the first string in the property + * value up to the first : character or till \0. + * @options The Remainder (without : or \0 at the end) will be written + * to *options if not NULL. + */ +struct device_node *of_find_node_by_chosen(const char *propname, + const char **options) +{ + const char *value, *p; + char *buf; struct device_node *dn; - const char *name; - const char *p; - char *q; - if (!dev->device_node) - return 0; + value = of_get_property(of_chosen, propname, NULL); + if (!value) + return NULL; - name = of_get_property(of_chosen, "stdout-path", NULL); - if (!name) - name = of_get_property(of_chosen, "linux,stdout-path", NULL); + p = strchrnul(value, ':'); + buf = xstrndup(value, p - value); - if (!name) - return 0; + dn = of_find_node_by_path_or_alias(NULL, buf); - /* This could make use of strchrnul if it were available */ - p = strchr(name, ':'); - if (!p) - p = name + strlen(name); + free(buf); - q = xstrndup(name, p - name); + if (options && *p) + *options = p + 1; - dn = of_find_node_by_path_or_alias(NULL, q); + return dn; +} - free(q); +struct device_node *of_get_stdoutpath(unsigned int *baudrate) +{ + const char *opts = NULL; + struct device_node *dn; + + dn = of_find_node_by_chosen("stdout-path", &opts); + if (!dn) + dn = of_find_node_by_chosen("linux,stdout-path", &opts); + if (!dn) + return NULL; + + if (baudrate && opts) { + unsigned rate = simple_strtoul(opts, NULL, 10); + if (rate) + *baudrate = rate; + } + + return dn; +} + +int of_device_is_stdout_path(struct device *dev, unsigned int *baudrate) +{ + unsigned int tmp = *baudrate; - return dn == dev->device_node; + if (!dev || !dev->of_node || dev->of_node != of_get_stdoutpath(&tmp)) + return false; + + *baudrate = tmp; + return true; } /** @@ -2294,6 +2861,21 @@ int of_device_enable_path(const char *path) } /** + * of_device_enable_by_alias - enable a device node by alias + * @alias - the alias of the device tree node to enable + */ +int of_device_enable_by_alias(const char *alias) +{ + struct device_node *node; + + node = of_find_node_by_alias(NULL, alias); + if (!node) + return -ENODEV; + + return of_device_enable(node); +} + +/** * of_device_disable - disable a devicenode device * @node - the node to disable * @@ -2322,6 +2904,52 @@ int of_device_disable_path(const char *path) } /** + * of_device_disable_by_alias - disable a devicenode by alias + * @alias - the alias of the device tree node to disable + */ +int of_device_disable_by_alias(const char *alias) +{ + struct device_node *node; + + node = of_find_node_by_alias(NULL, alias); + if (!node) + return -ENODEV; + + return of_device_disable(node); +} + +/** + * of_read_file - unflatten oftree file + * @filename - path to file to unflatten its contents + * + * Returns the root node of the tree or an error pointer on error. + */ +struct device_node *of_read_file(const char *filename) +{ + void *fdt; + size_t size; + struct device_node *root; + + fdt = read_file(filename, &size); + if (!fdt) { + pr_err("unable to read %s: %m\n", filename); + return ERR_PTR(-errno); + } + + if (IS_ENABLED(CONFIG_FILETYPE) && file_detect_type(fdt, size) != filetype_oftree) { + pr_err("%s is not a flat device tree file.\n", filename); + root = ERR_PTR(-EINVAL); + goto out; + } + + root = of_unflatten_dtb(fdt, size); +out: + free(fdt); + + return root; +} + +/** * of_get_reproducible_name() - get a reproducible name of a node * @node: The node to get a name from * @@ -2404,12 +3032,23 @@ struct device_node *of_find_node_by_reproducible_name(struct device_node *from, return NULL; } +struct device_node *of_get_node_by_reproducible_name(struct device_node *dstroot, + struct device_node *srcnp) +{ + struct device_node *dstnp; + char *name; + + name = of_get_reproducible_name(srcnp); + dstnp = of_find_node_by_reproducible_name(dstroot, name); + free(name); + + return dstnp; +} + /** * of_graph_parse_endpoint() - parse common endpoint node properties * @node: pointer to endpoint device_node * @endpoint: pointer to the OF endpoint data structure - * - * The caller should hold a reference to @node. */ int of_graph_parse_endpoint(const struct device_node *node, struct of_endpoint *endpoint) @@ -2417,8 +3056,8 @@ int of_graph_parse_endpoint(const struct device_node *node, struct device_node *port_node = of_get_parent(node); if (!port_node) - pr_warn("%s(): endpoint %s has no parent node\n", - __func__, node->full_name); + pr_warn("%s(): endpoint %pOF has no parent node\n", + __func__, node); memset(endpoint, 0, sizeof(*endpoint)); @@ -2490,15 +3129,15 @@ struct device_node *of_graph_get_next_endpoint(const struct device_node *parent, port = of_get_child_by_name(parent, "port"); if (!port) { - pr_err("%s(): no port node found in %s\n", - __func__, parent->full_name); + pr_err("%s(): no port node found in %pOF\n", + __func__, parent); return NULL; } } else { port = of_get_parent(prev); if (!port) { - pr_warn("%s(): endpoint %s has no parent node\n", - __func__, prev->full_name); + pr_warn("%s(): endpoint %pOF has no parent node\n", + __func__, prev); return NULL; } } diff --git a/drivers/of/device.c b/drivers/of/device.c index 67a67bd565..77c027b57e 100644 --- a/drivers/of/device.c +++ b/drivers/of/device.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only #include <common.h> #include <of.h> #include <of_device.h> @@ -11,16 +12,16 @@ * system is in its list of supported devices. */ const struct of_device_id *of_match_device(const struct of_device_id *matches, - const struct device_d *dev) + const struct device *dev) { - if ((!matches) || (!dev->device_node)) + if ((!matches) || (!dev->of_node)) return NULL; - return of_match_node(matches, dev->device_node); + return of_match_node(matches, dev->of_node); } EXPORT_SYMBOL(of_match_device); -const void *of_device_get_match_data(const struct device_d *dev) +const void *of_device_get_match_data(const struct device *dev) { const struct of_device_id *match; @@ -31,3 +32,15 @@ const void *of_device_get_match_data(const struct device_d *dev) return match->data; } EXPORT_SYMBOL(of_device_get_match_data); + +const char *of_device_get_match_compatible(const struct device *dev) +{ + const struct of_device_id *match; + + match = of_match_device(dev->driver->of_compatible, dev); + if (!match) + return NULL; + + return match->compatible; +} +EXPORT_SYMBOL(of_device_get_match_compatible); diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index cf3f1ee147..8dca41990c 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -1,21 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * fdt.c - flat devicetree functions * * Copyright (c) 2013 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * based on Linux devicetree support - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> #include <of.h> @@ -25,8 +14,24 @@ #include <memory.h> #include <linux/sizes.h> #include <linux/ctype.h> +#include <linux/log2.h> +#include <linux/overflow.h> +#include <linux/string_helpers.h> #include <linux/err.h> +static inline bool __dt_ptr_ok(const struct fdt_header *fdt, const void *p, + unsigned elem_size, unsigned elem_align) +{ + if (!p || (const void *)fdt > p || !PTR_IS_ALIGNED(p, elem_align) || + p + elem_size > (const void *)fdt + be32_to_cpu(fdt->totalsize)) { + pr_err("unflatten: offset overflows or misaligns FDT\n"); + return false; + } + + return true; +} +#define dt_ptr_ok(fdt, p) __dt_ptr_ok(fdt, p, sizeof(*(p)), __alignof__(*(p))) + static inline uint32_t dt_struct_advance(struct fdt_header *f, uint32_t dt, int size) { dt += size; @@ -38,29 +43,40 @@ static inline uint32_t dt_struct_advance(struct fdt_header *f, uint32_t dt, int return dt; } -static inline char *dt_string(struct fdt_header *f, char *strstart, uint32_t ofs) +static inline const char *dt_string(struct fdt_header *f, const char *strstart, uint32_t ofs) { + const char *str; + if (ofs > f->size_dt_strings) return NULL; - else - return strstart + ofs; + + str = strstart + ofs; + + return string_is_terminated(str, f->size_dt_strings - ofs) ? str : NULL; } static int of_reservemap_num_entries(const struct fdt_header *fdt) { - const struct fdt_reserve_entry *r; + /* + * FDT may violate spec mandated 8-byte alignment if unflattening it out of + * a FIT image property, so play it safe here. + */ + const struct fdt_reserve_entry_unaligned { + fdt64_t address; + fdt64_t size; + } __packed *r; int n = 0; r = (void *)fdt + be32_to_cpu(fdt->off_mem_rsvmap); - while (r->size) { + while (dt_ptr_ok(fdt, r) && r->size) { n++; r++; if (n == OF_MAX_RESERVE_MAP) return -EINVAL; } - return n; + return r->size == 0 ? n : -ESPIPE; } /** @@ -81,7 +97,6 @@ static int of_unflatten_reservemap(struct device_node *root, int n; struct property *p; struct device_node *memreserve; - __be32 cells; n = of_reservemap_num_entries(fdt); if (n <= 0) @@ -91,16 +106,6 @@ static int of_unflatten_reservemap(struct device_node *root, if (!memreserve) return -ENOMEM; - cells = cpu_to_be32(2); - - p = of_new_property(memreserve, "#address-cells", &cells, sizeof(__be32)); - if (!p) - return -ENOMEM; - - p = of_new_property(memreserve, "#size-cells", &cells, sizeof(__be32)); - if (!p) - return -ENOMEM; - p = of_new_property(memreserve, "reg", (void *)fdt + be32_to_cpu(fdt->off_mem_rsvmap), n * sizeof(struct fdt_reserve_entry)); @@ -110,6 +115,44 @@ static int of_unflatten_reservemap(struct device_node *root, return 0; } +static int fdt_parse_header(const struct fdt_header *fdt, size_t fdt_size, + struct fdt_header *out) +{ + if (fdt_size < sizeof(struct fdt_header)) + return -EINVAL; + + if (fdt->magic != cpu_to_fdt32(FDT_MAGIC)) { + pr_err("bad magic: 0x%08x\n", fdt32_to_cpu(fdt->magic)); + return -EINVAL; + } + + if (fdt->version != cpu_to_fdt32(17)) { + pr_err("bad dt version: 0x%08x\n", fdt32_to_cpu(fdt->version)); + return -EINVAL; + } + + out->totalsize = fdt32_to_cpu(fdt->totalsize); + out->off_dt_struct = fdt32_to_cpu(fdt->off_dt_struct); + out->size_dt_struct = fdt32_to_cpu(fdt->size_dt_struct); + out->off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings); + out->size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings); + + if (out->totalsize > fdt_size) + return -EINVAL; + + if (size_add(out->off_dt_struct, out->size_dt_struct) > out->totalsize) { + pr_err("unflatten: dt size exceeds total size\n"); + return -ESPIPE; + } + + if (size_add(out->off_dt_strings, out->size_dt_strings) > out->totalsize) { + pr_err("unflatten: string size exceeds total size\n"); + return -ESPIPE; + } + + return 0; +} + /** * of_unflatten_dtb - unflatten a dtb binary blob * @infdt - the fdt blob to unflatten @@ -117,7 +160,8 @@ static int of_unflatten_reservemap(struct device_node *root, * Parse a flat device tree binary blob and return a pointer to the * unflattened tree. */ -static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops) +static struct device_node *__of_unflatten_dtb(const void *infdt, int size, + bool constprops) { const void *nodep; /* property node pointer */ uint32_t tag; /* tag */ @@ -134,31 +178,9 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops unsigned int maxlen; const struct fdt_header *fdt = infdt; - if (fdt->magic != cpu_to_fdt32(FDT_MAGIC)) { - pr_err("bad magic: 0x%08x\n", fdt32_to_cpu(fdt->magic)); - return ERR_PTR(-EINVAL); - } - - if (fdt->version != cpu_to_fdt32(17)) { - pr_err("bad dt version: 0x%08x\n", fdt32_to_cpu(fdt->version)); - return ERR_PTR(-EINVAL); - } - - f.totalsize = fdt32_to_cpu(fdt->totalsize); - f.off_dt_struct = fdt32_to_cpu(fdt->off_dt_struct); - f.size_dt_struct = fdt32_to_cpu(fdt->size_dt_struct); - f.off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings); - f.size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings); - - if (f.off_dt_struct + f.size_dt_struct > f.totalsize) { - pr_err("unflatten: dt size exceeds total size\n"); - return ERR_PTR(-ESPIPE); - } - - if (f.off_dt_strings + f.size_dt_strings > f.totalsize) { - pr_err("unflatten: string size exceeds total size\n"); - return ERR_PTR(-ESPIPE); - } + ret = fdt_parse_header(infdt, size, &f); + if (ret < 0) + return ERR_PTR(ret); dt_struct = f.off_dt_struct; dt_strings = (void *)fdt + f.off_dt_strings; @@ -172,7 +194,13 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops goto err; while (1) { - tag = be32_to_cpu(*(uint32_t *)(infdt + dt_struct)); + __be32 *tagp = (uint32_t *)(infdt + dt_struct); + if (!dt_ptr_ok(infdt, tagp)) { + ret = -ESPIPE; + goto err; + } + + tag = be32_to_cpu(*tagp); switch (tag) { case FDT_BEGIN_NODE: @@ -187,10 +215,21 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops goto err; } - if (!node) + if (!node) { + /* The root node must have an empty name */ + if (*pathp) { + ret = -EINVAL; + goto err; + } node = root; - else + } else { + /* Only the root node may have an empty name */ + if (!*pathp) { + ret = -EINVAL; + goto err; + } node = of_new_node(node, pathp); + } dt_struct = dt_struct_advance(&f, dt_struct, sizeof(struct fdt_node_header) + len + 1); @@ -216,7 +255,7 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops nodep = fdt_prop->data; name = dt_string(&f, dt_strings, fdt32_to_cpu(fdt_prop->nameoff)); - if (!name) { + if (!name || !node) { ret = -ESPIPE; goto err; } @@ -266,9 +305,9 @@ err: * Parse a flat device tree binary blob and return a pointer to the unflattened * tree. The tree must be freed after use with of_delete_node(). */ -struct device_node *of_unflatten_dtb(const void *infdt) +struct device_node *of_unflatten_dtb(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, false); + return __of_unflatten_dtb(infdt, size, false); } /** @@ -282,9 +321,9 @@ struct device_node *of_unflatten_dtb(const void *infdt) * whole lifetime of the returned tree. This is normally not what you want, so * use of_unflatten_dtb() instead. */ -struct device_node *of_unflatten_dtb_const(const void *infdt) +struct device_node *of_unflatten_dtb_const(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, true); + return __of_unflatten_dtb(infdt, size, true); } struct fdt { @@ -306,15 +345,15 @@ static int lstrcpy(char *dest, const char *src) int len = 0; int maxlen = 1023; - while (*src) { - *dest++ = *src++; + do { + *dest++ = *src; len++; if (!maxlen) return -ENOSPC; maxlen--; - } + } while (*src++); - return len; + return len - 1; } static void *memalign_realloc(void *orig, size_t oldsize, size_t newsize) @@ -347,24 +386,41 @@ static void *memalign_realloc(void *orig, size_t oldsize, size_t newsize) static int fdt_ensure_space(struct fdt *fdt, int dtsize) { + size_t new_size; + void *previous; + /* * We assume strings and names have a maximum length of 1024 * whereas properties can be longer. We allocate new memory * if we have less than 1024 bytes (+ the property size left. */ if (fdt->str_size - fdt->str_nextofs < 1024) { - fdt->strings = realloc(fdt->strings, fdt->str_size * 2); - if (!fdt->strings) + previous = fdt->strings; + new_size = fdt->str_size * 2; + + fdt->strings = realloc(previous, new_size); + if (!fdt->strings) { + free(previous); return -ENOMEM; - fdt->str_size *= 2; + } + + fdt->str_size = new_size; } if (fdt->dt_size - fdt->dt_nextofs < 1024 + dtsize) { - fdt->dt = memalign_realloc(fdt->dt, fdt->dt_size, - fdt->dt_size * 2); - if (!fdt->dt) + previous = fdt->dt; + new_size = fdt->dt_size * 2; + + if (new_size <= dtsize) + new_size = roundup_pow_of_two(fdt->dt_size + dtsize); + + fdt->dt = memalign_realloc(previous, fdt->dt_size, new_size); + if (!fdt->dt) { + free(previous); return -ENOMEM; - fdt->dt_size *= 2; + } + + fdt->dt_size = new_size; } return 0; @@ -479,7 +535,7 @@ void *of_flatten_dtb(struct device_node *node) if (ret) goto out_free; - memreserve = of_find_node_by_name(node, "memreserve"); + memreserve = of_find_node_by_name_address(node, "memreserve"); if (memreserve) { const void *entries = of_get_property(memreserve, "reg", &len); @@ -558,9 +614,7 @@ void of_clean_reserve_map(void) * @__fdt: The devicetree blob * * This adds the reservemap entries previously collected in - * of_add_reserve_entry() to a devicetree binary blob. This also - * adds the devicetree itself to the reserved list, so after calling - * this function the tree should not be relocated anymore. + * of_add_reserve_entry() to a devicetree binary blob. */ void fdt_add_reserve_map(void *__fdt) { @@ -588,10 +642,135 @@ void fdt_add_reserve_map(void *__fdt) fdt_res++; } - of_write_number(&fdt_res->address, (unsigned long)__fdt, 2); - of_write_number(&fdt_res->size, be32_to_cpu(fdt->totalsize), 2); - fdt_res++; - of_write_number(&fdt_res->address, 0, 2); of_write_number(&fdt_res->size, 0, 2); } + +void fdt_print_reserve_map(const void *__fdt) +{ + const struct fdt_header *fdt = __fdt; + const struct fdt_reserve_entry *fdt_res = + __fdt + be32_to_cpu(fdt->off_mem_rsvmap); + int n = 0; + + while (1) { + uint64_t size = fdt64_to_cpu(fdt_res->size); + uint64_t address = fdt64_to_cpu(fdt_res->address); + + if (!size) + break; + + printf("/memreserve/ #%d: 0x%08llx - 0x%08llx\n", n, address, address + size - 1); + + n++; + fdt_res++; + if (n == OF_MAX_RESERVE_MAP) + return; + } +} + +static int fdt_string_is_compatible(const char *haystack, int haystack_len, + const char *needle, int needle_len) +{ + const char *p; + int index = 0; + + while (haystack_len >= needle_len) { + if (memcmp(needle, haystack, needle_len + 1) == 0) + return OF_DEVICE_COMPATIBLE_MAX_SCORE - (index << 2); + + p = memchr(haystack, '\0', haystack_len); + if (!p) + return 0; + haystack_len -= (p - haystack) + 1; + haystack = p + 1; + index++; + } + + return 0; +} + +int fdt_machine_is_compatible(const struct fdt_header *fdt, size_t fdt_size, const char *compat) +{ + uint32_t tag; + const struct fdt_property *fdt_prop; + const char *name; + uint32_t dt_struct; + const struct fdt_node_header *fnh; + const void *dt_strings; + struct fdt_header f; + int ret, len; + int expect = FDT_BEGIN_NODE; + int compat_len = strlen(compat); + + ret = fdt_parse_header(fdt, fdt_size, &f); + if (ret < 0) + return 0; + + dt_struct = f.off_dt_struct; + dt_strings = (const void *)fdt + f.off_dt_strings; + + while (1) { + const __be32 *tagp = (const void *)fdt + dt_struct; + if (!dt_ptr_ok(fdt, tagp)) + return 0; + + tag = be32_to_cpu(*tagp); + if (tag != FDT_NOP && tag != expect) + return 0; + + switch (tag) { + case FDT_BEGIN_NODE: + fnh = (const void *)fdt + dt_struct; + + /* The root node must have an empty name */ + if (fnh->name[0] != '\0') + return 0; + + dt_struct = dt_struct_advance(&f, dt_struct, + sizeof(struct fdt_node_header) + 1); + + /* + * Quoting Device Tree Specification v0.4 §5.4.2: + * + * [T]his process requires that all property definitions for + * a particular node precede any subnode definitions for that + * node. Although the structure would not be ambiguous if + * properties and subnodes were intermingled, the code needed + * to process a flat tree is simplified by this requirement. + * + * So let's make use of this simplification. + */ + expect = FDT_PROP; + break; + + case FDT_PROP: + fdt_prop = (const void *)fdt + dt_struct; + len = fdt32_to_cpu(fdt_prop->len); + + name = dt_string(&f, dt_strings, fdt32_to_cpu(fdt_prop->nameoff)); + if (!name) + return 0; + + if (strcmp(name, "compatible")) { + dt_struct = dt_struct_advance(&f, dt_struct, + sizeof(struct fdt_property) + len); + break; + } + + return fdt_string_is_compatible(fdt_prop->data, len, compat, compat_len); + + case FDT_NOP: + dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE); + break; + + default: + return 0; + } + + if (!dt_struct) + return 0; + } + + return 0; +} diff --git a/drivers/of/mem_generic.c b/drivers/of/mem_generic.c index 9094243c04..be618d51f5 100644 --- a/drivers/of/mem_generic.c +++ b/drivers/of/mem_generic.c @@ -1,15 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only #include <common.h> #include <of.h> #include <memory.h> -void of_add_memory_bank(struct device_node *node, bool dump, int r, +int of_add_memory_bank(struct device_node *node, bool dump, int r, u64 base, u64 size) { static char str[6]; sprintf(str, "ram%d", r); - barebox_add_memory_bank(str, base, size); if (dump) pr_info("%s: %s: 0x%llx@0x%llx\n", node->name, str, size, base); + + return barebox_add_memory_bank(str, base, size); } diff --git a/drivers/of/of_firmware.c b/drivers/of/of_firmware.c index 0135631fb8..c1b69aac04 100644 --- a/drivers/of/of_firmware.c +++ b/drivers/of/of_firmware.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de> */ @@ -6,34 +6,35 @@ #include <firmware.h> #include <of.h> -struct overlay_info { - const char *firmware_path; -}; - static struct firmware_mgr *of_node_get_mgr(struct device_node *np) { struct device_node *mgr_node; do { - if (of_device_is_compatible(np, "fpga-region")) { - mgr_node = of_parse_phandle(np, "fpga-mgr", 0); - if (mgr_node) - return firmwaremgr_find_by_node(mgr_node); - } + mgr_node = of_parse_phandle_from(np, of_find_root_node(np), + "fpga-mgr", 0); + if (mgr_node) + return firmwaremgr_find_by_node(mgr_node); } while ((np = of_get_parent(np)) != NULL); return NULL; } +struct fw_load_entry { + struct firmware_mgr *mgr; + char *firmware; + struct list_head list; +}; + +static LIST_HEAD(fw_load_list); + static int load_firmware(struct device_node *target, - struct device_node *fragment, void *data) + struct device_node *fragment, void *unused) { - struct overlay_info *info = data; const char *firmware_name; - const char *firmware_path = info->firmware_path; - char *firmware; int err; struct firmware_mgr *mgr; + struct fw_load_entry *fle; err = of_property_read_string(fragment, "firmware-name", &firmware_name); @@ -43,44 +44,96 @@ static int load_firmware(struct device_node *target, else if (err) return -EINVAL; + if (!target) + return -EINVAL; + + if (!of_device_is_compatible(target, "fpga-region")) + return 0; + mgr = of_node_get_mgr(target); if (!mgr) return -EINVAL; - firmware = basprintf("%s/%s", firmware_path, firmware_name); - if (!firmware) - return -ENOMEM; + fle = xzalloc(sizeof(*fle)); + fle->mgr = mgr; + fle->firmware = xstrdup(firmware_name); - err = firmwaremgr_load_file(mgr, firmware); + list_add_tail(&fle->list, &fw_load_list); - free(firmware); + return 0; +} + +/* + * The dt overlay API says that a "firmware-name" property found in an overlay + * node compatible to "fpga-region" triggers loading of the firmware with the + * name given in the "firmware-name" property. + * + * barebox applies overlays to the Kernel device tree as part of booting the + * Kernel. When a firmware is needed for an overlay then it shall be loaded, + * so that the Kernel already finds the firmware loaded. + * + * In barebox overlays are applied as a of_fixup to the desired tree. The fixups + * might be executed multiple times not only as part of booting the Kernel, but + * also during of_diff command execution and other actions. It's not desired + * that we (re-)load all firmwares each time this happens, so the process is + * splitted up. During application of an overlay the needed firmwares are only + * collected to a list, but not actually loaded. Only once it's clear we want to + * boot with that device tree the firmwares are loaded by explicitly calling + * of_overlay_load_firmware(). + */ - return err; +/** + * of_overlay_pre_load_firmware() - check overlay node for firmware to load + * @root: The device tree to apply the overlay to + * @overlay: The overlay + * + * This function checks the given overlay for firmware to load. If a firmware + * is needed then it is not directly loaded, but instead added to a list of + * firmware to be loaded. The firmware files on this list can then be loaded + * with of_overlay_load_firmware(). + * + * Return: 0 for success or negative error code otherwise + */ +int of_overlay_pre_load_firmware(struct device_node *root, struct device_node *overlay) +{ + return of_process_overlay(root, overlay, load_firmware, NULL); } -int of_firmware_load_overlay(struct device_node *overlay, const char *path) +/** + * of_overlay_load_firmware() - load all firmware files + * + * This function loads all firmware files previously collected in + * of_overlay_pre_load_firmware(). + * + * Return: 0 when all firmware files could be loaded, negative error code + * otherwise. + */ +int of_overlay_load_firmware(void) { - struct overlay_info info = { - .firmware_path = path, - }; - int err; - struct device_node *root; - struct device_node *resolved; - struct device_node *ovl; + struct fw_load_entry *fle; + int ret; - root = of_get_root_node(); - /* - * If we cannot resolve the symbols in the overlay, ensure that the - * overlay does depend on firmware to be loaded. - */ - resolved = of_resolve_phandles(root, overlay); - ovl = resolved ? resolved : overlay; + list_for_each_entry(fle, &fw_load_list, list) { + ret = firmwaremgr_load_file(fle->mgr, fle->firmware); + if (ret) + return ret; + } - err = of_process_overlay(root, ovl, - load_firmware, &info); + return 0; +} - if (resolved) - of_delete_node(resolved); +/** + * of_overlay_load_firmware_clear() - Clear list of firmware files + * + * This function clears the list of firmware files. + */ +void of_overlay_load_firmware_clear(void) +{ + struct fw_load_entry *fle, *tmp; - return err; + list_for_each_entry_safe(fle, tmp, &fw_load_list, list) { + list_del(&fle->list); + free(fle->firmware); + free(fle); + } } diff --git a/drivers/of/of_gpio.c b/drivers/of/of_gpio.c index 7cbeeaf69e..25496a3cf1 100644 --- a/drivers/of/of_gpio.c +++ b/drivers/of/of_gpio.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-only + #include <common.h> #include <errno.h> #include <of.h> @@ -28,13 +30,58 @@ static void of_gpio_flags_quirks(struct device_node *np, * be actively ignored. */ if ((*flags & OF_GPIO_ACTIVE_LOW) && !active_low) { - pr_warn("%s GPIO handle specifies active low - ignored\n", - np->full_name); + pr_warn("%pOF GPIO handle specifies active low - ignored\n", + np); *flags &= ~OF_GPIO_ACTIVE_LOW; } if (active_low) *flags |= OF_GPIO_ACTIVE_LOW; } + + /* Legacy handling of stmmac's active-low PHY reset line */ + if (IS_ENABLED(CONFIG_DRIVER_NET_DESIGNWARE_EQOS) && + !strcmp(propname, "snps,reset-gpio") && + of_property_read_bool(np, "snps,reset-active-low")) + *flags |= OF_GPIO_ACTIVE_LOW; + +} + +static struct gpio_chip *of_find_gpiochip_by_xlate( + struct of_phandle_args *gpiospec) +{ + struct gpio_chip *chip; + struct device *dev; + + dev = of_find_device_by_node(gpiospec->np); + if (!dev) { + pr_debug("%s: unable to find device of node %pOF\n", + __func__, gpiospec->np); + return NULL; + } + + chip = gpio_get_chip_by_dev(dev); + if (!chip) { + pr_debug("%s: unable to find gpiochip\n", __func__); + return NULL; + } + + if (!chip->ops->of_xlate || + chip->ops->of_xlate(chip, gpiospec, NULL) < 0) { + pr_err("%s: failed to execute of_xlate\n", __func__); + return NULL; + } + + return chip; +} + +static int of_xlate_and_get_gpiod_flags(struct gpio_chip *chip, + struct of_phandle_args *gpiospec, + enum of_gpio_flags *flags) +{ + if (chip->of_gpio_n_cells != gpiospec->args_count) + return -EINVAL; + + return chip->ops->of_xlate(chip, gpiospec, flags); } /** @@ -51,38 +98,36 @@ static void of_gpio_flags_quirks(struct device_node *np, int of_get_named_gpio_flags(struct device_node *np, const char *propname, int index, enum of_gpio_flags *flags) { - struct of_phandle_args out_args; - struct device_d *dev; + struct of_phandle_args gpiospec; + struct gpio_chip *chip; int ret; ret = of_parse_phandle_with_args(np, propname, "#gpio-cells", - index, &out_args); + index, &gpiospec); if (ret) { pr_debug("%s: cannot parse %s property: %d\n", __func__, propname, ret); return ret; } - dev = of_find_device_by_node(out_args.np); - if (!dev) { - pr_debug("%s: unable to find device of node %s\n", - __func__, out_args.np->full_name); - return -EPROBE_DEFER; + chip = of_find_gpiochip_by_xlate(&gpiospec); + if (!chip) { + ret = -EPROBE_DEFER; + goto out; } - ret = gpio_get_num(dev, out_args.args[0]); - if (ret == -EPROBE_DEFER) - return ret; + ret = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags); if (ret < 0) { pr_err("%s: unable to get gpio num of device %s: %d\n", - __func__, dev_name(dev), ret); - return ret; + __func__, dev_name(chip->dev), ret); + goto out; } - if (flags) { - *flags = out_args.args[1]; + if (flags) of_gpio_flags_quirks(np, propname, flags, index); - } + +out: + of_node_put(gpiospec.np); return ret; } diff --git a/drivers/of/of_mtd.c b/drivers/of/of_mtd.c index 0956ee15d3..d28f5109b1 100644 --- a/drivers/of/of_mtd.c +++ b/drivers/of/of_mtd.c @@ -1,14 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> * * OF helpers for mtd. - * - * This file is released under the GPLv2 - * */ #include <common.h> #include <of_mtd.h> #include <linux/mtd/nand.h> +#include <linux/mtd/rawnand.h> /** * It maps 'enum nand_ecc_modes_t' found in include/linux/mtd/nand.h @@ -16,10 +15,12 @@ * device driver can get nand ecc from device tree. */ static const char *nand_ecc_modes[] = { + [NAND_ECC_INVALID] = "invalid", [NAND_ECC_NONE] = "none", [NAND_ECC_SOFT] = "soft", [NAND_ECC_HW] = "hw", [NAND_ECC_HW_SYNDROME] = "hw_syndrome", + [NAND_ECC_ON_DIE] = "on-die", [NAND_ECC_HW_OOB_FIRST] = "hw_oob_first", [NAND_ECC_SOFT_BCH] = "soft_bch", }; diff --git a/drivers/of/of_net.c b/drivers/of/of_net.c index cee4597195..75a24073da 100644 --- a/drivers/of/of_net.c +++ b/drivers/of/of_net.c @@ -1,14 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * OF helpers for network devices. * - * This file is released under the GPLv2 - * * Initially copied out of arch/powerpc/kernel/prom_parse.c */ #include <common.h> #include <net.h> #include <of_net.h> #include <linux/phy.h> +#include <linux/nvmem-consumer.h> /** * It maps 'enum phy_interface_t' found in include/linux/phy.h @@ -24,6 +24,7 @@ static const char *phy_modes[] = { [PHY_INTERFACE_MODE_TBI] = "tbi", [PHY_INTERFACE_MODE_REVMII] = "rev-mii", [PHY_INTERFACE_MODE_RMII] = "rmii", + [PHY_INTERFACE_MODE_REVRMII] = "rev-rmii", [PHY_INTERFACE_MODE_RGMII] = "rgmii", [PHY_INTERFACE_MODE_RGMII_ID] = "rgmii-id", [PHY_INTERFACE_MODE_RGMII_RXID] = "rgmii-rxid", @@ -67,12 +68,55 @@ int of_get_phy_mode(struct device_node *np) } EXPORT_SYMBOL_GPL(of_get_phy_mode); +static int of_get_mac_addr(struct device_node *np, const char *name, u8 *addr) +{ + struct property *pp = of_find_property(np, name, NULL); + + if (pp && pp->length == ETH_ALEN && is_valid_ether_addr(pp->value)) { + memcpy(addr, pp->value, ETH_ALEN); + return 0; + } + return -ENODEV; +} + +int of_get_mac_addr_nvmem(struct device_node *np, u8 *addr) +{ + struct nvmem_cell *cell; + const void *mac; + size_t len; + + if (!IS_ENABLED(CONFIG_NVMEM)) + return -ENODEV; + + cell = of_nvmem_cell_get(np, "mac-address"); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + mac = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + + if (IS_ERR(mac)) + return PTR_ERR(mac); + + if (len != ETH_ALEN || !is_valid_ether_addr(mac)) { + kfree(mac); + return -EINVAL; + } + + memcpy(addr, mac, ETH_ALEN); + kfree(mac); + + return 0; +} + /** * Search the device tree for the best MAC address to use. 'mac-address' is * checked first, because that is supposed to contain to "most recent" MAC * address. If that isn't set, then 'local-mac-address' is checked next, - * because that is the default address. If that isn't set, then the obsolete - * 'address' is checked, just in case we're using an old device tree. + * because that is the default address. If that isn't set, then the obsolete + * 'address' is checked, just in case we're using an old device tree. If any + * of the above isn't set, then try to get MAC address from nvmem cell named + * 'mac-address'. * * Note that the 'address' property is supposed to contain a virtual address of * the register set, but some DTS files have redefined that property to be the @@ -85,18 +129,24 @@ EXPORT_SYMBOL_GPL(of_get_phy_mode); * this case, the real MAC is in 'local-mac-address', and 'mac-address' exists * but is all zeros. */ -const void *of_get_mac_address(struct device_node *np) +int of_get_mac_address(struct device_node *np, u8 *addr) { - const void *p; - int len, i; - const char *str[] = { "mac-address", "local-mac-address", "address" }; - - for (i = 0; i < ARRAY_SIZE(str); i++) { - p = of_get_property(np, str[i], &len); - if (p && (len == 6) && is_valid_ether_addr(p)) - return p; - } + int ret; + + if (!np) + return -ENODEV; + + ret = of_get_mac_addr(np, "mac-address", addr); + if (!ret) + return 0; + + ret = of_get_mac_addr(np, "local-mac-address", addr); + if (!ret) + return 0; + + ret = of_get_mac_addr(np, "address", addr); + if (!ret) + return 0; - return NULL; + return of_get_mac_addr_nvmem(np, addr); } -EXPORT_SYMBOL(of_get_mac_address); diff --git a/drivers/of/of_path.c b/drivers/of/of_path.c index 5c3a020345..42efb1ad1d 100644 --- a/drivers/of/of_path.c +++ b/drivers/of/of_path.c @@ -1,20 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * of_path.c * * Copyright (c) 2013 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #include <common.h> @@ -24,14 +12,14 @@ #include <linux/mtd/mtd.h> -struct device_d *of_find_device_by_node_path(const char *path) +struct device *of_find_device_by_node_path(const char *path) { - struct device_d *dev; + struct device *dev; for_each_device(dev) { - if (!dev->device_node) + if (!dev->of_node) continue; - if (!strcmp(path, dev->device_node->full_name)) + if (!strcmp(path, dev->of_node->full_name)) return dev; } @@ -39,21 +27,27 @@ struct device_d *of_find_device_by_node_path(const char *path) } /** - * __of_find_path + * __of_cdev_find * * @node: The node to find the cdev for, can be the device or a * partition in the device - * @part: Optionally, a description of a parition of @node. See of_find_path - * @outpath: if this function returns 0 outpath will contain the path belonging - * to the input path description. Must be freed with free(). - * @flags: use OF_FIND_PATH_FLAGS_BB to return the .bb device if available + * @part: Optionally, a description of a partition of @node. See of_find_path * */ -static int __of_find_path(struct device_node *node, const char *part, char **outpath, unsigned flags) +static struct cdev *__of_cdev_find(struct device_node *node, const char *part) { - struct device_d *dev; + struct device *dev; struct cdev *cdev; - bool add_bb = false; + + /* + * On EFI, where devices are not instantiated from device tree, the + * state backend may point at a top-level fixed-partitions partition + * subnode with a partuuid property, which will be looked up globally. + * + * In order to support this binding, we do not early exit when + * of_partition_ensure_probed fails, but instead try the custom binding. + */ + (void)of_partition_ensure_probed(node); dev = of_find_device_by_node_path(node->full_name); if (!dev) { @@ -66,24 +60,17 @@ static int __of_find_path(struct device_node *node, const char *part, char **out /* when partuuid is specified short-circuit the search for the cdev */ ret = of_property_read_string(node, "partuuid", &uuid); - if (!ret) { - cdev = cdev_by_partuuid(uuid); - if (!cdev) - return -ENODEV; - - *outpath = basprintf("/dev/%s", cdev->name); - - return 0; - } + if (!ret) + return cdev_by_partuuid(uuid) ?: ERR_PTR(-ENODEV); } dev = of_find_device_by_node_path(devnode->full_name); if (!dev) - return -ENODEV; + return ERR_PTR(-ENODEV); } if (dev->bus && !dev->driver) - return -EPROBE_DEFER; + return ERR_PTR(-EPROBE_DEFER); device_detect(dev); @@ -92,8 +79,40 @@ static int __of_find_path(struct device_node *node, const char *part, char **out else cdev = cdev_by_device_node(node); - if (!cdev) - return -ENOENT; + return cdev ?: ERR_PTR(-ENOENT); +} + +/** + * of_cdev_find + * + * @node: The node to find the cdev for, can be the device or a + * partition in the device + * + */ +struct cdev *of_cdev_find(struct device_node *node) +{ + return __of_cdev_find(node, NULL); +} + +/** + * __of_find_path + * + * @node: The node to find the cdev for, can be the device or a + * partition in the device + * @part: Optionally, a description of a partition of @node. See of_find_path + * @outpath: if this function returns 0 outpath will contain the path belonging + * to the input path description. Must be freed with free(). + * @flags: use OF_FIND_PATH_FLAGS_BB to return the .bb device if available + * + */ +static int __of_find_path(struct device_node *node, const char *part, char **outpath, unsigned flags) +{ + bool add_bb = false; + struct cdev *cdev; + + cdev = __of_cdev_find(node, part); + if (IS_ERR(cdev)) + return PTR_ERR(cdev); if ((flags & OF_FIND_PATH_FLAGS_BB) && cdev->mtd && mtd_can_have_bb(cdev->mtd)) @@ -162,9 +181,9 @@ struct device_node *of_find_node_by_devpath(struct device_node *root, const char part_size = cdev->size; pr_debug("%s path %s: is a partition with offset 0x%08llx, size 0x%08llx\n", __func__, path, part_offset, part_size); - np = cdev->master->device_node; + np = cdev_of_node(cdev->master); } else { - np = cdev->device_node; + np = cdev_of_node(cdev); } /* @@ -173,14 +192,14 @@ struct device_node *of_find_node_by_devpath(struct device_node *root, const char */ rnp = of_find_node_by_path_from(root, np->full_name); if (!rnp) { - pr_debug("%s path %s: %s not found in passed tree\n", __func__, path, - np->full_name); + pr_debug("%s path %s: %pOF not found in passed tree\n", __func__, path, + np); return NULL; } if (!is_partition) { - pr_debug("%s path %s: returning full device node %s\n", __func__, path, - rnp->full_name); + pr_debug("%s path %s: returning full device node %pOF\n", __func__, path, + rnp); return rnp; } @@ -207,7 +226,7 @@ struct device_node *of_find_node_by_devpath(struct device_node *root, const char ns = of_n_size_cells(np); if (len < (na + ns) * sizeof(__be32)) { - pr_err("reg property too small in %s\n", np->full_name); + pr_err("reg property too small in %pOF\n", np); continue; } @@ -215,8 +234,8 @@ struct device_node *of_find_node_by_devpath(struct device_node *root, const char size = of_read_number(reg + na, ns); if (part_offset == offset && part_size == size) { - pr_debug("%s path %s: found matching partition in %s\n", __func__, path, - np->full_name); + pr_debug("%s path %s: found matching partition in %pOF\n", __func__, path, + np); return np; } } diff --git a/drivers/of/of_pci.c b/drivers/of/of_pci.c index 2d0fbd2e5f..ebb5ba6df3 100644 --- a/drivers/of/of_pci.c +++ b/drivers/of/of_pci.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-only + #include <common.h> #include <errno.h> #include <of.h> diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index a35eddfa08..73c7a91db9 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Functions for working with device tree overlays * @@ -11,6 +11,13 @@ #include <common.h> #include <of.h> #include <errno.h> +#include <globalvar.h> +#include <magicvar.h> +#include <string.h> +#include <libfile.h> +#include <fs.h> +#include <libbb.h> +#include <fnmatch.h> static struct device_node *find_target(struct device_node *root, struct device_node *fragment) @@ -58,6 +65,9 @@ static int of_overlay_apply(struct device_node *target, if (of_prop_cmp(prop->name, "name") == 0) continue; + if (of_prop_cmp(prop->name, "phandle") == 0) + target->phandle = be32_to_cpup(prop->value); + err = of_set_property(target, prop->name, prop->value, prop->length, true); if (err) @@ -92,8 +102,10 @@ static char *of_overlay_fix_path(struct device_node *root, if (of_get_child_by_name(fragment, "__overlay__")) break; } - if (!fragment) + if (!fragment) { + pr_info("could not find __overlay__ node\n"); return NULL; + } target = find_target(root, fragment); if (!target) @@ -102,10 +114,10 @@ static char *of_overlay_fix_path(struct device_node *root, prefix = of_get_child_by_name(fragment, "__overlay__")->full_name; path_tail = path + strlen(prefix); - return basprintf("%s%s", target->full_name, path_tail); + return basprintf("%pOF%s", target, path_tail); } -static void of_overlay_apply_symbols(struct device_node *root, +static int of_overlay_apply_symbols(struct device_node *root, struct device_node *overlay) { const char *old_path; @@ -117,9 +129,14 @@ static void of_overlay_apply_symbols(struct device_node *root, root_symbols = of_get_child_by_name(root, "__symbols__"); overlay_symbols = of_get_child_by_name(overlay, "__symbols__"); - if (!overlay_symbols || !root_symbols) { - pr_info("overlay/root doesn't have a __symbols__ node\n"); - return; + if (!overlay_symbols) { + pr_debug("overlay doesn't have a __symbols__ node\n"); + return 0; + } + + if (!root_symbols) { + pr_info("root doesn't have a __symbols__ node\n"); + return 0; } list_for_each_entry(prop, &overlay_symbols->properties, list) { @@ -128,11 +145,15 @@ static void of_overlay_apply_symbols(struct device_node *root, old_path = of_property_get_value(prop); new_path = of_overlay_fix_path(root, overlay, old_path); + if (!new_path) + return -EINVAL; pr_debug("add symbol %s with new path %s\n", prop->name, new_path); of_property_write_string(root_symbols, prop->name, new_path); } + + return 0; } static int of_overlay_apply_fragment(struct device_node *root, @@ -152,6 +173,8 @@ static int of_overlay_apply_fragment(struct device_node *root, return of_overlay_apply(target, overlay); } +static char *of_overlay_compatible; + /** * Apply the overlay on the passed devicetree root * @root: the devicetree onto which the overlay will be applied @@ -162,14 +185,20 @@ int of_overlay_apply_tree(struct device_node *root, { struct device_node *resolved; struct device_node *fragment; - int err; + int err = 0; resolved = of_resolve_phandles(root, overlay); if (!resolved) return -EINVAL; + err = of_overlay_pre_load_firmware(root, resolved); + if (err) + goto out_err; + /* Copy symbols from resolved overlay to base device tree */ - of_overlay_apply_symbols(root, resolved); + err = of_overlay_apply_symbols(root, resolved); + if (err) + goto out_err; /* Copy nodes and properties from resolved overlay to root */ for_each_child_of_node(resolved, fragment) { @@ -178,11 +207,133 @@ int of_overlay_apply_tree(struct device_node *root, pr_warn("failed to apply %s\n", fragment->name); } + /* We are patching the live tree, reload aliases */ + if (root == of_get_root_node()) + of_alias_scan(); + +out_err: of_delete_node(resolved); return err; } +static char *of_overlay_filter; + +static LIST_HEAD(of_overlay_filters); + +static struct of_overlay_filter *of_overlay_find_filter(const char *name) +{ + struct of_overlay_filter *f; + + list_for_each_entry(f, &of_overlay_filters, list) + if (!strcmp(f->name, name)) + return f; + return NULL; +} + +static bool of_overlay_matches_filter(const char *filename, struct device_node *ovl) +{ + struct of_overlay_filter *filter; + char *p, *path, *n; + bool apply = false; + bool have_filename_filter = false; + bool have_content_filter = false; + + p = path = strdup(of_overlay_filter); + + while ((n = strsep_unescaped(&p, " "))) { + int score = 0; + + if (!*n) + continue; + + filter = of_overlay_find_filter(n); + if (!filter) { + pr_err("Ignoring unknown filter %s\n", n); + continue; + } + + if (filter->filter_filename) + have_filename_filter = true; + if (filter->filter_content) + have_content_filter = true; + + if (filename) { + if (filter->filter_filename && + filter->filter_filename(filter, kbasename(filename))) + score++; + } else { + score++; + } + + if (ovl) { + if (filter->filter_content && + filter->filter_content(filter, ovl)) + score++; + } else { + score++; + } + + if (score == 2) { + apply = true; + break; + } + } + + free(path); + + /* No filter found at all, no match */ + if (!have_filename_filter && !have_content_filter) + return false; + + /* Want to match filename, but we do not have a filename_filter */ + if (filename && !have_filename_filter) + return true; + + /* Want to match content, but we do not have a content_filter */ + if (ovl && !have_content_filter) + return true; + + if (apply) + pr_debug("filename %s, overlay %p: match against filter %s\n", + filename ?: "<NONE>", + ovl, filter->name); + else + pr_debug("filename %s, overlay %p: no match\n", + filename ?: "<NONE>", ovl); + + return apply; +} + +int of_overlay_apply_file(struct device_node *root, const char *filename, + bool filter) +{ + struct device_node *ovl; + int ret; + + if (filter && !of_overlay_matches_filter(filename, NULL)) + return 0; + + ovl = of_read_file(filename); + if (IS_ERR(ovl)) + return PTR_ERR(ovl); + + if (filter && !of_overlay_matches_filter(NULL, ovl)) + return 0; + + ret = of_overlay_apply_tree(root, ovl); + if (ret == -ENODEV) + pr_debug("Not applied %s (not compatible)\n", filename); + else if (ret) + pr_err("Cannot apply %s: %s\n", filename, strerror(-ret)); + else + pr_info("Applied %s\n", filename); + + of_delete_node(ovl); + + return ret; +} + static int of_overlay_fixup(struct device_node *root, void *data) { struct device_node *overlay = data; @@ -215,12 +366,13 @@ int of_process_overlay(struct device_node *root, target = find_target(root, fragment); if (!target) - continue; + pr_debug("cannot find target for fragment %s\n", + fragment->name); err = process(target, ovl, data); if (err) { pr_warn("failed to process overlay for %s\n", - target->name); + target ? target->name : "unknown"); break; } } @@ -241,3 +393,216 @@ int of_register_overlay(struct device_node *overlay) { return of_register_fixup(of_overlay_fixup, overlay); } + +static char *of_overlay_filepattern; +static char *of_overlay_dir; +static char *of_overlay_basedir; + +/** + * of_overlay_set_basedir - set the overlay basedir + * @path: The new overlay basedir + * + * This specifies the base directory where overlay files are expected. By + * default this is the root directory, but it is overwritten by blspec to + * point to the rootfs of the about-to-be-booted system. + */ +void of_overlay_set_basedir(const char *path) +{ + free(of_overlay_basedir); + of_overlay_basedir = strdup(path); +} + +static int of_overlay_apply_dir(struct device_node *root, const char *dirname, + bool filter) +{ + int ret = 0; + DIR *dir; + + if (!dirname || !*dirname) + return 0; + + pr_debug("Applying overlays from %s\n", dirname); + + dir = opendir(dirname); + if (!dir) + return -errno; + + while (1) { + struct dirent *ent; + char *filename; + + ent = readdir(dir); + if (!ent) + break; + + if (!strcmp(dir->d.d_name, ".") || !strcmp(dir->d.d_name, "..")) + continue; + + filename = basprintf("%s/%s", dirname, dir->d.d_name); + + of_overlay_apply_file(root, filename, filter); + + free(filename); + } + + closedir(dir); + + return ret; +} + +static int of_overlay_global_fixup(struct device_node *root, void *data) +{ + char *dir; + int ret; + + if (*of_overlay_dir == '/') + return of_overlay_apply_dir(root, of_overlay_dir, true); + + if (*of_overlay_dir == '\0') + return 0; + + dir = concat_path_file(of_overlay_basedir, of_overlay_dir); + + ret = of_overlay_apply_dir(root, dir, true); + + free(dir); + + return ret; +} + +/** + * of_overlay_register_filter - register a new overlay filter + * @filter: The new filter + * + * Register a new overlay filter. A filter can either match on + * the filename or on the content of an overlay, but not on both. + * If that's desired two filters have to be registered. + * + * @return: 0 for success, negative error code otherwise + */ +int of_overlay_register_filter(struct of_overlay_filter *filter) +{ + if (filter->filter_filename && filter->filter_content) + return -EINVAL; + + list_add_tail(&filter->list, &of_overlay_filters); + + return 0; +} + +/** + * of_overlay_filter_filename - A filter that matches on the filename of + * an overlay + * @f: The filter + * @filename: The filename of the overlay + * + * This filter matches when the filename matches one of the patterns given + * in global.of.overlay.filepattern. global.of.overlay.filepattern shall + * contain a space separated list of wildcard patterns. + * + * @return: True when the overlay shall be applied, false otherwise. + */ +static bool of_overlay_filter_filename(struct of_overlay_filter *f, + const char *filename) +{ + char *p, *path, *n; + int ret; + bool apply; + + p = path = strdup(of_overlay_filepattern); + + while ((n = strsep_unescaped(&p, " "))) { + if (!*n) + continue; + + ret = fnmatch(n, filename, 0); + + if (!ret) { + apply = true; + goto out; + } + } + + apply = false; +out: + free(path); + + return apply; +} + +static struct of_overlay_filter of_overlay_filepattern_filter = { + .name = "filepattern", + .filter_filename = of_overlay_filter_filename, +}; + +/** + * of_overlay_filter_compatible - A filter that matches on the compatible of + * an overlay + * @f: The filter + * @ovl: The overlay + * + * This filter matches when the compatible of an overlay matches to one + * of the compatibles given in global.of.overlay.compatible. When the + * overlay doesn't contain a compatible entry it is considered matching. + * Also when no compatibles are given in global.of.overlay.compatible + * all overlays will match. + * + * @return: True when the overlay shall be applied, false otherwise. + */ +static bool of_overlay_filter_compatible(struct of_overlay_filter *f, + struct device_node *ovl) +{ + char *p, *n, *compatibles; + bool res = false; + + if (!of_overlay_compatible || !*of_overlay_compatible) + return true; + if (!of_find_property(ovl, "compatible", NULL)) + return true; + + p = compatibles = xstrdup(of_overlay_compatible); + + while ((n = strsep_unescaped(&p, " "))) { + if (!*n) + continue; + + if (of_device_is_compatible(ovl, n)) { + res = true; + break; + } + } + + free(compatibles); + + return res; +} + +static struct of_overlay_filter of_overlay_compatible_filter = { + .name = "compatible", + .filter_content = of_overlay_filter_compatible, +}; + +static int of_overlay_init(void) +{ + of_overlay_filepattern = strdup("*"); + of_overlay_filter = strdup("filepattern compatible"); + of_overlay_set_basedir("/"); + + globalvar_add_simple_string("of.overlay.compatible", &of_overlay_compatible); + globalvar_add_simple_string("of.overlay.filepattern", &of_overlay_filepattern); + globalvar_add_simple_string("of.overlay.filter", &of_overlay_filter); + globalvar_add_simple_string("of.overlay.dir", &of_overlay_dir); + + of_overlay_register_filter(&of_overlay_filepattern_filter); + of_overlay_register_filter(&of_overlay_compatible_filter); + + of_register_fixup(of_overlay_global_fixup, NULL); + + return 0; +} +device_initcall(of_overlay_init); + +BAREBOX_MAGICVAR(global.of.overlay.compatible, "space separated list of compatibles an overlay must match"); +BAREBOX_MAGICVAR(global.of.overlay.filepattern, "space separated list of filepatterns an overlay must match"); +BAREBOX_MAGICVAR(global.of.overlay.dir, "Directory to look for dt overlays"); +BAREBOX_MAGICVAR(global.of.overlay.filter, "space separated list of filters"); diff --git a/drivers/of/partition.c b/drivers/of/partition.c index 655b67f854..df66751fe9 100644 --- a/drivers/of/partition.c +++ b/drivers/of/partition.c @@ -1,21 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * partition.c - devicetree partition parsing * * Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * based on Linux devicetree support - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> #include <of.h> @@ -23,6 +12,7 @@ #include <linux/mtd/mtd.h> #include <linux/err.h> #include <nand.h> +#include <linux/nvmem-provider.h> #include <init.h> #include <globalvar.h> @@ -36,14 +26,12 @@ enum of_binding_name { struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node) { + struct devfs_partition partinfo = {}; const char *partname; char *filename; struct cdev *new; const __be32 *reg; - u64 offset, size; - const char *name; int len; - unsigned long flags = 0; int na, ns; if (!node) @@ -57,12 +45,12 @@ struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node) ns = of_n_size_cells(node); if (len < (na + ns) * sizeof(__be32)) { - pr_err("reg property too small in %s\n", node->full_name); + pr_err("reg property too small in %pOF\n", node); return NULL; } - offset = of_read_number(reg, na); - size = of_read_number(reg + na, ns); + partinfo.offset = of_read_number(reg, na); + partinfo.size = of_read_number(reg + na, ns); partname = of_get_property(node, "label", NULL); if (!partname) @@ -70,22 +58,31 @@ struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node) if (!partname) return NULL; - name = (char *)partname; - - debug("add partition: %s.%s 0x%08llx 0x%08llx\n", cdev->name, partname, offset, size); + debug("add partition: %s.%s 0x%08llx 0x%08llx\n", cdev->name, partname, + partinfo.offset, partinfo.size); if (of_get_property(node, "read-only", NULL)) - flags = DEVFS_PARTITION_READONLY; + partinfo.flags = DEVFS_PARTITION_READONLY; - filename = basprintf("%s.%s", cdev->name, partname); + partinfo.name = filename = basprintf("%s.%s", cdev->name, partname); - new = devfs_add_partition(cdev->name, offset, size, flags, filename); - if (IS_ERR(new)) + new = cdevfs_add_partition(cdev, &partinfo); + if (IS_ERR(new)) { + pr_err("Adding partition %s failed: %pe\n", filename, new); new = NULL; + goto out; + } + + new->device_node = node; + new->flags |= DEVFS_PARTITION_FROM_OF | DEVFS_PARTITION_FOR_FIXUP; - if (new) - new->device_node = node;; + if (IS_ENABLED(CONFIG_NVMEM) && of_device_is_compatible(node, "nvmem-cells")) { + struct nvmem_device *nvmem = nvmem_partition_register(new); + if (IS_ERR(nvmem)) + dev_warn(cdev->dev, "nvmem registeration failed: %pe\n", nvmem); + } +out: free(filename); return new; @@ -98,7 +95,7 @@ int of_parse_partitions(struct cdev *cdev, struct device_node *node) if (!node) return -EINVAL; - cdev->device_node = node; + cdev_set_of_node(cdev, node); subnode = of_get_child_by_name(node, "partitions"); if (subnode) { @@ -114,6 +111,50 @@ int of_parse_partitions(struct cdev *cdev, struct device_node *node) return 0; } +/** + * of_partition_ensure_probed - ensure a parition is probed + * @np: pointer to a partition or to a partitionable device + * Unfortunately, there is no completely reliable way + * to differentiate partitions from devices prior to + * probing, because partitions may also have compatibles. + * We only handle nvmem-cells, so anything besides that + * is assumed to be a device that should be probed directly. + * + * Returns zero on success or a negative error code otherwise + */ +int of_partition_ensure_probed(struct device_node *np) +{ + struct device_node *parent = of_get_parent(np); + + /* root node is not a partition */ + if (!parent) + return -EINVAL; + + /* Check if modern partitions binding */ + if (of_device_is_compatible(parent, "fixed-partitions")) { + parent = of_get_parent(parent); + + /* + * Can't call of_partition_ensure_probed on root node. + * This catches barebox-specific partuuid binding + * (top-level partition node) + */ + if (!of_get_parent(parent)) + return -EINVAL; + + return of_device_ensure_probed(parent); + } + + /* Check if legacy partitions binding */ + if (!of_property_present(np, "compatible") || + of_device_is_compatible(np, "nvmem-cells")) + return of_device_ensure_probed(parent); + + /* Doesn't look like a partition, so let's probe directly */ + return of_device_ensure_probed(np); +} +EXPORT_SYMBOL_GPL(of_partition_ensure_probed); + static void delete_subnodes(struct device_node *np) { struct device_node *part, *tmp; @@ -126,22 +167,18 @@ static void delete_subnodes(struct device_node *np) } } -static int of_partition_fixup(struct device_node *root, void *ctx) +int of_fixup_partitions(struct device_node *np, struct cdev *cdev) { - struct cdev *cdev = ctx, *partcdev; - struct device_node *np, *part, *partnode; - char *name; + struct cdev *partcdev; + struct device_node *part, *partnode; int ret; int n_cells, n_parts = 0; if (of_partition_binding == MTD_OF_BINDING_DONTTOUCH) return 0; - if (!cdev->device_node) - return -EINVAL; - list_for_each_entry(partcdev, &cdev->partitions, partition_entry) { - if (partcdev->flags & DEVFS_PARTITION_FROM_TABLE) + if (!(partcdev->flags & DEVFS_PARTITION_FOR_FIXUP)) continue; n_parts++; } @@ -154,15 +191,6 @@ static int of_partition_fixup(struct device_node *root, void *ctx) else n_cells = 1; - name = of_get_reproducible_name(cdev->device_node); - np = of_find_node_by_reproducible_name(root, name); - free(name); - if (!np) { - dev_err(cdev->dev, "Cannot find nodepath %s, cannot fixup\n", - cdev->device_node->full_name); - return -EINVAL; - } - partnode = of_get_child_by_name(np, "partitions"); if (partnode) { if (of_partition_binding == MTD_OF_BINDING_LEGACY) { @@ -201,7 +229,7 @@ static int of_partition_fixup(struct device_node *root, void *ctx) u8 tmp[16 * 16]; /* Up to 64-bit address + 64-bit size */ loff_t partoffset; - if (partcdev->flags & DEVFS_PARTITION_FROM_TABLE) + if (!(partcdev->flags & DEVFS_PARTITION_FOR_FIXUP)) continue; if (partcdev->mtd) @@ -245,6 +273,30 @@ static int of_partition_fixup(struct device_node *root, void *ctx) return 0; } +static int of_partition_fixup(struct device_node *root, void *ctx) +{ + struct cdev *cdev = ctx; + struct device_node *cdev_np, *np; + char *name; + + cdev_np = cdev_of_node(cdev); + if (!cdev_np) + return -EINVAL; + + if (list_empty(&cdev->partitions)) + return 0; + + name = of_get_reproducible_name(cdev_np); + np = of_find_node_by_reproducible_name(root, name); + free(name); + if (!np) { + dev_err(cdev->dev, "Cannot find nodepath %pOF, cannot fixup\n", cdev_np); + return -EINVAL; + } + + return of_fixup_partitions(np, cdev); +} + int of_partitions_register_fixup(struct cdev *cdev) { return of_register_fixup(of_partition_fixup, cdev); diff --git a/drivers/of/platform.c b/drivers/of/platform.c index b1a7eb6730..918607a518 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -1,27 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * platform.c - bus/device related devicetree functions * * Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * based on Linux devicetree support - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> +#include <deep-probe.h> #include <malloc.h> #include <of.h> #include <of_address.h> #include <linux/amba/bus.h> +#include <mmu.h> /** * of_find_device_by_node - Find the platform_device associated with a node @@ -29,11 +20,18 @@ * * Returns platform_device pointer, or NULL if not found */ -struct device_d *of_find_device_by_node(struct device_node *np) +struct device *of_find_device_by_node(struct device_node *np) { - struct device_d *dev; + struct device *dev; + + /* Not having a driver is not an error here */ + (void)of_device_ensure_probed(np); + + if (deep_probe_is_supported()) + return np->dev; + for_each_device(dev) - if (dev->device_node == np) + if (dev->of_node == np) return dev; return NULL; } @@ -47,9 +45,9 @@ EXPORT_SYMBOL(of_find_device_by_node); * derive a unique name. If it cannot, then it will prepend names from * parent nodes until a unique name can be derived. */ -static void of_device_make_bus_id(struct device_d *dev) +static void of_device_make_bus_id(struct device *dev) { - struct device_node *node = dev->device_node; + struct device_node *node = dev->of_node; const __be32 *reg; u64 addr; @@ -74,7 +72,64 @@ static void of_device_make_bus_id(struct device_d *dev) } } -static void of_dma_configure(struct device_d *dev, struct device_node *np) +static struct device_node *of_get_next_dma_parent(const struct device_node *np) +{ + struct of_phandle_args args; + int ret, index; + + index = of_property_match_string(np, "interconnect-names", "dma-mem"); + if (index < 0) + return of_get_parent(np); + + ret = of_parse_phandle_with_args(np, "interconnects", + "#interconnect-cells", + index, &args); + if (ret < 0) + return of_get_parent(np); + + return args.np; +} + +static enum dev_dma_coherence of_dma_get_coherence(struct device_node *node) +{ + if (IS_ENABLED(CONFIG_OF_DMA_COHERENCY)) { + while (node) { + if (of_property_read_bool(node, "dma-coherent")) + return DEV_DMA_COHERENT; + if (of_property_read_bool(node, "dma-noncoherent")) + return DEV_DMA_NON_COHERENT; + node = of_get_next_dma_parent(node); + } + } + + return DEV_DMA_COHERENCE_DEFAULT; +} + +/** + * of_dma_is_coherent - Check if device is coherent + * @np: device node + * + * It returns true if "dma-coherent" property was found + * for this device in the DT, or if DMA is coherent by + * default for OF devices on the current platform and no + * "dma-noncoherent" property was found for this device. + */ +bool of_dma_is_coherent(struct device_node *node) +{ + switch (of_dma_get_coherence(node)) { + case DEV_DMA_COHERENT: + return true; + case DEV_DMA_NON_COHERENT: + return false; + case DEV_DMA_COHERENCE_DEFAULT: + return IS_ENABLED(CONFIG_ARCH_DMA_DEFAULT_COHERENT); + } + + BUG(); +} +EXPORT_SYMBOL_GPL(of_dma_is_coherent); + +static void of_dma_configure(struct device *dev, struct device_node *np) { u64 dma_addr, paddr, size = 0; unsigned long offset; @@ -88,6 +143,7 @@ static void of_dma_configure(struct device_d *dev, struct device_node *np) } dev->dma_offset = offset; + dev->dma_coherent = of_dma_get_coherence(np); } /** @@ -98,17 +154,27 @@ static void of_dma_configure(struct device_d *dev, struct device_node *np) * Returns pointer to created platform device, or NULL if a device was not * registered. Unavailable devices will not get registered. */ -struct device_d *of_platform_device_create(struct device_node *np, - struct device_d *parent) +struct device *of_platform_device_create(struct device_node *np, + struct device *parent) { - struct device_d *dev; + struct device *dev; struct resource *res = NULL, temp_res; resource_size_t resinval; - int i, j, ret, num_reg = 0, match; + int i, ret, num_reg = 0; + u32 virt; if (!of_device_is_available(np)) return NULL; + /* + * Linux uses the OF_POPULATED flag to skip already populated/created + * devices. + */ + if (np->dev) { + device_rescan(np->dev); + return np->dev; + } + /* count the io resources */ if (of_can_translate_address(np)) while (of_address_to_resource(np, num_reg, &temp_res) == 0) @@ -124,41 +190,12 @@ struct device_d *of_platform_device_create(struct device_node *np, return NULL; } } - - /* - * A device may already be registered as platform_device. - * Instead of registering the same device again, just - * add this node to the existing device. - */ - for_each_device(dev) { - if (!dev->resource) - continue; - - for (i = 0, match = 0; i < num_reg; i++) - for (j = 0; j < dev->num_resources; j++) - if (dev->resource[j].start == - res[i].start && - dev->resource[j].end == - res[i].end) { - match++; - break; - } - - /* check if all address resources match */ - if (match == num_reg) { - debug("connecting %s to %s\n", - np->name, dev_name(dev)); - dev->device_node = np; - free(res); - return dev; - } - } } /* setup generic device info */ dev = xzalloc(sizeof(*dev)); dev->id = DEVICE_ID_SINGLE; - dev->device_node = np; + dev->of_node = np; dev->parent = parent; dev->resource = res; dev->num_resources = num_reg; @@ -166,22 +203,54 @@ struct device_d *of_platform_device_create(struct device_node *np, of_dma_configure(dev, np); + if (num_reg && !of_property_read_u32(np, "virtual-reg", &virt)) { + resource_size_t remap_offset = virt - res[0].start; + + for (i = 0; i < num_reg; i++) { + void *new_virt = (void *)res[i].start + remap_offset; + resource_size_t size = resource_size(&res[i]); + + ret = arch_remap_range(new_virt, res[i].start, size, MAP_UNCACHED); + if (!ret) { + debug("%s: remap device %s resource %d: %pa -> 0x%p\n", + __func__, dev_name(dev), i, &res[i].start, new_virt); + + res[i].start = (resource_size_t)new_virt; + res[i].end = res[i].start + size - 1; + } + } + } + resinval = (-1); debug("%s: register device %s, io=%pa\n", __func__, dev_name(dev), (num_reg) ? &dev->resource[0].start : &resinval); + BUG_ON(np->dev); + np->dev = dev; + ret = platform_device_register(dev); if (!ret) return dev; - free(dev); + np->dev = NULL; + + free_device(dev); if (num_reg) free(res); return NULL; } +struct driver dummy_driver = { + .name = "dummy-driver", +}; + +void of_platform_device_dummy_drv(struct device *dev) +{ + dev->driver = &dummy_driver; +} + /** * of_device_enable_and_register - Enable and register device * @np: pointer to node to enable create device for @@ -189,9 +258,9 @@ struct device_d *of_platform_device_create(struct device_node *np, * Returns pointer to created platform device, or NULL if a device was not * registered. Unavailable devices will not get registered. */ -struct device_d *of_device_enable_and_register(struct device_node *np) +struct device *of_device_enable_and_register(struct device_node *np) { - struct device_d *dev; + struct device *dev; of_device_enable(np); @@ -210,11 +279,11 @@ EXPORT_SYMBOL(of_device_enable_and_register); * Returns pointer to created platform device, or NULL if a device was not * registered. Unavailable devices will not get registered. */ -struct device_d *of_device_enable_and_register_by_name(const char *name) +struct device *of_device_enable_and_register_by_name(const char *name) { struct device_node *node; - node = of_find_node_by_name(NULL, name); + node = of_find_node_by_name_address(NULL, name); if (!node) node = of_find_node_by_path(name); @@ -232,7 +301,7 @@ EXPORT_SYMBOL(of_device_enable_and_register_by_name); * Returns pointer to created platform device, or NULL if a device was not * registered. Unavailable devices will not get registered. */ -struct device_d *of_device_enable_and_register_by_alias(const char *alias) +struct device *of_device_enable_and_register_by_alias(const char *alias) { struct device_node *node; @@ -245,21 +314,28 @@ struct device_d *of_device_enable_and_register_by_alias(const char *alias) EXPORT_SYMBOL(of_device_enable_and_register_by_alias); #ifdef CONFIG_ARM_AMBA -static struct device_d *of_amba_device_create(struct device_node *np) +static struct device *of_amba_device_create(struct device_node *np) { struct amba_device *dev; int ret; - debug("Creating amba device %s\n", np->full_name); + debug("Creating amba device %pOF\n", np); if (!of_device_is_available(np)) return NULL; + /* + * Linux uses the OF_POPULATED flag to skip already populated/created + * devices. + */ + if (np->dev) + return np->dev; + dev = xzalloc(sizeof(*dev)); /* setup generic device info */ dev->dev.id = DEVICE_ID_SINGLE; - dev->dev.device_node = np; + dev->dev.of_node = np; of_device_make_bus_id(&dev->dev); ret = of_address_to_resource(np, 0, &dev->res); @@ -278,14 +354,17 @@ static struct device_d *of_amba_device_create(struct device_node *np) if (ret) goto amba_err_free; + np->dev = &dev->dev; + return &dev->dev; amba_err_free: + free_device_res(&dev->dev); free(dev); return NULL; } #else /* CONFIG_ARM_AMBA */ -static inline struct amba_device *of_amba_device_create(struct device_node *np) +static inline struct device *of_amba_device_create(struct device_node *np) { return NULL; } @@ -302,16 +381,16 @@ static inline struct amba_device *of_amba_device_create(struct device_node *np) */ static int of_platform_bus_create(struct device_node *bus, const struct of_device_id *matches, - struct device_d *parent) + struct device *parent) { struct device_node *child; - struct device_d *dev; + struct device *dev; int rc = 0; /* Make sure it has a compatible property */ if (!of_get_property(bus, "compatible", NULL)) { - pr_debug("%s() - skipping %s, no compatible prop\n", - __func__, bus->full_name); + pr_debug("%s() - skipping %pOF, no compatible prop\n", + __func__, bus); return 0; } @@ -325,7 +404,7 @@ static int of_platform_bus_create(struct device_node *bus, return 0; for_each_child_of_node(bus, child) { - pr_debug(" create child: %s\n", child->full_name); + pr_debug(" create child: %pOF\n", child); rc = of_platform_bus_create(child, matches, dev); if (rc) break; @@ -348,7 +427,7 @@ static int of_platform_bus_create(struct device_node *bus, */ int of_platform_populate(struct device_node *root, const struct of_device_id *matches, - struct device_d *parent) + struct device *parent) { struct device_node *child; int rc = 0; @@ -367,3 +446,218 @@ int of_platform_populate(struct device_node *root, return rc; } EXPORT_SYMBOL_GPL(of_platform_populate); + +static struct device *of_device_create_on_demand(struct device_node *np) +{ + struct device_node *parent; + struct device *parent_dev, *dev; + + parent = of_get_parent(np); + if (!parent) + return NULL; + + if (!np->dev && parent->dev) + device_rescan(parent->dev); + + /* Create all parent devices needed for the requested device */ + parent_dev = parent->dev ? : of_device_create_on_demand(parent); + if (IS_ERR(parent_dev)) + return parent_dev; + + /* + * Parent devices like i2c/spi controllers are populating their own + * devices. So it can be that the requested device already exists after + * the parent device creation. + */ + if (np->dev) + return np->dev; + + if (!of_property_present(np, "compatible")) + return NULL; + + pr_debug("Creating device for %pOF\n", np); + + if (of_device_is_compatible(np, "arm,primecell")) + dev = of_amba_device_create(np); + else + dev = of_platform_device_create(np, parent_dev); + + return dev ? : ERR_PTR(-EPROBE_DEFER); +} + +/** + * of_device_ensure_probed() - ensures that a device is probed + * + * @np: the device_node handle which should be probed + * + * Ensures that the device is populated and probed so frameworks can make use of + * it. + * + * Return: %0 on success + * %-EPROBE_DEFER if either the device can't be populated, the driver is + * missing or the driver probe returns an error. + */ +int of_device_ensure_probed(struct device_node *np) +{ + struct device *dev; + + if (!np || !deep_probe_is_supported()) + return 0; + + dev = of_device_create_on_demand(np); + if (IS_ERR_OR_NULL(dev)) + return -EPROBE_DEFER; + + /* + * The deep-probe mechanism relies on the fact that all necessary + * drivers are added before the device creation. Furthermore deep-probe + * is the answer to the EPROBE_DEFER errno so we must ensure that the + * driver was probed successfully after the device creation. Both + * requirements are fulfilled if 'dev->driver' is not NULL. + */ + if (!dev->driver) + return -EPROBE_DEFER; + + return 0; +} +EXPORT_SYMBOL_GPL(of_device_ensure_probed); + +/** + * of_device_ensure_probed_by_alias() - ensures that a device is probed + * + * @alias: the alias string to search for a device + * + * The function search for a given alias string and ensures that the device is + * populated and probed if found. + * + * Return: %0 on success + * %-EPROBE_DEFER if either the device can't be populated, the driver is + * missing or the driver probe returns an error + * %-EINVAL if alias can't be found + */ +int of_device_ensure_probed_by_alias(const char *alias) +{ + struct device_node *dev_node; + + if (!deep_probe_is_supported()) + return 0; + + dev_node = of_find_node_by_alias(NULL, alias); + if (!dev_node) + return -EINVAL; + + return of_device_ensure_probed(dev_node); +} +EXPORT_SYMBOL_GPL(of_device_ensure_probed_by_alias); + +/** + * of_devices_ensure_probed_by_dev_id() - ensures that devices are probed + * + * @ids: the matching 'struct of_device_id' ids + * + * The function start searching the device tree from @np and populates and + * probes devices which match @ids. + * + * Return: %0 on success + * %-EPROBE_DEFER if either the device wasn't found, can't be populated, + * the driver is missing or the driver probe returns an error + */ +int of_devices_ensure_probed_by_dev_id(const struct of_device_id *ids) +{ + struct device_node *np; + int err, ret = 0; + + if (!deep_probe_is_supported()) + return 0; + + for_each_matching_node(np, ids) { + if (!of_device_is_available(np)) + continue; + + err = of_device_ensure_probed(np); + if (err) + ret = err; + } + + return ret; +} +EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_dev_id); + +/** + * of_devices_ensure_probed_by_property() - ensures that devices are probed + * + * @property_name: The property name to search for + * + * The function starts searching the whole device tree and populates and probes + * devices which matches @property_name. + * + * Return: %0 on success + * %-EPROBE_DEFER if either the device wasn't found, can't be populated, + * the driver is missing or the driver probe returns an error + */ +int of_devices_ensure_probed_by_property(const char *property_name) +{ + struct device_node *node; + int err, ret = 0; + + if (!deep_probe_is_supported()) + return 0; + + for_each_node_with_property(node, property_name) { + if (!of_device_is_available(node)) + continue; + + err = of_device_ensure_probed(node); + if (err) + ret = err; + } + + return ret; +} +EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_property); + +int of_devices_ensure_probed_by_name(const char *name) +{ + struct device_node *node; + int err, ret = 0; + + if (!deep_probe_is_supported()) + return 0; + + for_each_node_by_name(node, name) { + if (!of_device_is_available(node)) + continue; + + err = of_device_ensure_probed(node); + if (err) + ret = err; + } + + return ret; +} +EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_name); + +static int of_stdoutpath_init(void) +{ + struct device_node *np; + + np = of_get_stdoutpath(NULL); + if (!np) + return 0; + + /* + * With deep probe support the device providing the console + * can come quite late in the probe order. Make sure it's + * probed now so that we get output earlier. + */ + return of_device_ensure_probed(np); +} +postconsole_initcall(of_stdoutpath_init); + +static int of_timer_init(void) +{ + of_devices_ensure_probed_by_name("timer"); + + return 0; +} +postcore_initcall(of_timer_init); diff --git a/drivers/of/reserved-mem.c b/drivers/of/reserved-mem.c new file mode 100644 index 0000000000..599a7c968a --- /dev/null +++ b/drivers/of/reserved-mem.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2021 Rouven Czerwinski <r.czerwinski@pengutronix.de>, Pengutronix + +#define pr_fmt(fmt) "of-reserved-mem: " fmt + +#include <stdio.h> +#include <of.h> +#include <of_address.h> +#include <memory.h> +#include <linux/ioport.h> + +#define MEMRESERVE_NCELLS 2 + +static void request_region(struct resource *r) +{ + struct memory_bank *bank; + + for_each_memory_bank(bank) { + if (!resource_contains(bank->res, r)) + continue; + + pr_debug("reserving %s at %pad-%pad\n", r->name, &r->start, &r->end); + + if (!reserve_sdram_region(r->name, r->start, resource_size(r))) + pr_warn("couldn't request reserved sdram region %pa-%pa\n", + &r->start, &r->end); + break; + } +} + +static int of_reserved_mem_walk(void) +{ + struct device_node *node, *child; + int ncells = 0; + const __be32 *reg; + + node = of_find_node_by_path("/reserved-memory"); + if (node) { + for_each_available_child_of_node(node, child) { + struct resource resource = {}; + + /* skip e.g. linux,cma */ + if (!of_get_property(child, "reg", NULL)) + continue; + + of_address_to_resource(child, 0, &resource); + + resource.name = child->name; + resource.flags = IORESOURCE_MEM; + + request_region(&resource); + } + } + + node = of_find_node_by_path("/memreserve"); + reg = of_get_property(node, "reg", &ncells); + ncells /= sizeof(__be32); + if (reg) { + char name[sizeof "fdt-memreserve-4294967295"]; + int i = 0, n = 0; + + while (i < ncells) { + struct resource resource = {}; + u64 size; + + snprintf(name, sizeof(name), "fdt-memreserve-%u", n++); + resource.name = name; + resource.flags = IORESOURCE_MEM; + + resource.start = of_read_number(reg + i, MEMRESERVE_NCELLS); + i += MEMRESERVE_NCELLS; + + size = of_read_number(reg + i, MEMRESERVE_NCELLS); + i += MEMRESERVE_NCELLS; + + if (!size) + continue; + + resource.end = resource.start + size - 1; + + request_region(&resource); + } + } + + return 0; +} +postmem_initcall(of_reserved_mem_walk); diff --git a/drivers/of/resolver.c b/drivers/of/resolver.c index 9107c1fbb6..2457ae96a4 100644 --- a/drivers/of/resolver.c +++ b/drivers/of/resolver.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Functions for dealing with DT resolution * @@ -160,9 +160,7 @@ static int adjust_local_phandle_references(struct device_node *local_fixups, } for_each_child_of_node(local_fixups, child) { - for_each_child_of_node(overlay, overlay_child) - if (!of_node_cmp(child->name, overlay_child->name)) - break; + overlay_child = of_get_child_by_name(overlay, child->name); if (!overlay_child) return -EINVAL; @@ -216,7 +214,7 @@ struct device_node *of_resolve_phandles(struct device_node *root, * to a phandle defined in the overlay. We must update the references, * because we just adjusted the definitions. */ - local_fixups = of_find_node_by_name(result, "__local_fixups__"); + local_fixups = of_find_node_by_name_address(result, "__local_fixups__"); err = adjust_local_phandle_references(local_fixups, result, delta); if (err) { pr_err("failed to fix phandles in overlay\n"); @@ -229,7 +227,7 @@ struct device_node *of_resolve_phandles(struct device_node *root, * the base device tree. We must update the references, because they * are otherwise undefined. */ - overlay_fixups = of_find_node_by_name(result, "__fixups__"); + overlay_fixups = of_find_node_by_name_address(result, "__fixups__"); if (!overlay_fixups) { pr_debug("overlay does not contain phandles to base devicetree\n"); goto out; |