| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Self tests for device tree subsystem |
| */ |
| |
| #define pr_fmt(fmt) "### dt-test ### " fmt |
| |
| #include <linux/memblock.h> |
| #include <linux/clk.h> |
| #include <linux/err.h> |
| #include <linux/errno.h> |
| #include <linux/hashtable.h> |
| #include <linux/libfdt.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/mutex.h> |
| #include <linux/slab.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| |
| #include <linux/i2c.h> |
| #include <linux/i2c-mux.h> |
| |
| #include <linux/bitops.h> |
| |
| #include <kunit/test.h> |
| |
| #include "of_private.h" |
| #include "test-common.h" |
| |
| static int of_unittest_check_node_linkage(struct device_node *np) |
| { |
| struct device_node *child; |
| int count = 0, rc; |
| |
| for_each_child_of_node(np, child) { |
| if (child->parent != np) { |
| pr_err("Child node %pOFn links to wrong parent %pOFn\n", |
| child, np); |
| rc = -EINVAL; |
| goto put_child; |
| } |
| |
| rc = of_unittest_check_node_linkage(child); |
| if (rc < 0) |
| goto put_child; |
| count += rc; |
| } |
| |
| return count + 1; |
| put_child: |
| of_node_put(child); |
| return rc; |
| } |
| |
| static void of_unittest_check_tree_linkage(struct kunit *test) |
| { |
| struct device_node *np; |
| int allnode_count = 0, child_count; |
| |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, of_root); |
| |
| for_each_of_allnodes(np) |
| allnode_count++; |
| child_count = of_unittest_check_node_linkage(of_root); |
| |
| KUNIT_EXPECT_GT_MSG(test, child_count, 0, |
| "Device node data structure is corrupted\n"); |
| KUNIT_EXPECT_EQ_MSG( |
| test, child_count, allnode_count, |
| "allnodes list size (%i) doesn't match sibling lists size (%i)\n", |
| allnode_count, child_count); |
| pr_debug("allnodes list size (%i); sibling lists size (%i)\n", allnode_count, child_count); |
| } |
| |
| static void of_unittest_printf_one(struct kunit *test, |
| struct device_node *np, |
| const char *fmt, |
| const char *expected) |
| { |
| unsigned char *buf; |
| int buf_size; |
| int size, i; |
| |
| buf_size = strlen(expected) + 10; |
| buf = kmalloc(buf_size, GFP_KERNEL); |
| if (!buf) |
| return; |
| |
| /* Baseline; check conversion with a large size limit */ |
| memset(buf, 0xff, buf_size); |
| size = snprintf(buf, buf_size - 2, fmt, np); |
| |
| KUNIT_EXPECT_STREQ_MSG( |
| test, buf, expected, |
| "sprintf failed; fmt='%s' expected='%s' rslt='%s'\n", |
| fmt, expected, buf); |
| KUNIT_EXPECT_EQ_MSG( |
| test, buf[size+1], 0xff, |
| "sprintf failed; fmt='%s' expected='%s' rslt='%s'\n", |
| fmt, expected, buf); |
| |
| /* Make sure length limits work */ |
| size++; |
| for (i = 0; i < 2; i++, size--) { |
| /* Clear the buffer, and make sure it works correctly still */ |
| memset(buf, 0xff, buf_size); |
| snprintf(buf, size+1, fmt, np); |
| KUNIT_EXPECT_STREQ_MSG( |
| test, buf, expected, |
| "snprintf failed; size=%i fmt='%s' expected='%s' rslt='%s'\n", |
| size, fmt, expected, buf); |
| KUNIT_EXPECT_EQ_MSG( |
| test, buf[size+1], 0xff, |
| "snprintf failed; size=%i fmt='%s' expected='%s' rslt='%s'\n", |
| size, fmt, expected, buf); |
| } |
| kfree(buf); |
| } |
| |
| static void of_unittest_printf(struct kunit *test) |
| { |
| struct device_node *np; |
| const char *full_name = "/testcase-data/platform-tests/test-device@1/dev@100"; |
| char phandle_str[16] = ""; |
| |
| np = of_find_node_by_path(full_name); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| num_to_str(phandle_str, sizeof(phandle_str), np->phandle, 0); |
| |
| of_unittest_printf_one(test, np, "%pOF", full_name); |
| of_unittest_printf_one(test, np, "%pOFf", full_name); |
| of_unittest_printf_one(test, np, "%pOFn", "dev"); |
| of_unittest_printf_one(test, np, "%2pOFn", "dev"); |
| of_unittest_printf_one(test, np, "%5pOFn", " dev"); |
| of_unittest_printf_one(test, np, "%pOFnc", "dev:test-sub-device"); |
| of_unittest_printf_one(test, np, "%pOFp", phandle_str); |
| of_unittest_printf_one(test, np, "%pOFP", "dev@100"); |
| of_unittest_printf_one(test, np, "ABC %pOFP ABC", "ABC dev@100 ABC"); |
| of_unittest_printf_one(test, np, "%10pOFP", " dev@100"); |
| of_unittest_printf_one(test, np, "%-10pOFP", "dev@100 "); |
| of_unittest_printf_one(test, of_root, "%pOFP", "/"); |
| of_unittest_printf_one(test, np, "%pOFF", "----"); |
| of_unittest_printf_one(test, np, "%pOFPF", "dev@100:----"); |
| of_unittest_printf_one(test, |
| np, |
| "%pOFPFPc", |
| "dev@100:----:dev@100:test-sub-device"); |
| of_unittest_printf_one(test, np, "%pOFc", "test-sub-device"); |
| of_unittest_printf_one(test, np, "%pOFC", |
| "\"test-sub-device\",\"test-compat2\",\"test-compat3\""); |
| } |
| |
| struct node_hash { |
| struct hlist_node node; |
| struct device_node *np; |
| }; |
| |
| static DEFINE_HASHTABLE(phandle_ht, 8); |
| static void of_unittest_check_phandles(struct kunit *test) |
| { |
| struct device_node *np; |
| struct node_hash *nh; |
| struct hlist_node *tmp; |
| int i, dup_count = 0, phandle_count = 0; |
| |
| for_each_of_allnodes(np) { |
| if (!np->phandle) |
| continue; |
| |
| hash_for_each_possible(phandle_ht, nh, node, np->phandle) { |
| KUNIT_EXPECT_NE_MSG( |
| test, nh->np->phandle, np->phandle, |
| "Duplicate phandle! %i used by %pOF and %pOF\n", |
| np->phandle, nh->np, np); |
| if (nh->np->phandle == np->phandle) { |
| dup_count++; |
| break; |
| } |
| } |
| |
| nh = kzalloc(sizeof(*nh), GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, nh); |
| |
| nh->np = np; |
| hash_add(phandle_ht, &nh->node, np->phandle); |
| phandle_count++; |
| } |
| KUNIT_EXPECT_EQ_MSG(test, dup_count, 0, |
| "Found %i duplicates in %i phandles\n", |
| dup_count, phandle_count); |
| |
| /* Clean up */ |
| hash_for_each_safe(phandle_ht, i, tmp, nh, node) { |
| hash_del(&nh->node); |
| kfree(nh); |
| } |
| } |
| |
| static void of_unittest_parse_phandle_with_args(struct kunit *test) |
| { |
| struct device_node *np; |
| struct of_phandle_args args; |
| int i, rc = 0; |
| |
| np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_count_phandle_with_args( |
| np, "phandle-list", "#phandle-cells"), |
| 7, |
| "of_count_phandle_with_args() returned %i, expected 7\n", rc); |
| |
| for (i = 0; i < 8; i++) { |
| bool passed = true; |
| |
| memset(&args, 0, sizeof(args)); |
| rc = of_parse_phandle_with_args(np, "phandle-list", |
| "#phandle-cells", i, &args); |
| |
| /* Test the values from tests-phandle.dtsi */ |
| switch (i) { |
| case 0: |
| passed &= !rc; |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == (i + 1)); |
| break; |
| case 1: |
| passed &= !rc; |
| passed &= (args.args_count == 2); |
| passed &= (args.args[0] == (i + 1)); |
| passed &= (args.args[1] == 0); |
| break; |
| case 2: |
| passed &= (rc == -ENOENT); |
| break; |
| case 3: |
| passed &= !rc; |
| passed &= (args.args_count == 3); |
| passed &= (args.args[0] == (i + 1)); |
| passed &= (args.args[1] == 4); |
| passed &= (args.args[2] == 3); |
| break; |
| case 4: |
| passed &= !rc; |
| passed &= (args.args_count == 2); |
| passed &= (args.args[0] == (i + 1)); |
| passed &= (args.args[1] == 100); |
| break; |
| case 5: |
| passed &= !rc; |
| passed &= (args.args_count == 0); |
| break; |
| case 6: |
| passed &= !rc; |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == (i + 1)); |
| break; |
| case 7: |
| passed &= (rc == -ENOENT); |
| break; |
| default: |
| passed = false; |
| } |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, passed, |
| "index %i - data error on node %pOF rc=%i\n", |
| i, args.np, rc); |
| } |
| |
| /* Check for missing list property */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args( |
| np, "phandle-list-missing", "#phandle-cells", 0, &args), |
| -ENOENT); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_count_phandle_with_args( |
| np, "phandle-list-missing", "#phandle-cells"), |
| -ENOENT); |
| |
| /* Check for missing cells property */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args( |
| np, "phandle-list", "#phandle-cells-missing", 0, &args), |
| -EINVAL); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_count_phandle_with_args( |
| np, "phandle-list", "#phandle-cells-missing"), |
| -EINVAL); |
| |
| /* Check for bad phandle in list */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args(np, "phandle-list-bad-phandle", |
| "#phandle-cells", 0, &args), |
| -EINVAL); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_count_phandle_with_args( |
| np, "phandle-list-bad-phandle", "#phandle-cells"), |
| -EINVAL); |
| |
| /* Check for incorrectly formed argument list */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args(np, "phandle-list-bad-args", |
| "#phandle-cells", 1, &args), |
| -EINVAL); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_count_phandle_with_args( |
| np, "phandle-list-bad-args", "#phandle-cells"), |
| -EINVAL); |
| } |
| |
| static void of_unittest_parse_phandle_with_args_map(struct kunit *test) |
| { |
| struct device_node *np, *p0, *p1, *p2, *p3; |
| struct of_phandle_args args; |
| int i, rc; |
| |
| np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-b"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| p0 = of_find_node_by_path("/testcase-data/phandle-tests/provider0"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p0); |
| |
| p1 = of_find_node_by_path("/testcase-data/phandle-tests/provider1"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p1); |
| |
| p2 = of_find_node_by_path("/testcase-data/phandle-tests/provider2"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p2); |
| |
| p3 = of_find_node_by_path("/testcase-data/phandle-tests/provider3"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p3); |
| |
| KUNIT_EXPECT_EQ(test, |
| of_count_phandle_with_args(np, |
| "phandle-list", |
| "#phandle-cells"), |
| 7); |
| |
| for (i = 0; i < 8; i++) { |
| bool passed = true; |
| |
| memset(&args, 0, sizeof(args)); |
| rc = of_parse_phandle_with_args_map(np, "phandle-list", |
| "phandle", i, &args); |
| |
| /* Test the values from tests-phandle.dtsi */ |
| switch (i) { |
| case 0: |
| passed &= !rc; |
| passed &= (args.np == p1); |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == 1); |
| break; |
| case 1: |
| passed &= !rc; |
| passed &= (args.np == p3); |
| passed &= (args.args_count == 3); |
| passed &= (args.args[0] == 2); |
| passed &= (args.args[1] == 5); |
| passed &= (args.args[2] == 3); |
| break; |
| case 2: |
| passed &= (rc == -ENOENT); |
| break; |
| case 3: |
| passed &= !rc; |
| passed &= (args.np == p0); |
| passed &= (args.args_count == 0); |
| break; |
| case 4: |
| passed &= !rc; |
| passed &= (args.np == p1); |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == 3); |
| break; |
| case 5: |
| passed &= !rc; |
| passed &= (args.np == p0); |
| passed &= (args.args_count == 0); |
| break; |
| case 6: |
| passed &= !rc; |
| passed &= (args.np == p2); |
| passed &= (args.args_count == 2); |
| passed &= (args.args[0] == 15); |
| passed &= (args.args[1] == 0x20); |
| break; |
| case 7: |
| passed &= (rc == -ENOENT); |
| break; |
| default: |
| passed = false; |
| } |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, passed, |
| "index %i - data error on node %s rc=%i\n", |
| i, (args.np ? args.np->full_name : "missing np"), rc); |
| } |
| |
| /* Check for missing list property */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args_map( |
| np, "phandle-list-missing", "phandle", 0, &args), |
| -ENOENT); |
| |
| /* Check for missing cells,map,mask property */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args_map( |
| np, "phandle-list", "phandle-missing", 0, &args), |
| -EINVAL); |
| |
| /* Check for bad phandle in list */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args_map( |
| np, "phandle-list-bad-phandle", "phandle", 0, &args), |
| -EINVAL); |
| |
| /* Check for incorrectly formed argument list */ |
| memset(&args, 0, sizeof(args)); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_parse_phandle_with_args_map( |
| np, "phandle-list-bad-args", "phandle", 1, &args), |
| -EINVAL); |
| } |
| |
| static void of_unittest_property_string(struct kunit *test) |
| { |
| const char *strings[4]; |
| struct device_node *np; |
| int rc; |
| |
| np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| KUNIT_EXPECT_EQ( |
| test, |
| of_property_match_string(np, "phandle-list-names", "first"), |
| 0); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_property_match_string(np, "phandle-list-names", "second"), |
| 1); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_property_match_string(np, "phandle-list-names", "third"), |
| 2); |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_match_string(np, "phandle-list-names", "fourth"), |
| -ENODATA, |
| "unmatched string"); |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_match_string(np, "missing-property", "blah"), |
| -EINVAL, |
| "missing property"); |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_match_string(np, "empty-property", "blah"), |
| -ENODATA, |
| "empty property"); |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_match_string(np, "unterminated-string", "blah"), |
| -EILSEQ, |
| "unterminated string"); |
| |
| /* of_property_count_strings() tests */ |
| KUNIT_EXPECT_EQ(test, |
| of_property_count_strings(np, "string-property"), 1); |
| KUNIT_EXPECT_EQ(test, |
| of_property_count_strings(np, "phandle-list-names"), 3); |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_count_strings(np, "unterminated-string"), -EILSEQ, |
| "unterminated string"); |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_count_strings(np, "unterminated-string-list"), |
| -EILSEQ, |
| "unterminated string array"); |
| |
| /* of_property_read_string_index() tests */ |
| rc = of_property_read_string_index(np, "string-property", 0, strings); |
| KUNIT_ASSERT_EQ(test, rc, 0); |
| KUNIT_EXPECT_STREQ(test, strings[0], "foobar"); |
| |
| strings[0] = NULL; |
| rc = of_property_read_string_index(np, "string-property", 1, strings); |
| KUNIT_EXPECT_EQ(test, rc, -ENODATA); |
| KUNIT_EXPECT_EQ(test, strings[0], NULL); |
| |
| rc = of_property_read_string_index(np, "phandle-list-names", 0, strings); |
| KUNIT_ASSERT_EQ(test, rc, 0); |
| KUNIT_EXPECT_STREQ(test, strings[0], "first"); |
| |
| rc = of_property_read_string_index(np, "phandle-list-names", 1, strings); |
| KUNIT_ASSERT_EQ(test, rc, 0); |
| KUNIT_EXPECT_STREQ(test, strings[0], "second"); |
| |
| rc = of_property_read_string_index(np, "phandle-list-names", 2, strings); |
| KUNIT_ASSERT_EQ(test, rc, 0); |
| KUNIT_EXPECT_STREQ(test, strings[0], "third"); |
| |
| strings[0] = NULL; |
| rc = of_property_read_string_index(np, "phandle-list-names", 3, strings); |
| KUNIT_EXPECT_EQ(test, rc, -ENODATA); |
| KUNIT_EXPECT_EQ(test, strings[0], NULL); |
| |
| strings[0] = NULL; |
| rc = of_property_read_string_index(np, "unterminated-string", 0, strings); |
| KUNIT_EXPECT_EQ(test, rc, -EILSEQ); |
| KUNIT_EXPECT_EQ(test, strings[0], NULL); |
| |
| rc = of_property_read_string_index(np, "unterminated-string-list", 0, strings); |
| KUNIT_ASSERT_EQ(test, rc, 0); |
| KUNIT_EXPECT_STREQ(test, strings[0], "first"); |
| |
| strings[0] = NULL; |
| rc = of_property_read_string_index(np, "unterminated-string-list", 2, strings); /* should fail */ |
| KUNIT_EXPECT_EQ(test, rc, -EILSEQ); |
| KUNIT_EXPECT_EQ(test, strings[0], NULL); |
| |
| /* of_property_read_string_array() tests */ |
| strings[1] = NULL; |
| KUNIT_EXPECT_EQ( |
| test, |
| of_property_read_string_array( |
| np, "string-property", strings, 4), |
| 1); |
| KUNIT_EXPECT_EQ( |
| test, |
| of_property_read_string_array( |
| np, "phandle-list-names", strings, 4), |
| 3); |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_read_string_array( |
| np, "unterminated-string", strings, 4), |
| -EILSEQ, |
| "unterminated string"); |
| /* -- An incorrectly formed string should cause a failure */ |
| KUNIT_EXPECT_EQ_MSG( |
| test, |
| of_property_read_string_array( |
| np, "unterminated-string-list", strings, 4), |
| -EILSEQ, |
| "unterminated string array"); |
| /* -- parsing the correctly formed strings should still work: */ |
| strings[2] = NULL; |
| rc = of_property_read_string_array(np, "unterminated-string-list", strings, 2); |
| KUNIT_EXPECT_EQ(test, rc, 2); |
| KUNIT_EXPECT_EQ(test, strings[2], NULL); |
| |
| strings[1] = NULL; |
| rc = of_property_read_string_array(np, "phandle-list-names", strings, 1); |
| KUNIT_ASSERT_EQ(test, rc, 1); |
| KUNIT_EXPECT_EQ_MSG(test, strings[1], NULL, |
| "Overwrote end of string array"); |
| } |
| |
| #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 of_unittest_property_copy(struct kunit *test) |
| { |
| #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); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new); |
| KUNIT_EXPECT_TRUE_MSG(test, propcmp(&p1, new), |
| "empty property didn't copy correctly"); |
| kfree(new->value); |
| kfree(new->name); |
| kfree(new); |
| |
| new = __of_prop_dup(&p2, GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new); |
| KUNIT_EXPECT_TRUE_MSG(test, propcmp(&p2, new), |
| "non-empty property didn't copy correctly"); |
| kfree(new->value); |
| kfree(new->name); |
| kfree(new); |
| #endif |
| } |
| |
| static void of_unittest_changeset(struct kunit *test) |
| { |
| #ifdef CONFIG_OF_DYNAMIC |
| struct property *ppadd, padd = { .name = "prop-add", .length = 1, .value = "" }; |
| struct property *ppname_n1, pname_n1 = { .name = "name", .length = 3, .value = "n1" }; |
| struct property *ppname_n2, pname_n2 = { .name = "name", .length = 3, .value = "n2" }; |
| struct property *ppname_n21, pname_n21 = { .name = "name", .length = 3, .value = "n21" }; |
| struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" }; |
| struct property *ppremove; |
| struct device_node *n1, *n2, *n21, *nchangeset, *nremove, *parent, *np; |
| struct of_changeset chgset; |
| |
| n1 = __of_node_dup(NULL, "n1"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, n1); |
| |
| n2 = __of_node_dup(NULL, "n2"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, n2); |
| |
| n21 = __of_node_dup(NULL, "n21"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, n21); |
| |
| nchangeset = of_find_node_by_path("/testcase-data/changeset"); |
| nremove = of_get_child_by_name(nchangeset, "node-remove"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, nremove); |
| |
| ppadd = __of_prop_dup(&padd, GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ppadd); |
| |
| ppname_n1 = __of_prop_dup(&pname_n1, GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ppname_n1); |
| |
| ppname_n2 = __of_prop_dup(&pname_n2, GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ppname_n2); |
| |
| ppname_n21 = __of_prop_dup(&pname_n21, GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ppname_n21); |
| |
| ppupdate = __of_prop_dup(&pupdate, GFP_KERNEL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ppupdate); |
| |
| parent = nchangeset; |
| n1->parent = parent; |
| n2->parent = parent; |
| n21->parent = n2; |
| |
| ppremove = of_find_property(parent, "prop-remove", NULL); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ppremove); |
| |
| of_changeset_init(&chgset); |
| |
| KUNIT_EXPECT_FALSE_MSG(test, of_changeset_attach_node(&chgset, n1), |
| "fail attach n1\n"); |
| KUNIT_EXPECT_FALSE_MSG( |
| test, of_changeset_add_property(&chgset, n1, ppname_n1), |
| "fail add prop name\n"); |
| |
| KUNIT_EXPECT_FALSE_MSG(test, of_changeset_attach_node(&chgset, n2), |
| "fail attach n2\n"); |
| KUNIT_EXPECT_FALSE_MSG( |
| test, of_changeset_add_property(&chgset, n2, ppname_n2), |
| "fail add prop name\n"); |
| |
| KUNIT_EXPECT_FALSE_MSG(test, of_changeset_detach_node(&chgset, nremove), |
| "fail remove node\n"); |
| KUNIT_EXPECT_FALSE_MSG( |
| test, of_changeset_add_property(&chgset, n21, ppname_n21), |
| "fail add prop name\n"); |
| |
| KUNIT_EXPECT_FALSE_MSG(test, of_changeset_attach_node(&chgset, n21), |
| "fail attach n21\n"); |
| |
| KUNIT_EXPECT_FALSE_MSG( |
| test, |
| of_changeset_add_property(&chgset, parent, ppadd), |
| "fail add prop prop-add\n"); |
| KUNIT_EXPECT_FALSE_MSG( |
| test, |
| of_changeset_update_property(&chgset, parent, ppupdate), |
| "fail update prop\n"); |
| KUNIT_EXPECT_FALSE_MSG( |
| test, |
| of_changeset_remove_property(&chgset, parent, ppremove), |
| "fail remove prop\n"); |
| |
| KUNIT_EXPECT_FALSE_MSG(test, of_changeset_apply(&chgset), |
| "apply failed\n"); |
| |
| of_node_put(nchangeset); |
| |
| /* Make sure node names are constructed correctly */ |
| KUNIT_EXPECT_NOT_ERR_OR_NULL_MSG( |
| test, |
| np = of_find_node_by_path("/testcase-data/changeset/n2/n21"), |
| "'%pOF' not added\n", n21); |
| of_node_put(np); |
| |
| KUNIT_EXPECT_FALSE(test, of_changeset_revert(&chgset)); |
| |
| of_changeset_destroy(&chgset); |
| #endif |
| } |
| |
| static void of_unittest_parse_interrupts(struct kunit *test) |
| { |
| struct device_node *np; |
| struct of_phandle_args args; |
| int i, rc; |
| |
| KUNIT_ASSERT_FALSE(test, of_irq_workarounds & OF_IMAP_OLDWORLD_MAC); |
| |
| np = of_find_node_by_path("/testcase-data/interrupts/interrupts0"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| for (i = 0; i < 4; i++) { |
| bool passed = true; |
| |
| memset(&args, 0, sizeof(args)); |
| rc = of_irq_parse_one(np, i, &args); |
| |
| passed &= !rc; |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == (i + 1)); |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, passed, |
| "index %i - data error on node %pOF rc=%i\n", |
| i, args.np, rc); |
| } |
| of_node_put(np); |
| |
| np = of_find_node_by_path("/testcase-data/interrupts/interrupts1"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| for (i = 0; i < 4; i++) { |
| bool passed = true; |
| |
| memset(&args, 0, sizeof(args)); |
| rc = of_irq_parse_one(np, i, &args); |
| |
| /* Test the values from tests-phandle.dtsi */ |
| switch (i) { |
| case 0: |
| passed &= !rc; |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == 9); |
| break; |
| case 1: |
| passed &= !rc; |
| passed &= (args.args_count == 3); |
| passed &= (args.args[0] == 10); |
| passed &= (args.args[1] == 11); |
| passed &= (args.args[2] == 12); |
| break; |
| case 2: |
| passed &= !rc; |
| passed &= (args.args_count == 2); |
| passed &= (args.args[0] == 13); |
| passed &= (args.args[1] == 14); |
| break; |
| case 3: |
| passed &= !rc; |
| passed &= (args.args_count == 2); |
| passed &= (args.args[0] == 15); |
| passed &= (args.args[1] == 16); |
| break; |
| default: |
| passed = false; |
| } |
| KUNIT_EXPECT_TRUE_MSG( |
| test, passed, |
| "index %i - data error on node %pOF rc=%i\n", |
| i, args.np, rc); |
| } |
| of_node_put(np); |
| } |
| |
| static void of_unittest_parse_interrupts_extended(struct kunit *test) |
| { |
| struct device_node *np; |
| struct of_phandle_args args; |
| int i, rc; |
| |
| KUNIT_ASSERT_FALSE(test, of_irq_workarounds & OF_IMAP_OLDWORLD_MAC); |
| |
| np = of_find_node_by_path("/testcase-data/interrupts/interrupts-extended0"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| for (i = 0; i < 7; i++) { |
| bool passed = true; |
| |
| memset(&args, 0, sizeof(args)); |
| rc = of_irq_parse_one(np, i, &args); |
| |
| /* Test the values from tests-phandle.dtsi */ |
| switch (i) { |
| case 0: |
| passed &= !rc; |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == 1); |
| break; |
| case 1: |
| passed &= !rc; |
| passed &= (args.args_count == 3); |
| passed &= (args.args[0] == 2); |
| passed &= (args.args[1] == 3); |
| passed &= (args.args[2] == 4); |
| break; |
| case 2: |
| passed &= !rc; |
| passed &= (args.args_count == 2); |
| passed &= (args.args[0] == 5); |
| passed &= (args.args[1] == 6); |
| break; |
| case 3: |
| passed &= !rc; |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == 9); |
| break; |
| case 4: |
| passed &= !rc; |
| passed &= (args.args_count == 3); |
| passed &= (args.args[0] == 10); |
| passed &= (args.args[1] == 11); |
| passed &= (args.args[2] == 12); |
| break; |
| case 5: |
| passed &= !rc; |
| passed &= (args.args_count == 2); |
| passed &= (args.args[0] == 13); |
| passed &= (args.args[1] == 14); |
| break; |
| case 6: |
| passed &= !rc; |
| passed &= (args.args_count == 1); |
| passed &= (args.args[0] == 15); |
| break; |
| default: |
| passed = false; |
| } |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, passed, |
| "index %i - data error on node %pOF rc=%i\n", |
| i, args.np, rc); |
| } |
| of_node_put(np); |
| } |
| |
| static const struct of_device_id match_node_table[] = { |
| { .data = "A", .name = "name0", }, /* Name alone is lowest priority */ |
| { .data = "B", .type = "type1", }, /* followed by type alone */ |
| |
| { .data = "Ca", .name = "name2", .type = "type1", }, /* followed by both together */ |
| { .data = "Cb", .name = "name2", }, /* Only match when type doesn't match */ |
| { .data = "Cc", .name = "name2", .type = "type2", }, |
| |
| { .data = "E", .compatible = "compat3" }, |
| { .data = "G", .compatible = "compat2", }, |
| { .data = "H", .compatible = "compat2", .name = "name5", }, |
| { .data = "I", .compatible = "compat2", .type = "type1", }, |
| { .data = "J", .compatible = "compat2", .type = "type1", .name = "name8", }, |
| { .data = "K", .compatible = "compat2", .name = "name9", }, |
| {} |
| }; |
| |
| static struct { |
| const char *path; |
| const char *data; |
| } match_node_tests[] = { |
| { .path = "/testcase-data/match-node/name0", .data = "A", }, |
| { .path = "/testcase-data/match-node/name1", .data = "B", }, |
| { .path = "/testcase-data/match-node/a/name2", .data = "Ca", }, |
| { .path = "/testcase-data/match-node/b/name2", .data = "Cb", }, |
| { .path = "/testcase-data/match-node/c/name2", .data = "Cc", }, |
| { .path = "/testcase-data/match-node/name3", .data = "E", }, |
| { .path = "/testcase-data/match-node/name4", .data = "G", }, |
| { .path = "/testcase-data/match-node/name5", .data = "H", }, |
| { .path = "/testcase-data/match-node/name6", .data = "G", }, |
| { .path = "/testcase-data/match-node/name7", .data = "I", }, |
| { .path = "/testcase-data/match-node/name8", .data = "J", }, |
| { .path = "/testcase-data/match-node/name9", .data = "K", }, |
| }; |
| |
| static void of_unittest_match_node(struct kunit *test) |
| { |
| struct device_node *np; |
| const struct of_device_id *match; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(match_node_tests); i++) { |
| np = of_find_node_by_path(match_node_tests[i].path); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| match = of_match_node(match_node_table, np); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL_MSG(test, np, |
| "%s didn't match anything", |
| match_node_tests[i].path); |
| |
| KUNIT_EXPECT_STREQ_MSG( |
| test, |
| match->data, match_node_tests[i].data, |
| "%s got wrong match. expected %s, got %s\n", |
| match_node_tests[i].path, match_node_tests[i].data, |
| (const char *)match->data); |
| } |
| } |
| |
| static struct resource test_bus_res = { |
| .start = 0xfffffff8, |
| .end = 0xfffffff9, |
| .flags = IORESOURCE_MEM, |
| }; |
| static const struct platform_device_info test_bus_info = { |
| .name = "unittest-bus", |
| }; |
| static void of_unittest_platform_populate(struct kunit *test) |
| { |
| int irq; |
| struct device_node *np, *child, *grandchild; |
| struct platform_device *pdev, *test_bus; |
| const struct of_device_id match[] = { |
| { .compatible = "test-device", }, |
| {} |
| }; |
| |
| np = of_find_node_by_path("/testcase-data"); |
| of_platform_default_populate(np, NULL, NULL); |
| |
| /* Test that a missing irq domain returns -EPROBE_DEFER */ |
| np = of_find_node_by_path("/testcase-data/testcase-device1"); |
| pdev = of_find_device_by_node(np); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); |
| |
| if (!(of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)) { |
| irq = platform_get_irq(pdev, 0); |
| KUNIT_ASSERT_EQ(test, irq, -EPROBE_DEFER); |
| |
| /* Test that a parsing failure does not return -EPROBE_DEFER */ |
| np = of_find_node_by_path("/testcase-data/testcase-device2"); |
| pdev = of_find_device_by_node(np); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); |
| irq = platform_get_irq(pdev, 0); |
| KUNIT_ASSERT_TRUE_MSG(test, irq < 0 && irq != -EPROBE_DEFER, |
| "device parsing error failed - %d\n", |
| irq); |
| } |
| |
| np = of_find_node_by_path("/testcase-data/platform-tests"); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); |
| |
| test_bus = platform_device_register_full(&test_bus_info); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_bus); |
| test_bus->dev.of_node = np; |
| |
| /* |
| * Add a dummy resource to the test bus node after it is |
| * registered to catch problems with un-inserted resources. The |
| * DT code doesn't insert the resources, and it has caused the |
| * kernel to oops in the past. This makes sure the same bug |
| * doesn't crop up again. |
| */ |
| platform_device_add_resources(test_bus, &test_bus_res, 1); |
| |
| of_platform_populate(np, match, NULL, &test_bus->dev); |
| for_each_child_of_node(np, child) { |
| for_each_child_of_node(child, grandchild) |
| KUNIT_EXPECT_TRUE_MSG( |
| test, of_find_device_by_node(grandchild), |
| "Could not create device for node '%pOFn'\n", |
| grandchild); |
| } |
| |
| of_platform_depopulate(&test_bus->dev); |
| for_each_child_of_node(np, child) { |
| for_each_child_of_node(child, grandchild) |
| KUNIT_EXPECT_FALSE_MSG( |
| test, of_find_device_by_node(grandchild), |
| "device didn't get destroyed '%pOFn'\n", |
| grandchild); |
| } |
| |
| platform_device_unregister(test_bus); |
| of_node_put(np); |
| } |
| |
| #ifdef CONFIG_OF_OVERLAY |
| static int overlay_data_apply(const char *overlay_name, int *overlay_id); |
| |
| static int unittest_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 @%pOF\n", __func__, np); |
| |
| of_platform_populate(np, NULL, NULL, &pdev->dev); |
| |
| return 0; |
| } |
| |
| static int unittest_remove(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *np = dev->of_node; |
| |
| dev_dbg(dev, "%s for node @%pOF\n", __func__, np); |
| return 0; |
| } |
| |
| static const struct of_device_id unittest_match[] = { |
| { .compatible = "unittest", }, |
| {}, |
| }; |
| |
| static struct platform_driver unittest_driver = { |
| .probe = unittest_probe, |
| .remove = unittest_remove, |
| .driver = { |
| .name = "unittest", |
| .of_match_table = of_match_ptr(unittest_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; |
| } |
| |
| #if IS_BUILTIN(CONFIG_I2C) |
| |
| /* get the i2c client device instantiated at the path */ |
| static struct i2c_client *of_path_to_i2c_client(const char *path) |
| { |
| struct device_node *np; |
| struct i2c_client *client; |
| |
| np = of_find_node_by_path(path); |
| if (np == NULL) |
| return NULL; |
| |
| client = of_find_i2c_device_by_node(np); |
| of_node_put(np); |
| |
| return client; |
| } |
| |
| /* find out if a i2c client device exists at that path */ |
| static int of_path_i2c_client_exists(const char *path) |
| { |
| struct i2c_client *client; |
| |
| client = of_path_to_i2c_client(path); |
| if (client) |
| put_device(&client->dev); |
| return client != NULL; |
| } |
| #else |
| static int of_path_i2c_client_exists(const char *path) |
| { |
| return 0; |
| } |
| #endif |
| |
| enum overlay_type { |
| PDEV_OVERLAY, |
| I2C_OVERLAY |
| }; |
| |
| static int of_path_device_type_exists(const char *path, |
| enum overlay_type ovtype) |
| { |
| switch (ovtype) { |
| case PDEV_OVERLAY: |
| return of_path_platform_device_exists(path); |
| case I2C_OVERLAY: |
| return of_path_i2c_client_exists(path); |
| } |
| return 0; |
| } |
| |
| static const char *unittest_path(int nr, enum overlay_type ovtype) |
| { |
| const char *base; |
| static char buf[256]; |
| |
| switch (ovtype) { |
| case PDEV_OVERLAY: |
| base = "/testcase-data/overlay-node/test-bus"; |
| break; |
| case I2C_OVERLAY: |
| base = "/testcase-data/overlay-node/test-bus/i2c-test-bus"; |
| break; |
| default: |
| buf[0] = '\0'; |
| return buf; |
| } |
| snprintf(buf, sizeof(buf) - 1, "%s/test-unittest%d", base, nr); |
| buf[sizeof(buf) - 1] = '\0'; |
| return buf; |
| } |
| |
| static int of_unittest_device_exists(int unittest_nr, enum overlay_type ovtype) |
| { |
| const char *path; |
| |
| path = unittest_path(unittest_nr, ovtype); |
| |
| switch (ovtype) { |
| case PDEV_OVERLAY: |
| return of_path_platform_device_exists(path); |
| case I2C_OVERLAY: |
| return of_path_i2c_client_exists(path); |
| } |
| return 0; |
| } |
| |
| static const char *overlay_name_from_nr(int nr) |
| { |
| static char buf[256]; |
| |
| snprintf(buf, sizeof(buf) - 1, |
| "overlay_%d", nr); |
| buf[sizeof(buf) - 1] = '\0'; |
| |
| return buf; |
| } |
| |
| static const char *bus_path = "/testcase-data/overlay-node/test-bus"; |
| |
| /* it is guaranteed that overlay ids are assigned in sequence */ |
| #define MAX_UNITTEST_OVERLAYS 256 |
| static unsigned long overlay_id_bits[BITS_TO_LONGS(MAX_UNITTEST_OVERLAYS)]; |
| static int overlay_first_id = -1; |
| |
| static void of_unittest_track_overlay(int id) |
| { |
| if (overlay_first_id < 0) |
| overlay_first_id = id; |
| id -= overlay_first_id; |
| |
| /* we shouldn't need that many */ |
| BUG_ON(id >= MAX_UNITTEST_OVERLAYS); |
| overlay_id_bits[BIT_WORD(id)] |= BIT_MASK(id); |
| } |
| |
| static void of_unittest_untrack_overlay(int id) |
| { |
| if (overlay_first_id < 0) |
| return; |
| id -= overlay_first_id; |
| BUG_ON(id >= MAX_UNITTEST_OVERLAYS); |
| overlay_id_bits[BIT_WORD(id)] &= ~BIT_MASK(id); |
| } |
| |
| static void of_unittest_destroy_tracked_overlays(void) |
| { |
| int id, ret, defers, ovcs_id; |
| |
| if (overlay_first_id < 0) |
| return; |
| |
| /* try until no defers */ |
| do { |
| defers = 0; |
| /* remove in reverse order */ |
| for (id = MAX_UNITTEST_OVERLAYS - 1; id >= 0; id--) { |
| if (!(overlay_id_bits[BIT_WORD(id)] & BIT_MASK(id))) |
| continue; |
| |
| ovcs_id = id + overlay_first_id; |
| ret = of_overlay_remove(&ovcs_id); |
| if (ret == -ENODEV) { |
| pr_warn("%s: no overlay to destroy for #%d\n", |
| __func__, id + overlay_first_id); |
| continue; |
| } |
| if (ret != 0) { |
| defers++; |
| pr_warn("%s: overlay destroy failed for #%d\n", |
| __func__, id + overlay_first_id); |
| continue; |
| } |
| |
| overlay_id_bits[BIT_WORD(id)] &= ~BIT_MASK(id); |
| } |
| } while (defers > 0); |
| } |
| |
| static int of_unittest_apply_overlay(struct kunit *test, |
| int overlay_nr, |
| int *overlay_id) |
| { |
| const char *overlay_name; |
| |
| overlay_name = overlay_name_from_nr(overlay_nr); |
| |
| KUNIT_ASSERT_TRUE_MSG(test, |
| overlay_data_apply(overlay_name, overlay_id), |
| "could not apply overlay \"%s\"\n", overlay_name); |
| of_unittest_track_overlay(*overlay_id); |
| |
| return 0; |
| } |
| |
| /* apply an overlay while checking before and after states */ |
| static int of_unittest_apply_overlay_check(struct kunit *test, int overlay_nr, |
| int unittest_nr, int before, int after, |
| enum overlay_type ovtype) |
| { |
| int ret, ovcs_id; |
| |
| /* unittest device must not be in before state */ |
| KUNIT_ASSERT_EQ_MSG( |
| test, of_unittest_device_exists(unittest_nr, ovtype), before, |
| "%s with device @\"%s\" %s\n", overlay_name_from_nr(overlay_nr), |
| unittest_path(unittest_nr, ovtype), |
| !before ? "enabled" : "disabled"); |
| |
| ovcs_id = 0; |
| ret = of_unittest_apply_overlay(test, overlay_nr, &ovcs_id); |
| if (ret != 0) { |
| /* of_unittest_apply_overlay already set expectation */ |
| return ret; |
| } |
| |
| /* unittest device must be to set to after state */ |
| KUNIT_ASSERT_EQ_MSG( |
| test, of_unittest_device_exists(unittest_nr, ovtype), after, |
| "%s failed to create @\"%s\" %s\n", |
| overlay_name_from_nr(overlay_nr), |
| unittest_path(unittest_nr, ovtype), |
| !after ? "enabled" : "disabled"); |
| |
| return 0; |
| } |
| |
| /* apply an overlay and then revert it while checking before, after states */ |
| static int of_unittest_apply_revert_overlay_check( |
| struct kunit *test, int overlay_nr, |
| int unittest_nr, int before, int after, |
| enum overlay_type ovtype) |
| { |
| int ret, ovcs_id; |
| |
| /* unittest device must be in before state */ |
| KUNIT_ASSERT_EQ_MSG( |
| test, of_unittest_device_exists(unittest_nr, ovtype), before, |
| "%s with device @\"%s\" %s\n", overlay_name_from_nr(overlay_nr), |
| unittest_path(unittest_nr, ovtype), |
| !before ? "enabled" : "disabled"); |
| |
| /* apply the overlay */ |
| ovcs_id = 0; |
| ret = of_unittest_apply_overlay(test, overlay_nr, &ovcs_id); |
| if (ret != 0) { |
| /* of_unittest_apply_overlay already set expectation. */ |
| return ret; |
| } |
| |
| /* unittest device must be in after state */ |
| KUNIT_ASSERT_EQ_MSG( |
| test, of_unittest_device_exists(unittest_nr, ovtype), after, |
| "%s failed to create @\"%s\" %s\n", |
| overlay_name_from_nr(overlay_nr), |
| unittest_path(unittest_nr, ovtype), |
| !after ? "enabled" : "disabled"); |
| |
| KUNIT_ASSERT_EQ_MSG(test, of_overlay_remove(&ovcs_id), 0, |
| "%s failed to be destroyed @\"%s\"\n", |
| overlay_name_from_nr(overlay_nr), |
| unittest_path(unittest_nr, ovtype)); |
| |
| /* unittest device must be again in before state */ |
| KUNIT_ASSERT_EQ_MSG( |
| test, |
| of_unittest_device_exists(unittest_nr, PDEV_OVERLAY), before, |
| "%s with device @\"%s\" %s\n", |
| overlay_name_from_nr(overlay_nr), |
| unittest_path(unittest_nr, ovtype), |
| !before ? "enabled" : "disabled"); |
| |
| return 0; |
| } |
| |
| /* test activation of device */ |
| static void of_unittest_overlay_0(struct kunit *test) |
| { |
| /* device should enable */ |
| of_unittest_apply_overlay_check(test, 0, 0, 0, 1, PDEV_OVERLAY); |
| } |
| |
| /* test deactivation of device */ |
| static void of_unittest_overlay_1(struct kunit *test) |
| { |
| /* device should disable */ |
| of_unittest_apply_overlay_check(test, 1, 1, 1, 0, PDEV_OVERLAY); |
| } |
| |
| /* test activation of device */ |
| static void of_unittest_overlay_2(struct kunit *test) |
| { |
| /* device should enable */ |
| of_unittest_apply_overlay_check(test, 2, 2, 0, 1, PDEV_OVERLAY); |
| } |
| |
| /* test deactivation of device */ |
| static void of_unittest_overlay_3(struct kunit *test) |
| { |
| /* device should disable */ |
| of_unittest_apply_overlay_check(test, 3, 3, 1, 0, PDEV_OVERLAY); |
| } |
| |
| /* test activation of a full device node */ |
| static void of_unittest_overlay_4(struct kunit *test) |
| { |
| /* device should disable */ |
| of_unittest_apply_overlay_check(test, 4, 4, 0, 1, PDEV_OVERLAY); |
| } |
| |
| /* test overlay apply/revert sequence */ |
| static void of_unittest_overlay_5(struct kunit *test) |
| { |
| /* device should disable */ |
| of_unittest_apply_revert_overlay_check(test, 5, 5, 0, 1, PDEV_OVERLAY); |
| } |
| |
| /* test overlay application in sequence */ |
| static void of_unittest_overlay_6(struct kunit *test) |
| { |
| int i, ov_id[2], ovcs_id; |
| int overlay_nr = 6, unittest_nr = 6; |
| int before = 0, after = 1; |
| const char *overlay_name; |
| |
| /* unittest device must be in before state */ |
| for (i = 0; i < 2; i++) { |
| KUNIT_ASSERT_EQ_MSG(test, |
| of_unittest_device_exists(unittest_nr + i, |
| PDEV_OVERLAY), |
| before, |
| "%s with device @\"%s\" %s\n", |
| overlay_name_from_nr(overlay_nr + i), |
| unittest_path(unittest_nr + i, |
| PDEV_OVERLAY), |
| !before ? "enabled" : "disabled"); |
| } |
| |
| /* apply the overlays */ |
| for (i = 0; i < 2; i++) { |
| overlay_name = overlay_name_from_nr(overlay_nr + i); |
| |
| KUNIT_ASSERT_TRUE_MSG( |
| test, overlay_data_apply(overlay_name, &ovcs_id), |
| "could not apply overlay \"%s\"\n", overlay_name); |
| ov_id[i] = ovcs_id; |
| of_unittest_track_overlay(ov_id[i]); |
| } |
| |
| for (i = 0; i < 2; i++) { |
| /* unittest device must be in after state */ |
| KUNIT_ASSERT_EQ_MSG(test, |
| of_unittest_device_exists(unittest_nr + i, |
| PDEV_OVERLAY), |
| after, |
| "overlay @\"%s\" failed @\"%s\" %s\n", |
| overlay_name_from_nr(overlay_nr + i), |
| unittest_path(unittest_nr + i, |
| PDEV_OVERLAY), |
| !after ? "enabled" : "disabled"); |
| } |
| |
| for (i = 1; i >= 0; i--) { |
| ovcs_id = ov_id[i]; |
| KUNIT_ASSERT_FALSE_MSG( |
| test, of_overlay_remove(&ovcs_id), |
| "%s failed destroy @\"%s\"\n", |
| overlay_name_from_nr(overlay_nr + i), |
| unittest_path(unittest_nr + i, PDEV_OVERLAY)); |
| of_unittest_untrack_overlay(ov_id[i]); |
| } |
| |
| for (i = 0; i < 2; i++) { |
| /* unittest device must be again in before state */ |
| KUNIT_ASSERT_EQ_MSG(test, |
| of_unittest_device_exists(unittest_nr + i, |
| PDEV_OVERLAY), |
| before, |
| "%s with device @\"%s\" %s\n", |
| overlay_name_from_nr(overlay_nr + i), |
| unittest_path(unittest_nr + i, |
| PDEV_OVERLAY), |
| !before ? "enabled" : "disabled"); |
| } |
| } |
| |
| /* test overlay application in sequence */ |
| static void of_unittest_overlay_8(struct kunit *test) |
| { |
| int i, ov_id[2], ovcs_id; |
| int overlay_nr = 8, unittest_nr = 8; |
| const char *overlay_name; |
| |
| /* we don't care about device state in this test */ |
| |
| /* apply the overlays */ |
| for (i = 0; i < 2; i++) { |
| overlay_name = overlay_name_from_nr(overlay_nr + i); |
| |
| KUNIT_ASSERT_TRUE_MSG( |
| test, overlay_data_apply(overlay_name, &ovcs_id), |
| "could not apply overlay \"%s\"\n", overlay_name); |
| ov_id[i] = ovcs_id; |
| of_unittest_track_overlay(ov_id[i]); |
| } |
| |
| /* now try to remove first overlay (it should fail) */ |
| ovcs_id = ov_id[0]; |
| KUNIT_ASSERT_TRUE_MSG( |
| test, of_overlay_remove(&ovcs_id), |
| "%s was destroyed @\"%s\"\n", |
| overlay_name_from_nr(overlay_nr + 0), |
| unittest_path(unittest_nr, PDEV_OVERLAY)); |
| |
| /* removing them in order should work */ |
| for (i = 1; i >= 0; i--) { |
| ovcs_id = ov_id[i]; |
| KUNIT_ASSERT_FALSE_MSG( |
| test, of_overlay_remove(&ovcs_id), |
| "%s not destroyed @\"%s\"\n", |
| overlay_name_from_nr(overlay_nr + i), |
| unittest_path(unittest_nr, PDEV_OVERLAY)); |
| of_unittest_untrack_overlay(ov_id[i]); |
| } |
| } |
| |
| /* test insertion of a bus with parent devices */ |
| static void of_unittest_overlay_10(struct kunit *test) |
| { |
| char *child_path; |
| |
| /* device should disable */ |
| KUNIT_ASSERT_EQ_MSG( |
| test, |
| of_unittest_apply_overlay_check( |
| test, 10, 10, 0, 1, PDEV_OVERLAY), |
| 0, |
| "overlay test %d failed; overlay application\n", 10); |
| |
| child_path = kasprintf(GFP_KERNEL, "%s/test-unittest101", |
| unittest_path(10, PDEV_OVERLAY)); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, child_path); |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, of_path_device_type_exists(child_path, PDEV_OVERLAY), |
| "overlay test %d failed; no child device\n", 10); |
| kfree(child_path); |
| } |
| |
| /* test insertion of a bus with parent devices (and revert) */ |
| static void of_unittest_overlay_11(struct kunit *test) |
| { |
| /* device should disable */ |
| KUNIT_EXPECT_FALSE(test, of_unittest_apply_revert_overlay_check( |
| test, 11, 11, 0, 1, PDEV_OVERLAY)); |
| } |
| |
| #if IS_BUILTIN(CONFIG_I2C) && IS_ENABLED(CONFIG_OF_OVERLAY) |
| |
| struct unittest_i2c_bus_data { |
| struct platform_device *pdev; |
| struct i2c_adapter adap; |
| }; |
| |
| static int unittest_i2c_master_xfer(struct i2c_adapter *adap, |
| struct i2c_msg *msgs, int num) |
| { |
| struct unittest_i2c_bus_data *std = i2c_get_adapdata(adap); |
| |
| (void)std; |
| |
| return num; |
| } |
| |
| static u32 unittest_i2c_functionality(struct i2c_adapter *adap) |
| { |
| return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; |
| } |
| |
| static const struct i2c_algorithm unittest_i2c_algo = { |
| .master_xfer = unittest_i2c_master_xfer, |
| .functionality = unittest_i2c_functionality, |
| }; |
| |
| static int unittest_i2c_bus_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *np = dev->of_node; |
| struct unittest_i2c_bus_data *std; |
| struct i2c_adapter *adap; |
| int ret; |
| |
| if (np == NULL) { |
| dev_err(dev, "No OF data for device\n"); |
| return -EINVAL; |
| |
| } |
| |
| dev_dbg(dev, "%s for node @%pOF\n", __func__, np); |
| |
| std = devm_kzalloc(dev, sizeof(*std), GFP_KERNEL); |
| if (!std) { |
| dev_err(dev, "Failed to allocate unittest i2c data\n"); |
| return -ENOMEM; |
| } |
| |
| /* link them together */ |
| std->pdev = pdev; |
| platform_set_drvdata(pdev, std); |
| |
| adap = &std->adap; |
| i2c_set_adapdata(adap, std); |
| adap->nr = -1; |
| strlcpy(adap->name, pdev->name, sizeof(adap->name)); |
| adap->class = I2C_CLASS_DEPRECATED; |
| adap->algo = &unittest_i2c_algo; |
| adap->dev.parent = dev; |
| adap->dev.of_node = dev->of_node; |
| adap->timeout = 5 * HZ; |
| adap->retries = 3; |
| |
| ret = i2c_add_numbered_adapter(adap); |
| if (ret != 0) { |
| dev_err(dev, "Failed to add I2C adapter\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int unittest_i2c_bus_remove(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *np = dev->of_node; |
| struct unittest_i2c_bus_data *std = platform_get_drvdata(pdev); |
| |
| dev_dbg(dev, "%s for node @%pOF\n", __func__, np); |
| i2c_del_adapter(&std->adap); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id unittest_i2c_bus_match[] = { |
| { .compatible = "unittest-i2c-bus", }, |
| {}, |
| }; |
| |
| static struct platform_driver unittest_i2c_bus_driver = { |
| .probe = unittest_i2c_bus_probe, |
| .remove = unittest_i2c_bus_remove, |
| .driver = { |
| .name = "unittest-i2c-bus", |
| .of_match_table = of_match_ptr(unittest_i2c_bus_match), |
| }, |
| }; |
| |
| static int unittest_i2c_dev_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct device *dev = &client->dev; |
| struct device_node *np = client->dev.of_node; |
| |
| if (!np) { |
| dev_err(dev, "No OF node\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(dev, "%s for node @%pOF\n", __func__, np); |
| |
| return 0; |
| }; |
| |
| static int unittest_i2c_dev_remove(struct i2c_client *client) |
| { |
| struct device *dev = &client->dev; |
| struct device_node *np = client->dev.of_node; |
| |
| dev_dbg(dev, "%s for node @%pOF\n", __func__, np); |
| return 0; |
| } |
| |
| static const struct i2c_device_id unittest_i2c_dev_id[] = { |
| { .name = "unittest-i2c-dev" }, |
| { } |
| }; |
| |
| static struct i2c_driver unittest_i2c_dev_driver = { |
| .driver = { |
| .name = "unittest-i2c-dev", |
| }, |
| .probe = unittest_i2c_dev_probe, |
| .remove = unittest_i2c_dev_remove, |
| .id_table = unittest_i2c_dev_id, |
| }; |
| |
| #if IS_BUILTIN(CONFIG_I2C_MUX) |
| |
| static int unittest_i2c_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) |
| { |
| return 0; |
| } |
| |
| static int unittest_i2c_mux_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int i, nchans; |
| struct device *dev = &client->dev; |
| struct i2c_adapter *adap = to_i2c_adapter(dev->parent); |
| struct device_node *np = client->dev.of_node, *child; |
| struct i2c_mux_core *muxc; |
| u32 reg, max_reg; |
| |
| dev_dbg(dev, "%s for node @%pOF\n", __func__, np); |
| |
| if (!np) { |
| dev_err(dev, "No OF node\n"); |
| return -EINVAL; |
| } |
| |
| max_reg = (u32)-1; |
| for_each_child_of_node(np, child) { |
| if (of_property_read_u32(child, "reg", ®)) |
| continue; |
| if (max_reg == (u32)-1 || reg > max_reg) |
| max_reg = reg; |
| } |
| nchans = max_reg == (u32)-1 ? 0 : max_reg + 1; |
| if (nchans == 0) { |
| dev_err(dev, "No channels\n"); |
| return -EINVAL; |
| } |
| |
| muxc = i2c_mux_alloc(adap, dev, nchans, 0, 0, |
| unittest_i2c_mux_select_chan, NULL); |
| if (!muxc) |
| return -ENOMEM; |
| for (i = 0; i < nchans; i++) { |
| if (i2c_mux_add_adapter(muxc, 0, i, 0)) { |
| dev_err(dev, "Failed to register mux #%d\n", i); |
| i2c_mux_del_adapters(muxc); |
| return -ENODEV; |
| } |
| } |
| |
| i2c_set_clientdata(client, muxc); |
| |
| return 0; |
| }; |
| |
| static int unittest_i2c_mux_remove(struct i2c_client *client) |
| { |
| struct device *dev = &client->dev; |
| struct device_node *np = client->dev.of_node; |
| struct i2c_mux_core *muxc = i2c_get_clientdata(client); |
| |
| dev_dbg(dev, "%s for node @%pOF\n", __func__, np); |
| i2c_mux_del_adapters(muxc); |
| return 0; |
| } |
| |
| static const struct i2c_device_id unittest_i2c_mux_id[] = { |
| { .name = "unittest-i2c-mux" }, |
| { } |
| }; |
| |
| static struct i2c_driver unittest_i2c_mux_driver = { |
| .driver = { |
| .name = "unittest-i2c-mux", |
| }, |
| .probe = unittest_i2c_mux_probe, |
| .remove = unittest_i2c_mux_remove, |
| .id_table = unittest_i2c_mux_id, |
| }; |
| |
| #endif |
| |
| static int of_unittest_overlay_i2c_init(struct kunit *test) |
| { |
| KUNIT_ASSERT_EQ_MSG(test, i2c_add_driver(&unittest_i2c_dev_driver), 0, |
| "could not register unittest i2c device driver\n"); |
| |
| KUNIT_ASSERT_EQ_MSG( |
| test, platform_driver_register(&unittest_i2c_bus_driver), 0, |
| "could not register unittest i2c bus driver\n"); |
| |
| #if IS_BUILTIN(CONFIG_I2C_MUX) |
| KUNIT_ASSERT_EQ_MSG(test, i2c_add_driver(&unittest_i2c_mux_driver), 0, |
| "could not register unittest i2c mux driver\n"); |
| #endif |
| |
| return 0; |
| } |
| |
| static void of_unittest_overlay_i2c_cleanup(void) |
| { |
| #if IS_BUILTIN(CONFIG_I2C_MUX) |
| i2c_del_driver(&unittest_i2c_mux_driver); |
| #endif |
| platform_driver_unregister(&unittest_i2c_bus_driver); |
| i2c_del_driver(&unittest_i2c_dev_driver); |
| } |
| |
| static void of_unittest_overlay_i2c_12(struct kunit *test) |
| { |
| /* device should enable */ |
| of_unittest_apply_overlay_check(test, 12, 12, 0, 1, I2C_OVERLAY); |
| } |
| |
| /* test deactivation of device */ |
| static void of_unittest_overlay_i2c_13(struct kunit *test) |
| { |
| /* device should disable */ |
| of_unittest_apply_overlay_check(test, 13, 13, 1, 0, I2C_OVERLAY); |
| } |
| |
| /* just check for i2c mux existence */ |
| static void of_unittest_overlay_i2c_14(struct kunit *test) |
| { |
| KUNIT_SUCCEED(test); |
| } |
| |
| static void of_unittest_overlay_i2c_15(struct kunit *test) |
| { |
| /* device should enable */ |
| of_unittest_apply_overlay_check(test, 15, 15, 0, 1, I2C_OVERLAY); |
| } |
| |
| #else |
| |
| static inline void of_unittest_overlay_i2c_14(struct kunit *test) { } |
| static inline void of_unittest_overlay_i2c_15(struct kunit *test) { } |
| |
| #endif |
| |
| static void of_unittest_overlay(struct kunit *test) |
| { |
| struct device_node *bus_np = NULL; |
| |
| KUNIT_ASSERT_FALSE_MSG(test, platform_driver_register(&unittest_driver), |
| "could not register unittest driver\n"); |
| |
| bus_np = of_find_node_by_path(bus_path); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL_MSG( |
| test, bus_np, "could not find bus_path \"%s\"\n", bus_path); |
| |
| KUNIT_ASSERT_FALSE_MSG( |
| test, of_platform_default_populate(bus_np, NULL, NULL), |
| "could not populate bus @ \"%s\"\n", bus_path); |
| |
| KUNIT_ASSERT_TRUE_MSG( |
| test, of_unittest_device_exists(100, PDEV_OVERLAY), |
| "could not find unittest0 @ \"%s\"\n", |
| unittest_path(100, PDEV_OVERLAY)); |
| |
| KUNIT_ASSERT_FALSE_MSG( |
| test, of_unittest_device_exists(101, PDEV_OVERLAY), |
| "unittest1 @ \"%s\" should not exist\n", |
| unittest_path(101, PDEV_OVERLAY)); |
| |
| /* tests in sequence */ |
| of_unittest_overlay_0(test); |
| of_unittest_overlay_1(test); |
| of_unittest_overlay_2(test); |
| of_unittest_overlay_3(test); |
| of_unittest_overlay_4(test); |
| of_unittest_overlay_5(test); |
| of_unittest_overlay_6(test); |
| of_unittest_overlay_8(test); |
| |
| of_unittest_overlay_10(test); |
| of_unittest_overlay_11(test); |
| |
| #if IS_BUILTIN(CONFIG_I2C) |
| KUNIT_ASSERT_EQ_MSG(test, of_unittest_overlay_i2c_init(test), 0, |
| "i2c init failed\n"); |
| goto out; |
| |
| of_unittest_overlay_i2c_12(test); |
| of_unittest_overlay_i2c_13(test); |
| of_unittest_overlay_i2c_14(test); |
| of_unittest_overlay_i2c_15(test); |
| |
| of_unittest_overlay_i2c_cleanup(); |
| #endif |
| |
| of_unittest_destroy_tracked_overlays(); |
| |
| out: |
| of_node_put(bus_np); |
| } |
| |
| #else |
| static inline void of_unittest_overlay(struct kunit *test) { } |
| #endif |
| |
| #ifdef CONFIG_OF_OVERLAY |
| |
| /* |
| * __dtb_ot_begin[] and __dtb_ot_end[] are created by cmd_dt_S_dtb |
| * in scripts/Makefile.lib |
| */ |
| |
| #define OVERLAY_INFO_EXTERN(name) \ |
| extern uint8_t __dtb_##name##_begin[]; \ |
| extern uint8_t __dtb_##name##_end[] |
| |
| #define OVERLAY_INFO(overlay_name, expected) \ |
| { .dtb_begin = __dtb_##overlay_name##_begin, \ |
| .dtb_end = __dtb_##overlay_name##_end, \ |
| .expected_result = expected, \ |
| .name = #overlay_name, \ |
| } |
| |
| struct overlay_info { |
| uint8_t *dtb_begin; |
| uint8_t *dtb_end; |
| int expected_result; |
| int overlay_id; |
| char *name; |
| }; |
| |
| OVERLAY_INFO_EXTERN(overlay_base); |
| OVERLAY_INFO_EXTERN(overlay); |
| OVERLAY_INFO_EXTERN(overlay_0); |
| OVERLAY_INFO_EXTERN(overlay_1); |
| OVERLAY_INFO_EXTERN(overlay_2); |
| OVERLAY_INFO_EXTERN(overlay_3); |
| OVERLAY_INFO_EXTERN(overlay_4); |
| OVERLAY_INFO_EXTERN(overlay_5); |
| OVERLAY_INFO_EXTERN(overlay_6); |
| OVERLAY_INFO_EXTERN(overlay_7); |
| OVERLAY_INFO_EXTERN(overlay_8); |
| OVERLAY_INFO_EXTERN(overlay_9); |
| OVERLAY_INFO_EXTERN(overlay_10); |
| OVERLAY_INFO_EXTERN(overlay_11); |
| OVERLAY_INFO_EXTERN(overlay_12); |
| OVERLAY_INFO_EXTERN(overlay_13); |
| OVERLAY_INFO_EXTERN(overlay_15); |
| OVERLAY_INFO_EXTERN(overlay_bad_add_dup_node); |
| OVERLAY_INFO_EXTERN(overlay_bad_add_dup_prop); |
| OVERLAY_INFO_EXTERN(overlay_bad_phandle); |
| OVERLAY_INFO_EXTERN(overlay_bad_symbol); |
| |
| /* entries found by name */ |
| static struct overlay_info overlays[] = { |
| OVERLAY_INFO(overlay_base, -9999), |
| OVERLAY_INFO(overlay, 0), |
| OVERLAY_INFO(overlay_0, 0), |
| OVERLAY_INFO(overlay_1, 0), |
| OVERLAY_INFO(overlay_2, 0), |
| OVERLAY_INFO(overlay_3, 0), |
| OVERLAY_INFO(overlay_4, 0), |
| OVERLAY_INFO(overlay_5, 0), |
| OVERLAY_INFO(overlay_6, 0), |
| OVERLAY_INFO(overlay_7, 0), |
| OVERLAY_INFO(overlay_8, 0), |
| OVERLAY_INFO(overlay_9, 0), |
| OVERLAY_INFO(overlay_10, 0), |
| OVERLAY_INFO(overlay_11, 0), |
| OVERLAY_INFO(overlay_12, 0), |
| OVERLAY_INFO(overlay_13, 0), |
| OVERLAY_INFO(overlay_15, 0), |
| OVERLAY_INFO(overlay_bad_add_dup_node, -EINVAL), |
| OVERLAY_INFO(overlay_bad_add_dup_prop, -EINVAL), |
| OVERLAY_INFO(overlay_bad_phandle, -EINVAL), |
| OVERLAY_INFO(overlay_bad_symbol, -EINVAL), |
| /* end marker */ |
| {.dtb_begin = NULL, .dtb_end = NULL, .expected_result = 0, .name = NULL} |
| }; |
| |
| static struct device_node *overlay_base_root; |
| |
| static void * __init dt_alloc_memory(u64 size, u64 align) |
| { |
| return memblock_alloc(size, align); |
| } |
| |
| /* |
| * Create base device tree for the overlay unittest. |
| * |
| * This is called from very early boot code. |
| * |
| * Do as much as possible the same way as done in __unflatten_device_tree |
| * and other early boot steps for the normal FDT so that the overlay base |
| * unflattened tree will have the same characteristics as the real tree |
| * (such as having memory allocated by the early allocator). The goal |
| * is to test "the real thing" as much as possible, and test "test setup |
| * code" as little as possible. |
| * |
| * Have to stop before resolving phandles, because that uses kmalloc. |
| */ |
| void __init unittest_unflatten_overlay_base(void) |
| { |
| struct overlay_info *info; |
| u32 data_size; |
| void *new_fdt; |
| u32 size; |
| int found = 0; |
| const char *overlay_name = "overlay_base"; |
| |
| for (info = overlays; info && info->name; info++) { |
| if (!strcmp(overlay_name, info->name)) { |
| found = 1; |
| break; |
| } |
| } |
| if (!found) { |
| pr_err("no overlay data for %s\n", overlay_name); |
| return; |
| } |
| |
| info = &overlays[0]; |
| |
| if (info->expected_result != -9999) { |
| pr_err("No dtb 'overlay_base' to attach\n"); |
| return; |
| } |
| |
| data_size = info->dtb_end - info->dtb_begin; |
| if (!data_size) { |
| pr_err("No dtb 'overlay_base' to attach\n"); |
| return; |
| } |
| |
| size = fdt_totalsize(info->dtb_begin); |
| if (size != data_size) { |
| pr_err("dtb 'overlay_base' header totalsize != actual size"); |
| return; |
| } |
| |
| new_fdt = dt_alloc_memory(size, roundup_pow_of_two(FDT_V17_SIZE)); |
| if (!new_fdt) { |
| pr_err("alloc for dtb 'overlay_base' failed"); |
| return; |
| } |
| |
| memcpy(new_fdt, info->dtb_begin, size); |
| |
| __unflatten_device_tree(new_fdt, NULL, &overlay_base_root, |
| dt_alloc_memory, true); |
| } |
| |
| /* |
| * The purpose of of_unittest_overlay_data_add is to add an |
| * overlay in the normal fashion. This is a test of the whole |
| * picture, instead of testing individual elements. |
| * |
| * A secondary purpose is to be able to verify that the contents of |
| * /proc/device-tree/ contains the updated structure and values from |
| * the overlay. That must be verified separately in user space. |
| * |
| * Return 0 on unexpected error. |
| */ |
| static int overlay_data_apply(const char *overlay_name, int *overlay_id) |
| { |
| struct overlay_info *info; |
| int found = 0; |
| int ret; |
| u32 size; |
| |
| for (info = overlays; info && info->name; info++) { |
| if (!strcmp(overlay_name, info->name)) { |
| found = 1; |
| break; |
| } |
| } |
| if (!found) { |
| pr_err("no overlay data for %s\n", overlay_name); |
| return 0; |
| } |
| |
| size = info->dtb_end - info->dtb_begin; |
| if (!size) |
| pr_err("no overlay data for %s\n", overlay_name); |
| |
| ret = of_overlay_fdt_apply(info->dtb_begin, size, &info->overlay_id); |
| if (overlay_id) |
| *overlay_id = info->overlay_id; |
| if (ret < 0) |
| goto out; |
| |
| pr_debug("%s applied\n", overlay_name); |
| |
| out: |
| if (ret != info->expected_result) |
| pr_err("of_overlay_fdt_apply() expected %d, ret=%d, %s\n", |
| info->expected_result, ret, overlay_name); |
| |
| return (ret == info->expected_result); |
| } |
| |
| /* |
| * The purpose of of_unittest_overlay_high_level is to add an overlay |
| * in the normal fashion. This is a test of the whole picture, |
| * instead of individual elements. |
| * |
| * The first part of the function is _not_ normal overlay usage; it is |
| * finishing splicing the base overlay device tree into the live tree. |
| */ |
| static void of_unittest_overlay_high_level(struct kunit *test) |
| { |
| struct device_node *last_sibling; |
| struct device_node *np; |
| struct device_node *of_symbols; |
| struct device_node *overlay_base_symbols = 0; |
| struct device_node **pprev; |
| struct property *prop; |
| |
| KUNIT_ASSERT_TRUE_MSG(test, overlay_base_root, |
| "overlay_base_root not initialized\n"); |
| |
| /* |
| * Could not fixup phandles in unittest_unflatten_overlay_base() |
| * because kmalloc() was not yet available. |
| */ |
| of_overlay_mutex_lock(); |
| of_resolve_phandles(overlay_base_root); |
| of_overlay_mutex_unlock(); |
| |
| |
| /* |
| * do not allow overlay_base to duplicate any node already in |
| * tree, this greatly simplifies the code |
| */ |
| |
| /* |
| * remove overlay_base_root node "__local_fixups", after |
| * being used by of_resolve_phandles() |
| */ |
| pprev = &overlay_base_root->child; |
| for (np = overlay_base_root->child; np; np = np->sibling) { |
| if (of_node_name_eq(np, "__local_fixups__")) { |
| *pprev = np->sibling; |
| break; |
| } |
| pprev = &np->sibling; |
| } |
| |
| /* remove overlay_base_root node "__symbols__" if in live tree */ |
| of_symbols = of_get_child_by_name(of_root, "__symbols__"); |
| if (of_symbols) { |
| /* will have to graft properties from node into live tree */ |
| pprev = &overlay_base_root->child; |
| for (np = overlay_base_root->child; np; np = np->sibling) { |
| if (of_node_name_eq(np, "__symbols__")) { |
| overlay_base_symbols = np; |
| *pprev = np->sibling; |
| break; |
| } |
| pprev = &np->sibling; |
| } |
| } |
| |
| for_each_child_of_node(overlay_base_root, np) { |
| struct device_node *base_child; |
| for_each_child_of_node(of_root, base_child) { |
| KUNIT_ASSERT_STRNEQ_MSG( |
| test, np->full_name, base_child->full_name, |
| "illegal node name in overlay_base %pOFn", np); |
| } |
| } |
| |
| /* |
| * overlay 'overlay_base' is not allowed to have root |
| * properties, so only need to splice nodes into main device tree. |
| * |
| * root node of *overlay_base_root will not be freed, it is lost |
| * memory. |
| */ |
| |
| for (np = overlay_base_root->child; np; np = np->sibling) |
| np->parent = of_root; |
| |
| mutex_lock(&of_mutex); |
| |
| for (last_sibling = np = of_root->child; np; np = np->sibling) |
| last_sibling = np; |
| |
| if (last_sibling) |
| last_sibling->sibling = overlay_base_root->child; |
| else |
| of_root->child = overlay_base_root->child; |
| |
| for_each_of_allnodes_from(overlay_base_root, np) |
| __of_attach_node_sysfs(np); |
| |
| if (of_symbols) { |
| struct property *new_prop; |
| for_each_property_of_node(overlay_base_symbols, prop) { |
| |
| new_prop = __of_prop_dup(prop, GFP_KERNEL); |
| if (!new_prop) { |
| KUNIT_FAIL(test, |
| "__of_prop_dup() of '%s' from overlay_base node __symbols__", |
| prop->name); |
| goto err_unlock; |
| } |
| if (__of_add_property(of_symbols, new_prop)) { |
| /* "name" auto-generated by unflatten */ |
| if (!strcmp(new_prop->name, "name")) |
| continue; |
| KUNIT_FAIL(test, |
| "duplicate property '%s' in overlay_base node __symbols__", |
| prop->name); |
| goto err_unlock; |
| } |
| if (__of_add_property_sysfs(of_symbols, new_prop)) { |
| KUNIT_FAIL(test, |
| "unable to add property '%s' in overlay_base node __symbols__ to sysfs", |
| prop->name); |
| goto err_unlock; |
| } |
| } |
| } |
| |
| mutex_unlock(&of_mutex); |
| |
| |
| /* now do the normal overlay usage test */ |
| |
| KUNIT_EXPECT_TRUE_MSG(test, overlay_data_apply("overlay", NULL), |
| "Adding overlay 'overlay' failed\n"); |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, overlay_data_apply("overlay_bad_add_dup_node", NULL), |
| "Adding overlay 'overlay_bad_add_dup_node' failed\n"); |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, overlay_data_apply("overlay_bad_add_dup_prop", NULL), |
| "Adding overlay 'overlay_bad_add_dup_prop' failed\n"); |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, overlay_data_apply("overlay_bad_phandle", NULL), |
| "Adding overlay 'overlay_bad_phandle' failed\n"); |
| |
| KUNIT_EXPECT_TRUE_MSG( |
| test, overlay_data_apply("overlay_bad_symbol", NULL), |
| "Adding overlay 'overlay_bad_symbol' failed\n"); |
| |
| return; |
| |
| err_unlock: |
| mutex_unlock(&of_mutex); |
| } |
| |
| #else |
| |
| static inline void of_unittest_overlay_high_level(struct kunit *test) {} |
| |
| #endif |
| |
| static int of_test_init(struct kunit *test) |
| { |
| /* adding data for unittest */ |
| KUNIT_ASSERT_EQ(test, 0, unittest_data_add()); |
| |
| if (!of_aliases) |
| of_aliases = of_find_node_by_path("/aliases"); |
| |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, of_find_node_by_path( |
| "/testcase-data/phandle-tests/consumer-a")); |
| |
| if (IS_ENABLED(CONFIG_UML)) |
| unflatten_device_tree(); |
| |
| return 0; |
| } |
| |
| static struct kunit_case of_test_cases[] = { |
| KUNIT_CASE(of_unittest_check_tree_linkage), |
| KUNIT_CASE(of_unittest_check_phandles), |
| KUNIT_CASE(of_unittest_parse_phandle_with_args), |
| KUNIT_CASE(of_unittest_parse_phandle_with_args_map), |
| KUNIT_CASE(of_unittest_printf), |
| KUNIT_CASE(of_unittest_property_string), |
| KUNIT_CASE(of_unittest_property_copy), |
| KUNIT_CASE(of_unittest_changeset), |
| KUNIT_CASE(of_unittest_parse_interrupts), |
| KUNIT_CASE(of_unittest_parse_interrupts_extended), |
| KUNIT_CASE(of_unittest_match_node), |
| KUNIT_CASE(of_unittest_platform_populate), |
| KUNIT_CASE(of_unittest_overlay), |
| /* Double check linkage after removing testcase data */ |
| KUNIT_CASE(of_unittest_check_tree_linkage), |
| KUNIT_CASE(of_unittest_overlay_high_level), |
| {}, |
| }; |
| |
| static struct kunit_module of_test_module = { |
| .name = "of-test", |
| .init = of_test_init, |
| .test_cases = of_test_cases, |
| }; |
| module_test(of_test_module); |