of/overlay: Add overlay unittests
authorPantelis Antoniou <pantelis.antoniou@konsulko.com>
Tue, 28 Oct 2014 20:35:59 +0000 (22:35 +0200)
committerGrant Likely <grant.likely@linaro.org>
Mon, 24 Nov 2014 22:25:13 +0000 (22:25 +0000)
Add unittests for OF overlays.

It tests overlay device addition/removal and whether
the apply revert sequence is correct.

Changes since V1:
* Added local fixups entries.

Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
Signed-off-by: Grant Likely <grant.likely@linaro.org>
Documentation/devicetree/bindings/unittest.txt [new file with mode: 0644]
drivers/of/unittest-data/testcases.dts
drivers/of/unittest-data/tests-overlay.dtsi [new file with mode: 0644]
drivers/of/unittest.c

diff --git a/Documentation/devicetree/bindings/unittest.txt b/Documentation/devicetree/bindings/unittest.txt
new file mode 100644 (file)
index 0000000..0f92a22
--- /dev/null
@@ -0,0 +1,14 @@
+* OF selftest platform device
+
+** selftest
+
+Required properties:
+- compatible: must be "selftest"
+
+All other properties are optional.
+
+Example:
+       selftest {
+               compatible = "selftest";
+               status = "okay";
+       };
index b6bc41b2a1858d77a2f0515d6036d411b5ab1e67..12f7c3d649c884411effd84665a0e64a831c202f 100644 (file)
@@ -13,6 +13,7 @@
 #include "tests-interrupts.dtsi"
 #include "tests-match.dtsi"
 #include "tests-platform.dtsi"
