overlay linux,linux/overlay.c at master · torvalds/linux · GitHub

// SPDX-License-Identifier: GPL-2.0

/*

* Functions for working with device tree overlays

*

* Copyright (C) 2012 Pantelis Antoniou

* Copyright (C) 2012 Texas Instruments Inc.

*/

#define pr_fmt(fmt)"OF: overlay: " fmt

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#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 *overlay;

struct device_node *target;

};

/**

* struct overlay_changeset

* @id:changeset identifier

* @ovcs_list:list on which we are located

* @fdt:base of memory allocated to hold aligned FDT that was unflattened to create @overlay_tree

* @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 overlay_changeset {

int id;

struct list_head ovcs_list;

const void *fdt;

struct device_node *overlay_tree;

int count;

struct fragment *fragments;

bool symbols_fragment;

struct of_changeset cset;

};

/* flags are sticky - once set, do not reset */

static int devicetree_state_flags;

#define DTSF_APPLY_FAIL0x01

#define DTSF_REVERT_FAIL0x02

/*

* 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 target *target, 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 LIST_HEAD(ovcs_list);

static DEFINE_IDR(ovcs_idr);

static BLOCKING_NOTIFIER_HEAD(overlay_notify_chain);

/**

* of_overlay_notifier_register() - Register notifier for overlay operations

* @nb:Notifier block to register

*

* Register for notification on overlay operations on device tree nodes. The

* reported actions definied by @of_reconfig_change. The notifier callback

* furthermore receives a pointer to the affected device tree node.

*

* Note that a notifier callback is not supposed to store pointers to a device

* tree node or its content beyond @OF_OVERLAY_POST_REMOVE corresponding to the

* respective node it received.

*/

int of_overlay_notifier_register(struct notifier_block *nb)

{

return blocking_notifier_chain_register(&overlay_notify_chain, nb);

}

EXPORT_SYMBOL_GPL(of_overlay_notifier_register);

/**

* of_overlay_notifier_unregister() - Unregister notifier for overlay operations

* @nb:Notifier block to unregister

*/

int of_overlay_notifier_unregister(struct notifier_block *nb)

{

return blocking_notifier_chain_unregister(&overlay_notify_chain, nb);

}

EXPORT_SYMBOL_GPL(of_overlay_notifier_unregister);

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 < ovcs->count; i++) {

struct fragment *fragment = &ovcs->fragments[i];

nd.target = fragment->target;

nd.overlay = fragment->overlay;

ret = blocking_notifier_call_chain(&overlay_notify_chain,

action, &nd);

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;

}

/*

* 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 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 overlay_name_len;

int path_len;

int path_tail_len;

int target_path_len;

if (!prop->value)

return NULL;

if (strnlen(prop->value, prop->length) >= prop->length)

return NULL;

path = prop->value;

path_len = strlen(path);

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 (overlay_name_len > path_len)

return NULL;

path_tail = path + overlay_name_len;

path_tail_len = strlen(path_tail);

target_path = kasprintf(GFP_KERNEL, "%pOF", fragment->target);

if (!target_path)

return NULL;

target_path_len = strlen(target_path);

new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);

if (!new_prop)

goto err_free_target_path;

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;

strcpy(new_prop->value, target_path);

strcpy(new_prop->value + target_path_len, path_tail);

of_property_set_flag(new_prop, OF_DYNAMIC);

kfree(target_path);

return new_prop;

err_free_new_prop:

kfree(new_prop->name);

kfree(new_prop->value);

kfree(new_prop);

err_free_target_path:

kfree(target_path);

return NULL;

}

/**

* add_changeset_property() - add @overlay_prop to overlay changeset

* @ovcs:overlay changeset

* @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 live devicetree, add changeset

* entry to add @overlay_prop in @target, else add changeset entry to update

* value of @overlay_prop.

*

* @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.

*

* Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if

* invalid @overlay.

*/

static int add_changeset_property(struct overlay_changeset *ovcs,

struct target *target, struct property *overlay_prop,

bool is_symbols_prop)

{

struct property *new_prop = NULL, *prop;

int ret = 0;

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 (prop) {

if (!of_prop_cmp(prop->name, "#address-cells")) {

if (!of_prop_val_eq(prop, overlay_prop)) {

pr_err("ERROR: changing value of #address-cells is not allowed in %pOF\n",

target->np);

ret = -EINVAL;

}

return ret;

} else if (!of_prop_cmp(prop->name, "#size-cells")) {

if (!of_prop_val_eq(prop, overlay_prop)) {

pr_err("ERROR: changing value of #size-cells is not allowed in %pOF\n",

target->np);

ret = -EINVAL;

}

return ret;

}

}

if (is_symbols_prop) {

if (prop)

return -EINVAL;

new_prop = dup_and_fixup_symbol_prop(ovcs, overlay_prop);

} else {

new_prop = __of_prop_dup(overlay_prop, GFP_KERNEL);

}

if (!new_prop)

return -ENOMEM;

if (!prop) {

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->np,

new_prop);

}

