Implement a method of applying DT quirks early in the boot sequence.

A DT quirk is a subtree of the boot DT that can be applied to
a target in the base DT resulting in a modification of the live
tree. The format of the quirk nodes is that of a device tree overlay.

For details please refer to Documentation/devicetree/quirks.txt

Signed-off-by: Pantelis Antoniou <pantelis.anton...@konsulko.com>
---
 Documentation/devicetree/quirks.txt | 101 ++++++++++
 drivers/of/dynamic.c                | 358 ++++++++++++++++++++++++++++++++++++
 include/linux/of.h                  |  16 ++
 3 files changed, 475 insertions(+)
 create mode 100644 Documentation/devicetree/quirks.txt

diff --git a/Documentation/devicetree/quirks.txt 
b/Documentation/devicetree/quirks.txt
new file mode 100644
index 0000000..789319a
--- /dev/null
+++ b/Documentation/devicetree/quirks.txt
@@ -0,0 +1,101 @@
+A Device Tree quirk is the way which allows modification of the
+boot device tree under the control of a per-platform specific method.
+
+Take for instance the case of a board family that comprises of a
+number of different board revisions, all being incremental changes
+after an initial release.
+
+Since all board revisions must be supported via a single software image
+the only way to support this scheme is by having a different DTB for each
+revision with the bootloader selecting which one to use at boot time.
+
+While this may in theory work, in practice it is very cumbersome
+for the following reasons:
+
+1. The act of selecting a different boot device tree blob requires
+a reasonably advanced bootloader with some kind of configuration or
+scripting capabilities. Sadly this is not the case many times, the
+bootloader is extremely dumb and can only use a single dt blob.
+
+2. On many instances boot time is extremely critical; in some cases
+there are hard requirements like having working video feeds in under
+2 seconds from power-up. This leaves an extremely small time budget for
+boot-up, as low as 500ms to kernel entry. The sanest way to get there
+is by removing the standard bootloader from the normal boot sequence
+altogether by having a very small boot shim that loads the kernel and
+immediately jumps to kernel, like falcon-boot mode in u-boot does.
+
+3. Having different DTBs/DTSs for different board revisions easily leads to
+drift between versions. Since no developer is expected to have every single
+board revision at hand, things are easy to get out of sync, with board versions
+failing to boot even though the kernel is up to date.
+
+4. One final problem is the static way that device tree works.
+For example it might be desirable for various boards to have a way to
+selectively configure the boot device tree, possibly by the use of command
+line options.  For instance a device might be disabled if a given command line
+option is present, different configuration to various devices for debugging
+purposes can be selected and so on. Currently the only way to do so is by
+recompiling the DTS and installing, which is an chore for developers and
+a completely unreasonable expectation from end-users.
+
+Device Tree quirks solve all those problems by having an in-kernel interface
+which per-board/platform method can use to selectively modify the device tree
+right after unflattening.
+
+A DT quirk is a subtree of the boot DT that can be applied to
+a target in the base DT resulting in a modification of the live
+tree. The format of the quirk nodes is that of a device tree overlay.
+
+As an example the following DTS contains a quirk.
+
+/ {
+       foo: foo-node {
+               bar = <10>;
+       };
+
+       select-quirk = <&quirk>;
+
+       quirk: quirk {
+               fragment@0 {
+                       target = <&foo>;
+                       __overlay {
+                               bar = <0xf00>;
+                               baz = <11>;
+                       };
+               };
+       };
+};
+
+The quirk when applied would result at the following tree:
+
+/ {
+       foo: foo-node {
+               bar = <0xf00>;
+               baz = <11>;
+       };
+
+       select-quirk = <&quirk>;
+
+       quirk: quirk {
+               fragment@0 {
+                       target = <&foo>;
+                       __overlay {
+                               bar = <0xf00>;
+                               baz = <11>;
+                       };
+               };
+       };
+
+};
+
+The two public method used to accomplish this are of_quirk_apply_by_node()
+and of_quirk_apply_by_phandle();
+
+To apply the quirk, a per-platform method can retrieve the phandle from the
+select-quirk property and pass it to the of_quirk_apply_by_phandle() node.
+
+The method which the per-platform method is using to select the quirk to apply
+is out of the scope of the DT quirk definition, but possible methods include
+and are not limited to: revision encoding in a GPIO input range, board id
+located in external or internal EEPROM or flash, DMI board ids, etc.
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c
index 3351ef4..d275dc7 100644
--- a/drivers/of/dynamic.c
+++ b/drivers/of/dynamic.c
@@ -7,6 +7,7 @@
  */
 
 #include <linux/of.h>