+#include "tests-overlay.dtsi"
 
 /*
  * phandle fixup data - generated by dtc patches that aren't upstream.
                testcase-device2 {
                        interrupt-parent = <0x00000000>;
                };
+               overlay2 {
+                       fragment@0 {
+                               target = <0x00000000>;
+                       };
+               };
+               overlay3 {
+                       fragment@0 {
+                               target = <0x00000000>;
+                       };
+               };
+               overlay4 {
+                       fragment@0 {
+                               target = <0x00000000>;
+                       };
+               };
        };
 }; };
diff --git a/drivers/of/unittest-data/tests-overlay.dtsi b/drivers/of/unittest-data/tests-overlay.dtsi
new file mode 100644 (file)
index 0000000..75976da
--- /dev/null
@@ -0,0 +1,180 @@
+
+/ {
+       testcase-data {
+               overlay-node {
+
+                       /* test bus */
+                       selftestbus: test-bus {
+                               compatible = "simple-bus";
+                               #address-cells = <1>;
+                               #size-cells = <0>;
+
+                               selftest100: test-selftest100 {
+                                       compatible = "selftest";
+                                       status = "okay";
+                                       reg = <100>;
+                               };
+
+                               selftest101: test-selftest101 {
+                                       compatible = "selftest";
+                                       status = "disabled";
+                                       reg = <101>;
+                               };
+
+                               selftest0: test-selftest0 {
+                                       compatible = "selftest";
+                                       status = "disabled";
+                                       reg = <0>;
+                               };
+
+                               selftest1: test-selftest1 {
+                                       compatible = "selftest";
+                                       status = "okay";
+                                       reg = <1>;
+                               };
+
+                               selftest2: test-selftest2 {
+                                       compatible = "selftest";
+                                       status = "disabled";
+                                       reg = <2>;
+                               };
+
+                               selftest3: test-selftest3 {
+                                       compatible = "selftest";
+                                       status = "okay";
+                                       reg = <3>;
+                               };
+
+                               selftest5: test-selftest5 {
+                                       compatible = "selftest";
+                                       status = "disabled";
+                                       reg = <5>;
+                               };
+
+                               selftest6: test-selftest6 {
+                                       compatible = "selftest";
+                                       status = "disabled";
+                                       reg = <6>;
+                               };
+
+                               selftest7: test-selftest7 {
+                                       compatible = "selftest";
+                                       status = "disabled";
+                                       reg = <7>;
+                               };
+
+                               selftest8: test-selftest8 {
+                                       compatible = "selftest";
+                                       status = "disabled";
+                                       reg = <8>;
+                               };
+                       };
+               };
+
+               /* test enable using absolute target path */
+               overlay0 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/test-selftest0";
+                               __overlay__ {
+                                       status = "okay";
+                               };
+                       };
+               };
+
+               /* test disable using absolute target path */
+               overlay1 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/test-selftest1";
+                               __overlay__ {
+                                       status = "disabled";
+                               };
+                       };
+               };
+
+               /* test enable using label */
+               overlay2 {
+                       fragment@0 {
+                               target = <&selftest2>;
+                               __overlay__ {
+                                       status = "okay";
+                               };
+                       };
+               };
+
+               /* test disable using label */
+               overlay3 {
+                       fragment@0 {
+                               target = <&selftest3>;
+                               __overlay__ {
+                                       status = "disabled";
+                               };
+                       };
+               };
+
+               /* test insertion of a full node */
+               overlay4 {
+                       fragment@0 {
+                               target = <&selftestbus>;
+                               __overlay__ {
+
+                                       /* suppress DTC warning */
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       test-selftest4 {
+                                               compatible = "selftest";
+                                               status = "okay";
+                                               reg = <4>;
+                                       };
+                               };
+                       };
+               };
+
+               /* test overlay apply revert */
+               overlay5 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/test-selftest5";
+                               __overlay__ {
+                                       status = "okay";
+                               };
+                       };
+               };
+
+               /* test overlays application and removal in sequence */
+               overlay6 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/test-selftest6";
+                               __overlay__ {
+                                       status = "okay";
+                               };
+                       };
+               };
+               overlay7 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/test-selftest7";
+                               __overlay__ {
+                                       status = "okay";
+                               };
+                       };
+               };
+
+               /* test overlays application and removal in bad sequence */
+               overlay8 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/test-selftest8";
+                               __overlay__ {
+                                       status = "okay";
+                               };
+                       };
+               };
+               overlay9 {
+                       fragment@0 {
+                               target-path = "/testcase-data/overlay-node/test-bus/test-selftest8";
+                               __overlay__ {
+                                       property-foo = "bar";
+                               };
+                       };
+               };
+
+       };
+};
index 1720b039cac74cb261018d567ac9097d2e8833cd..cc0c5ec5d464a74ab1a42d7acc6c5de220f34274 100644 (file)
@@ -17,6 +17,8 @@
 #include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
 
 #include "of_private.h"
 
@@ -933,6 +935,484 @@ static void selftest_data_remove(void)
        }
 }
 
