diff options
Diffstat (limited to 'drivers/of/selftest.c')
-rw-r--r-- | drivers/of/selftest.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/drivers/of/selftest.c b/drivers/of/selftest.c index 077314eebb95..d41002667833 100644 --- a/drivers/of/selftest.c +++ b/drivers/of/selftest.c | |||
@@ -9,6 +9,7 @@ | |||
9 | #include <linux/errno.h> | 9 | #include <linux/errno.h> |
10 | #include <linux/module.h> | 10 | #include <linux/module.h> |
11 | #include <linux/of.h> | 11 | #include <linux/of.h> |
12 | #include <linux/of_fdt.h> | ||
12 | #include <linux/of_irq.h> | 13 | #include <linux/of_irq.h> |
13 | #include <linux/of_platform.h> | 14 | #include <linux/of_platform.h> |
14 | #include <linux/list.h> | 15 | #include <linux/list.h> |
@@ -16,11 +17,17 @@ | |||
16 | #include <linux/slab.h> | 17 | #include <linux/slab.h> |
17 | #include <linux/device.h> | 18 | #include <linux/device.h> |
18 | 19 | ||
20 | #include "of_private.h" | ||
21 | |||
19 | static struct selftest_results { | 22 | static struct selftest_results { |
20 | int passed; | 23 | int passed; |
21 | int failed; | 24 | int failed; |
22 | } selftest_results; | 25 | } selftest_results; |
23 | 26 | ||
27 | #define NO_OF_NODES 2 | ||
28 | static struct device_node *nodes[NO_OF_NODES]; | ||
29 | static int last_node_index; | ||
30 | |||
24 | #define selftest(result, fmt, ...) { \ | 31 | #define selftest(result, fmt, ...) { \ |
25 | if (!(result)) { \ | 32 | if (!(result)) { \ |
26 | selftest_results.failed++; \ | 33 | selftest_results.failed++; \ |
@@ -266,6 +273,81 @@ static void __init of_selftest_property_match_string(void) | |||
266 | selftest(rc == -EILSEQ, "unterminated string; rc=%i", rc); | 273 | selftest(rc == -EILSEQ, "unterminated string; rc=%i", rc); |
267 | } | 274 | } |
268 | 275 | ||
276 | #define propcmp(p1, p2) (((p1)->length == (p2)->length) && \ | ||
277 | (p1)->value && (p2)->value && \ | ||
278 | !memcmp((p1)->value, (p2)->value, (p1)->length) && \ | ||
279 | !strcmp((p1)->name, (p2)->name)) | ||
280 | static void __init of_selftest_property_copy(void) | ||
281 | { | ||
282 | #ifdef CONFIG_OF_DYNAMIC | ||
283 | struct property p1 = { .name = "p1", .length = 0, .value = "" }; | ||
284 | struct property p2 = { .name = "p2", .length = 5, .value = "abcd" }; | ||
285 | struct property *new; | ||
286 | |||
287 | new = __of_prop_dup(&p1, GFP_KERNEL); | ||
288 | selftest(new && propcmp(&p1, new), "empty property didn't copy correctly\n"); | ||
289 | kfree(new->value); | ||
290 | kfree(new->name); | ||
291 | kfree(new); | ||
292 | |||
293 | new = __of_prop_dup(&p2, GFP_KERNEL); | ||
294 | selftest(new && propcmp(&p2, new), "non-empty property didn't copy correctly\n"); | ||
295 | kfree(new->value); | ||
296 | kfree(new->name); | ||
297 | kfree(new); | ||
298 | #endif | ||
299 | } | ||
300 | |||
301 | static void __init of_selftest_changeset(void) | ||
302 | { | ||
303 | #ifdef CONFIG_OF_DYNAMIC | ||
304 | struct property *ppadd, padd = { .name = "prop-add", .length = 0, .value = "" }; | ||
305 | struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" }; | ||
306 | struct property *ppremove; | ||
307 | struct device_node *n1, *n2, *n21, *nremove, *parent; | ||
308 | struct of_changeset chgset; | ||
309 | |||
310 | of_changeset_init(&chgset); | ||
311 | n1 = __of_node_alloc("/testcase-data/changeset/n1", GFP_KERNEL); | ||
312 | selftest(n1, "testcase setup failure\n"); | ||
313 | n2 = __of_node_alloc("/testcase-data/changeset/n2", GFP_KERNEL); | ||
314 | selftest(n2, "testcase setup failure\n"); | ||
315 | n21 = __of_node_alloc("/testcase-data/changeset/n2/n21", GFP_KERNEL); | ||
316 | selftest(n21, "testcase setup failure %p\n", n21); | ||
317 | nremove = of_find_node_by_path("/testcase-data/changeset/node-remove"); | ||
318 | selftest(nremove, "testcase setup failure\n"); | ||
319 | ppadd = __of_prop_dup(&padd, GFP_KERNEL); | ||
320 | selftest(ppadd, "testcase setup failure\n"); | ||
321 | ppupdate = __of_prop_dup(&pupdate, GFP_KERNEL); | ||
322 | selftest(ppupdate, "testcase setup failure\n"); | ||
323 | parent = nremove->parent; | ||
324 | n1->parent = parent; | ||
325 | n2->parent = parent; | ||
326 | n21->parent = n2; | ||
327 | n2->child = n21; | ||
328 | ppremove = of_find_property(parent, "prop-remove", NULL); | ||
329 | selftest(ppremove, "failed to find removal prop"); | ||
330 | |||
331 | of_changeset_init(&chgset); | ||
332 | selftest(!of_changeset_attach_node(&chgset, n1), "fail attach n1\n"); | ||
333 | selftest(!of_changeset_attach_node(&chgset, n2), "fail attach n2\n"); | ||
334 | selftest(!of_changeset_detach_node(&chgset, nremove), "fail remove node\n"); | ||
335 | selftest(!of_changeset_attach_node(&chgset, n21), "fail attach n21\n"); | ||
336 | selftest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop\n"); | ||
337 | selftest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n"); | ||
338 | selftest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n"); | ||
339 | mutex_lock(&of_mutex); | ||
340 | selftest(!of_changeset_apply(&chgset), "apply failed\n"); | ||
341 | mutex_unlock(&of_mutex); | ||
342 | |||
343 | mutex_lock(&of_mutex); | ||
344 | selftest(!of_changeset_revert(&chgset), "revert failed\n"); | ||
345 | mutex_unlock(&of_mutex); | ||
346 | |||
347 | of_changeset_destroy(&chgset); | ||
348 | #endif | ||
349 | } | ||
350 | |||
269 | static void __init of_selftest_parse_interrupts(void) | 351 | static void __init of_selftest_parse_interrupts(void) |
270 | { | 352 | { |
271 | struct device_node *np; | 353 | struct device_node *np; |
@@ -517,9 +599,156 @@ static void __init of_selftest_platform_populate(void) | |||
517 | } | 599 | } |
518 | } | 600 | } |
519 | 601 | ||
602 | /** | ||
603 | * update_node_properties - adds the properties | ||
604 | * of np into dup node (present in live tree) and | ||
605 | * updates parent of children of np to dup. | ||
606 | * | ||
607 | * @np: node already present in live tree | ||
608 | * @dup: node present in live tree to be updated | ||
609 | */ | ||
610 | static void update_node_properties(struct device_node *np, | ||
611 | struct device_node *dup) | ||
612 | { | ||
613 | struct property *prop; | ||
614 | struct device_node *child; | ||
615 | |||
616 | for_each_property_of_node(np, prop) | ||
617 | of_add_property(dup, prop); | ||
618 | |||
619 | for_each_child_of_node(np, child) | ||
620 | child->parent = dup; | ||
621 | } | ||
622 | |||
623 | /** | ||
624 | * attach_node_and_children - attaches nodes | ||
625 | * and its children to live tree | ||
626 | * | ||
627 | * @np: Node to attach to live tree | ||
628 | */ | ||
629 | static int attach_node_and_children(struct device_node *np) | ||
630 | { | ||
631 | struct device_node *next, *root = np, *dup; | ||
632 | |||
633 | if (!np) { | ||
634 | pr_warn("%s: No tree to attach; not running tests\n", | ||
635 | __func__); | ||
636 | return -ENODATA; | ||
637 | } | ||
638 | |||
639 | |||
640 | /* skip root node */ | ||
641 | np = np->child; | ||
642 | /* storing a copy in temporary node */ | ||
643 | dup = np; | ||
644 | |||
645 | while (dup) { | ||
646 | nodes[last_node_index++] = dup; | ||
647 | dup = dup->sibling; | ||
648 | } | ||
649 | dup = NULL; | ||
650 | |||
651 | while (np) { | ||
652 | next = np->allnext; | ||
653 | dup = of_find_node_by_path(np->full_name); | ||
654 | if (dup) | ||
655 | update_node_properties(np, dup); | ||
656 | else { | ||
657 | np->child = NULL; | ||
658 | if (np->parent == root) | ||
659 | np->parent = of_allnodes; | ||
660 | of_attach_node(np); | ||
661 | } | ||
662 | np = next; | ||
663 | } | ||
664 | |||
665 | return 0; | ||
666 | } | ||
667 | |||
668 | /** | ||
669 | * selftest_data_add - Reads, copies data from | ||
670 | * linked tree and attaches it to the live tree | ||
671 | */ | ||
672 | static int __init selftest_data_add(void) | ||
673 | { | ||
674 | void *selftest_data; | ||
675 | struct device_node *selftest_data_node; | ||
676 | extern uint8_t __dtb_testcases_begin[]; | ||
677 | extern uint8_t __dtb_testcases_end[]; | ||
678 | const int size = __dtb_testcases_end - __dtb_testcases_begin; | ||
679 | |||
680 | if (!size || !of_allnodes) { | ||
681 | pr_warn("%s: No testcase data to attach; not running tests\n", | ||
682 | __func__); | ||
683 | return -ENODATA; | ||
684 | } | ||
685 | |||
686 | /* creating copy */ | ||
687 | selftest_data = kmemdup(__dtb_testcases_begin, size, GFP_KERNEL); | ||
688 | |||
689 | if (!selftest_data) { | ||
690 | pr_warn("%s: Failed to allocate memory for selftest_data; " | ||
691 | "not running tests\n", __func__); | ||
692 | return -ENOMEM; | ||
693 | } | ||
694 | of_fdt_unflatten_tree(selftest_data, &selftest_data_node); | ||
695 | |||
696 | /* attach the sub-tree to live tree */ | ||
697 | return attach_node_and_children(selftest_data_node); | ||
698 | } | ||
699 | |||
700 | /** | ||
701 | * detach_node_and_children - detaches node | ||
702 | * and its children from live tree | ||
703 | * | ||
704 | * @np: Node to detach from live tree | ||
705 | */ | ||
706 | static void detach_node_and_children(struct device_node *np) | ||
707 | { | ||
708 | while (np->child) | ||
709 | detach_node_and_children(np->child); | ||
710 | |||
711 | while (np->sibling) | ||
712 | detach_node_and_children(np->sibling); | ||
713 | |||
714 | of_detach_node(np); | ||
715 | } | ||
716 | |||
717 | /** | ||
718 | * selftest_data_remove - removes the selftest data | ||
719 | * nodes from the live tree | ||
720 | */ | ||
721 | static void selftest_data_remove(void) | ||
722 | { | ||
723 | struct device_node *np; | ||
724 | struct property *prop; | ||
725 | |||
726 | while (last_node_index >= 0) { | ||
727 | if (nodes[last_node_index]) { | ||
728 | np = of_find_node_by_path(nodes[last_node_index]->full_name); | ||
729 | if (strcmp(np->full_name, "/aliases") != 0) { | ||
730 | detach_node_and_children(np->child); | ||
731 | of_detach_node(np); | ||
732 | } else { | ||
733 | for_each_property_of_node(np, prop) { | ||
734 | if (strcmp(prop->name, "testcase-alias") == 0) | ||
735 | of_remove_property(np, prop); | ||
736 | } | ||
737 | } | ||
738 | } | ||
739 | last_node_index--; | ||
740 | } | ||
741 | } | ||
742 | |||
520 | static int __init of_selftest(void) | 743 | static int __init of_selftest(void) |
521 | { | 744 | { |
522 | struct device_node *np; | 745 | struct device_node *np; |
746 | int res; | ||
747 | |||
748 | /* adding data for selftest */ | ||
749 | res = selftest_data_add(); | ||
750 | if (res) | ||
751 | return res; | ||
523 | 752 | ||
524 | np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a"); | 753 | np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a"); |
525 | if (!np) { | 754 | if (!np) { |
@@ -533,12 +762,18 @@ static int __init of_selftest(void) | |||
533 | of_selftest_dynamic(); | 762 | of_selftest_dynamic(); |
534 | of_selftest_parse_phandle_with_args(); | 763 | of_selftest_parse_phandle_with_args(); |
535 | of_selftest_property_match_string(); | 764 | of_selftest_property_match_string(); |
765 | of_selftest_property_copy(); | ||
766 | of_selftest_changeset(); | ||
536 | of_selftest_parse_interrupts(); | 767 | of_selftest_parse_interrupts(); |
537 | of_selftest_parse_interrupts_extended(); | 768 | of_selftest_parse_interrupts_extended(); |
538 | of_selftest_match_node(); | 769 | of_selftest_match_node(); |
539 | of_selftest_platform_populate(); | 770 | of_selftest_platform_populate(); |
540 | pr_info("end of selftest - %i passed, %i failed\n", | 771 | pr_info("end of selftest - %i passed, %i failed\n", |
541 | selftest_results.passed, selftest_results.failed); | 772 | selftest_results.passed, selftest_results.failed); |
773 | |||
774 | /* removing selftest data from live tree */ | ||
775 | selftest_data_remove(); | ||
776 | |||
542 | return 0; | 777 | return 0; |
543 | } | 778 | } |
544 | late_initcall(of_selftest); | 779 | late_initcall(of_selftest); |