summaryrefslogtreecommitdiffstats
path: root/drivers/of
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2019-10-17 08:10:24 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2019-10-17 08:10:24 +0200
commit854cdfbe265ebd99619bc6edc594d59b07c156fe (patch)
tree072c0cab0db0e2b703db22add9608d16e8712140 /drivers/of
parent54709a8e5ed1e1d6c07bc24321aede08f27a1d58 (diff)
parent27112f5b5541d736a560dbc9f059d879d4b354a3 (diff)
downloadbarebox-854cdfbe265ebd99619bc6edc594d59b07c156fe.tar.gz
barebox-854cdfbe265ebd99619bc6edc594d59b07c156fe.tar.xz
Merge branch 'for-next/of-overlay'
Diffstat (limited to 'drivers/of')
-rw-r--r--drivers/of/Kconfig25
-rw-r--r--drivers/of/Makefile1
-rw-r--r--drivers/of/of_firmware.c86
-rw-r--r--drivers/of/overlay.c247
-rw-r--r--drivers/of/resolver.c279
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;
+
+}