[PATCH 2/4] of: DT quirks infrastructure

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

 




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.antoniou@xxxxxxxxxxxx>
---
 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 devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux