Merge tag 'at91-drivers2' of git://github.com/at91linux/linux-at91 into next/drivers
[cascardo/linux.git] / drivers / of / selftest.c
index 077314e..a737cb5 100644 (file)
@@ -9,6 +9,7 @@
 #include <linux/errno.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_fdt.h>
 #include <linux/of_irq.h>
 #include <linux/of_platform.h>
 #include <linux/list.h>
 #include <linux/slab.h>
 #include <linux/device.h>
 
+#include "of_private.h"
+
 static struct selftest_results {
        int passed;
        int failed;
 } selftest_results;
 
+#define NO_OF_NODES 2
+static struct device_node *nodes[NO_OF_NODES];
+static int last_node_index;
+static bool selftest_live_tree;
+
 #define selftest(result, fmt, ...) { \
        if (!(result)) { \
                selftest_results.failed++; \
@@ -266,6 +274,81 @@ static void __init of_selftest_property_match_string(void)
        selftest(rc == -EILSEQ, "unterminated string; rc=%i", rc);
 }
 
+#define propcmp(p1, p2) (((p1)->length == (p2)->length) && \
+                       (p1)->value && (p2)->value && \
+                       !memcmp((p1)->value, (p2)->value, (p1)->length) && \
+                       !strcmp((p1)->name, (p2)->name))
+static void __init of_selftest_property_copy(void)
+{
+#ifdef CONFIG_OF_DYNAMIC
+       struct property p1 = { .name = "p1", .length = 0, .value = "" };
+       struct property p2 = { .name = "p2", .length = 5, .value = "abcd" };
+       struct property *new;
+
+       new = __of_prop_dup(&p1, GFP_KERNEL);
+       selftest(new && propcmp(&p1, new), "empty property didn't copy correctly\n");
+       kfree(new->value);
+       kfree(new->name);
+       kfree(new);
+
+       new = __of_prop_dup(&p2, GFP_KERNEL);
+       selftest(new && propcmp(&p2, new), "non-empty property didn't copy correctly\n");
+       kfree(new->value);
+       kfree(new->name);
+       kfree(new);
+#endif
+}
+
+static void __init of_selftest_changeset(void)
+{
+#ifdef CONFIG_OF_DYNAMIC
+       struct property *ppadd, padd = { .name = "prop-add", .length = 0, .value = "" };
+       struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" };
+       struct property *ppremove;
+       struct device_node *n1, *n2, *n21, *nremove, *parent;
+       struct of_changeset chgset;
+
+       of_changeset_init(&chgset);
+       n1 = __of_node_alloc("/testcase-data/changeset/n1", GFP_KERNEL);
+       selftest(n1, "testcase setup failure\n");
+       n2 = __of_node_alloc("/testcase-data/changeset/n2", GFP_KERNEL);
+       selftest(n2, "testcase setup failure\n");
+       n21 = __of_node_alloc("/testcase-data/changeset/n2/n21", GFP_KERNEL);
+       selftest(n21, "testcase setup failure %p\n", n21);
+       nremove = of_find_node_by_path("/testcase-data/changeset/node-remove");
+       selftest(nremove, "testcase setup failure\n");
+       ppadd = __of_prop_dup(&padd, GFP_KERNEL);
+       selftest(ppadd, "testcase setup failure\n");
+       ppupdate = __of_prop_dup(&pupdate, GFP_KERNEL);
+       selftest(ppupdate, "testcase setup failure\n");
+       parent = nremove->parent;
+       n1->parent = parent;
+       n2->parent = parent;
+       n21->parent = n2;
+       n2->child = n21;
+       ppremove = of_find_property(parent, "prop-remove", NULL);
+       selftest(ppremove, "failed to find removal prop");
+
+       of_changeset_init(&chgset);
+       selftest(!of_changeset_attach_node(&chgset, n1), "fail attach n1\n");
+       selftest(!of_changeset_attach_node(&chgset, n2), "fail attach n2\n");
+       selftest(!of_changeset_detach_node(&chgset, nremove), "fail remove node\n");
+       selftest(!of_changeset_attach_node(&chgset, n21), "fail attach n21\n");
+       selftest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop\n");
+       selftest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n");
+       selftest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n");
+       mutex_lock(&of_mutex);
+       selftest(!of_changeset_apply(&chgset), "apply failed\n");
+       mutex_unlock(&of_mutex);
+
+       mutex_lock(&of_mutex);
+       selftest(!of_changeset_revert(&chgset), "revert failed\n");
+       mutex_unlock(&of_mutex);
+
+       of_changeset_destroy(&chgset);
+#endif
+}
+
 static void __init of_selftest_parse_interrupts(void)
 {
        struct device_node *np;
@@ -517,9 +600,177 @@ static void __init of_selftest_platform_populate(void)
        }
 }
 
