diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2019-10-17 08:10:24 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2019-10-17 08:10:24 +0200 |
commit | 854cdfbe265ebd99619bc6edc594d59b07c156fe (patch) | |
tree | 072c0cab0db0e2b703db22add9608d16e8712140 /drivers/of | |
parent | 54709a8e5ed1e1d6c07bc24321aede08f27a1d58 (diff) | |
parent | 27112f5b5541d736a560dbc9f059d879d4b354a3 (diff) | |
download | barebox-854cdfbe265ebd99619bc6edc594d59b07c156fe.tar.gz barebox-854cdfbe265ebd99619bc6edc594d59b07c156fe.tar.xz |
Merge branch 'for-next/of-overlay'
Diffstat (limited to 'drivers/of')
-rw-r--r-- | drivers/of/Kconfig | 25 | ||||
-rw-r--r-- | drivers/of/Makefile | 1 | ||||
-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 |
5 files changed, 638 insertions, 0 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/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; + +} |