]> Git Repo - linux.git/blobdiff - drivers/of/dynamic.c
of: dynamic: fix memory leak related to properties of __of_node_dup
[linux.git] / drivers / of / dynamic.c
index 301b6db2b48db7d5826add0fff76721fdf125b3f..c454941b34ec8aa7d09e83e2f72328110728349a 100644 (file)
 
 #include "of_private.h"
 
+static struct device_node *kobj_to_device_node(struct kobject *kobj)
+{
+       return container_of(kobj, struct device_node, kobj);
+}
+
 /**
  * of_node_get() - Increment refcount of a node
  * @node:      Node to inc refcount, NULL is supported to simplify writing of
@@ -43,28 +48,6 @@ void of_node_put(struct device_node *node)
 }
 EXPORT_SYMBOL(of_node_put);
 
-void __of_detach_node_sysfs(struct device_node *np)
-{
-       struct property *pp;
-
-       if (!IS_ENABLED(CONFIG_SYSFS))
-               return;
-
-       BUG_ON(!of_node_is_initialized(np));
-       if (!of_kset)
-               return;
-
-       /* only remove properties if on sysfs */
-       if (of_node_is_attached(np)) {
-               for_each_property_of_node(np, pp)
-                       __of_sysfs_remove_bin_file(np, pp);
-               kobject_del(&np->kobj);
-       }
-
-       /* finally remove the kobj_init ref */
-       of_node_put(np);
-}
-
 static BLOCKING_NOTIFIER_HEAD(of_reconfig_chain);
 
 int of_reconfig_notifier_register(struct notifier_block *nb)
@@ -315,6 +298,18 @@ int of_detach_node(struct device_node *np)
 }
 EXPORT_SYMBOL_GPL(of_detach_node);
 
+static void property_list_free(struct property *prop_list)
+{
+       struct property *prop, *next;
+
+       for (prop = prop_list; prop != NULL; prop = next) {
+               next = prop->next;
+               kfree(prop->name);
+               kfree(prop->value);
+               kfree(prop);
+       }
+}
+
 /**
  * of_node_release() - release a dynamically allocated node
  * @kref: kref element of the node to be released
@@ -324,7 +319,6 @@ EXPORT_SYMBOL_GPL(of_detach_node);
 void of_node_release(struct kobject *kobj)
 {
        struct device_node *node = kobj_to_device_node(kobj);
-       struct property *prop = node->properties;
 
        /* We should never be releasing nodes that haven't been detached. */
        if (!of_node_check_flag(node, OF_DETACHED)) {
@@ -335,18 +329,9 @@ void of_node_release(struct kobject *kobj)
        if (!of_node_check_flag(node, OF_DYNAMIC))
                return;
 
-       while (prop) {
-               struct property *next = prop->next;
-               kfree(prop->name);
-               kfree(prop->value);
-               kfree(prop);
-               prop = next;
+       property_list_free(node->properties);
+       property_list_free(node->deadprops);
 
-               if (!prop) {
-                       prop = node->deadprops;
-                       node->deadprops = NULL;
-               }
-       }
        kfree(node->full_name);
        kfree(node->data);
        kfree(node);
@@ -508,11 +493,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);
@@ -534,11 +520,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)
@@ -672,32 +659,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;
 }
 
 /**
@@ -724,31 +761,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;
 }
 
 /**
@@ -775,7 +855,7 @@ int of_changeset_revert(struct of_changeset *ocs)
 EXPORT_SYMBOL_GPL(of_changeset_revert);
 
 /**
- * of_changeset_action - Perform a changeset action
+ * of_changeset_action - Add an action to the tail of the changeset list
  *
  * @ocs:       changeset pointer
  * @action:    action to perform
This page took 0.038554 seconds and 4 git commands to generate.