diff options
Diffstat (limited to 'drivers/of')
-rw-r--r-- | drivers/of/Kconfig | 25 | ||||
-rw-r--r-- | drivers/of/Makefile | 1 | ||||
-rw-r--r-- | drivers/of/base.c | 153 | ||||
-rw-r--r-- | drivers/of/of_firmware.c | 86 | ||||
-rw-r--r-- | drivers/of/overlay.c | 247 | ||||
-rw-r--r-- | drivers/of/resolver.c | 279 |
6 files changed, 779 insertions, 12 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 24cf4465a8..7436fc2de1 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -50,3 +50,28 @@ config OF_BAREBOX_ENV_IN_FS help Allow the devie tree configuration of the barebox environment path to specify a file in filesystem, which will be mounted. + +config OF_OVERLAY + select OFTREE + bool "Devicetree overlays" + help + Overlays allow to patch the devicetree. Unlike Linux, Barebox does + not patch the live devicetree, but applies the overlays as fixup to + the devicetree. Furthermore, overlays cannot be removed after they + have been applied. + +config OF_OVERLAY_LIVE + depends on OF_OVERLAY + bool "Support devicetree overlays on live devicetree" + help + This option allows to use devicetree overlays with the live + devicetree. It is not required to apply overlays to any other + devicetree. + + This builds the build-in devicetree with __symbols__, which + significantly increases the size of the dtb file. + + Enable this option only if you actually need the live devicetree + while applying in the devicetree overlay. This is usually the case if + applying the overlay has other side effects than changing the + devicetree. diff --git a/drivers/of/Makefile b/drivers/of/Makefile index ec43870061..b6847752d2 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -6,3 +6,4 @@ obj-y += partition.o obj-y += of_net.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/base.c b/drivers/of/base.c index d72c687ef3..98ef5fc0d4 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1777,24 +1777,21 @@ struct device_node *of_get_child_by_name(const struct device_node *node, } EXPORT_SYMBOL(of_get_child_by_name); -void of_print_nodes(struct device_node *node, int indent) +static void __of_print_nodes(struct device_node *node, int indent, const char *prefix) { struct device_node *n; struct property *p; - int i; if (!node) return; - for (i = 0; i < indent; i++) - printf("\t"); + if (!prefix) + prefix = ""; - printf("%s%s\n", node->name, node->name ? " {" : "{"); + printf("%s%*s%s%s\n", prefix, indent * 8, "", node->name, node->name ? " {" : "{"); list_for_each_entry(p, &node->properties, list) { - for (i = 0; i < indent + 1; i++) - printf("\t"); - printf("%s", p->name); + printf("%s%*s%s", prefix, (indent + 1) * 8, "", p->name); if (p->length) { printf(" = "); of_print_property(of_property_get_value(p), p->length); @@ -1803,12 +1800,136 @@ void of_print_nodes(struct device_node *node, int indent) } list_for_each_entry(n, &node->children, parent_list) { - of_print_nodes(n, indent + 1); + __of_print_nodes(n, indent + 1, prefix); } + printf("%s%*s};\n", prefix, indent * 8, ""); +} + +void of_print_nodes(struct device_node *node, int indent) +{ + __of_print_nodes(node, indent, NULL); +} + +static void __of_print_property(struct property *p, int indent) +{ + int i; + for (i = 0; i < indent; i++) printf("\t"); - printf("};\n"); + + printf("%s", p->name); + if (p->length) { + printf(" = "); + of_print_property(of_property_get_value(p), p->length); + } + printf(";\n"); +} + +static int __of_print_parents(struct device_node *node) +{ + int indent, i; + + if (!node->parent) + return 0; + + indent = __of_print_parents(node->parent); + + for (i = 0; i < indent; i++) + printf("\t"); + + printf("%s {\n", node->name); + + return indent + 1; +} + +static void of_print_parents(struct device_node *node, int *printed) +{ + if (*printed) + return; + + __of_print_parents(node); + + *printed = 1; +} + +static void of_print_close(struct device_node *node, int *printed) +{ + int depth = 0, i, j; + + if (!*printed) + return; + + while ((node = node->parent)) + depth++; + + for (i = depth; i > 0; i--) { + for (j = 0; j + 1 < i; j++) + printf("\t"); + printf("};\n"); + } +} + +/** + * of_diff - compare two device trees against each other + * @a: The first device tree + * @b: The second device tree + * @indent: The initial indentation level when printing + * + * 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) +{ + struct property *ap, *bp; + struct device_node *ca, *cb; + int printed = 0; + + list_for_each_entry(ap, &a->properties, list) { + bp = of_find_property(b, ap->name, NULL); + if (!bp) { + of_print_parents(a, &printed); + printf("- "); + __of_print_property(ap, indent); + continue; + } + + if (ap->length != bp->length || memcmp(of_property_get_value(ap), of_property_get_value(bp), bp->length)) { + of_print_parents(a, &printed); + printf("- "); + __of_print_property(ap, indent); + printf("+ "); + __of_print_property(bp, indent); + } + } + + list_for_each_entry(bp, &b->properties, list) { + ap = of_find_property(a, bp->name, NULL); + if (!ap) { + of_print_parents(a, &printed); + printf("+ "); + __of_print_property(bp, indent); + } + } + + for_each_child_of_node(a, ca) { + cb = of_get_child_by_name(b, ca->name); + if (cb) { + of_diff(ca, cb, indent + 1); + } else { + of_print_parents(a, &printed); + __of_print_nodes(ca, indent, "-"); + } + } + + for_each_child_of_node(b, cb) { + if (!of_get_child_by_name(a, cb->name)) { + of_print_parents(a, &printed); + __of_print_nodes(cb, indent, "+"); + } + } + + of_print_close(a, &printed); } struct device_node *of_new_node(struct device_node *parent, const char *name) @@ -2307,9 +2428,17 @@ struct device_node *of_find_node_by_reproducible_name(struct device_node *from, { struct device_node *np; - of_tree_for_each_node_from(np, from) - if (!of_node_cmp(of_get_reproducible_name(np), name)) + of_tree_for_each_node_from(np, from) { + char *rep = of_get_reproducible_name(np); + int res; + + res = of_node_cmp(rep, name); + + free(rep); + + if (!res) return np; + } return NULL; } diff --git a/drivers/of/of_firmware.c b/drivers/of/of_firmware.c new file mode 100644 index 0000000000..0135631fb8 --- /dev/null +++ b/drivers/of/of_firmware.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de> + */ +#include <common.h> +#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); + } + } while ((np = of_get_parent(np)) != NULL); + + return NULL; +} + +static int load_firmware(struct device_node *target, + struct device_node *fragment, void *data) +{ + struct overlay_info *info = data; + const char *firmware_name; + const char *firmware_path = info->firmware_path; + char *firmware; + int err; + struct firmware_mgr *mgr; + + err = of_property_read_string(fragment, + "firmware-name", &firmware_name); + /* Nothing to do if property does not exist. */ + if (err == -EINVAL) + return 0; + else if (err) + return -EINVAL; + + mgr = of_node_get_mgr(target); + if (!mgr) + return -EINVAL; + + firmware = basprintf("%s/%s", firmware_path, firmware_name); + if (!firmware) + return -ENOMEM; + + err = firmwaremgr_load_file(mgr, firmware); + + free(firmware); + + return err; +} + +int of_firmware_load_overlay(struct device_node *overlay, const char *path) +{ + struct overlay_info info = { + .firmware_path = path, + }; + int err; + struct device_node *root; + struct device_node *resolved; + struct device_node *ovl; + + 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; + + err = of_process_overlay(root, ovl, + load_firmware, &info); + + if (resolved) + of_delete_node(resolved); + + return err; +} diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c new file mode 100644 index 0000000000..de79e05cbc --- /dev/null +++ b/drivers/of/overlay.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions for working with device tree overlays + * + * Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com> + * Copyright (C) 2012 Texas Instruments Inc. + * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de> + */ +#define pr_fmt(fmt) "of_overlay: " fmt + +#include <common.h> +#include <of.h> +#include <errno.h> + +static struct device_node *find_target(struct device_node *root, + struct device_node *fragment) +{ + struct device_node *node; + const char *path; + u32 phandle; + int ret; + + ret = of_property_read_u32(fragment, "target", &phandle); + if (!ret) { + node = of_find_node_by_phandle_from(phandle, root); + if (!node) + pr_err("fragment %pOF: phandle 0x%x not found\n", + fragment, phandle); + return node; + } + + ret = of_property_read_string(fragment, "target-path", &path); + if (!ret) { + node = of_find_node_by_path_from(root, path); + if (!node) + pr_err("fragment %pOF: path '%s' not found\n", + fragment, path); + return node; + } + + pr_err("fragment %pOF: no target property\n", fragment); + + return NULL; +} + +static int of_overlay_apply(struct device_node *target, + const struct device_node *overlay) +{ + struct device_node *child; + struct device_node *target_child; + struct property *prop; + int err; + + if (target == NULL || overlay == NULL) + return -EINVAL; + + list_for_each_entry(prop, &overlay->properties, list) { + if (of_prop_cmp(prop->name, "name") == 0) + continue; + + err = of_set_property(target, prop->name, prop->value, + prop->length, true); + if (err) + return err; + } + + for_each_child_of_node(overlay, child) { + target_child = of_get_child_by_name(target, child->name); + if (!target_child) + target_child = of_new_node(target, child->name); + if (!target_child) + return -ENOMEM; + + err = of_overlay_apply(target_child, child); + if (err) + return err; + } + + return 0; +} + +static char *of_overlay_fix_path(struct device_node *root, + struct device_node *overlay, const char *path) +{ + struct device_node *fragment; + struct device_node *target; + const char *path_tail; + const char *prefix; + + fragment = of_find_node_by_path_from(overlay, path); + while ((fragment = of_get_parent(fragment)) != NULL) { + if (of_get_child_by_name(fragment, "__overlay__")) + break; + } + if (!fragment) + return NULL; + + target = find_target(root, fragment); + if (!target) + return NULL; + + prefix = of_get_child_by_name(fragment, "__overlay__")->full_name; + path_tail = path + strlen(prefix); + + return basprintf("%s%s", target->full_name, path_tail); +} + +static int of_overlay_apply_symbols(struct device_node *root, + struct device_node *overlay) +{ + const char *old_path; + char *new_path; + struct property *prop; + struct device_node *root_symbols; + struct device_node *overlay_symbols; + + root_symbols = of_get_child_by_name(root, "__symbols__"); + if (!root_symbols) + return -EINVAL; + + overlay_symbols = of_get_child_by_name(overlay, "__symbols__"); + if (!overlay_symbols) + return -EINVAL; + + list_for_each_entry(prop, &overlay_symbols->properties, list) { + if (of_prop_cmp(prop->name, "name") == 0) + continue; + + old_path = of_property_get_value(prop); + new_path = of_overlay_fix_path(root, overlay, old_path); + + 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, + struct device_node *fragment) +{ + struct device_node *target; + struct device_node *overlay; + + overlay = of_get_child_by_name(fragment, "__overlay__"); + if (!overlay) + return 0; + + target = find_target(root, fragment); + if (!target) + return -EINVAL; + + return of_overlay_apply(target, overlay); +} + +/** + * Apply the overlay on the passed devicetree root + * @root: the devicetree onto which the overlay will be applied + * @overlay: the devicetree to apply as an overlay + */ +int of_overlay_apply_tree(struct device_node *root, + struct device_node *overlay) +{ + struct device_node *resolved; + struct device_node *fragment; + int err; + + resolved = of_resolve_phandles(root, overlay); + if (!resolved) + return -EINVAL; + + /* Copy symbols from resolved overlay to base device tree */ + err = of_overlay_apply_symbols(root, resolved); + if (err) + pr_warn("failed to copy symbols from overlay"); + + /* Copy nodes and properties from resolved overlay to root */ + for_each_child_of_node(resolved, fragment) { + err = of_overlay_apply_fragment(root, fragment); + if (err) + pr_warn("failed to apply %s", fragment->name); + } + + of_delete_node(resolved); + + return err; +} + +static int of_overlay_fixup(struct device_node *root, void *data) +{ + struct device_node *overlay = data; + + return of_overlay_apply_tree(root, overlay); +} + +/** + * Iterate the overlay and call process for each fragment + * + * If process() fails for any fragment, the function will stop to process + * other fragments and return the error of the failed process() call. + */ +int of_process_overlay(struct device_node *root, + struct device_node *overlay, + int (*process)(struct device_node *target, + struct device_node *overlay, void *data), + void *data) +{ + struct device_node *fragment; + int err = 0; + + for_each_child_of_node(overlay, fragment) { + struct device_node *ovl; + struct device_node *target; + + ovl = of_get_child_by_name(fragment, "__overlay__"); + if (!ovl) + continue; + + target = find_target(root, fragment); + if (!target) + continue; + + err = process(target, ovl, data); + if (err) { + pr_warn("failed to process overlay for %s\n", + target->name); + break; + } + } + + return err; +} + +/** + * Register a devicetree overlay + * + * The overlay is not applied to the live device tree, but registered as fixup + * for the fixed up device tree. Therefore, drivers relying on the overlay + * must use the fixed device tree. + * + * The fixup takes ownership of the overlay. + */ +int of_register_overlay(struct device_node *overlay) +{ + return of_register_fixup(of_overlay_fixup, overlay); +} diff --git a/drivers/of/resolver.c b/drivers/of/resolver.c new file mode 100644 index 0000000000..9107c1fbb6 --- /dev/null +++ b/drivers/of/resolver.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions for dealing with DT resolution + * + * Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com> + * Copyright (C) 2012 Texas Instruments Inc. + * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de> + */ +#define pr_fmt(fmt) "of_resolver: " fmt + +#include <common.h> +#include <of.h> +#include <errno.h> + +/** + * Recursively update phandles in overlay by adding delta + */ +static void adjust_overlay_phandles(struct device_node *overlay, int delta) +{ + struct device_node *child; + struct property *prop; + + if (overlay->phandle != 0) + overlay->phandle += delta; + + list_for_each_entry(prop, &overlay->properties, list) { + if (of_prop_cmp(prop->name, "phandle") != 0 && + of_prop_cmp(prop->name, "linux,phandle") != 0) + continue; + if (prop->length < 4) + continue; + + be32_add_cpu(prop->value, delta); + } + + for_each_child_of_node(overlay, child) + adjust_overlay_phandles(child, delta); +} + +/** + * Update all unresolved phandles in the overlay using prop_fixup + * + * prop_fixup contains a list of tuples of path:property_name:offset, each of + * which refers to a property that is phandle to a node in the base + * devicetree. + */ +static int update_usages_of_a_phandle_reference(struct device_node *overlay, + struct property *prop_fixup, + phandle phandle) +{ + struct device_node *refnode; + struct property *prop; + char *value, *cur, *end, *node_path, *prop_name, *s; + int offset, len; + int err = 0; + + pr_debug("resolve references to %s to phandle 0x%x\n", + prop_fixup->name, phandle); + + value = kmemdup(prop_fixup->value, prop_fixup->length, GFP_KERNEL); + if (!value) + return -ENOMEM; + + end = value + prop_fixup->length; + for (cur = value; cur < end; cur += len + 1) { + len = strlen(cur); + + node_path = cur; + s = strchr(cur, ':'); + if (!s) { + err = -EINVAL; + goto err_fail; + } + *s++ = '\0'; + + prop_name = s; + s = strchr(s, ':'); + if (!s) { + err = -EINVAL; + goto err_fail; + } + *s++ = '\0'; + + err = kstrtoint(s, 10, &offset); + if (err) + goto err_fail; + + refnode = of_find_node_by_path_from(overlay, node_path); + if (!refnode) + continue; + + prop = of_find_property(refnode, prop_name, NULL); + if (!prop) { + err = -ENOENT; + goto err_fail; + } + + if (offset < 0 || offset + sizeof(__be32) > prop->length) { + err = -EINVAL; + goto err_fail; + } + + *(__be32 *)(prop->value + offset) = cpu_to_be32(phandle); + } + +err_fail: + kfree(value); + + if (err) + pr_debug("failed to resolve references to %s\n", + prop_fixup->name); + + return err; +} + +/* + * Adjust the local phandle references by the given phandle delta. + * + * Subtree @local_fixups, which is overlay node __local_fixups__, + * mirrors the fragment node structure at the root of the overlay. + * + * For each property in the fragments that contains a phandle reference, + * @local_fixups has a property of the same name that contains a list + * of offsets of the phandle reference(s) within the respective property + * value(s). The values at these offsets will be fixed up. + */ +static int adjust_local_phandle_references(struct device_node *local_fixups, + struct device_node *overlay, int phandle_delta) +{ + struct device_node *child, *overlay_child; + struct property *prop_fix, *prop; + int err, i, count; + unsigned int off; + + if (!local_fixups) + return 0; + + list_for_each_entry(prop_fix, &local_fixups->properties, list) { + if (!of_prop_cmp(prop_fix->name, "name") || + !of_prop_cmp(prop_fix->name, "phandle") || + !of_prop_cmp(prop_fix->name, "linux,phandle")) + continue; + + if ((prop_fix->length % sizeof(__be32)) != 0 || + prop_fix->length == 0) + return -EINVAL; + count = prop_fix->length / sizeof(__be32); + + prop = of_find_property(overlay, prop_fix->name, NULL); + if (!prop) + return -EINVAL; + + for (i = 0; i < count; i++) { + off = be32_to_cpu(((__be32 *)prop_fix->value)[i]); + if ((off + sizeof(__be32)) > prop->length) + return -EINVAL; + + be32_add_cpu(prop->value + off, phandle_delta); + } + } + + 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; + if (!overlay_child) + return -EINVAL; + + err = adjust_local_phandle_references(child, overlay_child, + phandle_delta); + if (err) + return err; + } + + return 0; +} + +/** + * of_resolve_phandles - Resolve phandles in overlay based on root + * + * Rename phandles in overlay to avoid conflicts with the base devicetree and + * replace all phandles in the overlay with their renamed versions. Resolve + * phandles referring to nodes in the base devicetree with the phandle from + * the base devicetree. + * + * Returns a new device_node with resolved phandles which must be deleted by + * the caller of this function. + */ +struct device_node *of_resolve_phandles(struct device_node *root, + const struct device_node *overlay) +{ + struct device_node *result; + struct device_node *local_fixups; + struct device_node *refnode; + struct device_node *symbols; + struct device_node *overlay_fixups; + struct property *prop; + const char *refpath; + phandle delta; + int err; + + result = of_copy_node(NULL, overlay); + if (!result) + return NULL; + + delta = of_get_tree_max_phandle(root) + 1; + + /* + * Rename the phandles in the devicetree overlay to prevent conflicts + * with the phandles in the base devicetree. + */ + adjust_overlay_phandles(result, delta); + + /* + * __local_fixups__ contains all locations in the overlay that refer + * 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__"); + err = adjust_local_phandle_references(local_fixups, result, delta); + if (err) { + pr_err("failed to fix phandles in overlay\n"); + goto err; + } + + /* + * __fixups__ contains all locations in the overlay that refer to a + * phandle that is not defined in the overlay and should be defined in + * the base device tree. We must update the references, because they + * are otherwise undefined. + */ + overlay_fixups = of_find_node_by_name(result, "__fixups__"); + if (!overlay_fixups) { + pr_debug("overlay does not contain phandles to base devicetree\n"); + goto out; + } + + symbols = of_find_node_by_path_from(root, "/__symbols__"); + if (!symbols) { + pr_err("__symbols__ missing from base devicetree\n"); + goto err; + } + + list_for_each_entry(prop, &overlay_fixups->properties, list) { + if (!of_prop_cmp(prop->name, "name")) + continue; + + err = of_property_read_string(symbols, prop->name, &refpath); + if (err) { + pr_err("cannot find node %s in base devicetree\n", + prop->name); + goto err; + } + + refnode = of_find_node_by_path_from(root, refpath); + if (!refnode) { + pr_err("cannot find path %s in base devicetree\n", + refpath); + err = -EINVAL; + goto err; + } + + err = update_usages_of_a_phandle_reference(result, prop, + refnode->phandle); + if (err) { + pr_err("failed to update phandles for %s in overlay", + prop->name); + goto err; + } + } + +out: + return result; +err: + of_delete_node(result); + + return NULL; + +} |