diff options
Diffstat (limited to 'drivers/of/overlay.c')
-rw-r--r-- | drivers/of/overlay.c | 1049 |
1 files changed, 672 insertions, 377 deletions
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 8ecfee31ab6d..c150abb9049d 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -25,252 +25,378 @@ #include "of_private.h" /** - * struct of_overlay_info - Holds a single overlay info + * struct fragment - info about fragment nodes in overlay expanded device tree * @target: target of the overlay operation - * @overlay: pointer to the overlay contents node - * - * Holds a single overlay state, including all the overlay logs & - * records. + * @overlay: pointer to the __overlay__ node */ -struct of_overlay_info { +struct fragment { struct device_node *target; struct device_node *overlay; - bool is_symbols_node; }; /** - * struct of_overlay - Holds a complete overlay transaction - * @node: List on which we are located - * @count: Count of ovinfo structures - * @ovinfo_tab: Overlay info table (count sized) - * @cset: Changeset to be used - * - * Holds a complete overlay transaction + * struct overlay_changeset + * @ovcs_list: list on which we are located + * @overlay_tree: expanded device tree that contains the fragment nodes + * @count: count of fragment structures + * @fragments: fragment nodes in the overlay expanded device tree + * @symbols_fragment: last element of @fragments[] is the __symbols__ node + * @cset: changeset to apply fragments to live device tree */ -struct of_overlay { +struct overlay_changeset { int id; - struct list_head node; + struct list_head ovcs_list; + struct device_node *overlay_tree; int count; - struct of_overlay_info *ovinfo_tab; + struct fragment *fragments; + bool symbols_fragment; struct of_changeset cset; }; -static int of_overlay_apply_one(struct of_overlay *ov, - struct device_node *target, const struct device_node *overlay, - bool is_symbols_node); +/* flags are sticky - once set, do not reset */ +static int devicetree_state_flags; +#define DTSF_APPLY_FAIL 0x01 +#define DTSF_REVERT_FAIL 0x02 + +/* + * If a changeset apply or revert encounters an error, an attempt will + * be made to undo partial changes, but may fail. If the undo fails + * we do not know the state of the devicetree. + */ +static int devicetree_corrupt(void) +{ + return devicetree_state_flags & + (DTSF_APPLY_FAIL | DTSF_REVERT_FAIL); +} + +static int build_changeset_next_level(struct overlay_changeset *ovcs, + struct device_node *target_node, + const struct device_node *overlay_node); + +/* + * of_resolve_phandles() finds the largest phandle in the live tree. + * of_overlay_apply() may add a larger phandle to the live tree. + * Do not allow race between two overlays being applied simultaneously: + * mutex_lock(&of_overlay_phandle_mutex) + * of_resolve_phandles() + * of_overlay_apply() + * mutex_unlock(&of_overlay_phandle_mutex) + */ +static DEFINE_MUTEX(of_overlay_phandle_mutex); + +void of_overlay_mutex_lock(void) +{ + mutex_lock(&of_overlay_phandle_mutex); +} + +void of_overlay_mutex_unlock(void) +{ + mutex_unlock(&of_overlay_phandle_mutex); +} -static BLOCKING_NOTIFIER_HEAD(of_overlay_chain); + +static LIST_HEAD(ovcs_list); +static DEFINE_IDR(ovcs_idr); + +static BLOCKING_NOTIFIER_HEAD(overlay_notify_chain); int of_overlay_notifier_register(struct notifier_block *nb) { - return blocking_notifier_chain_register(&of_overlay_chain, nb); + return blocking_notifier_chain_register(&overlay_notify_chain, nb); } EXPORT_SYMBOL_GPL(of_overlay_notifier_register); int of_overlay_notifier_unregister(struct notifier_block *nb) { - return blocking_notifier_chain_unregister(&of_overlay_chain, nb); + return blocking_notifier_chain_unregister(&overlay_notify_chain, nb); } EXPORT_SYMBOL_GPL(of_overlay_notifier_unregister); -static int of_overlay_notify(struct of_overlay *ov, - enum of_overlay_notify_action action) +static char *of_overlay_action_name[] = { + "pre-apply", + "post-apply", + "pre-remove", + "post-remove", +}; + +static int overlay_notify(struct overlay_changeset *ovcs, + enum of_overlay_notify_action action) { struct of_overlay_notify_data nd; int i, ret; - for (i = 0; i < ov->count; i++) { - struct of_overlay_info *ovinfo = &ov->ovinfo_tab[i]; + for (i = 0; i < ovcs->count; i++) { + struct fragment *fragment = &ovcs->fragments[i]; - nd.target = ovinfo->target; - nd.overlay = ovinfo->overlay; + nd.target = fragment->target; + nd.overlay = fragment->overlay; - ret = blocking_notifier_call_chain(&of_overlay_chain, + ret = blocking_notifier_call_chain(&overlay_notify_chain, action, &nd); - if (ret) - return notifier_to_errno(ret); + if (ret == NOTIFY_OK || ret == NOTIFY_STOP) + return 0; + if (ret) { + ret = notifier_to_errno(ret); + pr_err("overlay changeset %s notifier error %d, target: %pOF\n", + of_overlay_action_name[action], ret, nd.target); + return ret; + } } return 0; } -static struct property *dup_and_fixup_symbol_prop(struct of_overlay *ov, - const struct property *prop) +/* + * The values of properties in the "/__symbols__" node are paths in + * the ovcs->overlay_tree. When duplicating the properties, the paths + * need to be adjusted to be the correct path for the live device tree. + * + * The paths refer to a node in the subtree of a fragment node's "__overlay__" + * node, for example "/fragment@0/__overlay__/symbol_path_tail", + * where symbol_path_tail can be a single node or it may be a multi-node path. + * + * The duplicated property value will be modified by replacing the + * "/fragment_name/__overlay/" portion of the value with the target + * path from the fragment node. + */ +static struct property *dup_and_fixup_symbol_prop( + struct overlay_changeset *ovcs, const struct property *prop) { - struct of_overlay_info *ovinfo; - struct property *new; - const char *overlay_name; - char *label_path; - char *symbol_path; + struct fragment *fragment; + struct property *new_prop; + struct device_node *fragment_node; + struct device_node *overlay_node; + const char *path; + const char *path_tail; const char *target_path; int k; - int label_path_len; int overlay_name_len; + int path_len; + int path_tail_len; int target_path_len; if (!prop->value) return NULL; - symbol_path = prop->value; - - new = kzalloc(sizeof(*new), GFP_KERNEL); - if (!new) + if (strnlen(prop->value, prop->length) >= prop->length) return NULL; + path = prop->value; + path_len = strlen(path); - for (k = 0; k < ov->count; k++) { - ovinfo = &ov->ovinfo_tab[k]; - overlay_name = ovinfo->overlay->full_name; - overlay_name_len = strlen(overlay_name); - if (!strncasecmp(symbol_path, overlay_name, overlay_name_len)) + if (path_len < 1) + return NULL; + fragment_node = __of_find_node_by_path(ovcs->overlay_tree, path + 1); + overlay_node = __of_find_node_by_path(fragment_node, "__overlay__/"); + of_node_put(fragment_node); + of_node_put(overlay_node); + + for (k = 0; k < ovcs->count; k++) { + fragment = &ovcs->fragments[k]; + if (fragment->overlay == overlay_node) break; } + if (k >= ovcs->count) + return NULL; + + overlay_name_len = snprintf(NULL, 0, "%pOF", fragment->overlay); - if (k >= ov->count) - goto err_free; + if (overlay_name_len > path_len) + return NULL; + path_tail = path + overlay_name_len; + path_tail_len = strlen(path_tail); - target_path = ovinfo->target->full_name; + target_path = kasprintf(GFP_KERNEL, "%pOF", fragment->target); + if (!target_path) + return NULL; target_path_len = strlen(target_path); - label_path = symbol_path + overlay_name_len; - label_path_len = strlen(label_path); + new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); + if (!new_prop) + goto err_free_target_path; - new->name = kstrdup(prop->name, GFP_KERNEL); - new->length = target_path_len + label_path_len + 1; - new->value = kzalloc(new->length, GFP_KERNEL); + new_prop->name = kstrdup(prop->name, GFP_KERNEL); + new_prop->length = target_path_len + path_tail_len + 1; + new_prop->value = kzalloc(new_prop->length, GFP_KERNEL); + if (!new_prop->name || !new_prop->value) + goto err_free_new_prop; - if (!new->name || !new->value) - goto err_free; + strcpy(new_prop->value, target_path); + strcpy(new_prop->value + target_path_len, path_tail); - strcpy(new->value, target_path); - strcpy(new->value + target_path_len, label_path); + of_property_set_flag(new_prop, OF_DYNAMIC); - /* mark the property as dynamic */ - of_property_set_flag(new, OF_DYNAMIC); + return new_prop; - return new; +err_free_new_prop: + kfree(new_prop->name); + kfree(new_prop->value); + kfree(new_prop); +err_free_target_path: + kfree(target_path); - err_free: - kfree(new->name); - kfree(new->value); - kfree(new); return NULL; - - } -static int of_overlay_apply_single_property(struct of_overlay *ov, - struct device_node *target, struct property *prop, - bool is_symbols_node) +/** + * add_changeset_property() - add @overlay_prop to overlay changeset + * @ovcs: overlay changeset + * @target_node: where to place @overlay_prop in live tree + * @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 + * value of @overlay_prop. + * + * Some special properties are not updated (no error returned). + * + * Update of property in symbols node is not allowed. + * + * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if + * invalid @overlay. + */ +static int add_changeset_property(struct overlay_changeset *ovcs, + struct device_node *target_node, + struct property *overlay_prop, + bool is_symbols_prop) { - struct property *propn = NULL, *tprop; + struct property *new_prop = NULL, *prop; + int ret = 0; - /* NOTE: Multiple changes of single properties not supported */ - tprop = of_find_property(target, prop->name, NULL); + prop = of_find_property(target_node, overlay_prop->name, NULL); - /* special properties are not meant to be updated (silent NOP) */ - if (of_prop_cmp(prop->name, "name") == 0 || - of_prop_cmp(prop->name, "phandle") == 0 || - of_prop_cmp(prop->name, "linux,phandle") == 0) + 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 (is_symbols_node) { - /* changing a property in __symbols__ node not allowed */ - if (tprop) + if (is_symbols_prop) { + if (prop) return -EINVAL; - propn = dup_and_fixup_symbol_prop(ov, prop); + new_prop = dup_and_fixup_symbol_prop(ovcs, overlay_prop); } else { - propn = __of_prop_dup(prop, GFP_KERNEL); + new_prop = __of_prop_dup(overlay_prop, GFP_KERNEL); } - if (propn == NULL) + if (!new_prop) return -ENOMEM; - /* not found? add */ - if (tprop == NULL) - return of_changeset_add_property(&ov->cset, target, propn); - - /* found? update */ - return of_changeset_update_property(&ov->cset, target, propn); + if (!prop) + ret = of_changeset_add_property(&ovcs->cset, target_node, + new_prop); + else + ret = of_changeset_update_property(&ovcs->cset, target_node, + new_prop); + + if (ret) { + kfree(new_prop->name); + kfree(new_prop->value); + kfree(new_prop); + } + return ret; } -static int of_overlay_apply_single_device_node(struct of_overlay *ov, - struct device_node *target, struct device_node *child) +/** + * add_changeset_node() - add @node (and children) to overlay changeset + * @ovcs: overlay changeset + * @target_node: where to place @node in live tree + * @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 already exists in @target_node, 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 + * build_changeset_next_level(). + * + * NOTE: 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) { - const char *cname; + const char *node_kbasename; struct device_node *tchild; int ret = 0; - cname = kbasename(child->full_name); - if (cname == NULL) - return -ENOMEM; + node_kbasename = kbasename(node->full_name); - /* NOTE: Multiple mods of created nodes not supported */ - for_each_child_of_node(target, tchild) - if (!of_node_cmp(cname, kbasename(tchild->full_name))) + for_each_child_of_node(target_node, tchild) + if (!of_node_cmp(node_kbasename, kbasename(tchild->full_name))) break; - if (tchild != NULL) { - /* new overlay phandle value conflicts with existing value */ - if (child->phandle) - return -EINVAL; - - /* apply overlay recursively */ - ret = of_overlay_apply_one(ov, tchild, child, 0); - of_node_put(tchild); - } else { - /* create empty tree as a target */ - tchild = __of_node_dup(child, "%pOF/%s", target, cname); + if (!tchild) { + tchild = __of_node_dup(node, "%pOF/%s", + target_node, node_kbasename); if (!tchild) return -ENOMEM; - /* point to parent */ - tchild->parent = target; + tchild->parent = target_node; - ret = of_changeset_attach_node(&ov->cset, tchild); + ret = of_changeset_attach_node(&ovcs->cset, tchild); if (ret) return ret; - ret = of_overlay_apply_one(ov, tchild, child, 0); - if (ret) - return ret; + return build_changeset_next_level(ovcs, tchild, node); } + if (node->phandle && tchild->phandle) + ret = -EINVAL; + else + ret = build_changeset_next_level(ovcs, tchild, node); + of_node_put(tchild); + return ret; } -/* - * Apply a single overlay node recursively. +/** + * build_changeset_next_level() - add level of overlay changeset + * @ovcs: overlay changeset + * @target_node: 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 + * @ovcs->cset changeset. If an added node has child nodes, they will + * be added recursively. * - * Note that the in case of an error the target node is left - * in a inconsistent state. Error recovery should be performed - * by using the changeset. + * Do not allow symbols node to have any children. + * + * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if + * invalid @overlay_node. */ -static int of_overlay_apply_one(struct of_overlay *ov, - struct device_node *target, const struct device_node *overlay, - bool is_symbols_node) +static int build_changeset_next_level(struct overlay_changeset *ovcs, + struct device_node *target_node, + const struct device_node *overlay_node) { struct device_node *child; struct property *prop; int ret; - for_each_property_of_node(overlay, prop) { - ret = of_overlay_apply_single_property(ov, target, prop, - is_symbols_node); + for_each_property_of_node(overlay_node, prop) { + ret = add_changeset_property(ovcs, target_node, prop, 0); if (ret) { - pr_err("Failed to apply prop @%pOF/%s\n", - target, prop->name); + pr_debug("Failed to apply prop @%pOF/%s, err=%d\n", + target_node, prop->name, ret); return ret; } } - /* do not allow symbols node to have any children */ - if (is_symbols_node) - return 0; - - for_each_child_of_node(overlay, child) { - ret = of_overlay_apply_single_device_node(ov, target, child); - if (ret != 0) { - pr_err("Failed to apply single node @%pOF/%s\n", - target, child->name); + for_each_child_of_node(overlay_node, child) { + ret = add_changeset_node(ovcs, target_node, child); + if (ret) { + pr_debug("Failed to apply node @%pOF/%s, err=%d\n", + target_node, child->name, ret); of_node_put(child); return ret; } @@ -279,28 +405,72 @@ static int of_overlay_apply_one(struct of_overlay *ov, return 0; } +/* + * 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, + 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); + if (ret) { + pr_debug("Failed to apply prop @%pOF/%s, err=%d\n", + target_node, prop->name, ret); + return ret; + } + } + + return 0; +} + /** - * of_overlay_apply() - Apply @count overlays pointed at by @ovinfo_tab - * @ov: Overlay to apply + * build_changeset() - populate overlay changeset in @ovcs from @ovcs->fragments + * @ovcs: Overlay changeset * - * 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. + * Create changeset @ovcs->cset to contain the nodes and properties of the + * overlay device tree fragments in @ovcs->fragments[]. If an error occurs, + * any portions of the changeset that were successfully created will remain + * in @ovcs->cset. + * + * Returns 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if + * invalid overlay in @ovcs->fragments[]. */ -static int of_overlay_apply(struct of_overlay *ov) +static int build_changeset(struct overlay_changeset *ovcs) { - int i, err; - - /* first we apply the overlays atomically */ - for (i = 0; i < ov->count; i++) { - struct of_overlay_info *ovinfo = &ov->ovinfo_tab[i]; + struct fragment *fragment; + int fragments_count, i, ret; + + /* + * if there is a symbols fragment in ovcs->fragments[i] it is + * the final element in the array + */ + if (ovcs->symbols_fragment) + fragments_count = ovcs->count - 1; + else + fragments_count = ovcs->count; + + for (i = 0; i < fragments_count; i++) { + fragment = &ovcs->fragments[i]; + + ret = build_changeset_next_level(ovcs, fragment->target, + fragment->overlay); + if (ret) { + pr_debug("apply failed '%pOF'\n", fragment->target); + return ret; + } + } - err = of_overlay_apply_one(ov, ovinfo->target, ovinfo->overlay, - ovinfo->is_symbols_node); - if (err != 0) { - pr_err("apply failed '%pOF'\n", ovinfo->target); - return err; + if (ovcs->symbols_fragment) { + fragment = &ovcs->fragments[ovcs->count - 1]; + ret = build_changeset_symbols_node(ovcs, fragment->target, + fragment->overlay); + if (ret) { + pr_debug("apply failed '%pOF'\n", fragment->target); + return ret; } } @@ -309,10 +479,10 @@ static int of_overlay_apply(struct of_overlay *ov) /* * Find the target node using a number of different strategies - * in order of preference + * in order of preference: * - * "target" property containing the phandle of the target - * "target-path" property containing the path of the target + * 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) { @@ -320,14 +490,12 @@ static struct device_node *find_target_node(struct device_node *info_node) u32 val; int ret; - /* first try to go by using the target as a phandle */ ret = of_property_read_u32(info_node, "target", &val); - if (ret == 0) + if (!ret) return of_find_node_by_phandle(val); - /* now try to locate by path */ ret = of_property_read_string(info_node, "target-path", &path); - if (ret == 0) + if (!ret) return of_find_node_by_path(path); pr_err("Failed to find target for node %p (%s)\n", @@ -337,228 +505,290 @@ static struct device_node *find_target_node(struct device_node *info_node) } /** - * of_fill_overlay_info() - Fill an overlay info structure - * @ov Overlay to fill - * @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. + * init_overlay_changeset() - initialize overlay changeset from overlay tree + * @ovcs Overlay changeset to build + * @tree: Contains all the overlay fragments and overlay fixup nodes + * + * Initialize @ovcs. Populate @ovcs->fragments with node information from + * the top level of @tree. The relevant top level nodes are the fragment + * nodes and the __symbols__ node. Any other top level node will be ignored. + * + * Returns 0 on success, -ENOMEM if memory allocation failure, -EINVAL if error + * detected in @tree, or -ENOSPC if idr_alloc() error. */ -static int of_fill_overlay_info(struct of_overlay *ov, - struct device_node *info_node, struct of_overlay_info *ovinfo) +static int init_overlay_changeset(struct overlay_changeset *ovcs, + struct device_node *tree) { - ovinfo->overlay = of_get_child_by_name(info_node, "__overlay__"); - if (ovinfo->overlay == NULL) - goto err_fail; + struct device_node *node, *overlay_node; + struct fragment *fragment; + struct fragment *fragments; + int cnt, ret; - ovinfo->target = find_target_node(info_node); - if (ovinfo->target == NULL) - goto err_fail; + /* + * Warn for some issues. Can not return -EINVAL for these until + * of_unittest_apply_overlay() is fixed to pass these checks. + */ + if (!of_node_check_flag(tree, OF_DYNAMIC)) + pr_debug("%s() tree is not dynamic\n", __func__); - return 0; + if (!of_node_check_flag(tree, OF_DETACHED)) + pr_debug("%s() tree is not detached\n", __func__); -err_fail: - of_node_put(ovinfo->target); - of_node_put(ovinfo->overlay); + if (!of_node_is_root(tree)) + pr_debug("%s() tree is not root\n", __func__); - memset(ovinfo, 0, sizeof(*ovinfo)); - return -EINVAL; -} + ovcs->overlay_tree = tree; -/** - * of_build_overlay_info() - Build an overlay info array - * @ov Overlay to build - * @tree: Device node containing all the overlays - * - * Helper function that given a tree containing overlay information, - * allocates and builds an overlay info array containing it, ready - * for use using of_overlay_apply. - * - * Returns 0 on success with the @cntp @ovinfop pointers valid, - * while on error a negative error value is returned. - */ -static int of_build_overlay_info(struct of_overlay *ov, - struct device_node *tree) -{ - struct device_node *node; - struct of_overlay_info *ovinfo; - int cnt, err; + INIT_LIST_HEAD(&ovcs->ovcs_list); + + of_changeset_init(&ovcs->cset); + + ovcs->id = idr_alloc(&ovcs_idr, ovcs, 1, 0, GFP_KERNEL); + if (ovcs->id <= 0) + return ovcs->id; - /* worst case; every child is a node */ cnt = 0; - for_each_child_of_node(tree, node) - cnt++; - if (of_get_child_by_name(tree, "__symbols__")) + /* fragment nodes */ + for_each_child_of_node(tree, node) { + overlay_node = of_get_child_by_name(node, "__overlay__"); + if (overlay_node) { + cnt++; + of_node_put(overlay_node); + } + } + + node = of_get_child_by_name(tree, "__symbols__"); + if (node) { cnt++; + of_node_put(node); + } - ovinfo = kcalloc(cnt, sizeof(*ovinfo), GFP_KERNEL); - if (ovinfo == NULL) - return -ENOMEM; + fragments = kcalloc(cnt, sizeof(*fragments), GFP_KERNEL); + if (!fragments) { + ret = -ENOMEM; + goto err_free_idr; + } cnt = 0; for_each_child_of_node(tree, node) { - err = of_fill_overlay_info(ov, node, &ovinfo[cnt]); - if (err == 0) - cnt++; + fragment = &fragments[cnt]; + fragment->overlay = of_get_child_by_name(node, "__overlay__"); + if (fragment->overlay) { + fragment->target = find_target_node(node); + if (!fragment->target) { + of_node_put(fragment->overlay); + ret = -EINVAL; + goto err_free_fragments; + } else { + cnt++; + } + } } + /* + * if there is a symbols fragment in ovcs->fragments[i] it is + * the final element in the array + */ node = of_get_child_by_name(tree, "__symbols__"); if (node) { - ovinfo[cnt].overlay = node; - ovinfo[cnt].target = of_find_node_by_path("/__symbols__"); - ovinfo[cnt].is_symbols_node = 1; - - if (!ovinfo[cnt].target) { - pr_err("no symbols in root of device tree.\n"); - return -EINVAL; + ovcs->symbols_fragment = 1; + fragment = &fragments[cnt]; + fragment->overlay = node; + fragment->target = of_find_node_by_path("/__symbols__"); + + if (!fragment->target) { + pr_err("symbols in overlay, but not in live tree\n"); + ret = -EINVAL; + goto err_free_fragments; } cnt++; } - /* if nothing filled, return error */ - if (cnt == 0) { - kfree(ovinfo); - return -ENODEV; + if (!cnt) { + ret = -EINVAL; + goto err_free_fragments; } - ov->count = cnt; - ov->ovinfo_tab = ovinfo; + ovcs->count = cnt; + ovcs->fragments = fragments; return 0; + +err_free_fragments: + kfree(fragments); +err_free_idr: + idr_remove(&ovcs_idr, ovcs->id); + + pr_err("%s() failed, ret = %d\n", __func__, ret); + + return ret; } -/** - * of_free_overlay_info() - Free an overlay info array - * @ov Overlay to free the overlay info from - * @ovinfo_tab: Array of overlay_info's to free - * - * Releases the memory of a previously allocated ovinfo array - * by of_build_overlay_info. - * Returns 0, or an error if the arguments are bogus. - */ -static int of_free_overlay_info(struct of_overlay *ov) +static void free_overlay_changeset(struct overlay_changeset *ovcs) { - struct of_overlay_info *ovinfo; int i; - /* do it in reverse */ - for (i = ov->count - 1; i >= 0; i--) { - ovinfo = &ov->ovinfo_tab[i]; + if (!ovcs->cset.entries.next) + return; + of_changeset_destroy(&ovcs->cset); + + if (ovcs->id) + idr_remove(&ovcs_idr, ovcs->id); - of_node_put(ovinfo->target); - of_node_put(ovinfo->overlay); + for (i = 0; i < ovcs->count; i++) { + of_node_put(ovcs->fragments[i].target); + of_node_put(ovcs->fragments[i].overlay); } - kfree(ov->ovinfo_tab); + kfree(ovcs->fragments); - return 0; + kfree(ovcs); } -static LIST_HEAD(ov_list); -static DEFINE_IDR(ov_idr); - /** - * of_overlay_create() - Create and apply an overlay - * @tree: Device node containing all the overlays + * of_overlay_apply() - Create and apply an overlay changeset + * @tree: Expanded overlay device tree + * @ovcs_id: Pointer to overlay changeset id * - * Creates and applies an overlay while also keeping track - * of the overlay in a list. This list can be used to prevent - * illegal overlay removals. + * Creates and applies an overlay changeset. + * + * If an error occurs in a pre-apply notifier, then no changes are made + * to the device tree. + * + + * A non-zero return value will not have created the changeset if error is from: + * - parameter checks + * - building the changeset + * - overlay changset pre-apply notifier * - * Returns the id of the created overlay, or a negative error number + * If an error is returned by an overlay changeset pre-apply notifier + * then no further overlay changeset pre-apply notifier will be called. + * + * A non-zero return value will have created the changeset if error is from: + * - overlay changeset entry notifier + * - overlay changset post-apply notifier + * + * If an error is returned by an overlay changeset post-apply notifier + * then no further overlay changeset post-apply notifier will be called. + * + * If more than one notifier returns an error, then the last notifier + * error to occur is returned. + * + * If an error occurred while applying the overlay changeset, then an + * attempt is made to revert any changes that were made to the + * device tree. If there were any errors during the revert attempt + * then the state of the device tree can not be determined, and any + * following attempt to apply or remove an overlay changeset will be + * refused. + * + * Returns 0 on success, or a negative error number. Overlay changeset + * id is returned to *ovcs_id. */ -int of_overlay_create(struct device_node *tree) + +int of_overlay_apply(struct device_node *tree, int *ovcs_id) { - struct of_overlay *ov; - int err, id; + struct overlay_changeset *ovcs; + int ret = 0, ret_revert, ret_tmp; - /* allocate the overlay structure */ - ov = kzalloc(sizeof(*ov), GFP_KERNEL); - if (ov == NULL) - return -ENOMEM; - ov->id = -1; + *ovcs_id = 0; + + if (devicetree_corrupt()) { + pr_err("devicetree state suspect, refuse to apply overlay\n"); + ret = -EBUSY; + goto out; + } + + ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL); + if (!ovcs) { + ret = -ENOMEM; + goto out; + } - INIT_LIST_HEAD(&ov->node); + of_overlay_mutex_lock(); - of_changeset_init(&ov->cset); + ret = of_resolve_phandles(tree); + if (ret) + goto err_overlay_unlock; mutex_lock(&of_mutex); - id = idr_alloc(&ov_idr, ov, 0, 0, GFP_KERNEL); - if (id < 0) { - err = id; - goto err_destroy_trans; - } - ov->id = id; + ret = init_overlay_changeset(ovcs, tree); + if (ret) + goto err_free_overlay_changeset; - /* build the overlay info structures */ - err = of_build_overlay_info(ov, tree); - if (err) { - pr_err("of_build_overlay_info() failed for tree@%pOF\n", - tree); - goto err_free_idr; + ret = overlay_notify(ovcs, OF_OVERLAY_PRE_APPLY); + if (ret) { + pr_err("overlay changeset pre-apply notify error %d\n", ret); + goto err_free_overlay_changeset; } - err = of_overlay_notify(ov, OF_OVERLAY_PRE_APPLY); - if (err < 0) { - pr_err("%s: Pre-apply notifier failed (err=%d)\n", - __func__, err); - goto err_free_idr; + ret = build_changeset(ovcs); + if (ret) + goto err_free_overlay_changeset; + + ret_revert = 0; + ret = __of_changeset_apply_entries(&ovcs->cset, &ret_revert); + if (ret) { + if (ret_revert) { + pr_debug("overlay changeset revert error %d\n", + ret_revert); + devicetree_state_flags |= DTSF_APPLY_FAIL; + } + goto err_free_overlay_changeset; + } else { + ret = __of_changeset_apply_notify(&ovcs->cset); + if (ret) + pr_err("overlay changeset entry notify error %d\n", + ret); + /* fall through */ } - /* apply the overlay */ - err = of_overlay_apply(ov); - if (err) - goto err_abort_trans; - - /* apply the changeset */ - err = __of_changeset_apply(&ov->cset); - if (err) - goto err_revert_overlay; + list_add_tail(&ovcs->ovcs_list, &ovcs_list); + *ovcs_id = ovcs->id; + ret_tmp = overlay_notify(ovcs, OF_OVERLAY_POST_APPLY); + if (ret_tmp) { + pr_err("overlay changeset post-apply notify error %d\n", + ret_tmp); + if (!ret) + ret = ret_tmp; + } - /* add to the tail of the overlay list */ - list_add_tail(&ov->node, &ov_list); + mutex_unlock(&of_mutex); + of_overlay_mutex_unlock(); - of_overlay_notify(ov, OF_OVERLAY_POST_APPLY); + goto out; - mutex_unlock(&of_mutex); +err_overlay_unlock: + of_overlay_mutex_unlock(); - return id; +err_free_overlay_changeset: + free_overlay_changeset(ovcs); -err_revert_overlay: -err_abort_trans: - of_free_overlay_info(ov); -err_free_idr: - idr_remove(&ov_idr, ov->id); -err_destroy_trans: - of_changeset_destroy(&ov->cset); - kfree(ov); mutex_unlock(&of_mutex); - return err; +out: + pr_debug("%s() err=%d\n", __func__, ret); + + return ret; } -EXPORT_SYMBOL_GPL(of_overlay_create); +EXPORT_SYMBOL_GPL(of_overlay_apply); -/* check whether the given node, lies under the given tree */ -static int overlay_subtree_check(struct device_node *tree, - struct device_node *dn) +/* + * Find @np in @tree. + * + * Returns 1 if @np is @tree or is contained in @tree, else 0 + */ +static int find_node(struct device_node *tree, struct device_node *np) { struct device_node *child; - /* match? */ - if (tree == dn) + if (tree == np) return 1; for_each_child_of_node(tree, child) { - if (overlay_subtree_check(child, dn)) { + if (find_node(child, np)) { of_node_put(child); return 1; } @@ -567,29 +797,39 @@ static int overlay_subtree_check(struct device_node *tree, return 0; } -/* check whether this overlay is the topmost */ -static int overlay_is_topmost(struct of_overlay *ov, struct device_node *dn) +/* + * Is @remove_ce_node a child of, a parent of, or the same as any + * node in an overlay changeset more topmost than @remove_ovcs? + * + * Returns 1 if found, else 0 + */ +static int node_overlaps_later_cs(struct overlay_changeset *remove_ovcs, + struct device_node *remove_ce_node) { - struct of_overlay *ovt; + struct overlay_changeset *ovcs; struct of_changeset_entry *ce; - list_for_each_entry_reverse(ovt, &ov_list, node) { - /* if we hit ourselves, we're done */ - if (ovt == ov) + list_for_each_entry_reverse(ovcs, &ovcs_list, ovcs_list) { + if (ovcs == remove_ovcs) break; - /* check against each subtree affected by this overlay */ - list_for_each_entry(ce, &ovt->cset.entries, node) { - if (overlay_subtree_check(ce->np, dn)) { - pr_err("%s: #%d clashes #%d @%pOF\n", - __func__, ov->id, ovt->id, dn); - return 0; + list_for_each_entry(ce, &ovcs->cset.entries, node) { + if (find_node(ce->np, remove_ce_node)) { + pr_err("%s: #%d overlaps with #%d @%pOF\n", + __func__, remove_ovcs->id, ovcs->id, + remove_ce_node); + return 1; + } + if (find_node(remove_ce_node, ce->np)) { + pr_err("%s: #%d overlaps with #%d @%pOF\n", + __func__, remove_ovcs->id, ovcs->id, + remove_ce_node); + return 1; } } } - /* overlay is topmost */ - return 1; + return 0; } /* @@ -602,13 +842,13 @@ static int overlay_is_topmost(struct of_overlay *ov, struct device_node *dn) * the one closest to the tail. If another overlay has affected this * device node and is closest to the tail, then removal is not permited. */ -static int overlay_removal_is_ok(struct of_overlay *ov) +static int overlay_removal_is_ok(struct overlay_changeset *remove_ovcs) { - struct of_changeset_entry *ce; + struct of_changeset_entry *remove_ce; - list_for_each_entry(ce, &ov->cset.entries, node) { - if (!overlay_is_topmost(ov, ce->np)) { - pr_err("overlay #%d is not topmost\n", ov->id); + list_for_each_entry(remove_ce, &remove_ovcs->cset.entries, node) { + if (node_overlaps_later_cs(remove_ovcs, remove_ce->np)) { + pr_err("overlay #%d is not topmost\n", remove_ovcs->id); return 0; } } @@ -617,75 +857,130 @@ static int overlay_removal_is_ok(struct of_overlay *ov) } /** - * of_overlay_destroy() - Removes an overlay - * @id: Overlay id number returned by a previous call to of_overlay_create + * of_overlay_remove() - Revert and free an overlay changeset + * @ovcs_id: Pointer to overlay changeset id * - * Removes an overlay if it is permissible. + * Removes an overlay if it is permissible. @ovcs_id was previously returned + * by of_overlay_apply(). * - * Returns 0 on success, or a negative error number + * If an error occurred while attempting to revert the overlay changeset, + * then an attempt is made to re-apply any changeset entry that was + * reverted. If an error occurs on re-apply then the state of the device + * tree can not be determined, and any following attempt to apply or remove + * an overlay changeset will be refused. + * + * A non-zero return value will not revert the changeset if error is from: + * - parameter checks + * - overlay changset pre-remove notifier + * - overlay changeset entry revert + * + * If an error is returned by an overlay changeset pre-remove notifier + * then no further overlay changeset pre-remove notifier will be called. + * + * If more than one notifier returns an error, then the last notifier + * error to occur is returned. + * + * A non-zero return value will revert the changeset if error is from: + * - overlay changeset entry notifier + * - overlay changset post-remove notifier + * + * If an error is returned by an overlay changeset post-remove notifier + * then no further overlay changeset post-remove notifier will be called. + * + * Returns 0 on success, or a negative error number. *ovcs_id is set to + * zero after reverting the changeset, even if a subsequent error occurs. */ -int of_overlay_destroy(int id) +int of_overlay_remove(int *ovcs_id) { - struct of_overlay *ov; - int err; + struct overlay_changeset *ovcs; + int ret, ret_apply, ret_tmp; - mutex_lock(&of_mutex); + ret = 0; - ov = idr_find(&ov_idr, id); - if (ov == NULL) { - err = -ENODEV; - pr_err("destroy: Could not find overlay #%d\n", id); + if (devicetree_corrupt()) { + pr_err("suspect devicetree state, refuse to remove overlay\n"); + ret = -EBUSY; goto out; } - /* check whether the overlay is safe to remove */ - if (!overlay_removal_is_ok(ov)) { - err = -EBUSY; - goto out; + mutex_lock(&of_mutex); + + ovcs = idr_find(&ovcs_idr, *ovcs_id); + if (!ovcs) { + ret = -ENODEV; + pr_err("remove: Could not find overlay #%d\n", *ovcs_id); + goto out_unlock; } - of_overlay_notify(ov, OF_OVERLAY_PRE_REMOVE); - list_del(&ov->node); - __of_changeset_revert(&ov->cset); - of_overlay_notify(ov, OF_OVERLAY_POST_REMOVE); - of_free_overlay_info(ov); - idr_remove(&ov_idr, id); - of_changeset_destroy(&ov->cset); - kfree(ov); + if (!overlay_removal_is_ok(ovcs)) { + ret = -EBUSY; + goto out_unlock; + } - err = 0; + ret = overlay_notify(ovcs, OF_OVERLAY_PRE_REMOVE); + if (ret) { + pr_err("overlay changeset pre-remove notify error %d\n", ret); + goto out_unlock; + } -out: + list_del(&ovcs->ovcs_list); + + ret_apply = 0; + ret = __of_changeset_revert_entries(&ovcs->cset, &ret_apply); + if (ret) { + if (ret_apply) + devicetree_state_flags |= DTSF_REVERT_FAIL; + goto out_unlock; + } else { + ret = __of_changeset_revert_notify(&ovcs->cset); + if (ret) { + pr_err("overlay changeset entry notify error %d\n", + ret); + /* fall through - changeset was reverted */ + } + } + + *ovcs_id = 0; + + ret_tmp = overlay_notify(ovcs, OF_OVERLAY_POST_REMOVE); + if (ret_tmp) { + pr_err("overlay changeset post-remove notify error %d\n", + ret_tmp); + if (!ret) + ret = ret_tmp; + } + + free_overlay_changeset(ovcs); + +out_unlock: mutex_unlock(&of_mutex); - return err; +out: + pr_debug("%s() err=%d\n", __func__, ret); + + return ret; } -EXPORT_SYMBOL_GPL(of_overlay_destroy); +EXPORT_SYMBOL_GPL(of_overlay_remove); /** - * of_overlay_destroy_all() - Removes all overlays from the system + * of_overlay_remove_all() - Reverts and frees all overlay changesets * * Removes all overlays from the system in the correct order. * * Returns 0 on success, or a negative error number */ -int of_overlay_destroy_all(void) +int of_overlay_remove_all(void) { - struct of_overlay *ov, *ovn; - - mutex_lock(&of_mutex); + struct overlay_changeset *ovcs, *ovcs_n; + int ret; /* the tail of list is guaranteed to be safe to remove */ - list_for_each_entry_safe_reverse(ov, ovn, &ov_list, node) { - list_del(&ov->node); - __of_changeset_revert(&ov->cset); - of_free_overlay_info(ov); - idr_remove(&ov_idr, ov->id); - kfree(ov); + list_for_each_entry_safe_reverse(ovcs, ovcs_n, &ovcs_list, ovcs_list) { + ret = of_overlay_remove(&ovcs->id); + if (ret) + return ret; } - mutex_unlock(&of_mutex); - return 0; } -EXPORT_SYMBOL_GPL(of_overlay_destroy_all); +EXPORT_SYMBOL_GPL(of_overlay_remove_all); |