Hi Pantelis, On 11/5/13 12:41 PM, Pantelis Antoniou wrote: > Introduce DT overlay support. > Using this functionality it is possible to dynamically overlay a part of > the kernel's tree with another tree that's been dynamically loaded. > It is also possible to remove node and properties. > > Note that the I2C client devices are 'special', as in they're not platform > devices. They need to be registered with an I2C specific method. > > In general I2C is just no good platform device citizen and needs > special casing. > > Signed-off-by: Pantelis Antoniou <panto@xxxxxxxxxxxxxxxxxxxxxxx> > --- > Documentation/devicetree/overlay-notes.txt | 179 ++++++ > drivers/of/Kconfig | 10 + > drivers/of/Makefile | 1 + > drivers/of/overlay.c | 869 +++++++++++++++++++++++++++++ > include/linux/of.h | 113 ++++ > 5 files changed, 1172 insertions(+) > create mode 100644 Documentation/devicetree/overlay-notes.txt > create mode 100644 drivers/of/overlay.c > > diff --git a/Documentation/devicetree/overlay-notes.txt b/Documentation/devicetree/overlay-notes.txt > new file mode 100644 > index 0000000..5289cbb > --- /dev/null > +++ b/Documentation/devicetree/overlay-notes.txt > @@ -0,0 +1,179 @@ > +Device Tree Overlay Notes > +------------------------- > + > +This document describes the implementation of the in-kernel > +device tree overlay functionality residing in drivers/of/overlay.c and is a > +companion document to Documentation/devicetree/dt-object-internal.txt[1] & > +Documentation/devicetree/dynamic-resolution-notes.txt[2] > + > +How overlays work > +----------------- > + > +A Device Tree's overlay purpose is to modify the kernel's live tree, and > +have the modification affecting the state of the the kernel in a way that > +is reflecting the changes. > +Since the kernel mainly deals with devices, any new device node that result > +in an active device should have it created while if the device node is either > +disabled or removed all together, the affected device should be deregistered. > + > +Lets take an example where we have a foo board with the following base tree > +which is taken from [1]. > + > +---- foo.dts ----------------------------------------------------------------- > + /* FOO platform */ > + / { > + compatible = "corp,foo"; > + > + /* shared resources */ > + res: res { > + }; > + > + /* On chip peripherals */ > + ocp: ocp { > + /* peripherals that are always instantiated */ > + peripheral1 { ... }; > + } > + }; > +---- foo.dts ----------------------------------------------------------------- > + > +The overlay bar.dts, when loaded (and resolved as described in [2]) should > + > +---- bar.dts ----------------------------------------------------------------- > +/plugin/; /* allow undefined label references and record them */ > +/ { > + .... /* various properties for loader use; i.e. part id etc. */ > + fragment@0 { > + target = <&ocp>; > + __overlay__ { > + /* bar peripheral */ > + bar { > + compatible = "corp,bar"; > + ... /* various properties and child nodes */ > + } > + }; > + }; > +}; > +---- bar.dts ----------------------------------------------------------------- > + > +result in foo+bar.dts > + > +---- foo+bar.dts ------------------------------------------------------------- > + /* FOO platform + bar peripheral */ > + / { > + compatible = "corp,foo"; > + > + /* shared resources */ > + res: res { > + }; > + > + /* On chip peripherals */ > + ocp: ocp { > + /* peripherals that are always instantiated */ > + peripheral1 { ... }; > + > + /* bar peripheral */ > + bar { > + compatible = "corp,bar"; > + ... /* various properties and child nodes */ > + } > + } > + }; > +---- foo+bar.dts ------------------------------------------------------------- > + > +As a result of the the overlay, a new device node (bar) has been created > +so a bar platform device will be registered and if a matching device driver > +is loaded the device will be created as expected. > + > +Overlay in-kernel API > +--------------------- > + > +The steps typically required to get an overlay to work are as follows: > + > +1. Use of_build_overlay_info() to create an array of initialized and > +ready to use of_overlay_info structures. > +2. Call of_overlay() to apply the overlays declared in the array. > +3. If the overlay needs to be removed, call of_overlay_revert(). > +4. Finally release the memory taken by the overlay info array by > +of_free_overlay_info(). > + > +/** > + * of_build_overlay_info - Build an overlay info array > + * @tree: Device node containing all the overlays > + * @cntp: Pointer to where the overlay info count will be help > + * @ovinfop: Pointer to the pointer of an overlay info structure. > + * > + * Helper function that given a tree containing overlay information, > + * allocates and builds an overlay info array containing it, ready > + * for use using of_overlay. > + * > + * Returns 0 on success with the @cntp @ovinfop pointers valid, > + * while on error a negative error value is returned. > + */ > +int of_build_overlay_info(struct device_node *tree, > + int *cntp, struct of_overlay_info **ovinfop); > + > +/** > + * of_free_overlay_info - Free an overlay info array > + * @count: Number of of_overlay_info's > + * @ovinfo_tab: Array of overlay_info's to free > + * > + * Releases the memory of a previously allocate ovinfo array > + * by of_build_overlay_info. > + * Returns 0, or an error if the arguments are bogus. > + */ > +int of_free_overlay_info(int count, struct of_overlay_info *ovinfo_tab); > + > +/** > + * of_overlay - Apply @count overlays pointed at by @ovinfo_tab > + * @count: Number of of_overlay_info's > + * @ovinfo_tab: Array of overlay_info's to apply > + * > + * Applies the overlays given, while handling all error conditions > + * appropriately. Either the operation succeeds, or if it fails the > + * live tree is reverted to the state before the attempt. > + * Returns 0, or an error if the overlay attempt failed. > + */ > +int of_overlay(int count, struct of_overlay_info *ovinfo_tab); > + > +/** > + * of_overlay_revert - Revert a previously applied overlay > + * @count: Number of of_overlay_info's > + * @ovinfo_tab: Array of overlay_info's to apply > + * > + * Revert a previous overlay. The state of the live tree > + * is reverted to the one before the overlay. > + * Returns 0, or an error if the overlay table is not given. > + */ > +int of_overlay_revert(int count, struct of_overlay_info *ovinfo_tab); > + > +Overlay DTS Format > +------------------ > + > +The DTS of an overlay should have the following format: > + > +{ > + /* ignored properties by the overlay */ > + > + fragment@0 { /* first child node */ > + target=<phandle>; /* target of the overlay */ > + __overlay__ { > + property-a; /* add property-a to the target */ > + -property-b; /* remove property-b from target */ > + node-a { /* add to an existing, or create a node-a */ > + ... > + }; > + -node-b { /* remove an existing node-b */ > + ... > + }; > + }; > + } > + fragment@1 { /* second child node */ > + ... > + }; > + /* more fragments follow */ > +} > + > +It should be noted that the DT overlay format described is the one expected > +by the of_build_overlay_info() function, which is a helper function. There > +is nothing stopping someone coming up with his own DTS format and that will > +end up filling in the fields of the of_overlay_info array. > diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig > index 2a00ae5..c2d5596 100644 > --- a/drivers/of/Kconfig > +++ b/drivers/of/Kconfig > @@ -83,4 +83,14 @@ config OF_RESOLVE > Enable OF dynamic resolution support. This allows you to > load Device Tree object fragments are run time. > > +config OF_OVERLAY > + bool "OF overlay support" > + depends on OF > + select OF_DYNAMIC > + select OF_DEVICE > + select OF_RESOLVE > + help > + OpenFirmware overlay support. Allows you to modify on runtime the > + live tree using overlays. > + > endmenu # OF > diff --git a/drivers/of/Makefile b/drivers/of/Makefile > index 93da457..ca466e4 100644 > --- a/drivers/of/Makefile > +++ b/drivers/of/Makefile > @@ -10,3 +10,4 @@ obj-$(CONFIG_OF_PCI) += of_pci.o > obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o > obj-$(CONFIG_OF_MTD) += of_mtd.o > obj-$(CONFIG_OF_RESOLVE) += resolver.o > +obj-$(CONFIG_OF_OVERLAY) += overlay.o > diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c > new file mode 100644 > index 0000000..5bed1f4 > --- /dev/null > +++ b/drivers/of/overlay.c > @@ -0,0 +1,869 @@ > +/* > + * Functions for working with device tree overlays > + * > + * Copyright (C) 2012 Pantelis Antoniou <panto@xxxxxxxxxxxxxxxxxxxxxxx> > + * Copyright (C) 2012 Texas Instruments Inc. > + * > + * 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. > + */ > +#undef DEBUG > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/i2c.h> > +#include <linux/string.h> > +#include <linux/ctype.h> > +#include <linux/errno.h> > +#include <linux/string.h> > +#include <linux/slab.h> > +#include <linux/err.h> > + > +/** > + * Apply a single overlay node recursively. > + * > + * Property or node names that start with '-' signal that > + * the property/node is to be removed. > + * > + * All the property notifiers are appropriately called. > + * Note that the in case of an error the target node is left > + * in a inconsistent state. Error recovery should be performed > + * by recording the modification using the of notifiers. > + */ > +static int of_overlay_apply_one(struct device_node *target, > + const struct device_node *overlay) > +{ > + const char *pname, *cname; > + struct device_node *child, *tchild; > + struct property *prop, *propn, *tprop; > + int remove; > + char *full_name; > + const char *suffix; > + int ret; > + > + /* sanity checks */ > + if (target == NULL || overlay == NULL) > + return -EINVAL; > + > + for_each_property_of_node(overlay, prop) { > + > + /* don't touch, 'name' */ > + if (of_prop_cmp(prop->name, "name") == 0) > + continue; > + > + /* default is add */ > + remove = 0; > + pname = prop->name; > + if (*pname == '-') { /* skip, - notes removal */ > + pname++; > + remove = 1; > + propn = NULL; > + } else { > + propn = __of_copy_property(prop, > + GFP_KERNEL); > + if (propn == NULL) > + return -ENOMEM; > + } > + > + tprop = of_find_property(target, pname, NULL); > + > + /* found? */ > + if (tprop != NULL) { > + if (propn != NULL) > + ret = of_update_property(target, propn); > + else > + ret = of_remove_property(target, tprop); > + } else { > + if (propn != NULL) > + ret = of_add_property(target, propn); > + else > + ret = 0; > + } > + if (ret != 0) > + return ret; > + } > + > + __for_each_child_of_node(overlay, child) { > + > + /* default is add */ > + remove = 0; > + cname = child->name; > + if (*cname == '-') { /* skip, - notes removal */ > + cname++; > + remove = 1; > + } > + > + /* special case for nodes with a suffix */ > + suffix = strrchr(child->full_name, '@'); > + if (suffix != NULL) { > + cname = kbasename(child->full_name); > + WARN_ON(cname == NULL); /* sanity check */ > + if (cname == NULL) > + continue; > + if (*cname == '-') > + cname++; > + } > + > + tchild = of_get_child_by_name(target, cname); > + if (tchild != NULL) { > + > + if (!remove) { > + > + /* apply overlay recursively */ > + ret = of_overlay_apply_one(tchild, child); > + of_node_put(tchild); > + > + if (ret != 0) > + return ret; > + > + } else { > + > + ret = of_detach_node(tchild); > + of_node_put(tchild); > + } > + > + } else { > + > + if (!remove) { > + full_name = kasprintf(GFP_KERNEL, "%s/%s", > + target->full_name, cname); > + if (full_name == NULL) > + return -ENOMEM; > + > + /* create empty tree as a target */ > + tchild = __of_create_empty_node(cname, > + child->type, full_name, > + child->phandle, GFP_KERNEL); > + > + /* free either way */ > + kfree(full_name); > + > + if (tchild == NULL) > + return -ENOMEM; > + > + /* point to parent */ > + tchild->parent = target; > + > + ret = of_attach_node(tchild); > + if (ret != 0) > + return ret; > + > + /* apply the overlay */ > + ret = of_overlay_apply_one(tchild, child); > + if (ret != 0) { > + __of_free_tree(tchild); > + return ret; > + } > + } > + } > + } > + > + return 0; > +} > + > +/** > + * Lookup an overlay device entry > + */ > +struct of_overlay_device_entry *of_overlay_device_entry_lookup( > + struct of_overlay_info *ovinfo, struct device_node *node) > +{ > + struct of_overlay_device_entry *de; > + > + /* no need for locks, we'de under the ovinfo->lock */ > + list_for_each_entry(de, &ovinfo->de_list, node) { > + if (de->np == node) > + return de; > + } > + return NULL; > +} > + > +/** > + * Add an overlay log entry > + */ > +static int of_overlay_log_entry_entry_add(struct of_overlay_info *ovinfo, > + unsigned long action, struct device_node *dn, > + struct property *prop) > +{ > + struct of_overlay_log_entry *le; > + > + /* check */ > + if (ovinfo == NULL || dn == NULL) > + return -EINVAL; > + > + le = kzalloc(sizeof(*le), GFP_KERNEL); > + if (le == NULL) { > + pr_err("%s: Failed to allocate\n", __func__); > + return -ENOMEM; > + } > + > + /* get a reference to the node */ > + le->action = action; > + le->np = of_node_get(dn); > + le->prop = prop; > + > + if (action == OF_RECONFIG_UPDATE_PROPERTY && prop) > + le->old_prop = of_find_property(dn, prop->name, NULL); > + > + list_add_tail(&le->node, &ovinfo->le_list); > + > + return 0; > +} > + > +/** > + * Add an overlay device entry > + */ > +static void of_overlay_device_entry_entry_add(struct of_overlay_info *ovinfo, > + struct device_node *node, > + struct platform_device *pdev, struct i2c_client *client, > + int prevstate, int state) > +{ > + struct of_overlay_device_entry *de; > + int fresh; > + > + /* check */ > + if (ovinfo == NULL) > + return; > + > + fresh = 0; > + de = of_overlay_device_entry_lookup(ovinfo, node); > + if (de == NULL) { > + de = kzalloc(sizeof(*de), GFP_KERNEL); > + if (de == NULL) { > + pr_err("%s: Failed to allocate\n", __func__); > + return; > + } > + fresh = 1; > + de->prevstate = -1; > + } > + > + if (de->np == NULL) > + de->np = of_node_get(node); > + if (de->pdev == NULL && pdev) > + de->pdev = of_dev_get(pdev); > + if (de->client == NULL && client) > + de->client = i2c_use_client(client); > + if (fresh) > + de->prevstate = prevstate; > + de->state = state; > + > + if (fresh) > + list_add_tail(&de->node, &ovinfo->de_list); > +} > + > +/** > + * Overlay OF notifier > + * > + * Called every time there's a property/node modification > + * Every modification causes a log entry addition, while > + * any modification that causes a node's state to change > + * from/to disabled to/from enabled causes a device entry > + * addition. > + */ > +static int of_overlay_notify(struct notifier_block *nb, > + unsigned long action, void *arg) > +{ > + struct of_overlay_info *ovinfo; > + struct device_node *node; > + struct property *prop, *sprop, *cprop; > + struct of_prop_reconfig *pr; > + struct platform_device *pdev; > + struct i2c_client *client; > + struct device_node *tnode; > + int depth; > + int prevstate, state; > + int err = 0; > + > + ovinfo = container_of(nb, struct of_overlay_info, notifier); > + > + /* prep vars */ > + switch (action) { > + case OF_RECONFIG_ATTACH_NODE: > + case OF_RECONFIG_DETACH_NODE: > + node = arg; > + if (node == NULL) > + return notifier_from_errno(-EINVAL); > + prop = NULL; > + break; > + case OF_RECONFIG_ADD_PROPERTY: > + case OF_RECONFIG_REMOVE_PROPERTY: > + case OF_RECONFIG_UPDATE_PROPERTY: > + pr = arg; > + if (pr == NULL) > + return notifier_from_errno(-EINVAL); > + node = pr->dn; > + if (node == NULL) > + return notifier_from_errno(-EINVAL); > + prop = pr->prop; > + if (prop == NULL) > + return notifier_from_errno(-EINVAL); > + break; > + default: > + return notifier_from_errno(0); > + } > + > + /* add to the log */ > + err = of_overlay_log_entry_entry_add(ovinfo, action, node, prop); > + if (err != 0) > + return notifier_from_errno(err); > + > + /* come up with the device entry (if any) */ > + pdev = NULL; > + client = NULL; > + state = 0; > + prevstate = 0; > + > + /* determine the state the node will end up */ > + switch (action) { > + case OF_RECONFIG_ATTACH_NODE: > + /* we demand that a compatible node is present */ > + state = of_find_property(node, "compatible", NULL) && > + of_device_is_available(node); > + break; > + case OF_RECONFIG_DETACH_NODE: > + prevstate = of_find_property(node, "compatible", NULL) && > + of_device_is_available(node); > + state = 0; > + pdev = of_find_device_by_node(node); > + client = of_find_i2c_device_by_node(node); > + break; > + case OF_RECONFIG_ADD_PROPERTY: > + case OF_RECONFIG_REMOVE_PROPERTY: > + case OF_RECONFIG_UPDATE_PROPERTY: > + /* either one cause a change in state */ > + if (strcmp(prop->name, "status") != 0 && > + strcmp(prop->name, "compatible") != 0) > + return notifier_from_errno(0); > + > + if (strcmp(prop->name, "status") == 0) { > + /* status */ > + cprop = of_find_property(node, "compatible", NULL); > + sprop = action != OF_RECONFIG_REMOVE_PROPERTY ? > + prop : NULL; > + } else { > + /* compatible */ > + sprop = of_find_property(node, "status", NULL); > + cprop = action != OF_RECONFIG_REMOVE_PROPERTY ? > + prop : NULL; > + } > + > + prevstate = of_find_property(node, "compatible", NULL) && > + of_device_is_available(node); > + state = cprop && cprop->length > 0 && > + (!sprop || (sprop->length > 0 && > + (strcmp(sprop->value, "okay") == 0 || > + strcmp(sprop->value, "ok") == 0))); > + break; > + > + default: > + return notifier_from_errno(0); > + } > + > + /* find depth */ > + depth = 1; > + tnode = node; > + while (tnode != NULL && tnode != ovinfo->target) { > + tnode = tnode->parent; > + depth++; > + } > + > + /* respect overlay's maximum depth */ > + if (ovinfo->device_depth != 0 && depth > ovinfo->device_depth) { > + pr_debug("OF: skipping device creation for node=%s depth=%d\n", > + node->name, depth); > + goto out; > + } > + > + if (state == 0) { > + pdev = of_find_device_by_node(node); > + client = of_find_i2c_device_by_node(node); > + } > + > + of_overlay_device_entry_entry_add(ovinfo, node, pdev, client, > + prevstate, state); > +out: > + > + return notifier_from_errno(err); > +} > + > +/** > + * Prepare for the overlay, for now it just registers the > + * notifier. > + */ > +static int of_overlay_prep_one(struct of_overlay_info *ovinfo) > +{ > + int err; > + > + err = of_reconfig_notifier_register(&ovinfo->notifier); > + if (err != 0) { > + pr_err("%s: failed to register notifier for '%s'\n", > + __func__, ovinfo->target->full_name); > + return err; > + } > + return 0; > +} > + > +static int of_overlay_device_entry_change(struct of_overlay_info *ovinfo, > + struct of_overlay_device_entry *de, int revert) > +{ > + struct i2c_adapter *adap = NULL; > + struct i2c_client *client; > + struct platform_device *pdev, *parent_pdev = NULL; > + int state; > + > + state = !!de->state ^ !!revert; > + > + if (de->np && de->np->parent) { > + pr_debug("%s: parent is %s\n", > + __func__, de->np->parent->full_name); > + adap = of_find_i2c_adapter_by_node(de->np->parent); > + if (adap == NULL) > + parent_pdev = of_find_device_by_node(de->np->parent); > + } > + > + if (state) { > + > + /* try to see if it's an I2C client node */ > + if (adap == NULL) { > + > + pr_debug("%s: creating new platform device " > + "new_node='%s' %p\n", > + __func__, de->np->full_name, de->np); > + > + pdev = of_platform_device_create(de->np, NULL, > + parent_pdev ? &parent_pdev->dev : NULL); > + if (pdev == NULL) { > + pr_warn("%s: Failed to create platform device " > + "for '%s'\n", > + __func__, de->np->full_name); > + } else > + de->pdev = pdev; > + > + } else { > + > + client = of_find_i2c_device_by_node(de->np); > + if (client != NULL) { > + /* bus already created the device; do nothing */ > + put_device(&client->dev); > + } else { > + pr_debug("%s: creating new i2c_client device " > + "new_node='%s' %p\n", > + __func__, de->np->full_name, de->np); > + > + client = of_i2c_register_device(adap, de->np); > + > + if (client == NULL) { > + pr_warn("%s: Failed to create i2c client device " > + "for '%s'\n", > + __func__, de->np->full_name); > + } else > + de->client = client; > + } > + } > + > + } else { > + > + if (de->pdev) { > + pr_debug("%s: removing pdev %s\n", __func__, > + dev_name(&de->pdev->dev)); > + platform_device_unregister(de->pdev); > + de->pdev = NULL; > + } > + > + if (de->client) { > + pr_debug("%s: removing i2c client %s\n", __func__, > + dev_name(&de->client->dev)); > + i2c_unregister_device(de->client); > + de->client = NULL; > + } > + } > + > + if (adap) > + put_device(&adap->dev); > + > + if (parent_pdev) > + of_dev_put(parent_pdev); > + > + return 0; > +} > + > +/** > + * Revert one overlay > + * Either due to an error, or due to normal overlay removal. > + * Using the log entries, we revert any change to the live tree. > + * In the same manner, using the device entries we enable/disable > + * the platform devices appropriately. > + */ > +static void of_overlay_revert_one(struct of_overlay_info *ovinfo) > +{ > + struct of_overlay_device_entry *de, *den; > + struct of_overlay_log_entry *le, *len; > + struct property *prop, **propp; > + int ret; > + unsigned long flags; > + > + if (!ovinfo || !ovinfo->target || !ovinfo->overlay) > + return; > + > + pr_debug("%s: Reverting overlay on '%s'\n", __func__, > + ovinfo->target->full_name); > + > + /* overlay applied correctly, now create/destroy pdevs */ > + list_for_each_entry_safe_reverse(de, den, &ovinfo->de_list, node) { > + of_overlay_device_entry_change(ovinfo, de, 1); > + of_node_put(de->np); > + list_del(&de->node); > + kfree(de); > + } > + > + list_for_each_entry_safe_reverse(le, len, &ovinfo->le_list, node) { > + > + ret = 0; > + switch (le->action) { > + case OF_RECONFIG_ATTACH_NODE: > + pr_debug("Reverting ATTACH_NODE %s\n", > + le->np->full_name); > + ret = of_detach_node(le->np); > + break; > + > + case OF_RECONFIG_DETACH_NODE: > + pr_debug("Reverting DETACH_NODE %s\n", > + le->np->full_name); > + ret = of_attach_node(le->np); > + break; > + > + case OF_RECONFIG_ADD_PROPERTY: > + pr_debug("Reverting ADD_PROPERTY %s %s\n", > + le->np->full_name, le->prop->name); > + ret = of_remove_property(le->np, le->prop); > + break; > + > + case OF_RECONFIG_REMOVE_PROPERTY: > + case OF_RECONFIG_UPDATE_PROPERTY: > + > + pr_debug("Reverting %s_PROPERTY %s %s\n", > + le->action == OF_RECONFIG_REMOVE_PROPERTY ? > + "REMOVE" : "UPDATE", > + le->np->full_name, le->prop->name); > + > + /* property is possibly on deadprops (avoid alloc) */ > + raw_spin_lock_irqsave(&devtree_lock, flags); > + prop = le->action == OF_RECONFIG_REMOVE_PROPERTY ? > + le->prop : le->old_prop; > + propp = &le->np->deadprops; > + while (*propp != NULL) { > + if (*propp == prop) > + break; > + propp = &(*propp)->next; > + } > + if (*propp != NULL) { > + /* remove it from deadprops */ > + (*propp)->next = prop->next; > + raw_spin_unlock_irqrestore(&devtree_lock, > + flags); > + } else { > + raw_spin_unlock_irqrestore(&devtree_lock, > + flags); > + /* not found, just make a copy */ > + prop = __of_copy_property(prop, GFP_KERNEL); > + if (prop == NULL) { > + pr_err("%s: Failed to copy property\n", > + __func__); > + break; > + } > + } > + > + if (le->action == OF_RECONFIG_REMOVE_PROPERTY) > + ret = of_add_property(le->np, prop); > + else > + ret = of_update_property(le->np, prop); > + break; > + > + default: > + /* nothing */ > + break; > + } > + > + if (ret != 0) > + pr_err("%s: revert on node %s Failed!\n", > + __func__, le->np->full_name); > + > + of_node_put(le->np); > + > + list_del(&le->node); > + > + kfree(le); > + } > +} > + > +/** > + * Perform the post overlay work. > + * > + * We unregister the notifier, and in the case on an error we > + * revert the overlay. > + * If the overlay applied correctly, we iterare over the device entries > + * and create/destroy the platform devices appropriately. > + */ > +static int of_overlay_post_one(struct of_overlay_info *ovinfo, int err) > +{ > + struct of_overlay_device_entry *de, *den; > + > + of_reconfig_notifier_unregister(&ovinfo->notifier); > + > + if (err != 0) { > + /* revert this (possible partially applied) overlay */ > + of_overlay_revert_one(ovinfo); > + return 0; > + } > + > + /* overlay applied correctly, now create/destroy pdevs */ > + list_for_each_entry_safe(de, den, &ovinfo->de_list, node) { > + > + /* no state change? just remove this entry */ > + if (de->prevstate == de->state) { > + of_node_put(de->np); > + list_del(&de->node); > + kfree(de); > + } else { > + of_overlay_device_entry_change(ovinfo, de, 0); > + } > + } > + > + return 0; > +} > + > +/** > + * of_overlay - Apply @count overlays pointed at by @ovinfo_tab > + * @count: Number of of_overlay_info's > + * @ovinfo_tab: Array of overlay_info's to apply > + * > + * Applies the overlays given, while handling all error conditions > + * appropriately. Either the operation succeeds, or if it fails the > + * live tree is reverted to the state before the attempt. > + * Returns 0, or an error if the overlay attempt failed. > + */ > +int of_overlay(int count, struct of_overlay_info *ovinfo_tab) > +{ > + struct of_overlay_info *ovinfo; > + int i, err; > + > + if (!ovinfo_tab) > + return -EINVAL; > + > + /* first we apply the overlays atomically */ > + for (i = 0; i < count; i++) { > + > + ovinfo = &ovinfo_tab[i]; > + > + mutex_lock(&ovinfo->lock); > + > + err = of_overlay_prep_one(ovinfo); > + if (err == 0) > + err = of_overlay_apply_one(ovinfo->target, > + ovinfo->overlay); > + of_overlay_post_one(ovinfo, err); > + > + mutex_unlock(&ovinfo->lock); > + > + if (err != 0) { > + pr_err("%s: overlay failed '%s'\n", > + __func__, ovinfo->target->full_name); > + goto err_fail; > + } > + } > + > + return 0; > + > +err_fail: > + while (--i >= 0) { > + ovinfo = &ovinfo_tab[i]; > + > + mutex_lock(&ovinfo->lock); > + of_overlay_revert_one(ovinfo); > + mutex_unlock(&ovinfo->lock); > + } > + > + return err; > +} > + > +/** > + * of_overlay_revert - Revert a previously applied overlay > + * @count: Number of of_overlay_info's > + * @ovinfo_tab: Array of overlay_info's to apply > + * > + * Revert a previous overlay. The state of the live tree > + * is reverted to the one before the overlay. > + * Returns 0, or an error if the overlay table is not given. > + */ > +int of_overlay_revert(int count, struct of_overlay_info *ovinfo_tab) > +{ > + struct of_overlay_info *ovinfo; > + int i; > + > + if (!ovinfo_tab) > + return -EINVAL; > + > + /* revert the overlays in reverse */ > + for (i = count - 1; i >= 0; i--) { > + > + ovinfo = &ovinfo_tab[i]; > + > + mutex_lock(&ovinfo->lock); > + of_overlay_revert_one(ovinfo); > + mutex_unlock(&ovinfo->lock); > + > + } > + > + return 0; > +} > + > +/** > + * of_init_overlay_info - Initialize a single of_overlay_info structure > + * @ovinfo: Pointer to the overlay info structure to initialize > + * > + * Initialize a single overlay info structure. > + */ > +void of_init_overlay_info(struct of_overlay_info *ovinfo) > +{ > + memset(ovinfo, 0, sizeof(ovinfo)); > + mutex_init(&ovinfo->lock); > + INIT_LIST_HEAD(&ovinfo->de_list); > + INIT_LIST_HEAD(&ovinfo->le_list); > + > + ovinfo->notifier.notifier_call = of_overlay_notify; > +} > + > +/** > + * of_fill_overlay_info - Fill an overlay info structure > + * @info_node: Device node containing the overlay > + * @ovinfo: Pointer to the overlay info structure to fill > + * > + * Fills an overlay info structure with the overlay information > + * from a device node. This device node must have a target property > + * which contains a phandle of the overlay target node, and an > + * __overlay__ child node which has the overlay contents. > + * Both ovinfo->target & ovinfo->overlay have their references taken. > + * > + * Returns 0 on success, or a negative error value. > + */ > +int of_fill_overlay_info(struct device_node *info_node, > + struct of_overlay_info *ovinfo) > +{ > + u32 val; > + int ret; > + > + if (!info_node || !ovinfo) > + return -EINVAL; > + > + ret = of_property_read_u32(info_node, "target", &val); > + if (ret != 0) > + goto err_fail; > + > + ovinfo->target = of_find_node_by_phandle(val); > + if (ovinfo->target == NULL) > + goto err_fail; > + > + ovinfo->overlay = of_get_child_by_name(info_node, "__overlay__"); > + if (ovinfo->overlay == NULL) > + goto err_fail; > + > + ret = of_property_read_u32(info_node, "depth", &val); > + if (ret == 0) > + ovinfo->device_depth = val; > + else > + ovinfo->device_depth = 0; > + > + > + return 0; > + > +err_fail: > + of_node_put(ovinfo->target); > + of_node_put(ovinfo->overlay); > + > + memset(ovinfo, 0, sizeof(*ovinfo)); > + return -EINVAL; > +} > + > +/** > + * of_build_overlay_info - Build an overlay info array > + * @tree: Device node containing all the overlays > + * @cntp: Pointer to where the overlay info count will be help > + * @ovinfop: Pointer to the pointer of an overlay info structure. > + * > + * Helper function that given a tree containing overlay information, > + * allocates and builds an overlay info array containing it, ready > + * for use using of_overlay. > + * > + * Returns 0 on success with the @cntp @ovinfop pointers valid, > + * while on error a negative error value is returned. > + */ > +int of_build_overlay_info(struct device_node *tree, > + int *cntp, struct of_overlay_info **ovinfop) > +{ > + struct device_node *node; > + struct of_overlay_info *ovinfo; > + int cnt, err; > + > + if (tree == NULL || cntp == NULL || ovinfop == NULL) > + return -EINVAL; > + > + /* worst case; every child is a node */ > + cnt = 0; > + for_each_child_of_node(tree, node) > + cnt++; > + > + ovinfo = kzalloc(cnt * sizeof(*ovinfo), GFP_KERNEL); > + if (ovinfo == NULL) > + return -ENOMEM; > + > + cnt = 0; > + for_each_child_of_node(tree, node) { > + > + of_init_overlay_info(&ovinfo[cnt]); > + err = of_fill_overlay_info(node, &ovinfo[cnt]); > + if (err == 0) > + cnt++; > + } > + > + /* if nothing filled, return error */ > + if (cnt == 0) { > + kfree(ovinfo); > + return -ENODEV; > + } > + > + *cntp = cnt; > + *ovinfop = ovinfo; > + > + return 0; > +} > + > +/** > + * of_free_overlay_info - Free an overlay info array > + * @count: Number of of_overlay_info's > + * @ovinfo_tab: Array of overlay_info's to free > + * > + * Releases the memory of a previously allocate ovinfo array > + * by of_build_overlay_info. > + * Returns 0, or an error if the arguments are bogus. > + */ > +int of_free_overlay_info(int count, struct of_overlay_info *ovinfo_tab) > +{ > + struct of_overlay_info *ovinfo; > + int i; > + > + if (!ovinfo_tab || count < 0) > + return -EINVAL; > + > + /* do it in reverse */ > + for (i = count - 1; i >= 0; i--) { > + ovinfo = &ovinfo_tab[i]; > + > + of_node_put(ovinfo->target); > + of_node_put(ovinfo->overlay); > + } > + kfree(ovinfo_tab); > + > + return 0; > +} > + > diff --git a/include/linux/of.h b/include/linux/of.h > index 22d42e5..d9c8130 100644 > --- a/include/linux/of.h > +++ b/include/linux/of.h > @@ -23,6 +23,7 @@ > #include <linux/spinlock.h> > #include <linux/topology.h> > #include <linux/notifier.h> > +#include <linux/i2c.h> > > #include <asm/byteorder.h> > #include <asm/errno.h> > @@ -738,4 +739,116 @@ static inline int of_resolve(struct device_node *resolve) > > #endif > > +/** > + * Overlay support > + */ > + > +/** > + * struct of_overlay_log_entry - Holds a DT log entry > + * @node: list_head for the log list > + * @action: notifier action > + * @np: pointer to the device node affected > + * @prop: pointer to the property affected > + * @old_prop: hold a pointer to the original property > + * > + * Every modification of the device tree during application of the > + * overlay is held in a list of of_overlay_log_entry structures. > + * That way we can recover from a partial application, or we can > + * revert the overlay properly. > + */ > +struct of_overlay_log_entry { > + struct list_head node; > + unsigned long action; > + struct device_node *np; > + struct property *prop; > + struct property *old_prop; > +}; > + > +/** > + * struct of_overlay_device_entry - Holds an overlay device entry > + * @node: list_head for the device list > + * @np: device node pointer to the device node affected > + * @pdev: pointer to the platform device affected > + * @client: pointer to the I2C client device affected > + * @state: new device state > + * @prevstate: previous device state > + * > + * When the overlay results in a device node's state to change this > + * fact is recorded in a list of device entries. After the overlay > + * is applied we can create/destroy the platform devices according > + * to the new state of the live tree. > + */ > +struct of_overlay_device_entry { > + struct list_head node; > + struct device_node *np; > + struct platform_device *pdev; > + struct i2c_client *client; > + int prevstate; > + int state; > +}; > + > +/** > + * struct of_overlay_info - Holds a single overlay info > + * @target: target of the overlay operation > + * @overlay: pointer to the overlay contents node > + * @lock: Lock to hold when accessing the lists > + * @le_list: List of the overlay logs > + * @de_list: List of the overlay records > + * @notifier: of reconfiguration notifier > + * > + * Holds a single overlay state, including all the overlay logs & > + * records. > + */ > +struct of_overlay_info { > + struct device_node *target; > + struct device_node *overlay; > + struct mutex lock; > + struct list_head le_list; > + struct list_head de_list; > + struct notifier_block notifier; > + int device_depth; > +}; > + > +#ifdef CONFIG_OF_OVERLAY > + > +int of_overlay(int count, struct of_overlay_info *ovinfo_tab); > +int of_overlay_revert(int count, struct of_overlay_info *ovinfo_tab); > + > +int of_fill_overlay_info(struct device_node *info_node, > + struct of_overlay_info *ovinfo); > +int of_build_overlay_info(struct device_node *tree, > + int *cntp, struct of_overlay_info **ovinfop); > +int of_free_overlay_info(int cnt, struct of_overlay_info *ovinfo); > + > +#else > + > +static inline int of_overlay(int count, struct of_overlay_info *ovinfo_tab) > +{ > + return -ENOTSUPP; > +} > + > +static inline int of_overlay_revert(int count, struct of_overlay_info *ovinfo_tab) > +{ > + return -ENOTSUPP; > +} > + > +static inline int of_fill_overlay_info(struct device_node *info_node, > + struct of_overlay_info *ovinfo) > +{ > + return -ENOTSUPP; > +} > + > +static inline int of_build_overlay_info(struct device_node *tree, > + int *cntp, struct of_overlay_info **ovinfop) > +{ > + return -ENOTSUPP; > +} > + > +int of_free_overlay_info(int cnt, struct of_overlay_info *ovinfo) > +{ This needs a static inline, otherwise I get a build failure if CONFIG_OF_OVERLAY is not set. Dinh > + return -ENOTSUPP; > +} > + > +#endif > + > #endif /* _LINUX_OF_H */ -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html