+#include <linux/of_fdt.h>
 #include <linux/spinlock.h>
 #include <linux/slab.h>
 #include <linux/string.h>
@@ -779,3 +780,360 @@ int of_changeset_action(struct of_changeset *ocs, 
unsigned long action,
        list_add_tail(&ce->node, &ocs->entries);
        return 0;
 }
+
+/* fixup a symbol entry for a quirk if it exists */
+static int quirk_fixup_symbol(struct device_node *dns, struct device_node *dnp)
+{
+       struct device_node *dn;
+       struct property *prop;
+       const char *names, *namep;
+       int lens, lenp;
+       char *p;
+
+       dn = of_find_node_by_path("/__symbols__");
+       if (!dn)
+               return 0;
+
+       names = of_node_full_name(dns);
+       lens = strlen(names);
+       namep = of_node_full_name(dnp);
+       lenp = strlen(namep);
+       for_each_property_of_node(dn, prop) {
+               /* be very concervative at matching */
+               if (lens == (prop->length - 1) &&
+                       ((const char *)prop->value)[prop->length] == '\0' &&
+                       strcmp(prop->value, names) == 0)
+                       break;
+       }
+       if (prop == NULL)
+               return 0;
+       p = early_init_dt_alloc_memory_arch(lenp + 1, __alignof__(char));
+       if (!p) {
+               pr_err("%s: symbol fixup %s failed\n", __func__, prop->name);
+               return -ENOMEM;
+       }
+       strcpy(p, namep);
+
+       pr_debug("%s: symbol fixup %s: %s -> %s\n", __func__,
+                       prop->name, names, namep);
+
+       prop->value = p;
+       prop->length = lenp + 1;
+
+       return 0;
+}
+
+/* create a new quirk node */
+static struct device_node *new_quirk_node(
+               struct device_node *dns,
+               struct device_node *dnt,
+               const char *name)
+{
+       struct device_node *dnp;
+       int dnlen, len, ret;
+       struct property **pp, *prop;
+       char *p;
+
+       dnlen = strlen(dnt->full_name);
+       len = dnlen + 1 + strlen(name) + 1;
+       dnp = early_init_dt_alloc_memory_arch(
+                       sizeof(struct device_node) + len,
+                       __alignof__(struct device_node));
+       if (dnp == NULL) {
+               pr_err("%s: allocation failure at %pO\n", __func__,
+                               dns);
+               return NULL;
+       }
+       memset(dnp, 0, sizeof(*dnp));
+       of_node_init(dnp);
+       p = (char *)dnp + sizeof(*dnp);
+
+       /* build full name */
+       dnp->full_name = p;
+       memcpy(p, dnt->full_name, dnlen);
+       p += dnlen;
+       if (dnlen != 1)
+               *p++ = '/';
+       strcpy(p, name);
+
+       dnp->parent = dnt;
+
+       /* we now move the phandle properties */
+       for (pp = &dns->properties; (prop = *pp) != NULL; ) {
+
+               /* do not touch normal properties */
+               if (strcmp(prop->name, "name") &&
+                   strcmp(prop->name, "phandle") &&
+                   strcmp(prop->name, "linux,phandle") &&
+                   strcmp(prop->name, "ibm,phandle")) {
+                       pp = &(*pp)->next;
+                       continue;
+               }
+
+               /* move to the new node */
+               *pp = prop->next;
+               /* don't advance */
+
+               prop->next = dnp->properties;
+               dnp->properties = prop;
+
+               if ((strcmp(prop->name, "phandle") == 0 ||
+                   strcmp(prop->name, "linux,phandle") == 0 ||
+                   strcmp(prop->name, "ibm,phandle") == 0) &&
+                               dnp->phandle == 0) {
+                       dnp->phandle = be32_to_cpup(prop->value);
+                       /* remove the phandle from the source */
+                       dns->phandle = 0;
+               }
+       }
+
+       dnp->name = of_get_property(dnp, "name", NULL);
+       dnp->type = of_get_property(dnp, "device_type", NULL);
+       if (!dnp->name)
+               dnp->name = "<NULL>";
+       if (!dnp->type)
+               dnp->type = "<NULL>";
+
+       ret = quirk_fixup_symbol(dns, dnp);
+       if (ret != 0)
+               pr_warn("%s: Failed to fixup symbol %pO\n", __func__, dnp);
+
+       return dnp;
+}
+
+/* apply a quirk fragment node recursively */
+static int of_apply_quirk_fragment_node(struct device_node *dn,
+               struct device_node *dnt)
+{
+       struct property *prop, *tprop, **pp;
+       struct device_node *dnp, **dnpp, *child;
+       const char *name, *namet;
+       int i, ret;
+
+       if (!dn || !dnt)
+               return -EINVAL;
+
+       /* iterate over all properties */
+       for (pp = &dn->properties; (prop = *pp) != NULL; pp = &prop->next) {
+
+               /* do not touch auto-generated properties */
+               if (!strcmp(prop->name, "name") ||
+                   !strcmp(prop->name, "phandle") ||
+                   !strcmp(prop->name, "linux,phandle") ||
+                   !strcmp(prop->name, "ibm,phandle") ||
+                   !strcmp(prop->name, "__remove_property__") ||
+                   !strcmp(prop->name, "__remove_node__"))
+                       continue;
+
+               pr_debug("%s: change property %s from %pO to %pO\n",
+                               __func__, prop->name, dn, dnt);
+
+               tprop = of_find_property(dnt, prop->name, NULL);
+               if (tprop) {
+                       tprop->value = prop->value;
+                       tprop->length = prop->length;
+                       continue;
+               }
+               tprop = early_init_dt_alloc_memory_arch(
+                               sizeof(struct property),
+                               __alignof__(struct property));
+               if (!tprop) {
+                       pr_err("%s: allocation failure at %pO\n", __func__,
+                                       dn);
+                       return -ENOMEM;
+               }
+               tprop->name = prop->name;
+               tprop->value = prop->value;
+               tprop->length = prop->length;
+
+               /* link */
+               tprop->next = dnt->properties;
+               dnt->properties = tprop;
+       }
+
+       /* now handle property removals (if any) */
+       for (i = 0; of_property_read_string_index(dn, "__remove_property__",
+                               i, &name) == 0; i++) {
+
+               /* remove property directly (we don't care about dead props) */
+               for (pp = &dnt->properties; (prop = *pp) != NULL;
+                               pp = &prop->next) {
+                       if (!strcmp(prop->name, name)) {
+                               *pp = prop->next;
+                               pr_info("%s: remove property %s at %pO\n",
+                                       __func__, name, dnt);
+                               break;
+                       }
+               }
+       }
+
+       /* now handle node removals (if any) */
+       for (i = 0; of_property_read_string_index(dn, "__remove_node__",
+                               i, &name) == 0; i++) {
+
+               /* remove node directly (we don't care about dead props) */
+               for (dnpp = &dnt->child; (dnp = *dnpp) != NULL;
+                               dnpp = &dnp->sibling) {
+
+                       /* find path component */
+                       namet = strrchr(dnp->full_name, '/');
+                       if (!namet)     /* root */
+                               namet = dnp->full_name;
+                       else
+                               namet++;
+                       if (!strcmp(namet, name)) {
+                               *dnpp = dnp->sibling;
+                               pr_info("%s: remove node %s at %pO\n",
+                                       __func__, namet, dnt);
+                               break;
+                       }
+               }
+       }
+
+       /* now iterate over childen */
+       for_each_child_of_node(dn, child) {
+               /* locate path component */
+               name = strrchr(child->full_name, '/');
+               if (name == NULL)       /* root? */
+                       name = child->full_name;
+               else
+                       name++;
+
+               /* find node (if it exists) */
+               for (dnpp = &dnt->child; (dnp = *dnpp) != NULL;
+                               dnpp = &dnp->sibling) {
+
+                       namet = strrchr(dnp->full_name, '/');
+                       if (!namet)     /* root */
+                               namet = dnp->full_name;
+                       else
+                               namet++;
+
+                       if (!strcmp(namet, name))
+                               break;
+               }
+
+               /* not found, create node */
+               if (dnp == NULL) {
+                       dnp = new_quirk_node(child, dnt, name);
+                       if (dnp == NULL) {
+                               pr_err("%s: allocation failure at %pO\n",
+                                               __func__, dn);
+                               of_node_put(child);
+                               return -ENOMEM;
+                       }
+                       dnp->sibling = *dnpp;
+                       *dnpp = dnp;
+
+                       pr_debug("%s: new node %pO\n", __func__, dnp);
+               }
+               pr_debug("%s: recursing %pO\n", __func__, dnp);
+
+               ret = of_apply_quirk_fragment_node(child, dnp);
+               if (ret != 0) {
+                       of_node_put(child);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+/* apply a single quirk fragment located at dn */
+static int of_apply_single_quirk_fragment(struct device_node *dn)
+{
+       struct device_node *dnt, *dno;
+       const char *path;
+       u32 val;
+       int ret;
+
+       /* first try to go by using the target as a phandle */
+       dno = NULL;
+       dnt = NULL;
+       ret = of_property_read_u32(dn, "target", &val);
+       if (ret == 0)
+               dnt = of_find_node_by_phandle(val);
+
+       if (dnt == NULL) {
+               /* now try to locate by path */
+               ret = of_property_read_string(dn, "target-path",
+                               &path);
+               if (ret == 0)
+                       dnt = of_find_node_by_path(path);
+       }
+
+       if (dnt == NULL) {
+               pr_err("%s: Failed to find target for node %pO\n",
+                               __func__, dn);
+               ret = -ENODEV;
+               goto out;
+       }
+
+       pr_debug("%s: Found target at %pO\n", __func__, dnt);
+       dno = of_get_child_by_name(dn, "__overlay__");
+       if (!dno) {
+               pr_err("%s: Failed to find overlay node %pO\n", __func__, dn);
+               ret = -ENODEV;
+               goto out;
+       }
+
+       ret = of_apply_quirk_fragment_node(dno, dnt);
+out:
+       of_node_put(dno);
+       of_node_put(dnt);
+
+       return ret;
+}
+
+/**
+ * of_quirk_apply_by_node - Apply a DT quirk found at the given node
+ *
+ * @dn:                device node pointer to the quirk
+ *
+ * Returns 0 on success, a negative error value in case of an error.
+ */
+int of_quirk_apply_by_node(struct device_node *dn)
+{
+       struct device_node *child;
+       int ret;
+
+       if (!dn)
+               return -ENODEV;
+
+       pr_debug("Apply quirk at %pO\n", dn);
+
+       ret = 0;
+       for_each_child_of_node(dn, child) {
+               ret = of_apply_single_quirk_fragment(child);
+               if (ret != 0) {
+                       of_node_put(child);
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+/**
+ * of_quirk_apply_by_node - Apply a DT quirk found by the given phandle
+ *
+ * ph:         phandle of the quirk node
+ *
+ * Returns 0 on success, a negative error value in case of an error.
+ */
+int of_quirk_apply_by_phandle(phandle ph)
+{
+       struct device_node *dn;
+       int ret;
+
+       dn = of_find_node_by_phandle(ph);
+       if (!dn) {
+               pr_err("Failed to find node with phandle %u\n", ph);
+               return -ENODEV;
+       }
+
+       ret = of_quirk_apply_by_node(dn);
+       of_node_put(dn);
+
+       return ret;
+}
diff --git a/include/linux/of.h b/include/linux/of.h
index 7ede449..02d8988 100644
--- a/include/linux/of.h
+++ b/include/linux/of.h
@@ -1075,4 +1075,20 @@ static inline int of_overlay_destroy_all(void)
 
 #endif
 
+/* early boot quirks */
+#ifdef CONFIG_OF_DYNAMIC
+int of_quirk_apply_by_node(struct device_node *dn);
+int of_quirk_apply_by_phandle(phandle ph);
+#else
+static inline int of_quirk_apply_by_node(struct device_node *dn)
+{
+       return -ENOTSUPP;
+}
+
+int of_quirk_apply_by_phandle(phandle ph)
+{
+       return -ENOTSUPP;
+}
+#endif
+
 #endif /* _LINUX_OF_H */
-- 
1.7.12

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to