Commit 24789c5c authored by Frank Rowand's avatar Frank Rowand Committed by Rob Herring

of: overlay: detect cases where device tree may become corrupt

When an attempt to apply an overlay changeset fails, an effort
is made to revert any partial application of the changeset.
When an attempt to remove an overlay changeset fails, an effort
is made to re-apply any partial reversion of the changeset.

The existing code does not check for failure to recover a failed
overlay changeset application or overlay changeset revert.

Add the missing checks and flag the devicetree as corrupt if the
state of the devicetree can not be determined.

Improve and expand the returned errors to more fully reflect the
result of the effort to undo the partial effects of a failed attempt
to apply or remove an overlay changeset.

If the device tree might be corrupt, do not allow further attempts
to apply or remove an overlay changeset.

When creating an overlay changeset from an overlay device tree,
add some additional warnings if the state of the overlay device
tree is not as expected.
Signed-off-by: default avatarFrank Rowand <frank.rowand@sony.com>
Signed-off-by: default avatarRob Herring <robh@kernel.org>
parent 61b4de4e
......@@ -204,7 +204,7 @@ static void __init tilcdc_convert_slave_node(void)
/* For all memory needed for the overlay tree. This memory can
be freed after the overlay has been applied. */
struct kfree_table kft;
int ret;
int ovcs_id, ret;
if (kfree_table_init(&kft))
return;
......@@ -247,7 +247,8 @@ static void __init tilcdc_convert_slave_node(void)
tilcdc_node_disable(slave);
ret = of_overlay_apply(overlay);
ovcs_id = 0;
ret = of_overlay_apply(overlay, &ovcs_id);
if (ret)
pr_err("%s: Applying overlay changeset failed: %d\n",
__func__, ret);
......
......@@ -491,11 +491,12 @@ static void __of_changeset_entry_invert(struct of_changeset_entry *ce,
}
}
static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool revert)
static int __of_changeset_entry_notify(struct of_changeset_entry *ce,
bool revert)
{
struct of_reconfig_data rd;
struct of_changeset_entry ce_inverted;
int ret;
int ret = 0;
if (revert) {
__of_changeset_entry_invert(ce, &ce_inverted);
......@@ -517,11 +518,12 @@ static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool reve
default:
pr_err("invalid devicetree changeset action: %i\n",
(int)ce->action);
return;
ret = -EINVAL;
}
if (ret)
pr_err("changeset notifier error @%pOF\n", ce->np);
return ret;
}
static int __of_changeset_entry_apply(struct of_changeset_entry *ce)
......@@ -655,32 +657,82 @@ void of_changeset_destroy(struct of_changeset *ocs)
}
EXPORT_SYMBOL_GPL(of_changeset_destroy);
int __of_changeset_apply(struct of_changeset *ocs)
/*
* Apply the changeset entries in @ocs.
* If apply fails, an attempt is made to revert the entries that were
* successfully applied.
*
* If multiple revert errors occur then only the final revert error is reported.
*
* Returns 0 on success, a negative error value in case of an error.
* If a revert error occurs, it is returned in *ret_revert.
*/
int __of_changeset_apply_entries(struct of_changeset *ocs, int *ret_revert)
{
struct of_changeset_entry *ce;
int ret;
int ret, ret_tmp;
/* perform the rest of the work */
pr_debug("changeset: applying...\n");
list_for_each_entry(ce, &ocs->entries, node) {
ret = __of_changeset_entry_apply(ce);
if (ret) {
pr_err("Error applying changeset (%d)\n", ret);
list_for_each_entry_continue_reverse(ce, &ocs->entries, node)
__of_changeset_entry_revert(ce);
list_for_each_entry_continue_reverse(ce, &ocs->entries,
node) {
ret_tmp = __of_changeset_entry_revert(ce);
if (ret_tmp)
*ret_revert = ret_tmp;
}
return ret;
}
}
pr_debug("changeset: applied, emitting notifiers.\n");
return 0;
}
/*
* Returns 0 on success, a negative error value in case of an error.
*
* If multiple changset entry notification errors occur then only the
* final notification error is reported.
*/
int __of_changeset_apply_notify(struct of_changeset *ocs)
{
struct of_changeset_entry *ce;
int ret = 0, ret_tmp;
pr_debug("changeset: emitting notifiers.\n");
/* drop the global lock while emitting notifiers */
mutex_unlock(&of_mutex);
list_for_each_entry(ce, &ocs->entries, node)
__of_changeset_entry_notify(ce, 0);
list_for_each_entry(ce, &ocs->entries, node) {
ret_tmp = __of_changeset_entry_notify(ce, 0);
if (ret_tmp)
ret = ret_tmp;
}
mutex_lock(&of_mutex);
pr_debug("changeset: notifiers sent.\n");
return 0;
return ret;
}
/*
* Returns 0 on success, a negative error value in case of an error.
*
* If a changeset entry apply fails, an attempt is made to revert any
* previous entries in the changeset. If any of the reverts fails,
* that failure is not reported. Thus the state of the device tree
* is unknown if an apply error occurs.
*/
static int __of_changeset_apply(struct of_changeset *ocs)
{
int ret, ret_revert = 0;
ret = __of_changeset_apply_entries(ocs, &ret_revert);
if (!ret)
ret = __of_changeset_apply_notify(ocs);
return ret;
}
/**
......@@ -707,31 +759,74 @@ int of_changeset_apply(struct of_changeset *ocs)
}
EXPORT_SYMBOL_GPL(of_changeset_apply);
int __of_changeset_revert(struct of_changeset *ocs)
/*
* Revert the changeset entries in @ocs.
* If revert fails, an attempt is made to re-apply the entries that were
* successfully removed.
*
* If multiple re-apply errors occur then only the final apply error is
* reported.
*
* Returns 0 on success, a negative error value in case of an error.
* If an apply error occurs, it is returned in *ret_apply.
*/
int __of_changeset_revert_entries(struct of_changeset *ocs, int *ret_apply)
{
struct of_changeset_entry *ce;
int ret;
int ret, ret_tmp;
pr_debug("changeset: reverting...\n");
list_for_each_entry_reverse(ce, &ocs->entries, node) {
ret = __of_changeset_entry_revert(ce);
if (ret) {
pr_err("Error reverting changeset (%d)\n", ret);
list_for_each_entry_continue(ce, &ocs->entries, node)
__of_changeset_entry_apply(ce);
list_for_each_entry_continue(ce, &ocs->entries, node) {
ret_tmp = __of_changeset_entry_apply(ce);
if (ret_tmp)
*ret_apply = ret_tmp;
}
return ret;
}
}
pr_debug("changeset: reverted, emitting notifiers.\n");
return 0;
}
/*
* If multiple changset entry notification errors occur then only the
* final notification error is reported.
*/
int __of_changeset_revert_notify(struct of_changeset *ocs)
{
struct of_changeset_entry *ce;
int ret = 0, ret_tmp;
pr_debug("changeset: emitting notifiers.\n");
/* drop the global lock while emitting notifiers */
mutex_unlock(&of_mutex);
list_for_each_entry_reverse(ce, &ocs->entries, node)
__of_changeset_entry_notify(ce, 1);
list_for_each_entry_reverse(ce, &ocs->entries, node) {
ret_tmp = __of_changeset_entry_notify(ce, 1);
if (ret_tmp)
ret = ret_tmp;
}
mutex_lock(&of_mutex);
pr_debug("changeset: notifiers sent.\n");
return 0;
return ret;
}
static int __of_changeset_revert(struct of_changeset *ocs)
{
int ret, ret_reply;
ret_reply = 0;
ret = __of_changeset_revert_entries(ocs, &ret_reply);
if (!ret)
ret = __of_changeset_revert_notify(ocs);
return ret;
}
/**
......
......@@ -39,8 +39,12 @@ extern struct kset *of_kset;
extern int of_property_notify(int action, struct device_node *np,
struct property *prop, struct property *old_prop);
extern void of_node_release(struct kobject *kobj);
extern int __of_changeset_apply(struct of_changeset *ocs);
extern int __of_changeset_revert(struct of_changeset *ocs);
extern int __of_changeset_apply_entries(struct of_changeset *ocs,
int *ret_revert);
extern int __of_changeset_apply_notify(struct of_changeset *ocs);
extern int __of_changeset_revert_entries(struct of_changeset *ocs,
int *ret_apply);
extern int __of_changeset_revert_notify(struct of_changeset *ocs);
#else /* CONFIG_OF_DYNAMIC */
static inline int of_property_notify(int action, struct device_node *np,
struct property *prop, struct property *old_prop)
......
This diff is collapsed.
......@@ -1217,7 +1217,7 @@ static void of_unittest_untrack_overlay(int id)
static void of_unittest_destroy_tracked_overlays(void)
{
int id, ret, defers;
int id, ret, defers, ovcs_id;
if (overlay_first_id < 0)
return;
......@@ -1230,7 +1230,8 @@ static void of_unittest_destroy_tracked_overlays(void)
if (!(overlay_id_bits[BIT_WORD(id)] & BIT_MASK(id)))
continue;
ret = of_overlay_remove(id + overlay_first_id);
ovcs_id = id + overlay_first_id;
ret = of_overlay_remove(&ovcs_id);
if (ret == -ENODEV) {
pr_warn("%s: no overlay to destroy for #%d\n",
__func__, id + overlay_first_id);
......@@ -1252,7 +1253,7 @@ static int of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
int *overlay_id)
{
struct device_node *np = NULL;
int ret, id = -1;
int ret;
np = of_find_node_by_path(overlay_path(overlay_nr));
if (np == NULL) {
......@@ -1262,23 +1263,20 @@ static int of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
goto out;
}
ret = of_overlay_apply(np);
*overlay_id = 0;
ret = of_overlay_apply(np, overlay_id);
if (ret < 0) {
unittest(0, "could not create overlay from \"%s\"\n",
overlay_path(overlay_nr));
goto out;
}
id = ret;
of_unittest_track_overlay(id);
of_unittest_track_overlay(*overlay_id);
ret = 0;
out:
of_node_put(np);
if (overlay_id)
*overlay_id = id;
return ret;
}
......@@ -1286,7 +1284,7 @@ static int of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
int before, int after, enum overlay_type ovtype)
{
int ret;
int ret, ovcs_id;
/* unittest device must not be in before state */
if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
......@@ -1297,7 +1295,8 @@ static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
return -EINVAL;
}
ret = of_unittest_apply_overlay(overlay_nr, unittest_nr, NULL);
ovcs_id = 0;
ret = of_unittest_apply_overlay(overlay_nr, unittest_nr, &ovcs_id);
if (ret != 0) {
/* of_unittest_apply_overlay already called unittest() */
return ret;
......@@ -1320,7 +1319,7 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
int unittest_nr, int before, int after,
enum overlay_type ovtype)
{
int ret, ov_id;
int ret, ovcs_id;
/* unittest device must be in before state */
if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
......@@ -1332,7 +1331,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
}
/* apply the overlay */
ret = of_unittest_apply_overlay(overlay_nr, unittest_nr, &ov_id);
ovcs_id = 0;
ret = of_unittest_apply_overlay(overlay_nr, unittest_nr, &ovcs_id);
if (ret != 0) {
/* of_unittest_apply_overlay already called unittest() */
return ret;
......@@ -1347,7 +1347,7 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
return -EINVAL;
}
ret = of_overlay_remove(ov_id);
ret = of_overlay_remove(&ovcs_id);
if (ret != 0) {
unittest(0, "overlay @\"%s\" failed to be destroyed @\"%s\"\n",
overlay_path(overlay_nr),
......@@ -1449,7 +1449,7 @@ static void of_unittest_overlay_5(void)
static void of_unittest_overlay_6(void)
{
struct device_node *np;
int ret, i, ov_id[2];
int ret, i, ov_id[2], ovcs_id;
int overlay_nr = 6, unittest_nr = 6;
int before = 0, after = 1;
......@@ -1476,13 +1476,14 @@ static void of_unittest_overlay_6(void)
return;
}
ret = of_overlay_apply(np);
ovcs_id = 0;
ret = of_overlay_apply(np, &ovcs_id);
if (ret < 0) {
unittest(0, "could not create overlay from \"%s\"\n",
overlay_path(overlay_nr + i));
return;
}
ov_id[i] = ret;
ov_id[i] = ovcs_id;
of_unittest_track_overlay(ov_id[i]);
}
......@@ -1500,7 +1501,8 @@ static void of_unittest_overlay_6(void)
}
for (i = 1; i >= 0; i--) {
ret = of_overlay_remove(ov_id[i]);
ovcs_id = ov_id[i];
ret = of_overlay_remove(&ovcs_id);
if (ret != 0) {
unittest(0, "overlay @\"%s\" failed destroy @\"%s\"\n",
overlay_path(overlay_nr + i),
......@@ -1531,7 +1533,7 @@ static void of_unittest_overlay_6(void)
static void of_unittest_overlay_8(void)
{
struct device_node *np;
int ret, i, ov_id[2];
int ret, i, ov_id[2], ovcs_id;
int overlay_nr = 8, unittest_nr = 8;
/* we don't care about device state in this test */
......@@ -1546,18 +1548,20 @@ static void of_unittest_overlay_8(void)
return;
}
ret = of_overlay_apply(np);
ovcs_id = 0;
ret = of_overlay_apply(np, &ovcs_id);
if (ret < 0) {
unittest(0, "could not create overlay from \"%s\"\n",
overlay_path(overlay_nr + i));
return;
}
ov_id[i] = ret;
ov_id[i] = ovcs_id;
of_unittest_track_overlay(ov_id[i]);
}
/* now try to remove first overlay (it should fail) */
ret = of_overlay_remove(ov_id[0]);
ovcs_id = ov_id[0];
ret = of_overlay_remove(&ovcs_id);
if (ret == 0) {
unittest(0, "overlay @\"%s\" was destroyed @\"%s\"\n",
overlay_path(overlay_nr + 0),
......@@ -1568,7 +1572,8 @@ static void of_unittest_overlay_8(void)
/* removing them in order should work */
for (i = 1; i >= 0; i--) {
ret = of_overlay_remove(ov_id[i]);
ovcs_id = ov_id[i];
ret = of_overlay_remove(&ovcs_id);
if (ret != 0) {
unittest(0, "overlay @\"%s\" not destroyed @\"%s\"\n",
overlay_path(overlay_nr + i),
......@@ -2149,13 +2154,11 @@ static int __init overlay_data_add(int onum)
goto out_free_np_overlay;
}
ret = of_overlay_apply(info->np_overlay);
info->overlay_id = 0;
ret = of_overlay_apply(info->np_overlay, &info->overlay_id);
if (ret < 0) {
pr_err("of_overlay_apply() (ret=%d), %d\n", ret, onum);
goto out_free_np_overlay;
} else {
info->overlay_id = ret;
ret = 0;
}
pr_debug("__dtb_overlay_begin applied, overlay id %d\n", ret);
......
......@@ -1298,7 +1298,7 @@ static inline bool of_device_is_system_power_controller(const struct device_node
*/
enum of_overlay_notify_action {
OF_OVERLAY_PRE_APPLY,
OF_OVERLAY_PRE_APPLY = 0,
OF_OVERLAY_POST_APPLY,
OF_OVERLAY_PRE_REMOVE,
OF_OVERLAY_POST_REMOVE,
......@@ -1312,8 +1312,8 @@ struct of_overlay_notify_data {
#ifdef CONFIG_OF_OVERLAY
/* ID based overlays; the API for external users */
int of_overlay_apply(struct device_node *tree);
int of_overlay_remove(int id);
int of_overlay_apply(struct device_node *tree, int *ovcs_id);
int of_overlay_remove(int *ovcs_id);
int of_overlay_remove_all(void);
int of_overlay_notifier_register(struct notifier_block *nb);
......@@ -1321,12 +1321,12 @@ int of_overlay_notifier_unregister(struct notifier_block *nb);
#else
static inline int of_overlay_apply(struct device_node *tree)
static inline int of_overlay_apply(struct device_node *tree, int *ovcs_id)
{
return -ENOTSUPP;
}
static inline int of_overlay_remove(int id)
static inline int of_overlay_remove(int *ovcs_id)
{
return -ENOTSUPP;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment