Add checks to (1) overlay apply process and (2) memory freeing
triggered by overlay release. The checks are intended to detect possible memory leaks and invalid overlays. The checks revealed bugs in existing code. Fixed the bugs. While fixing bugs, noted other issues, which are fixed in separate patches. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJb5Si3AAoJEKFvmrgMstOVle4P/0lnCPuf1ImXhRFz72x5oOKN oJecaYrkyHOaWpQ4UIN30Ht/yWa0SaMtgq2thPhrBW58YEvcssL4iSXTlgAeZEJv m+jPxFgpP/czXVMQXMnPa1gq1UwEIScB04K/xr9OsxpJWFlw1inr2tDEhXJh/IcK VNxTuaaHr8+yOk8SN5jDrAV4srOw779shjyBtPYwn63IaUOg/owyks/ElOLYLK6W S7ZGyPGnGgltU1OQhzGzfqZiF/t45Q1wpCCx9VIhY5/0DjCkdrI27T5eL1PSdnQr YwyPhXGelufj4lLUpbInFac0IBP49pLW0uhaAZtFGVFH8vyD+/r/yTDROwcvsEm+ PZfiX3h+H8SdlIQRR2cCSEb1F7oeWrxKpzyjIa7jaknMcL9619wmfkmosf/jxGFG IQcg/nXITTKz8VGhrfIGcrd6SQgnCIFIKWvX3bU5i/p2DjLyVSVrs+iThvQeuEuy ElsWzYD5zn3r3+lJ3jkt3rVmyu5btBtmaP1zsGM03eY+Zu2zusKpSlnBJa8PUIe6 jwDd8aMJzJcJgHYKZcbv8BQa2S8OKUjT0h+At3dK7ZT+UDXuCVWZ88iua45Fx9Hs 70ZrG4kSmASI+HU/UGBE8qvbysPlM5FBvHUFfROgswvmtHdfVQSDda2jQcx221Ja TsVbEPQd3VEWdUUJJqAK =SQXe -----END PGP SIGNATURE----- Merge tag 'kfree_validate_v7-for-4.20' of git://git.kernel.org/pub/scm/linux/kernel/git/frowand/linux into dt/next Pull overlay validation checks from Frank Rowand: "Add checks to (1) overlay apply process and (2) memory freeing triggered by overlay release. The checks are intended to detect possible memory leaks and invalid overlays. The checks revealed bugs in existing code. Fixed the bugs. While fixing bugs, noted other issues, which are fixed in separate patches." Signed-off-by: Rob Herring <robh@kernel.org>
This commit is contained in:
commit
f8274f14a9
|
@ -270,6 +270,8 @@ int dlpar_detach_node(struct device_node *dn)
|
|||
if (rc)
|
||||
return rc;
|
||||
|
||||
of_node_put(dn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -205,15 +205,24 @@ static void __of_attach_node(struct device_node *np)
|
|||
const __be32 *phandle;
|
||||
int sz;
|
||||
|
||||
np->name = __of_get_property(np, "name", NULL) ? : "<NULL>";
|
||||
np->type = __of_get_property(np, "device_type", NULL) ? : "<NULL>";
|
||||
if (!of_node_check_flag(np, OF_OVERLAY)) {
|
||||
np->name = __of_get_property(np, "name", NULL);
|
||||
np->type = __of_get_property(np, "device_type", NULL);
|
||||
if (!np->name)
|
||||
np->name = "<NULL>";
|
||||
if (!np->type)
|
||||
np->type = "<NULL>";
|
||||
|
||||
phandle = __of_get_property(np, "phandle", &sz);
|
||||
if (!phandle)
|
||||
phandle = __of_get_property(np, "linux,phandle", &sz);
|
||||
if (IS_ENABLED(CONFIG_PPC_PSERIES) && !phandle)
|
||||
phandle = __of_get_property(np, "ibm,phandle", &sz);
|
||||
np->phandle = (phandle && (sz >= 4)) ? be32_to_cpup(phandle) : 0;
|
||||
if (phandle && (sz >= 4))
|
||||
np->phandle = be32_to_cpup(phandle);
|
||||
else
|
||||
np->phandle = 0;
|
||||
}
|
||||
|
||||
np->child = NULL;
|
||||
np->sibling = np->parent->child;
|
||||
|
@ -272,9 +281,6 @@ void __of_detach_node(struct device_node *np)
|
|||
|
||||
/**
|
||||
* of_detach_node() - "Unplug" a node from the device tree.
|
||||
*
|
||||
* The caller must hold a reference to the node. The memory associated with
|
||||
* the node is not freed until its refcount goes to zero.
|
||||
*/
|
||||
int of_detach_node(struct device_node *np)
|
||||
{
|
||||
|
@ -330,6 +336,25 @@ void of_node_release(struct kobject *kobj)
|
|||
if (!of_node_check_flag(node, OF_DYNAMIC))
|
||||
return;
|
||||
|
||||
if (of_node_check_flag(node, OF_OVERLAY)) {
|
||||
|
||||
if (!of_node_check_flag(node, OF_OVERLAY_FREE_CSET)) {
|
||||
/* premature refcount of zero, do not free memory */
|
||||
pr_err("ERROR: memory leak before free overlay changeset, %pOF\n",
|
||||
node);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If node->properties non-empty then properties were added
|
||||
* to this node either by different overlay that has not
|
||||
* yet been removed, or by a non-overlay mechanism.
|
||||
*/
|
||||
if (node->properties)
|
||||
pr_err("ERROR: %s(), unexpected properties in %pOF\n",
|
||||
__func__, node);
|
||||
}
|
||||
|
||||
property_list_free(node->properties);
|
||||
property_list_free(node->deadprops);
|
||||
|
||||
|
@ -434,6 +459,16 @@ struct device_node *__of_node_dup(const struct device_node *np,
|
|||
|
||||
static void __of_changeset_entry_destroy(struct of_changeset_entry *ce)
|
||||
{
|
||||
if (ce->action == OF_RECONFIG_ATTACH_NODE &&
|
||||
of_node_check_flag(ce->np, OF_OVERLAY)) {
|
||||
if (kref_read(&ce->np->kobj.kref) > 1) {
|
||||
pr_err("ERROR: memory leak, expected refcount 1 instead of %d, of_node_get()/of_node_put() unbalanced - destroy cset entry: attach overlay node %pOF\n",
|
||||
kref_read(&ce->np->kobj.kref), ce->np);
|
||||
} else {
|
||||
of_node_set_flag(ce->np, OF_OVERLAY_FREE_CSET);
|
||||
}
|
||||
}
|
||||
|
||||
of_node_put(ce->np);
|
||||
list_del(&ce->node);
|
||||
kfree(ce);
|
||||
|
|
|
@ -133,6 +133,9 @@ int __of_attach_node_sysfs(struct device_node *np)
|
|||
}
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
|
||||
of_node_get(np);
|
||||
|
||||
rc = kobject_add(&np->kobj, parent, "%s", name);
|
||||
kfree(name);
|
||||
if (rc)
|
||||
|
@ -159,6 +162,5 @@ void __of_detach_node_sysfs(struct device_node *np)
|
|||
kobject_del(&np->kobj);
|
||||
}
|
||||
|
||||
/* finally remove the kobj_init ref */
|
||||
of_node_put(np);
|
||||
}
|
||||
|
|
|
@ -23,14 +23,34 @@
|
|||
|
||||
#include "of_private.h"
|
||||
|
||||
/**
|
||||
* struct target - info about current target node as recursing through overlay
|
||||
* @np: node where current level of overlay will be applied
|
||||
* @in_livetree: @np is a node in the live devicetree
|
||||
*
|
||||
* Used in the algorithm to create the portion of a changeset that describes
|
||||
* an overlay fragment, which is a devicetree subtree. Initially @np is a node
|
||||
* in the live devicetree where the overlay subtree is targeted to be grafted
|
||||
* into. When recursing to the next level of the overlay subtree, the target
|
||||
* also recurses to the next level of the live devicetree, as long as overlay
|
||||
* subtree node also exists in the live devicetree. When a node in the overlay
|
||||
* subtree does not exist at the same level in the live devicetree, target->np
|
||||
* points to a newly allocated node, and all subsequent targets in the subtree
|
||||
* will be newly allocated nodes.
|
||||
*/
|
||||
struct target {
|
||||
struct device_node *np;
|
||||
bool in_livetree;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct fragment - info about fragment nodes in overlay expanded device tree
|
||||
* @target: target of the overlay operation
|
||||
* @overlay: pointer to the __overlay__ node
|
||||
*/
|
||||
struct fragment {
|
||||
struct device_node *target;
|
||||
struct device_node *overlay;
|
||||
struct device_node *target;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -72,8 +92,7 @@ static int devicetree_corrupt(void)
|
|||
}
|
||||
|
||||
static int build_changeset_next_level(struct overlay_changeset *ovcs,
|
||||
struct device_node *target_node,
|
||||
const struct device_node *overlay_node);
|
||||
struct target *target, const struct device_node *overlay_node);
|
||||
|
||||
/*
|
||||
* of_resolve_phandles() finds the largest phandle in the live tree.
|
||||
|
@ -257,15 +276,23 @@ static struct property *dup_and_fixup_symbol_prop(
|
|||
/**
|
||||
* add_changeset_property() - add @overlay_prop to overlay changeset
|
||||
* @ovcs: overlay changeset
|
||||
* @target_node: where to place @overlay_prop in live tree
|
||||
* @target: where @overlay_prop will be placed
|
||||
* @overlay_prop: property to add or update, from overlay tree
|
||||
* @is_symbols_prop: 1 if @overlay_prop is from node "/__symbols__"
|
||||
*
|
||||
* If @overlay_prop does not already exist in @target_node, add changeset entry
|
||||
* to add @overlay_prop in @target_node, else add changeset entry to update
|
||||
* If @overlay_prop does not already exist in live devicetree, add changeset
|
||||
* entry to add @overlay_prop in @target, else add changeset entry to update
|
||||
* value of @overlay_prop.
|
||||
*
|
||||
* Some special properties are not updated (no error returned).
|
||||
* @target may be either in the live devicetree or in a new subtree that
|
||||
* is contained in the changeset.
|
||||
*
|
||||
* Some special properties are not added or updated (no error returned):
|
||||
* "name", "phandle", "linux,phandle".
|
||||
*
|
||||
* Properties "#address-cells" and "#size-cells" are not updated if they
|
||||
* are already in the live tree, but if present in the live tree, the values
|
||||
* in the overlay must match the values in the live tree.
|
||||
*
|
||||
* Update of property in symbols node is not allowed.
|
||||
*
|
||||
|
@ -273,20 +300,24 @@ static struct property *dup_and_fixup_symbol_prop(
|
|||
* invalid @overlay.
|
||||
*/
|
||||
static int add_changeset_property(struct overlay_changeset *ovcs,
|
||||
struct device_node *target_node,
|
||||
struct property *overlay_prop,
|
||||
struct target *target, struct property *overlay_prop,
|
||||
bool is_symbols_prop)
|
||||
{
|
||||
struct property *new_prop = NULL, *prop;
|
||||
int ret = 0;
|
||||
bool check_for_non_overlay_node = false;
|
||||
|
||||
prop = of_find_property(target_node, overlay_prop->name, NULL);
|
||||
|
||||
if (target->in_livetree)
|
||||
if (!of_prop_cmp(overlay_prop->name, "name") ||
|
||||
!of_prop_cmp(overlay_prop->name, "phandle") ||
|
||||
!of_prop_cmp(overlay_prop->name, "linux,phandle"))
|
||||
return 0;
|
||||
|
||||
if (target->in_livetree)
|
||||
prop = of_find_property(target->np, overlay_prop->name, NULL);
|
||||
else
|
||||
prop = NULL;
|
||||
|
||||
if (is_symbols_prop) {
|
||||
if (prop)
|
||||
return -EINVAL;
|
||||
|
@ -298,12 +329,36 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
|
|||
if (!new_prop)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!prop)
|
||||
ret = of_changeset_add_property(&ovcs->cset, target_node,
|
||||
if (!prop) {
|
||||
check_for_non_overlay_node = true;
|
||||
if (!target->in_livetree) {
|
||||
new_prop->next = target->np->deadprops;
|
||||
target->np->deadprops = new_prop;
|
||||
}
|
||||
ret = of_changeset_add_property(&ovcs->cset, target->np,
|
||||
new_prop);
|
||||
else
|
||||
ret = of_changeset_update_property(&ovcs->cset, target_node,
|
||||
} else if (!of_prop_cmp(prop->name, "#address-cells")) {
|
||||
if (!of_prop_val_eq(prop, new_prop)) {
|
||||
pr_err("ERROR: changing value of #address-cells is not allowed in %pOF\n",
|
||||
target->np);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
} else if (!of_prop_cmp(prop->name, "#size-cells")) {
|
||||
if (!of_prop_val_eq(prop, new_prop)) {
|
||||
pr_err("ERROR: changing value of #size-cells is not allowed in %pOF\n",
|
||||
target->np);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
} else {
|
||||
check_for_non_overlay_node = true;
|
||||
ret = of_changeset_update_property(&ovcs->cset, target->np,
|
||||
new_prop);
|
||||
}
|
||||
|
||||
if (check_for_non_overlay_node &&
|
||||
!of_node_check_flag(target->np, OF_OVERLAY))
|
||||
pr_err("WARNING: memory leak will occur if overlay removed, property: %pOF/%s\n",
|
||||
target->np, new_prop->name);
|
||||
|
||||
if (ret) {
|
||||
kfree(new_prop->name);
|
||||
|
@ -316,13 +371,13 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
|
|||
/**
|
||||
* add_changeset_node() - add @node (and children) to overlay changeset
|
||||
* @ovcs: overlay changeset
|
||||
* @target_node: where to place @node in live tree
|
||||
* @target: where @node will be placed in live tree or changeset
|
||||
* @node: node from within overlay device tree fragment
|
||||
*
|
||||
* If @node does not already exist in @target_node, add changeset entry
|
||||
* to add @node in @target_node.
|
||||
* If @node does not already exist in @target, add changeset entry
|
||||
* to add @node in @target.
|
||||
*
|
||||
* If @node already exists in @target_node, and the existing node has
|
||||
* If @node already exists in @target, and the existing node has
|
||||
* a phandle, the overlay node is not allowed to have a phandle.
|
||||
*
|
||||
* If @node has child nodes, add the children recursively via
|
||||
|
@ -342,49 +397,65 @@ static int add_changeset_property(struct overlay_changeset *ovcs,
|
|||
* a live devicetree created from Open Firmware.
|
||||
*
|
||||
* NOTE_2: Multiple mods of created nodes not supported.
|
||||
* If more than one fragment contains a node that does not already exist
|
||||
* in the live tree, then for each fragment of_changeset_attach_node()
|
||||
* will add a changeset entry to add the node. When the changeset is
|
||||
* applied, __of_attach_node() will attach the node twice (once for
|
||||
* each fragment). At this point the device tree will be corrupted.
|
||||
*
|
||||
* TODO: add integrity check to ensure that multiple fragments do not
|
||||
* create the same node.
|
||||
*
|
||||
* Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if
|
||||
* invalid @overlay.
|
||||
*/
|
||||
static int add_changeset_node(struct overlay_changeset *ovcs,
|
||||
struct device_node *target_node, struct device_node *node)
|
||||
struct target *target, struct device_node *node)
|
||||
{
|
||||
const char *node_kbasename;
|
||||
const __be32 *phandle;
|
||||
struct device_node *tchild;
|
||||
int ret = 0;
|
||||
struct target target_child;
|
||||
int ret = 0, size;
|
||||
|
||||
node_kbasename = kbasename(node->full_name);
|
||||
|
||||
for_each_child_of_node(target_node, tchild)
|
||||
for_each_child_of_node(target->np, tchild)
|
||||
if (!of_node_cmp(node_kbasename, kbasename(tchild->full_name)))
|
||||
break;
|
||||
|
||||
if (!tchild) {
|
||||
tchild = __of_node_dup(node, node_kbasename);
|
||||
tchild = __of_node_dup(NULL, node_kbasename);
|
||||
if (!tchild)
|
||||
return -ENOMEM;
|
||||
|
||||
tchild->parent = target_node;
|
||||
tchild->parent = target->np;
|
||||
tchild->name = __of_get_property(node, "name", NULL);
|
||||
tchild->type = __of_get_property(node, "device_type", NULL);
|
||||
|
||||
if (!tchild->name)
|
||||
tchild->name = "<NULL>";
|
||||
if (!tchild->type)
|
||||
tchild->type = "<NULL>";
|
||||
|
||||
/* ignore obsolete "linux,phandle" */
|
||||
phandle = __of_get_property(node, "phandle", &size);
|
||||
if (phandle && (size == 4))
|
||||
tchild->phandle = be32_to_cpup(phandle);
|
||||
|
||||
of_node_set_flag(tchild, OF_OVERLAY);
|
||||
|
||||
ret = of_changeset_attach_node(&ovcs->cset, tchild);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return build_changeset_next_level(ovcs, tchild, node);
|
||||
target_child.np = tchild;
|
||||
target_child.in_livetree = false;
|
||||
|
||||
ret = build_changeset_next_level(ovcs, &target_child, node);
|
||||
of_node_put(tchild);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (node->phandle && tchild->phandle)
|
||||
if (node->phandle && tchild->phandle) {
|
||||
ret = -EINVAL;
|
||||
else
|
||||
ret = build_changeset_next_level(ovcs, tchild, node);
|
||||
} else {
|
||||
target_child.np = tchild;
|
||||
target_child.in_livetree = target->in_livetree;
|
||||
ret = build_changeset_next_level(ovcs, &target_child, node);
|
||||
}
|
||||
of_node_put(tchild);
|
||||
|
||||
return ret;
|
||||
|
@ -393,7 +464,7 @@ static int add_changeset_node(struct overlay_changeset *ovcs,
|
|||
/**
|
||||
* build_changeset_next_level() - add level of overlay changeset
|
||||
* @ovcs: overlay changeset
|
||||
* @target_node: where to place @overlay_node in live tree
|
||||
* @target: where to place @overlay_node in live tree
|
||||
* @overlay_node: node from within an overlay device tree fragment
|
||||
*
|
||||
* Add the properties (if any) and nodes (if any) from @overlay_node to the
|
||||
|
@ -406,27 +477,26 @@ static int add_changeset_node(struct overlay_changeset *ovcs,
|
|||
* invalid @overlay_node.
|
||||
*/
|
||||
static int build_changeset_next_level(struct overlay_changeset *ovcs,
|
||||
struct device_node *target_node,
|
||||
const struct device_node *overlay_node)
|
||||
struct target *target, const struct device_node *overlay_node)
|
||||
{
|
||||
struct device_node *child;
|
||||
struct property *prop;
|
||||
int ret;
|
||||
|
||||
for_each_property_of_node(overlay_node, prop) {
|
||||
ret = add_changeset_property(ovcs, target_node, prop, 0);
|
||||
ret = add_changeset_property(ovcs, target, prop, 0);
|
||||
if (ret) {
|
||||
pr_debug("Failed to apply prop @%pOF/%s, err=%d\n",
|
||||
target_node, prop->name, ret);
|
||||
target->np, prop->name, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
for_each_child_of_node(overlay_node, child) {
|
||||
ret = add_changeset_node(ovcs, target_node, child);
|
||||
ret = add_changeset_node(ovcs, target, child);
|
||||
if (ret) {
|
||||
pr_debug("Failed to apply node @%pOF/%pOFn, err=%d\n",
|
||||
target_node, child, ret);
|
||||
target->np, child, ret);
|
||||
of_node_put(child);
|
||||
return ret;
|
||||
}
|
||||
|
@ -439,17 +509,17 @@ static int build_changeset_next_level(struct overlay_changeset *ovcs,
|
|||
* Add the properties from __overlay__ node to the @ovcs->cset changeset.
|
||||
*/
|
||||
static int build_changeset_symbols_node(struct overlay_changeset *ovcs,
|
||||
struct device_node *target_node,
|
||||
struct target *target,
|
||||
const struct device_node *overlay_symbols_node)
|
||||
{
|
||||
struct property *prop;
|
||||
int ret;
|
||||
|
||||
for_each_property_of_node(overlay_symbols_node, prop) {
|
||||
ret = add_changeset_property(ovcs, target_node, prop, 1);
|
||||
ret = add_changeset_property(ovcs, target, prop, 1);
|
||||
if (ret) {
|
||||
pr_debug("Failed to apply prop @%pOF/%s, err=%d\n",
|
||||
target_node, prop->name, ret);
|
||||
pr_debug("Failed to apply symbols prop @%pOF/%s, err=%d\n",
|
||||
target->np, prop->name, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -457,6 +527,98 @@ static int build_changeset_symbols_node(struct overlay_changeset *ovcs,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int find_dup_cset_node_entry(struct overlay_changeset *ovcs,
|
||||
struct of_changeset_entry *ce_1)
|
||||
{
|
||||
struct of_changeset_entry *ce_2;
|
||||
char *fn_1, *fn_2;
|
||||
int node_path_match;
|
||||
|
||||
if (ce_1->action != OF_RECONFIG_ATTACH_NODE &&
|
||||
ce_1->action != OF_RECONFIG_DETACH_NODE)
|
||||
return 0;
|
||||
|
||||
ce_2 = ce_1;
|
||||
list_for_each_entry_continue(ce_2, &ovcs->cset.entries, node) {
|
||||
if ((ce_2->action != OF_RECONFIG_ATTACH_NODE &&
|
||||
ce_2->action != OF_RECONFIG_DETACH_NODE) ||
|
||||
of_node_cmp(ce_1->np->full_name, ce_2->np->full_name))
|
||||
continue;
|
||||
|
||||
fn_1 = kasprintf(GFP_KERNEL, "%pOF", ce_1->np);
|
||||
fn_2 = kasprintf(GFP_KERNEL, "%pOF", ce_2->np);
|
||||
node_path_match = !strcmp(fn_1, fn_2);
|
||||
kfree(fn_1);
|
||||
kfree(fn_2);
|
||||
if (node_path_match) {
|
||||
pr_err("ERROR: multiple fragments add and/or delete node %pOF\n",
|
||||
ce_1->np);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_dup_cset_prop(struct overlay_changeset *ovcs,
|
||||
struct of_changeset_entry *ce_1)
|
||||
{
|
||||
struct of_changeset_entry *ce_2;
|
||||
char *fn_1, *fn_2;
|
||||
int node_path_match;
|
||||
|
||||
if (ce_1->action != OF_RECONFIG_ADD_PROPERTY &&
|
||||
ce_1->action != OF_RECONFIG_REMOVE_PROPERTY &&
|
||||
ce_1->action != OF_RECONFIG_UPDATE_PROPERTY)
|
||||
return 0;
|
||||
|
||||
ce_2 = ce_1;
|
||||
list_for_each_entry_continue(ce_2, &ovcs->cset.entries, node) {
|
||||
if ((ce_2->action != OF_RECONFIG_ADD_PROPERTY &&
|
||||
ce_2->action != OF_RECONFIG_REMOVE_PROPERTY &&
|
||||
ce_2->action != OF_RECONFIG_UPDATE_PROPERTY) ||
|
||||
of_node_cmp(ce_1->np->full_name, ce_2->np->full_name))
|
||||
continue;
|
||||
|
||||
fn_1 = kasprintf(GFP_KERNEL, "%pOF", ce_1->np);
|
||||
fn_2 = kasprintf(GFP_KERNEL, "%pOF", ce_2->np);
|
||||
node_path_match = !strcmp(fn_1, fn_2);
|
||||
kfree(fn_1);
|
||||
kfree(fn_2);
|
||||
if (node_path_match &&
|
||||
!of_prop_cmp(ce_1->prop->name, ce_2->prop->name)) {
|
||||
pr_err("ERROR: multiple fragments add, update, and/or delete property %pOF/%s\n",
|
||||
ce_1->np, ce_1->prop->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* changeset_dup_entry_check() - check for duplicate entries
|
||||
* @ovcs: Overlay changeset
|
||||
*
|
||||
* Check changeset @ovcs->cset for multiple {add or delete} node entries for
|
||||
* the same node or duplicate {add, delete, or update} properties entries
|
||||
* for the same property.
|
||||
*
|
||||
* Returns 0 on success, or -EINVAL if duplicate changeset entry found.
|
||||
*/
|
||||
static int changeset_dup_entry_check(struct overlay_changeset *ovcs)
|
||||
{
|
||||
struct of_changeset_entry *ce_1;
|
||||
int dup_entry = 0;
|
||||
|
||||
list_for_each_entry(ce_1, &ovcs->cset.entries, node) {
|
||||
dup_entry |= find_dup_cset_node_entry(ovcs, ce_1);
|
||||
dup_entry |= find_dup_cset_prop(ovcs, ce_1);
|
||||
}
|
||||
|
||||
return dup_entry ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* build_changeset() - populate overlay changeset in @ovcs from @ovcs->fragments
|
||||
* @ovcs: Overlay changeset
|
||||
|
@ -472,6 +634,7 @@ static int build_changeset_symbols_node(struct overlay_changeset *ovcs,
|
|||
static int build_changeset(struct overlay_changeset *ovcs)
|
||||
{
|
||||
struct fragment *fragment;
|
||||
struct target target;
|
||||
int fragments_count, i, ret;
|
||||
|
||||
/*
|
||||
|
@ -486,25 +649,32 @@ static int build_changeset(struct overlay_changeset *ovcs)
|
|||
for (i = 0; i < fragments_count; i++) {
|
||||
fragment = &ovcs->fragments[i];
|
||||
|
||||
ret = build_changeset_next_level(ovcs, fragment->target,
|
||||
target.np = fragment->target;
|
||||
target.in_livetree = true;
|
||||
ret = build_changeset_next_level(ovcs, &target,
|
||||
fragment->overlay);
|
||||
if (ret) {
|
||||
pr_debug("apply failed '%pOF'\n", fragment->target);
|
||||
pr_debug("fragment apply failed '%pOF'\n",
|
||||
fragment->target);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (ovcs->symbols_fragment) {
|
||||
fragment = &ovcs->fragments[ovcs->count - 1];
|
||||
ret = build_changeset_symbols_node(ovcs, fragment->target,
|
||||
|
||||
target.np = fragment->target;
|
||||
target.in_livetree = true;
|
||||
ret = build_changeset_symbols_node(ovcs, &target,
|
||||
fragment->overlay);
|
||||
if (ret) {
|
||||
pr_debug("apply failed '%pOF'\n", fragment->target);
|
||||
pr_debug("symbols fragment apply failed '%pOF'\n",
|
||||
fragment->target);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return changeset_dup_entry_check(ovcs);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -514,7 +684,7 @@ static int build_changeset(struct overlay_changeset *ovcs)
|
|||
* 1) "target" property containing the phandle of the target
|
||||
* 2) "target-path" property containing the path of the target
|
||||
*/
|
||||
static struct device_node *find_target_node(struct device_node *info_node)
|
||||
static struct device_node *find_target(struct device_node *info_node)
|
||||
{
|
||||
struct device_node *node;
|
||||
const char *path;
|
||||
|
@ -620,7 +790,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
|
|||
|
||||
fragment = &fragments[cnt];
|
||||
fragment->overlay = overlay_node;
|
||||
fragment->target = find_target_node(node);
|
||||
fragment->target = find_target(node);
|
||||
if (!fragment->target) {
|
||||
of_node_put(fragment->overlay);
|
||||
ret = -EINVAL;
|
||||
|
@ -808,7 +978,7 @@ static int of_overlay_apply(const void *fdt, struct device_node *tree,
|
|||
|
||||
ret = __of_changeset_apply_notify(&ovcs->cset);
|
||||
if (ret)
|
||||
pr_err("overlay changeset entry notify error %d\n", ret);
|
||||
pr_err("overlay apply changeset entry notify error %d\n", ret);
|
||||
/* notify failure is not fatal, continue */
|
||||
|
||||
list_add_tail(&ovcs->ovcs_list, &ovcs_list);
|
||||
|
@ -1067,7 +1237,7 @@ int of_overlay_remove(int *ovcs_id)
|
|||
|
||||
ret = __of_changeset_revert_notify(&ovcs->cset);
|
||||
if (ret)
|
||||
pr_err("overlay changeset entry notify error %d\n", ret);
|
||||
pr_err("overlay remove changeset entry notify error %d\n", ret);
|
||||
/* notify failure is not fatal, continue */
|
||||
|
||||
*ovcs_id = 0;
|
||||
|
|
|
@ -17,6 +17,8 @@ obj-$(CONFIG_OF_OVERLAY) += overlay.dtb.o \
|
|||
overlay_12.dtb.o \
|
||||
overlay_13.dtb.o \
|
||||
overlay_15.dtb.o \
|
||||
overlay_bad_add_dup_node.dtb.o \
|
||||
overlay_bad_add_dup_prop.dtb.o \
|
||||
overlay_bad_phandle.dtb.o \
|
||||
overlay_bad_symbol.dtb.o \
|
||||
overlay_base.dtb.o
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/*
|
||||
* &electric_1/motor-1 and &spin_ctrl_1 are the same node:
|
||||
* /testcase-data-2/substation@100/motor-1
|
||||
*
|
||||
* Thus the new node "controller" in each fragment will
|
||||
* result in an attempt to add the same node twice.
|
||||
* This will result in an error and the overlay apply
|
||||
* will fail.
|
||||
*/
|
||||
|
||||
&electric_1 {
|
||||
|
||||
motor-1 {
|
||||
controller {
|
||||
power_bus = < 0x1 0x2 >;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&spin_ctrl_1 {
|
||||
controller {
|
||||
power_bus_emergency = < 0x101 0x102 >;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/*
|
||||
* &electric_1/motor-1 and &spin_ctrl_1 are the same node:
|
||||
* /testcase-data-2/substation@100/motor-1
|
||||
*
|
||||
* Thus the property "rpm_avail" in each fragment will
|
||||
* result in an attempt to update the same property twice.
|
||||
* This will result in an error and the overlay apply
|
||||
* will fail.
|
||||
*/
|
||||
|
||||
&electric_1 {
|
||||
|
||||
motor-1 {
|
||||
rpm_avail = < 100 >;
|
||||
};
|
||||
};
|
||||
|
||||
&spin_ctrl_1 {
|
||||
rpm_avail = < 100 200 >;
|
||||
};
|
|
@ -30,6 +30,7 @@ hvac_1: hvac-medium-1 {
|
|||
spin_ctrl_1: motor-1 {
|
||||
compatible = "ot,ferris-wheel-motor";
|
||||
spin = "clockwise";
|
||||
rpm_avail = < 50 >;
|
||||
};
|
||||
|
||||
spin_ctrl_2: motor-8 {
|
||||
|
|
|
@ -379,6 +379,7 @@ static void __init of_unittest_parse_phandle_with_args(void)
|
|||
for (i = 0; i < 8; i++) {
|
||||
bool passed = true;
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list",
|
||||
"#phandle-cells", i, &args);
|
||||
|
||||
|
@ -432,6 +433,7 @@ static void __init of_unittest_parse_phandle_with_args(void)
|
|||
}
|
||||
|
||||
/* Check for missing list property */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list-missing",
|
||||
"#phandle-cells", 0, &args);
|
||||
unittest(rc == -ENOENT, "expected:%i got:%i\n", -ENOENT, rc);
|
||||
|
@ -440,6 +442,7 @@ static void __init of_unittest_parse_phandle_with_args(void)
|
|||
unittest(rc == -ENOENT, "expected:%i got:%i\n", -ENOENT, rc);
|
||||
|
||||
/* Check for missing cells property */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list",
|
||||
"#phandle-cells-missing", 0, &args);
|
||||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
@ -448,6 +451,7 @@ static void __init of_unittest_parse_phandle_with_args(void)
|
|||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
||||
/* Check for bad phandle in list */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list-bad-phandle",
|
||||
"#phandle-cells", 0, &args);
|
||||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
@ -456,6 +460,7 @@ static void __init of_unittest_parse_phandle_with_args(void)
|
|||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
||||
/* Check for incorrectly formed argument list */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list-bad-args",
|
||||
"#phandle-cells", 1, &args);
|
||||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
@ -506,6 +511,7 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
|
|||
for (i = 0; i < 8; i++) {
|
||||
bool passed = true;
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args_map(np, "phandle-list",
|
||||
"phandle", i, &args);
|
||||
|
||||
|
@ -563,21 +569,25 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
|
|||
}
|
||||
|
||||
/* Check for missing list property */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args_map(np, "phandle-list-missing",
|
||||
"phandle", 0, &args);
|
||||
unittest(rc == -ENOENT, "expected:%i got:%i\n", -ENOENT, rc);
|
||||
|
||||
/* Check for missing cells,map,mask property */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args_map(np, "phandle-list",
|
||||
"phandle-missing", 0, &args);
|
||||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
||||
/* Check for bad phandle in list */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args_map(np, "phandle-list-bad-phandle",
|
||||
"phandle", 0, &args);
|
||||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
||||
/* Check for incorrectly formed argument list */
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_parse_phandle_with_args_map(np, "phandle-list-bad-args",
|
||||
"phandle", 1, &args);
|
||||
unittest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
@ -787,7 +797,7 @@ static void __init of_unittest_parse_interrupts(void)
|
|||
for (i = 0; i < 4; i++) {
|
||||
bool passed = true;
|
||||
|
||||
args.args_count = 0;
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_irq_parse_one(np, i, &args);
|
||||
|
||||
passed &= !rc;
|
||||
|
@ -808,7 +818,7 @@ static void __init of_unittest_parse_interrupts(void)
|
|||
for (i = 0; i < 4; i++) {
|
||||
bool passed = true;
|
||||
|
||||
args.args_count = 0;
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_irq_parse_one(np, i, &args);
|
||||
|
||||
/* Test the values from tests-phandle.dtsi */
|
||||
|
@ -864,6 +874,7 @@ static void __init of_unittest_parse_interrupts_extended(void)
|
|||
for (i = 0; i < 7; i++) {
|
||||
bool passed = true;
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
rc = of_irq_parse_one(np, i, &args);
|
||||
|
||||
/* Test the values from tests-phandle.dtsi */
|
||||
|
@ -1071,20 +1082,44 @@ static void __init of_unittest_platform_populate(void)
|
|||
* of np into dup node (present in live tree) and
|
||||
* updates parent of children of np to dup.
|
||||
*
|
||||
* @np: node already present in live tree
|
||||
* @np: node whose properties are being added to the live tree
|
||||
* @dup: node present in live tree to be updated
|
||||
*/
|
||||
static void update_node_properties(struct device_node *np,
|
||||
struct device_node *dup)
|
||||
{
|
||||
struct property *prop;
|
||||
struct property *save_next;
|
||||
struct device_node *child;
|
||||
|
||||
for_each_property_of_node(np, prop)
|
||||
of_add_property(dup, prop);
|
||||
int ret;
|
||||
|
||||
for_each_child_of_node(np, child)
|
||||
child->parent = dup;
|
||||
|
||||
/*
|
||||
* "unittest internal error: unable to add testdata property"
|
||||
*
|
||||
* If this message reports a property in node '/__symbols__' then
|
||||
* the respective unittest overlay contains a label that has the
|
||||
* same name as a label in the live devicetree. The label will
|
||||
* be in the live devicetree only if the devicetree source was
|
||||
* compiled with the '-@' option. If you encounter this error,
|
||||
* please consider renaming __all__ of the labels in the unittest
|
||||
* overlay dts files with an odd prefix that is unlikely to be
|
||||
* used in a real devicetree.
|
||||
*/
|
||||
|
||||
/*
|
||||
* open code for_each_property_of_node() because of_add_property()
|
||||
* sets prop->next to NULL
|
||||
*/
|
||||
for (prop = np->properties; prop != NULL; prop = save_next) {
|
||||
save_next = prop->next;
|
||||
ret = of_add_property(dup, prop);
|
||||
if (ret)
|
||||
pr_err("unittest internal error: unable to add testdata property %pOF/%s",
|
||||
np, prop->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1093,18 +1128,23 @@ static void update_node_properties(struct device_node *np,
|
|||
*
|
||||
* @np: Node to attach to live tree
|
||||
*/
|
||||
static int attach_node_and_children(struct device_node *np)
|
||||
static void attach_node_and_children(struct device_node *np)
|
||||
{
|
||||
struct device_node *next, *dup, *child;
|
||||
unsigned long flags;
|
||||
const char *full_name;
|
||||
|
||||
full_name = kasprintf(GFP_KERNEL, "%pOF", np);
|
||||
|
||||
if (!strcmp(full_name, "/__local_fixups__") ||
|
||||
!strcmp(full_name, "/__fixups__"))
|
||||
return;
|
||||
|
||||
dup = of_find_node_by_path(full_name);
|
||||
kfree(full_name);
|
||||
if (dup) {
|
||||
update_node_properties(np, dup);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
child = np->child;
|
||||
|
@ -1125,8 +1165,6 @@ static int attach_node_and_children(struct device_node *np)
|
|||
attach_node_and_children(child);
|
||||
child = next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1433,8 +1471,7 @@ static void of_unittest_destroy_tracked_overlays(void)
|
|||
} while (defers > 0);
|
||||
}
|
||||
|
||||
static int __init of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
|
||||
int *overlay_id)
|
||||
static int __init of_unittest_apply_overlay(int overlay_nr, int *overlay_id)
|
||||
{
|
||||
const char *overlay_name;
|
||||
|
||||
|
@ -1467,7 +1504,7 @@ static int __init of_unittest_apply_overlay_check(int overlay_nr,
|
|||
}
|
||||
|
||||
ovcs_id = 0;
|
||||
ret = of_unittest_apply_overlay(overlay_nr, unittest_nr, &ovcs_id);
|
||||
ret = of_unittest_apply_overlay(overlay_nr, &ovcs_id);
|
||||
if (ret != 0) {
|
||||
/* of_unittest_apply_overlay already called unittest() */
|
||||
return ret;
|
||||
|
@ -1503,7 +1540,7 @@ static int __init of_unittest_apply_revert_overlay_check(int overlay_nr,
|
|||
|
||||
/* apply the overlay */
|
||||
ovcs_id = 0;
|
||||
ret = of_unittest_apply_overlay(overlay_nr, unittest_nr, &ovcs_id);
|
||||
ret = of_unittest_apply_overlay(overlay_nr, &ovcs_id);
|
||||
if (ret != 0) {
|
||||
/* of_unittest_apply_overlay already called unittest() */
|
||||
return ret;
|
||||
|
@ -2161,10 +2198,12 @@ OVERLAY_INFO_EXTERN(overlay_11);
|
|||
OVERLAY_INFO_EXTERN(overlay_12);
|
||||
OVERLAY_INFO_EXTERN(overlay_13);
|
||||
OVERLAY_INFO_EXTERN(overlay_15);
|
||||
OVERLAY_INFO_EXTERN(overlay_bad_add_dup_node);
|
||||
OVERLAY_INFO_EXTERN(overlay_bad_add_dup_prop);
|
||||
OVERLAY_INFO_EXTERN(overlay_bad_phandle);
|
||||
OVERLAY_INFO_EXTERN(overlay_bad_symbol);
|
||||
|
||||
/* order of entries is hard-coded into users of overlays[] */
|
||||
/* entries found by name */
|
||||
static struct overlay_info overlays[] = {
|
||||
OVERLAY_INFO(overlay_base, -9999),
|
||||
OVERLAY_INFO(overlay, 0),
|
||||
|
@ -2183,9 +2222,12 @@ static struct overlay_info overlays[] = {
|
|||
OVERLAY_INFO(overlay_12, 0),
|
||||
OVERLAY_INFO(overlay_13, 0),
|
||||
OVERLAY_INFO(overlay_15, 0),
|
||||
OVERLAY_INFO(overlay_bad_add_dup_node, -EINVAL),
|
||||
OVERLAY_INFO(overlay_bad_add_dup_prop, -EINVAL),
|
||||
OVERLAY_INFO(overlay_bad_phandle, -EINVAL),
|
||||
OVERLAY_INFO(overlay_bad_symbol, -EINVAL),
|
||||
{}
|
||||
/* end marker */
|
||||
{.dtb_begin = NULL, .dtb_end = NULL, .expected_result = 0, .name = NULL}
|
||||
};
|
||||
|
||||
static struct device_node *overlay_base_root;
|
||||
|
@ -2215,6 +2257,19 @@ void __init unittest_unflatten_overlay_base(void)
|
|||
u32 data_size;
|
||||
void *new_fdt;
|
||||
u32 size;
|
||||
int found = 0;
|
||||
const char *overlay_name = "overlay_base";
|
||||
|
||||
for (info = overlays; info && info->name; info++) {
|
||||
if (!strcmp(overlay_name, info->name)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
pr_err("no overlay data for %s\n", overlay_name);
|
||||
return;
|
||||
}
|
||||
|
||||
info = &overlays[0];
|
||||
|
||||
|
@ -2262,11 +2317,10 @@ static int __init overlay_data_apply(const char *overlay_name, int *overlay_id)
|
|||
{
|
||||
struct overlay_info *info;
|
||||
int found = 0;
|
||||
int k;
|
||||
int ret;
|
||||
u32 size;
|
||||
|
||||
for (k = 0, info = overlays; info && info->name; info++, k++) {
|
||||
for (info = overlays; info && info->name; info++) {
|
||||
if (!strcmp(overlay_name, info->name)) {
|
||||
found = 1;
|
||||
break;
|
||||
|
@ -2430,6 +2484,12 @@ static __init void of_unittest_overlay_high_level(void)
|
|||
unittest(overlay_data_apply("overlay", NULL),
|
||||
"Adding overlay 'overlay' failed\n");
|
||||
|
||||
unittest(overlay_data_apply("overlay_bad_add_dup_node", NULL),
|
||||
"Adding overlay 'overlay_bad_add_dup_node' failed\n");
|
||||
|
||||
unittest(overlay_data_apply("overlay_bad_add_dup_prop", NULL),
|
||||
"Adding overlay 'overlay_bad_add_dup_prop' failed\n");
|
||||
|
||||
unittest(overlay_data_apply("overlay_bad_phandle", NULL),
|
||||
"Adding overlay 'overlay_bad_phandle' failed\n");
|
||||
|
||||
|
|
|
@ -138,11 +138,16 @@ extern struct device_node *of_aliases;
|
|||
extern struct device_node *of_stdout;
|
||||
extern raw_spinlock_t devtree_lock;
|
||||
|
||||
/* flag descriptions (need to be visible even when !CONFIG_OF) */
|
||||
#define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
|
||||
#define OF_DETACHED 2 /* node has been detached from the device tree */
|
||||
#define OF_POPULATED 3 /* device already created for the node */
|
||||
#define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */
|
||||
/*
|
||||
* struct device_node flag descriptions
|
||||
* (need to be visible even when !CONFIG_OF)
|
||||
*/
|
||||
#define OF_DYNAMIC 1 /* (and properties) allocated via kmalloc */
|
||||
#define OF_DETACHED 2 /* detached from the device tree */
|
||||
#define OF_POPULATED 3 /* device already created */
|
||||
#define OF_POPULATED_BUS 4 /* platform bus created for children */
|
||||
#define OF_OVERLAY 5 /* allocated for an overlay */
|
||||
#define OF_OVERLAY_FREE_CSET 6 /* in overlay cset being freed */
|
||||
|
||||
#define OF_BAD_ADDR ((u64)-1)
|
||||
|
||||
|
@ -985,6 +990,12 @@ static inline int of_map_rid(struct device_node *np, u32 rid,
|
|||
#define of_node_cmp(s1, s2) strcasecmp((s1), (s2))
|
||||
#endif
|
||||
|
||||
static inline int of_prop_val_eq(struct property *p1, struct property *p2)
|
||||
{
|
||||
return p1->length == p2->length &&
|
||||
!memcmp(p1->value, p2->value, (size_t)p1->length);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_OF) && defined(CONFIG_NUMA)
|
||||
extern int of_node_to_nid(struct device_node *np);
|
||||
#else
|
||||
|
|
Loading…
Reference in New Issue