if (!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);

kfree(new_prop->value);

kfree(new_prop);

}

return ret;

}

/**

* add_changeset_node() - add @node (and children) to overlay changeset

* @ovcs:overlay changeset

* @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, add changeset entry

* to add @node in @target.

*

* 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

* build_changeset_next_level().

*

* NOTE_1: A live devicetree created from a flattened device tree (FDT) will

* not contain the full path in node->full_name. Thus an overlay

* created from an FDT also will not contain the full path in

* node->full_name. However, a live devicetree created from Open

* Firmware may have the full path in node->full_name.

*

* add_changeset_node() follows the FDT convention and does not include

* the full path in node->full_name. Even though it expects the overlay

* to not contain the full path, it uses kbasename() to remove the

* full path should it exist. It also uses kbasename() in comparisons

* to nodes in the live devicetree so that it can apply an overlay to

* a live devicetree created from Open Firmware.

*

* NOTE_2: Multiple mods of created nodes not supported.

*

* Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if

* invalid @overlay.

*/

static int add_changeset_node(struct overlay_changeset *ovcs,

struct target *target, struct device_node *node)

{

const char *node_kbasename;

const __be32 *phandle;

struct device_node *tchild;

struct target target_child;

int ret = 0, size;

node_kbasename = kbasename(node->full_name);

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(NULL, node_kbasename);

if (!tchild)

return -ENOMEM;

tchild->parent = target->np;

tchild->name = __of_get_property(node, "name", NULL);

if (!tchild->name)

tchild->name = "";

/* 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;

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) {

ret = -EINVAL;

} 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;

}

/**

* build_changeset_next_level() - add level of overlay changeset

* @ovcs:overlay changeset

* @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

* @ovcs->cset changeset. If an added node has child nodes, they will

* be added recursively.

*

* Do not allow symbols node to have any children.

*

* Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if

* invalid @overlay_node.

*/

static int build_changeset_next_level(struct overlay_changeset *ovcs,

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, prop, 0);

if (ret) {

pr_debug("Failed to apply prop @%pOF/%s, err=%d\n",

target->np, prop->name, ret);

return ret;

}

}

for_each_child_of_node(overlay_node, child) {

ret = add_changeset_node(ovcs, target, child);

if (ret) {

pr_debug("Failed to apply node @%pOF/%pOFn, err=%d\n",

target->np, child, ret);

of_node_put(child);

return ret;

}

}

return 0;

}

/*

* Add the properties from __overlay__ node to the @ovcs->cset changeset.

*/

static int build_changeset_symbols_node(struct overlay_changeset *ovcs,

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, prop, 1);

if (ret) {

pr_debug("Failed to apply symbols prop @%pOF/%s, err=%d\n",

target->np, prop->name, ret);

return ret;

}

}

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.

*

* Return: 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

*

* 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.

*

* Return: 0 on success, -ENOMEM if memory allocation failure, or -EINVAL if

* invalid overlay in @ovcs->fragments[].

*/

static int build_changeset(struct overlay_changeset *ovcs)

{

struct fragment *fragment;

struct target target;

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];

target.np = fragment->target;

target.in_livetree = true;

ret = build_changeset_next_level(ovcs, &target,

fragment->overlay);

if (ret) {

pr_debug("fragment apply failed '%pOF'\n",

fragment->target);

return ret;

}

}

if (ovcs->symbols_fragment) {

fragment = &ovcs->fragments[ovcs->count - 1];

target.np = fragment->target;

target.in_livetree = true;

ret = build_changeset_symbols_node(ovcs, &target,

fragment->overlay);

if (ret) {

pr_debug("symbols fragment apply failed '%pOF'\n",

fragment->target);

return ret;

}

}

return changeset_dup_entry_check(ovcs);

}

/*

* Find the target node using a number of different strategies

* in order of preference:

*

* 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(struct device_node *info_node)

{

struct device_node *node;

const char *path;

u32 val;

int ret;

ret = of_property_read_u32(info_node, "target", &val);

if (!ret) {

node = of_find_node_by_phandle(val);

if (!node)

pr_err("find target, node: %pOF, phandle 0x%x not found\n",

info_node, val);

return node;

}

ret = of_property_read_string(info_node, "target-path", &path);

if (!ret) {

node = of_find_node_by_path(path);

if (!node)

pr_err("find target, node: %pOF, path '%s' not found\n",

info_node, path);

return node;

}

pr_err("find target, node: %pOF, no target property\n", info_node);

return NULL;

}

/**

* init_overlay_changeset() - initialize overlay changeset from overlay tree

* @ovcs:Overlay changeset to build

* @fdt:base of memory allocated to hold aligned FDT that was unflattened to create @tree

* @tree:Contains 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.

*

* Return: 0 on success, -ENOMEM if memory allocation failure, -EINVAL if error

* detected in @tree, or -ENOSPC if idr_alloc() error.

*/