+/**
+ *     update_node_properties - adds the properties
+ *     of np into dup node (present in live tree) and
+ *     updates parent of children of np to dup.
+ *
+ *     @np:    node already present in live tree
+ *     @dup:   node present in live tree to be updated
+ */
+static void update_node_properties(struct device_node *np,
+                                       struct device_node *dup)
+{
+       struct property *prop;
+       struct device_node *child;
+
+       for_each_property_of_node(np, prop)
+               of_add_property(dup, prop);
+
+       for_each_child_of_node(np, child)
+               child->parent = dup;
+}
+
+/**
+ *     attach_node_and_children - attaches nodes
+ *     and its children to live tree
+ *
+ *     @np:    Node to attach to live tree
+ */
+static int attach_node_and_children(struct device_node *np)
+{
+       struct device_node *next, *root = np, *dup;
+
+       /* skip root node */
+       np = np->child;
+       /* storing a copy in temporary node */
+       dup = np;
+
+       while (dup) {
+               nodes[last_node_index++] = dup;
+               dup = dup->sibling;
+       }
+       dup = NULL;
+
+       while (np) {
+               next = np->allnext;
+               dup = of_find_node_by_path(np->full_name);
+               if (dup)
+                       update_node_properties(np, dup);
+               else {
+                       np->child = NULL;
+                       if (np->parent == root)
+                               np->parent = of_allnodes;
+                       of_attach_node(np);
+               }
+               np = next;
+       }
+
+       return 0;
+}
+
+/**
+ *     selftest_data_add - Reads, copies data from
+ *     linked tree and attaches it to the live tree
+ */
+static int __init selftest_data_add(void)
+{
+       void *selftest_data;
+       struct device_node *selftest_data_node, *np;
+       extern uint8_t __dtb_testcases_begin[];
+       extern uint8_t __dtb_testcases_end[];
+       const int size = __dtb_testcases_end - __dtb_testcases_begin;
+
+       if (!size) {
+               pr_warn("%s: No testcase data to attach; not running tests\n",
+                       __func__);
+               return -ENODATA;
+       }
+
+       /* creating copy */
+       selftest_data = kmemdup(__dtb_testcases_begin, size, GFP_KERNEL);
+
+       if (!selftest_data) {
+               pr_warn("%s: Failed to allocate memory for selftest_data; "
+                       "not running tests\n", __func__);
+               return -ENOMEM;
+       }
+       of_fdt_unflatten_tree(selftest_data, &selftest_data_node);
+       if (!selftest_data_node) {
+               pr_warn("%s: No tree to attach; not running tests\n", __func__);
+               return -ENODATA;
+       }
+
+       if (!of_allnodes) {
+               /* enabling flag for removing nodes */
+               selftest_live_tree = true;
+               of_allnodes = selftest_data_node;
+
+               for_each_of_allnodes(np)
+                       __of_attach_node_sysfs(np);
+               of_aliases = of_find_node_by_path("/aliases");
+               of_chosen = of_find_node_by_path("/chosen");
+               return 0;
+       }
+
+       /* attach the sub-tree to live tree */
+       return attach_node_and_children(selftest_data_node);
+}
+
+/**
+ *     detach_node_and_children - detaches node
+ *     and its children from live tree
+ *
+ *     @np:    Node to detach from live tree
+ */
+static void detach_node_and_children(struct device_node *np)
+{
+       while (np->child)
+               detach_node_and_children(np->child);
+
+       while (np->sibling)
+               detach_node_and_children(np->sibling);
+
+       of_detach_node(np);
+}
+
+/**
+ *     selftest_data_remove - removes the selftest data
+ *     nodes from the live tree
+ */
+static void selftest_data_remove(void)
+{
+       struct device_node *np;
+       struct property *prop;
+
+       if (selftest_live_tree) {
+               of_node_put(of_aliases);
+               of_node_put(of_chosen);
+               of_aliases = NULL;
+               of_chosen = NULL;
+               for_each_child_of_node(of_allnodes, np)
+                       detach_node_and_children(np);
+               __of_detach_node_sysfs(of_allnodes);
+               of_allnodes = NULL;
+               return;
+       }
+
+       while (last_node_index >= 0) {
+               if (nodes[last_node_index]) {
+                       np = of_find_node_by_path(nodes[last_node_index]->full_name);
+                       if (strcmp(np->full_name, "/aliases") != 0) {
+                               detach_node_and_children(np->child);
+                               of_detach_node(np);
+                       } else {
+                               for_each_property_of_node(np, prop) {
+                                       if (strcmp(prop->name, "testcase-alias") == 0)
+                                               of_remove_property(np, prop);
+                               }
+                       }
+               }
+               last_node_index--;
+       }
+}
+
 static int __init of_selftest(void)
 {
        struct device_node *np;
+       int res;
+
+       /* adding data for selftest */
+       res = selftest_data_add();
+       if (res)
+               return res;
 
        np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
        if (!np) {
@@ -533,12 +784,18 @@ static int __init of_selftest(void)
        of_selftest_dynamic();
        of_selftest_parse_phandle_with_args();
        of_selftest_property_match_string();
+       of_selftest_property_copy();
+       of_selftest_changeset();
        of_selftest_parse_interrupts();
        of_selftest_parse_interrupts_extended();
        of_selftest_match_node();
        of_selftest_platform_populate();
        pr_info("end of selftest - %i passed, %i failed\n",
                selftest_results.passed, selftest_results.failed);
+
+       /* removing selftest data from live tree */
+       selftest_data_remove();
+
        return 0;
 }
 late_initcall(of_selftest);