+#ifdef CONFIG_OF_OVERLAY
+
+static int selftest_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+
+       if (np == NULL) {
+               dev_err(dev, "No OF data for device\n");
+               return -EINVAL;
+
+       }
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+       return 0;
+}
+
+static int selftest_remove(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+
+       dev_dbg(dev, "%s for node @%s\n", __func__, np->full_name);
+       return 0;
+}
+
+static struct of_device_id selftest_match[] = {
+       { .compatible = "selftest", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, altera_jtaguart_match);
+
+static struct platform_driver selftest_driver = {
+       .probe                  = selftest_probe,
+       .remove                 = selftest_remove,
+       .driver = {
+               .name           = "selftest",
+               .owner          = THIS_MODULE,
+               .of_match_table = of_match_ptr(selftest_match),
+       },
+};
+
+/* get the platform device instantiated at the path */
+static struct platform_device *of_path_to_platform_device(const char *path)
+{
+       struct device_node *np;
+       struct platform_device *pdev;
+
+       np = of_find_node_by_path(path);
+       if (np == NULL)
+               return NULL;
+
+       pdev = of_find_device_by_node(np);
+       of_node_put(np);
+
+       return pdev;
+}
+
+/* find out if a platform device exists at that path */
+static int of_path_platform_device_exists(const char *path)
+{
+       struct platform_device *pdev;
+
+       pdev = of_path_to_platform_device(path);
+       platform_device_put(pdev);
+       return pdev != NULL;
+}
+
+static const char *selftest_path(int nr)
+{
+       static char buf[256];
+
+       snprintf(buf, sizeof(buf) - 1,
+               "/testcase-data/overlay-node/test-bus/test-selftest%d", nr);
+       buf[sizeof(buf) - 1] = '\0';
+
+       return buf;
+}
+
+static const char *overlay_path(int nr)
+{
+       static char buf[256];
+
+       snprintf(buf, sizeof(buf) - 1,
+               "/testcase-data/overlay%d", nr);
+       buf[sizeof(buf) - 1] = '\0';
+
+       return buf;
+}
+
+static const char *bus_path = "/testcase-data/overlay-node/test-bus";
+
+static int of_selftest_apply_overlay(int selftest_nr, int overlay_nr,
+               int *overlay_id)
+{
+       struct device_node *np = NULL;
+       int ret, id = -1;
+
+       np = of_find_node_by_path(overlay_path(overlay_nr));
+       if (np == NULL) {
+               selftest(0, "could not find overlay node @\"%s\"\n",
+                               overlay_path(overlay_nr));
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ret = of_overlay_create(np);
+       if (ret < 0) {
+               selftest(0, "could not create overlay from \"%s\"\n",
+                               overlay_path(overlay_nr));
+               goto out;
+       }
+       id = ret;
+
+       ret = 0;
+
+out:
+       of_node_put(np);
+
+       if (overlay_id)
+               *overlay_id = id;
+
+       return ret;
+}
+
+/* apply an overlay while checking before and after states */
+static int of_selftest_apply_overlay_check(int overlay_nr, int selftest_nr,
+               int before, int after)
+{
+       int ret;
+
+       /* selftest device must not be in before state */
+       if (of_path_platform_device_exists(selftest_path(selftest_nr))
+                       != before) {
+               selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+                               overlay_path(overlay_nr),
+                               selftest_path(selftest_nr),
+                               !before ? "enabled" : "disabled");
+               return -EINVAL;
+       }
+
+       ret = of_selftest_apply_overlay(overlay_nr, selftest_nr, NULL);
+       if (ret != 0) {
+               /* of_selftest_apply_overlay already called selftest() */
+               return ret;
+       }
+
+       /* selftest device must be to set to after state */
+       if (of_path_platform_device_exists(selftest_path(selftest_nr))
+                       != after) {
+               selftest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
+                               overlay_path(overlay_nr),
+                               selftest_path(selftest_nr),
+                               !after ? "enabled" : "disabled");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* apply an overlay and then revert it while checking before, after states */
+static int of_selftest_apply_revert_overlay_check(int overlay_nr,
+               int selftest_nr, int before, int after)
+{
+       int ret, ov_id;
+
+       /* selftest device must be in before state */
+       if (of_path_platform_device_exists(selftest_path(selftest_nr))
+                       != before) {
+               selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+                               overlay_path(overlay_nr),
+                               selftest_path(selftest_nr),
+                               !before ? "enabled" : "disabled");
+               return -EINVAL;
+       }
+
+       /* apply the overlay */
+       ret = of_selftest_apply_overlay(overlay_nr, selftest_nr, &ov_id);
+       if (ret != 0) {
+               /* of_selftest_apply_overlay already called selftest() */
+               return ret;
+       }
+
+       /* selftest device must be in after state */
+       if (of_path_platform_device_exists(selftest_path(selftest_nr))
+                       != after) {
+               selftest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
+                               overlay_path(overlay_nr),
+                               selftest_path(selftest_nr),
+                               !after ? "enabled" : "disabled");
+               return -EINVAL;
+       }
+
+       ret = of_overlay_destroy(ov_id);
+       if (ret != 0) {
+               selftest(0, "overlay @\"%s\" failed to be destroyed @\"%s\"\n",
+                               overlay_path(overlay_nr),
+                               selftest_path(selftest_nr));
+               return ret;
+       }
+
+       /* selftest device must be again in before state */
+       if (of_path_platform_device_exists(selftest_path(selftest_nr))
+                       != before) {
+               selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+                               overlay_path(overlay_nr),
+                               selftest_path(selftest_nr),
+                               !before ? "enabled" : "disabled");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* test activation of device */
+static void of_selftest_overlay_0(void)
+{
+       int ret;
+
+       /* device should enable */
+       ret = of_selftest_apply_overlay_check(0, 0, 0, 1);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 0);
+}
+
+/* test deactivation of device */
+static void of_selftest_overlay_1(void)
+{
+       int ret;
+
+       /* device should disable */
+       ret = of_selftest_apply_overlay_check(1, 1, 1, 0);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 1);
+}
+
+/* test activation of device */
+static void of_selftest_overlay_2(void)
+{
+       int ret;
+
+       /* device should enable */
+       ret = of_selftest_apply_overlay_check(2, 2, 0, 1);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 2);
+}
+
+/* test deactivation of device */
+static void of_selftest_overlay_3(void)
+{
+       int ret;
+
+       /* device should disable */
+       ret = of_selftest_apply_overlay_check(3, 3, 1, 0);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 3);
+}
+
+/* test activation of a full device node */
+static void of_selftest_overlay_4(void)
+{
+       int ret;
+
+       /* device should disable */
+       ret = of_selftest_apply_overlay_check(4, 4, 0, 1);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 4);
+}
+
+/* test overlay apply/revert sequence */
+static void of_selftest_overlay_5(void)
+{
+       int ret;
+
+       /* device should disable */
+       ret = of_selftest_apply_revert_overlay_check(5, 5, 0, 1);
+       if (ret != 0)
+               return;
+
+       selftest(1, "overlay test %d passed\n", 5);
+}
+
+/* test overlay application in sequence */
+static void of_selftest_overlay_6(void)
+{
+       struct device_node *np;
+       int ret, i, ov_id[2];
+       int overlay_nr = 6, selftest_nr = 6;
+       int before = 0, after = 1;
+
+       /* selftest device must be in before state */
+       for (i = 0; i < 2; i++) {
+               if (of_path_platform_device_exists(
+                                       selftest_path(selftest_nr + i))
+                               != before) {
+                       selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+                                       overlay_path(overlay_nr + i),
+                                       selftest_path(selftest_nr + i),
+                                       !before ? "enabled" : "disabled");
+                       return;
+               }
+       }
+
+       /* apply the overlays */
+       for (i = 0; i < 2; i++) {
+
+               np = of_find_node_by_path(overlay_path(overlay_nr + i));
+               if (np == NULL) {
+                       selftest(0, "could not find overlay node @\"%s\"\n",
+                                       overlay_path(overlay_nr + i));
+                       return;
+               }
+
+               ret = of_overlay_create(np);
+               if (ret < 0)  {
+                       selftest(0, "could not create overlay from \"%s\"\n",
+                                       overlay_path(overlay_nr + i));
+                       return;
+               }
+               ov_id[i] = ret;
+       }
+
+       for (i = 0; i < 2; i++) {
+               /* selftest device must be in after state */
+               if (of_path_platform_device_exists(
+                                       selftest_path(selftest_nr + i))
+                               != after) {
+                       selftest(0, "overlay @\"%s\" failed @\"%s\" %s\n",
+                                       overlay_path(overlay_nr + i),
+                                       selftest_path(selftest_nr + i),
+                                       !after ? "enabled" : "disabled");
+                       return;
+               }
+       }
+
+       for (i = 1; i >= 0; i--) {
+               ret = of_overlay_destroy(ov_id[i]);
+               if (ret != 0) {
+                       selftest(0, "overlay @\"%s\" failed destroy @\"%s\"\n",
+                                       overlay_path(overlay_nr + i),
+                                       selftest_path(selftest_nr + i));
+                       return;
+               }
+       }
+
+       for (i = 0; i < 2; i++) {
+               /* selftest device must be again in before state */
+               if (of_path_platform_device_exists(
+                                       selftest_path(selftest_nr + i))
+                               != before) {
+                       selftest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
+                                       overlay_path(overlay_nr + i),
+                                       selftest_path(selftest_nr + i),
+                                       !before ? "enabled" : "disabled");
+                       return;
+               }
+       }
+
+       selftest(1, "overlay test %d passed\n", 6);
+}
+
+/* test overlay application in sequence */
+static void of_selftest_overlay_8(void)
+{
+       struct device_node *np;
+       int ret, i, ov_id[2];
+       int overlay_nr = 8, selftest_nr = 8;
+
+       /* we don't care about device state in this test */
+
+       /* apply the overlays */
+       for (i = 0; i < 2; i++) {
+
+               np = of_find_node_by_path(overlay_path(overlay_nr + i));
+               if (np == NULL) {
+                       selftest(0, "could not find overlay node @\"%s\"\n",
+                                       overlay_path(overlay_nr + i));
+                       return;
+               }
+
+               ret = of_overlay_create(np);
+               if (ret < 0)  {
+                       selftest(0, "could not create overlay from \"%s\"\n",
+                                       overlay_path(overlay_nr + i));
+                       return;
+               }
+               ov_id[i] = ret;
+       }
+
+       /* now try to remove first overlay (it should fail) */
+       ret = of_overlay_destroy(ov_id[0]);
+       if (ret == 0) {
+               selftest(0, "overlay @\"%s\" was destroyed @\"%s\"\n",
+                               overlay_path(overlay_nr + 0),
+                               selftest_path(selftest_nr));
+               return;
+       }
+
+       /* removing them in order should work */
+       for (i = 1; i >= 0; i--) {
+               ret = of_overlay_destroy(ov_id[i]);
+               if (ret != 0) {
+                       selftest(0, "overlay @\"%s\" not destroyed @\"%s\"\n",
+                                       overlay_path(overlay_nr + i),
+                                       selftest_path(selftest_nr));
+                       return;
+               }
+       }
+
+       selftest(1, "overlay test %d passed\n", 8);
+}
+
+static void __init of_selftest_overlay(void)
+{
+       struct device_node *bus_np = NULL;
+       int ret;
+
+       ret = platform_driver_register(&selftest_driver);
+       if (ret != 0) {
+               selftest(0, "could not register selftest driver\n");
+               goto out;
+       }
+
+       bus_np = of_find_node_by_path(bus_path);
+       if (bus_np == NULL) {
+               selftest(0, "could not find bus_path \"%s\"\n", bus_path);
+               goto out;
+       }
+
+       ret = of_platform_populate(bus_np, of_default_bus_match_table,
+                       NULL, NULL);
+       if (ret != 0) {
+               selftest(0, "could not populate bus @ \"%s\"\n", bus_path);
+               goto out;
+       }
+
+       if (!of_path_platform_device_exists(selftest_path(100))) {
+               selftest(0, "could not find selftest0 @ \"%s\"\n",
+                               selftest_path(100));
+               goto out;
+       }
+
+       if (of_path_platform_device_exists(selftest_path(101))) {
+               selftest(0, "selftest1 @ \"%s\" should not exist\n",
+                               selftest_path(101));
+               goto out;
+       }
+
+       selftest(1, "basic infrastructure of overlays passed");
+
+       /* tests in sequence */
+       of_selftest_overlay_0();
+       of_selftest_overlay_1();
+       of_selftest_overlay_2();
+       of_selftest_overlay_3();
+       of_selftest_overlay_4();
+       of_selftest_overlay_5();
+       of_selftest_overlay_6();
+       of_selftest_overlay_8();
+
+out:
+       of_node_put(bus_np);
+}
+
+#else
+static inline void __init of_selftest_overlay(void) { }
+#endif
+
 static int __init of_selftest(void)
 {
        struct device_node *np;
@@ -965,6 +1445,7 @@ static int __init of_selftest(void)
        of_selftest_parse_interrupts_extended();
        of_selftest_match_node();
        of_selftest_platform_populate();
+       of_selftest_overlay();
 
        /* removing selftest data from live tree */
        selftest_data_remove();