From patchwork Wed Feb 18 14:59:34 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pantelis Antoniou X-Patchwork-Id: 5844491 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 8465BBF440 for ; Wed, 18 Feb 2015 15:03:37 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 2080020149 for ; Wed, 18 Feb 2015 15:03:36 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 04CDD20221 for ; Wed, 18 Feb 2015 15:03:33 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YO67V-0002fC-A9; Wed, 18 Feb 2015 15:00:57 +0000 Received: from mail-wi0-f181.google.com ([209.85.212.181]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1YO66m-0001EU-3j for linux-arm-kernel@lists.infradead.org; Wed, 18 Feb 2015 15:00:14 +0000 Received: by mail-wi0-f181.google.com with SMTP id r20so2775023wiv.2 for ; Wed, 18 Feb 2015 06:59:49 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Ft4wk2YMYtOaqYp05lSe2ikf73InKVVW3l5BvCOsJtE=; b=Mmch2ta4JF9cK2bB5pzNpIJO3THJcfoSlKAzzx58DqE0bLAeGm8KuzAmTmCAM1aUkp hZFPUNEhcnVXannx0pVdG7+cFStfX+HuPAjizoDplmeu0E7zE+naiCRnftmnU47Hf3+I cEOJDpTnMGDmEqeLZQJMqz0yIjE0fN2V32RYg8saI3YKz1bjD06qqSazKgK8UeUj1O8s Fu/4VhvWGnziJIWiwcI11ZRQDPeJ9fmrTZhoK1+lxTLsZJxofdJfdlGr0h/F7uRTRA2W JlIRo75lu9UUcXkFnFXxj4+8YR/WfWhzCm9iIS0N1GRpppIcky8fr569pVSz4MFm31F5 zRog== X-Gm-Message-State: ALoCoQlydKps0vFmFHNpeOrnlYyyUZOyrOJPR0LUpNOZwAvavuM0LAGyReZeiGHB8/wWPeOZ145S X-Received: by 10.180.73.75 with SMTP id j11mr5628787wiv.17.1424271589134; Wed, 18 Feb 2015 06:59:49 -0800 (PST) Received: from sles11esa.localdomain ([195.97.110.117]) by mx.google.com with ESMTPSA id gm2sm19752711wib.5.2015.02.18.06.59.46 (version=TLSv1 cipher=RC4-SHA bits=128/128); Wed, 18 Feb 2015 06:59:48 -0800 (PST) From: Pantelis Antoniou To: Grant Likely Subject: [PATCH 2/4] of: DT quirks infrastructure Date: Wed, 18 Feb 2015 16:59:34 +0200 Message-Id: <1424271576-1952-3-git-send-email-pantelis.antoniou@konsulko.com> X-Mailer: git-send-email 1.7.12 In-Reply-To: <1424271576-1952-1-git-send-email-pantelis.antoniou@konsulko.com> References: <1424271576-1952-1-git-send-email-pantelis.antoniou@konsulko.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150218_070012_534087_CF0FC837 X-CRM114-Status: GOOD ( 32.62 ) X-Spam-Score: 0.3 (/) Cc: devicetree@vger.kernel.org, Tony Lindgren , Pantelis Antoniou , Koen Kooi , Nicolas Ferre , linux-kernel@vger.kernel.org, Ludovic Desroches , linux-arm-kernel@lists.infradead.org, Pantelis Antoniou , Matt Porter , Guenter Roeck X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 +#include #include #include #include @@ -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 = ""; + if (!dnp->type) + dnp->type = ""; + + 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 */