Re: [PATCH 2/7] of: add support for devicetree overlays

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




On 9/5/19 12:51 PM, Michael Tretter wrote:
> The devicetree overlay support is based on the Linux driver for device
> tree overlays, but many features that are not required in Barebox are
> left out.
> 
> Unlike Linux, which applies the overlay to the live devicetree, Barebox
> registers a fixup for the overlay which is applied with other fixups to
> whatever tree is fixed. This is necessary to apply the overlay to
> devicetrees that are passed to Linux, which might differ from the
> devicetree that is currently live in Barebox.
> 
> Signed-off-by: Michael Tretter <m.tretter@xxxxxxxxxxxxxx>
> ---
>  drivers/of/Kconfig    |   9 ++
>  drivers/of/Makefile   |   1 +
>  drivers/of/overlay.c  | 200 ++++++++++++++++++++++++++++++
>  drivers/of/resolver.c | 278 ++++++++++++++++++++++++++++++++++++++++++
>  include/of.h          |  20 +++
>  5 files changed, 508 insertions(+)
>  create mode 100644 drivers/of/overlay.c
>  create mode 100644 drivers/of/resolver.c
> 
> diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
> index 24cf4465a8..1bb6639c5a 100644
> --- a/drivers/of/Kconfig
> +++ b/drivers/of/Kconfig
> @@ -50,3 +50,12 @@ 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.
> diff --git a/drivers/of/Makefile b/drivers/of/Makefile
> index ec43870061..9c6f8de814 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
> diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
> new file mode 100644
> index 0000000000..b9283e6a90
> --- /dev/null
> +++ b/drivers/of/overlay.c
> @@ -0,0 +1,200 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions for working with device tree overlays
> + *
> + * Copyright (C) 2012 Pantelis Antoniou <panto@xxxxxxxxxxxxxxxxxxxxxxx>
> + * Copyright (C) 2012 Texas Instruments Inc.
> + * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@xxxxxxxxxxxxxx>
> + */
> +
> +#include <common.h>
> +#include <notifier.h>

Still needed?

> +#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);
> +}
> +
> +/**
> + * Fix the passed root node using the device tree overlay in data
> + */
> +static int of_overlay_fixup(struct device_node *root, void *data)
> +{
> +	struct device_node *overlay = data;
> +	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 0;
> +}
> +
> +/**
> + * 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.
> + */
> +int of_register_overlay(struct device_node *overlay)
> +{
> +	of_register_fixup(of_overlay_fixup, overlay);
> +
> +	return 0;
> +}
> diff --git a/drivers/of/resolver.c b/drivers/of/resolver.c
> new file mode 100644
> index 0000000000..574c75cb4b
> --- /dev/null
> +++ b/drivers/of/resolver.c
> @@ -0,0 +1,278 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Functions for dealing with DT resolution
> + *
> + * Copyright (C) 2012 Pantelis Antoniou <panto@xxxxxxxxxxxxxxxxxxxxxxx>
> + * Copyright (C) 2012 Texas Instruments Inc.
> + * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@xxxxxxxxxxxxxx>
> + */
> +
> +#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;
> +
> +}
> diff --git a/include/of.h b/include/of.h
> index b5f54dd4e5..ac80dc3247 100644
> --- a/include/of.h
> +++ b/include/of.h
> @@ -3,6 +3,7 @@
>  
>  #include <fdt.h>
>  #include <errno.h>
> +#include <notifier.h>

Ditto.

>  #include <linux/types.h>
>  #include <linux/list.h>
>  #include <asm/byteorder.h>
> @@ -870,4 +871,23 @@ static inline struct device_node *of_find_root_node(struct device_node *node)
>  
>  	return node;
>  }
> +
> +#ifdef CONFIG_OF_OVERLAY
> +struct device_node *of_resolve_phandles(struct device_node *root,
> +					const struct device_node *overlay);
> +int of_register_overlay(struct device_node *overlay);
> +#else
> +static inline struct device_node *of_resolve_phandles(struct device_node *root,
> +					const struct device_node *overlay)
> +{
> +	return NULL;
> +}
> +
> +static inline int of_register_overlay(struct device_node *overlay)
> +{
> +	return -ENOSYS;
> +}
> +
> +#endif
> +
>  #endif /* __OF_H */
> 

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@xxxxxxxxxxxxxxxxxxx
http://lists.infradead.org/mailman/listinfo/barebox



[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux