summaryrefslogtreecommitdiff
path: root/drivers/of/dynamic.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/of/dynamic.c')
-rw-r--r--drivers/of/dynamic.c135
1 files changed, 115 insertions, 20 deletions
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c
index 4bc96af4d8e2..747d87619faf 100644
--- a/drivers/of/dynamic.c
+++ b/drivers/of/dynamic.c
@@ -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;
}
/**