static int init_overlay_changeset(struct overlay_changeset *ovcs,

const void *fdt, struct device_node *tree)

{

struct device_node *node, *overlay_node;

struct fragment *fragment;

struct fragment *fragments;

int cnt, id, ret;

/*

* 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__);

if (!of_node_check_flag(tree, OF_DETACHED))

pr_debug("%s() tree is not detached\n", __func__);

if (!of_node_is_root(tree))

pr_debug("%s() tree is not root\n", __func__);

ovcs->overlay_tree = tree;

ovcs->fdt = fdt;

INIT_LIST_HEAD(&ovcs->ovcs_list);

of_changeset_init(&ovcs->cset);

id = idr_alloc(&ovcs_idr, ovcs, 1, 0, GFP_KERNEL);

if (id <= 0)

return id;

cnt = 0;

/* 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);

}

fragments = kcalloc(cnt, sizeof(*fragments), GFP_KERNEL);

if (!fragments) {

ret = -ENOMEM;

goto err_free_idr;

}

cnt = 0;

for_each_child_of_node(tree, node) {

overlay_node = of_get_child_by_name(node, "__overlay__");

if (!overlay_node)

continue;

fragment = &fragments[cnt];

fragment->overlay = overlay_node;

fragment->target = find_target(node);

if (!fragment->target) {

of_node_put(fragment->overlay);

ret = -EINVAL;

of_node_put(node);

goto err_free_fragments;

}

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) {

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 (!cnt) {

pr_err("no fragments or symbols in overlay\n");

ret = -EINVAL;

goto err_free_fragments;

}

ovcs->id = id;

ovcs->count = cnt;

ovcs->fragments = fragments;

return 0;

err_free_fragments:

kfree(fragments);

err_free_idr:

idr_remove(&ovcs_idr, id);

pr_err("%s() failed, ret = %d\n", __func__, ret);

return ret;

}

static void free_overlay_changeset(struct overlay_changeset *ovcs)

{

int i;

if (ovcs->cset.entries.next)

of_changeset_destroy(&ovcs->cset);

if (ovcs->id)

idr_remove(&ovcs_idr, ovcs->id);

for (i = 0; i < ovcs->count; i++) {

of_node_put(ovcs->fragments[i].target);

of_node_put(ovcs->fragments[i].overlay);

}

kfree(ovcs->fragments);

/*

* There should be no live pointers into ovcs->overlay_tree and

* ovcs->fdt due to the policy that overlay notifiers are not allowed

* to retain pointers into the overlay devicetree.

*/

kfree(ovcs->overlay_tree);

kfree(ovcs->fdt);

kfree(ovcs);

}

/*

* internal documentation

*

* of_overlay_apply() - Create and apply an overlay changeset

* @fdt:base of memory allocated to hold the aligned FDT

* @tree:Expanded overlay device tree

* @ovcs_id:Pointer to overlay changeset id

*

* 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 changeset pre-apply notifier

*

* 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 changeset 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.

*/

static int of_overlay_apply(const void *fdt, struct device_node *tree,

int *ovcs_id)

{

struct overlay_changeset *ovcs;

int ret = 0, ret_revert, ret_tmp;

/*

* As of this point, fdt and tree belong to the overlay changeset.

* overlay changeset code is responsible for freeing them.

*/

if (devicetree_corrupt()) {

pr_err("devicetree state suspect, refuse to apply overlay\n");

kfree(fdt);

kfree(tree);

ret = -EBUSY;

goto out;

}

ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL);

if (!ovcs) {

kfree(fdt);

kfree(tree);

ret = -ENOMEM;

goto out;

}

of_overlay_mutex_lock();

mutex_lock(&of_mutex);

ret = of_resolve_phandles(tree);

if (ret)

goto err_free_tree;

ret = init_overlay_changeset(ovcs, fdt, tree);

if (ret)

goto err_free_tree;

/*

* after overlay_notify(), ovcs->overlay_tree related pointers may have

* leaked to drivers, so can not kfree() tree, aka ovcs->overlay_tree;

* and can not free memory containing aligned fdt. The aligned fdt

* is contained within the memory at ovcs->fdt, possibly at an offset

* from ovcs->fdt.

*/

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;

}

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;

}

ret = __of_changeset_apply_notify(&ovcs->cset);

if (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);

*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;

}

goto out_unlock;

err_free_tree:

kfree(fdt);

kfree(tree);

err_free_overlay_changeset:

free_overlay_changeset(ovcs);

out_unlock:

mutex_unlock(&of_mutex);

of_overlay_mutex_unlock();

out:

pr_debug("%s() err=%d\n", __func__, ret);

return ret;

}

int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,

int *ovcs_id)

{

void *new_fdt;

void *new_fdt_align;

int ret;

u32 size;

struct device_node *overlay_root = NULL;

*ovcs_id = 0;

if (overlay_fdt_size < sizeof(struct fdt_header) ||

fdt_check_header(overlay_fdt)) {

pr_err("Invalid overlay_fdt header\n");

return -EINVAL;

}

size = fdt_totalsize(overlay_fdt);

if (overlay_fdt_size < size)

return -EINVAL;

/*

* Must create permanent copy of FDT because of_fdt_unflatten_tree()

* will create pointers to the passed in FDT in the unflattened tree.

*/

new_fdt = kmalloc(size + FDT_ALIGN_SIZE, GFP_KERNEL);

if (!new_fdt)

return -ENOMEM;

new_fdt_align = PTR_ALIGN(new_fdt, FDT_ALIGN_SIZE);

memcpy(new_fdt_align, overlay_fdt, size);

of_fdt_unflatten_tree(new_fdt_align, NULL, &overlay_root);

if (!overlay_root) {

pr_err("unable to unflatten overlay_fdt\n");

ret = -EINVAL;

goto out_free_new_fdt;

}

ret = of_overlay_apply(new_fdt, overlay_root, ovcs_id);

if (ret < 0) {

/*

* new_fdt and overlay_root now belong to the overlay

* changeset.

* overlay changeset code is responsible for freeing them.

*/

goto out;

}

return 0;

out_free_new_fdt:

kfree(new_fdt);

out:

return ret;

}

EXPORT_SYMBOL_GPL(of_overlay_fdt_apply);

/*

* 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;

if (tree == np)

return 1;

for_each_child_of_node(tree, child) {

if (find_node(child, np)) {

of_node_put(child);

return 1;

}

}

return 0;

}

/*

* 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 overlay_changeset *ovcs;

struct of_changeset_entry *ce;

list_for_each_entry_reverse(ovcs, &ovcs_list, ovcs_list) {

if (ovcs == remove_ovcs)

break;

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;

}

}

}

return 0;

}

/*

* We can safely remove the overlay only if it's the top-most one.

* Newly applied overlays are inserted at the tail of the overlay list,

* so a top most overlay is the one that is closest to the tail.

*

* The topmost check is done by exploiting this property. For each

* affected device node in the log list we check if this overlay is

* 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 overlay_changeset *remove_ovcs)

{

struct of_changeset_entry *remove_ce;

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;

}

}

return 1;

}

/**

* of_overlay_remove() - Revert and free an overlay changeset

* @ovcs_id:Pointer to overlay changeset id

*

* Removes an overlay if it is permissible. @ovcs_id was previously returned

* by of_overlay_fdt_apply().

*

* 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 changeset 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 changeset 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.

*

* Return: 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_remove(int *ovcs_id)

{

struct overlay_changeset *ovcs;

int ret, ret_apply, ret_tmp;

if (devicetree_corrupt()) {

pr_err("suspect devicetree state, refuse to remove overlay\n");

ret = -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;

}

if (!overlay_removal_is_ok(ovcs)) {

ret = -EBUSY;

goto out_unlock;

}

ret = overlay_notify(ovcs, OF_OVERLAY_PRE_REMOVE);

if (ret) {

pr_err("overlay changeset pre-remove notify error %d\n", ret);

goto out_unlock;

}

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;

}

ret = __of_changeset_revert_notify(&ovcs->cset);

if (ret)

pr_err("overlay remove changeset entry notify error %d\n", ret);

/* notify failure is not fatal, continue */

*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);

out:

pr_debug("%s() err=%d\n", __func__, ret);

return ret;

}

EXPORT_SYMBOL_GPL(of_overlay_remove);

/**

* of_overlay_remove_all() - Reverts and frees all overlay changesets

*

* Removes all overlays from the system in the correct order.

*

* Return: 0 on success, or a negative error number

*/

int of_overlay_remove_all(void)

{

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(ovcs, ovcs_n, &ovcs_list, ovcs_list) {

ret = of_overlay_remove(&ovcs->id);

if (ret)

return ret;

}

return 0;

}

EXPORT_SYMBOL_GPL(of_overlay_remove_all);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值