| // Copyright 2020 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/devicetree/devicetree.h> |
| #include <lib/devicetree/testing/loaded-dtb.h> |
| #include <lib/fit/result.h> |
| #include <lib/stdcompat/array.h> |
| #include <lib/stdcompat/source_location.h> |
| #include <lib/stdcompat/span.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <filesystem> |
| #include <memory> |
| #include <type_traits> |
| |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| using devicetree::testing::LoadDtb; |
| |
| TEST(DevicetreeTest, NodeNameAndAddress) { |
| { |
| devicetree::Node node("abc"); |
| EXPECT_STREQ("abc", node.name()); |
| EXPECT_STREQ("", node.address()); |
| } |
| { |
| devicetree::Node node("abc@"); |
| EXPECT_STREQ("abc", node.name()); |
| EXPECT_STREQ("", node.address()); |
| } |
| { |
| devicetree::Node node("abc@def"); |
| EXPECT_STREQ("abc", node.name()); |
| EXPECT_STREQ("def", node.address()); |
| } |
| { |
| devicetree::Node node("@def"); |
| EXPECT_STREQ("", node.name()); |
| EXPECT_STREQ("def", node.address()); |
| } |
| } |
| |
| TEST(DevicetreeTest, EmptyTree) { |
| auto loaded_dtb = LoadDtb("empty.dtb"); |
| ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str()); |
| devicetree::Devicetree dt = loaded_dtb->fdt(); |
| |
| size_t seen = 0; |
| auto walker = [&seen](const devicetree::NodePath& path, const devicetree::PropertyDecoder&) { |
| if (seen++ == 0) { |
| size_t size = path.size(); |
| EXPECT_EQ(1, size); |
| if (size > 0) { |
| EXPECT_TRUE(path.back().empty()); // Root node. |
| } |
| } |
| return true; |
| }; |
| dt.Walk(walker); |
| EXPECT_EQ(1, seen); |
| } |
| |
| struct Node { |
| std::string_view name; |
| size_t size = 0; |
| bool prune = false; |
| }; |
| |
| // List of nodes are in pre order. |
| // post order does a translation for node[i](preorder) -> node[post_order[i]](postorder) |
| void DoAndVerifyWalk(devicetree::Devicetree& tree, cpp20::span<const Node> nodes, |
| cpp20::span<const size_t> post_order) { |
| // PreWalk Only check. |
| size_t seen = 0; |
| auto pre_walker = [&](const devicetree::NodePath& path, |
| const devicetree::PropertyDecoder& props) { |
| bool prune = false; |
| if (seen < nodes.size()) { |
| auto node = nodes[seen]; |
| size_t size = path.size(); |
| EXPECT_EQ(node.size, path.size()); |
| if (size > 0) { |
| EXPECT_STREQ(node.name, path.back()); |
| } |
| prune = node.prune; |
| } |
| ++seen; |
| return !prune; |
| }; |
| |
| // Only pre walk. |
| tree.Walk(pre_walker); |
| EXPECT_EQ(seen, nodes.size()); |
| seen = 0; |
| |
| size_t post_seen = 0; |
| auto post_walker = [&](const devicetree::NodePath& path, |
| const devicetree::PropertyDecoder& props) { |
| bool prune = false; |
| if (post_seen < nodes.size()) { |
| auto node = nodes[post_order[post_seen]]; |
| size_t size = path.size(); |
| EXPECT_EQ(node.size, path.size()); |
| if (size > 0) { |
| EXPECT_STREQ(node.name, path.back()); |
| } |
| prune = node.prune; |
| } |
| ++post_seen; |
| return !prune; |
| }; |
| |
| tree.Walk(pre_walker, post_walker); |
| EXPECT_EQ(seen, nodes.size()); |
| EXPECT_EQ(seen, post_seen); |
| } |
| |
| TEST(DevicetreeTest, NodesAreVisitedDepthFirst) { |
| /* |
| * |
| / \ |
| A E |
| / \ \ |
| B C F |
| / / \ |
| D G I |
| / |
| H |
| */ |
| auto loaded_dtb = LoadDtb("complex_no_properties.dtb"); |
| ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str()); |
| devicetree::Devicetree dt = loaded_dtb->fdt(); |
| |
| constexpr auto nodes = cpp20::to_array<Node>({ |
| {.name = "", .size = 1}, |
| {.name = "A", .size = 2}, |
| {.name = "B", .size = 3}, |
| {.name = "C", .size = 3}, |
| {.name = "D", .size = 4}, |
| {.name = "E", .size = 2}, |
| {.name = "F", .size = 3}, |
| {.name = "G", .size = 4}, |
| {.name = "H", .size = 5}, |
| {.name = "I", .size = 4}, |
| }); |
| |
| constexpr auto post_order = cpp20::to_array<size_t>({2, 4, 3, 1, 8, 7, 9, 6, 5, 0}); |
| |
| DoAndVerifyWalk(dt, nodes, post_order); |
| } |
| |
| TEST(DevicetreeTest, SubtreesArePruned) { |
| /* |
| * |
| / \ |
| A E |
| / \ \ |
| B C^ F^ |
| / / \ |
| D G I |
| / |
| H |
| |
| ^ = root of pruned subtree |
| */ |
| auto loaded_dtb = LoadDtb("complex_no_properties.dtb"); |
| ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str()); |
| devicetree::Devicetree dt = loaded_dtb->fdt(); |
| |
| constexpr auto nodes = cpp20::to_array<Node>({ |
| {.name = "", .size = 1}, |
| {.name = "A", .size = 2}, |
| {.name = "B", .size = 3}, |
| {.name = "C", .size = 3, .prune = true}, |
| {.name = "E", .size = 2}, |
| {.name = "F", .size = 3, .prune = true}, |
| }); |
| |
| constexpr auto post_order = cpp20::to_array<size_t>({2, 3, 1, 5, 4, 0}); |
| |
| DoAndVerifyWalk(dt, nodes, post_order); |
| } |
| |
| TEST(DevicetreeTest, WholeTreeIsPruned) { |
| /* |
| *^ |
| / \ |
| A E |
| / \ \ |
| B C F |
| / / \ |
| D G I |
| / |
| H |
| |
| ^ = root of pruned subtree |
| */ |
| |
| auto loaded_dtb = LoadDtb("complex_no_properties.dtb"); |
| ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str()); |
| devicetree::Devicetree dt = loaded_dtb->fdt(); |
| |
| constexpr auto nodes = cpp20::to_array<Node>({ |
| {.name = "", .size = 1, .prune = true}, |
| }); |
| |
| constexpr auto post_order = cpp20::to_array<size_t>({0}); |
| |
| DoAndVerifyWalk(dt, nodes, post_order); |
| } |
| |
| TEST(DevicetreeTest, AliaesNodeIsKept) { |
| auto loaded_dtb = LoadDtb("complex_with_alias_first.dtb"); |
| ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str()); |
| // aliases: |
| // foo = "/A/C"; |
| // bar = "/E/F"; |
| devicetree::Devicetree dt = loaded_dtb->fdt(); |
| |
| std::optional<devicetree::ResolvedPath> resolved; |
| dt.Walk([&resolved](const auto& path, const auto& decoder) { |
| if (path.back() == "A") { |
| if (auto res = decoder.ResolvePath("foo"); res.is_ok()) { |
| resolved = *res; |
| } |
| return false; |
| } |
| return true; |
| }); |
| |
| ASSERT_TRUE(resolved); |
| auto [foo, empty] = *resolved; |
| EXPECT_EQ(foo, "/A/C"); |
| EXPECT_TRUE(empty.empty()); |
| } |
| |
| TEST(DevicetreeTest, PropertiesAreTranslated) { |
| /* |
| * |
| / \ |
| A C |
| / \ |
| B D |
| */ |
| auto loaded_dtb = LoadDtb("simple_with_properties.dtb"); |
| ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str()); |
| devicetree::Devicetree dt = loaded_dtb->fdt(); |
| size_t seen = 0; |
| auto walker = [&seen](const devicetree::NodePath& path, |
| const devicetree::PropertyDecoder& decoder) { |
| auto& props = decoder.properties(); |
| switch (seen++) { |
| case 0: { // root |
| size_t size = path.size(); |
| EXPECT_EQ(1, size); |
| if (size > 0) { |
| EXPECT_TRUE(path.back().empty()); |
| } |
| |
| devicetree::Properties::iterator begin; |
| begin = props.begin(); // Can copy-assign. |
| EXPECT_EQ(begin, props.end()); |
| |
| break; |
| } |
| case 1: { // A |
| size_t size = path.size(); |
| EXPECT_EQ(2, size); |
| if (size > 0) { |
| EXPECT_STREQ("A", path.back()); |
| } |
| EXPECT_EQ(props.end(), std::next(props.begin(), 2)); // 2 properties. |
| |
| auto prop1 = *props.begin(); |
| EXPECT_STREQ("a1", prop1.name); |
| EXPECT_TRUE(prop1.value.AsBool().value()); |
| auto prop2 = *std::next(props.begin()); |
| EXPECT_STREQ("a2", prop2.name); |
| EXPECT_STREQ("root", *prop2.value.AsString()); |
| break; |
| } |
| case 2: { // B |
| size_t size = path.size(); |
| EXPECT_EQ(3, size); |
| if (size > 0) { |
| EXPECT_STREQ("B", path.back()); |
| } |
| EXPECT_EQ(props.end(), std::next(props.begin(), 3)); // 3 properties. |
| |
| auto prop1 = *props.begin(); |
| EXPECT_STREQ("b1", prop1.name); |
| EXPECT_EQ(0x1, prop1.value.AsUint32()); |
| auto prop2 = *std::next(props.begin()); |
| EXPECT_STREQ("b2", prop2.name); |
| EXPECT_EQ(0x10, prop2.value.AsUint32()); |
| auto prop3 = *std::next(props.begin(), 2); |
| EXPECT_STREQ("b3", prop3.name); |
| EXPECT_EQ(0x100, prop3.value.AsUint32()); |
| break; |
| } |
| case 3: { // C |
| size_t size = path.size(); |
| EXPECT_EQ(2, size); |
| if (size > 0) { |
| EXPECT_STREQ("C", path.back()); |
| } |
| EXPECT_EQ(props.end(), std::next(props.begin(), 2)); // 2 properties. |
| |
| auto prop1 = *props.begin(); |
| EXPECT_STREQ("c1", prop1.name); |
| EXPECT_STREQ("hello", *prop1.value.AsString()); |
| auto prop2 = *std::next(props.begin()); |
| EXPECT_STREQ("c2", prop2.name); |
| EXPECT_STREQ("world", *prop2.value.AsString()); |
| break; |
| } |
| case 4: { // D |
| size_t size = path.size(); |
| EXPECT_EQ(3, size); |
| if (size > 0) { |
| EXPECT_STREQ("D", path.back()); |
| } |
| EXPECT_EQ(props.end(), std::next(props.begin(), 3)); // 3 properties. |
| |
| auto prop1 = *props.begin(); |
| EXPECT_STREQ("d1", prop1.name); |
| EXPECT_EQ(0x1000, prop1.value.AsUint64()); |
| auto prop2 = *std::next(props.begin()); |
| EXPECT_STREQ("d2", prop2.name); |
| EXPECT_EQ(0x10000, prop2.value.AsUint64()); |
| auto prop3 = *std::next(props.begin(), 2); |
| EXPECT_STREQ("d3", prop3.name); |
| EXPECT_EQ(0x100000, prop3.value.AsUint64()); |
| break; |
| } |
| } |
| return true; |
| }; |
| dt.Walk(walker); |
| EXPECT_EQ(5, seen); |
| } |
| |
| TEST(DevicetreeTest, MemoryReservations) { |
| auto loaded_dtb = LoadDtb("memory_reservations.dtb"); |
| ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str()); |
| devicetree::Devicetree dt = loaded_dtb->fdt(); |
| |
| unsigned int i = 0; |
| for (auto [start, size] : dt.memory_reservations()) { |
| switch (i++) { |
| case 0: |
| EXPECT_EQ(start, 0x12340000); |
| EXPECT_EQ(size, 0x2000); |
| break; |
| case 1: |
| EXPECT_EQ(start, 0x56780000); |
| EXPECT_EQ(size, 0x3000); |
| break; |
| case 2: |
| EXPECT_EQ(start, 0x7fffffff12340000); |
| EXPECT_EQ(size, 0x400000000); |
| break; |
| case 3: |
| EXPECT_EQ(start, 0x00ffffff56780000); |
| EXPECT_EQ(size, 0x500000000); |
| break; |
| default: |
| EXPECT_LT(i, 4, "too many entries"); |
| break; |
| } |
| } |
| EXPECT_EQ(i, 4, "wrong number of entries"); |
| } |
| |
| TEST(DevicetreeTest, StringList) { |
| using namespace std::literals; |
| |
| unsigned int i = 0; |
| for (auto str : devicetree::StringList(""sv)) { |
| ++i; |
| EXPECT_FALSE(true, "list should be empty"); |
| EXPECT_TRUE(str.empty()); |
| } |
| EXPECT_EQ(i, 0); |
| |
| i = 0; |
| for (auto str : devicetree::StringList("one"sv)) { |
| ++i; |
| EXPECT_STREQ("one", str); |
| } |
| EXPECT_EQ(i, 1); |
| |
| i = 0; |
| for (auto str : devicetree::StringList("one\0two\0three"sv)) { |
| switch (i++) { |
| case 0: |
| EXPECT_STREQ("one", str); |
| break; |
| case 1: |
| EXPECT_STREQ("two", str); |
| break; |
| case 2: |
| EXPECT_STREQ("three", str); |
| break; |
| } |
| } |
| EXPECT_EQ(i, 3); |
| |
| // By convention, there is no element following a terminal separator. |
| i = 0; |
| for (auto str : devicetree::StringList("one\0\0two\0"sv)) { |
| switch (i++) { |
| case 0: |
| EXPECT_STREQ("one", str); |
| break; |
| case 2: |
| EXPECT_STREQ("two", str); |
| break; |
| default: |
| EXPECT_EQ(0, str.size()); |
| } |
| } |
| EXPECT_EQ(i, 3); |
| |
| i = 0; |
| for (auto str : devicetree::StringList<'/'>("foo/bar/baz"sv)) { |
| switch (i++) { |
| case 0: |
| EXPECT_STREQ("foo", str); |
| break; |
| case 1: |
| EXPECT_STREQ("bar", str); |
| break; |
| case 3: |
| EXPECT_STREQ("baz", str); |
| break; |
| } |
| } |
| EXPECT_EQ(i, 3); |
| } |
| |
| auto as_bytes = [](auto& val) { |
| using byte_type = std::conditional_t<std::is_const_v<std::remove_reference_t<decltype(val)>>, |
| const uint8_t, uint8_t>; |
| return cpp20::span<byte_type>(reinterpret_cast<byte_type*>(&val), sizeof(val)); |
| }; |
| |
| auto append = [](auto& vec, auto&& other) { vec.insert(vec.end(), other.begin(), other.end()); }; |
| |
| uint32_t byte_swap(uint32_t val) { |
| if constexpr (cpp20::endian::native == cpp20::endian::big) { |
| return val; |
| } else { |
| auto bytes = as_bytes(val); |
| return static_cast<uint32_t>(bytes[0]) << 24 | static_cast<uint32_t>(bytes[1]) << 16 | |
| static_cast<uint32_t>(bytes[2]) << 8 | static_cast<uint32_t>(bytes[3]); |
| } |
| } |
| |
| struct RegisterBlockPropertyBuilder { |
| void Add(uint64_t address, uint64_t size) { |
| uint32_t address_low = byte_swap(0x0000FFFF & address); |
| uint32_t size_low = byte_swap(0x0000FFFF & size); |
| |
| append(property_value, as_bytes(address_low)); |
| if (address_cells == 2) { |
| uint32_t address_high = byte_swap(address >> 32); |
| append(property_value, as_bytes(address_high)); |
| } |
| |
| append(property_value, as_bytes(size_low)); |
| if (size_cells == 2) { |
| uint32_t size_high = byte_swap(size >> 32); |
| append(property_value, as_bytes(size_high)); |
| } |
| } |
| |
| const uint32_t address_cells; |
| const uint32_t size_cells; |
| std::vector<uint8_t> property_value; |
| }; |
| |
| // Small helper so we can verify the behavior of CachedProperties. |
| struct PropertyBuilder { |
| devicetree::Properties Build() { |
| return devicetree::Properties( |
| {property_block.data(), property_block.size()}, |
| std::string_view(reinterpret_cast<const char*>(string_block.data()), string_block.size())); |
| } |
| |
| void Add(std::string_view name, uint32_t value) { |
| uint32_t name_off = byte_swap(static_cast<uint32_t>(string_block.size())); |
| // String must be null terminated. |
| append(string_block, name); |
| string_block.push_back('\0'); |
| |
| uint32_t len = byte_swap(sizeof(uint32_t)); |
| |
| if (!property_block.empty()) { |
| const uint32_t kFdtPropToken = byte_swap(0x00000003); |
| append(property_block, as_bytes(kFdtPropToken)); |
| } |
| // this are all 32b aliagned, no padding need. |
| append(property_block, as_bytes(len)); |
| append(property_block, as_bytes(name_off)); |
| uint32_t be_value = byte_swap(value); |
| append(property_block, as_bytes(be_value)); |
| } |
| |
| void Add(std::string_view alias, std::string_view absolute_path) { |
| constexpr std::array<uint8_t, sizeof(uint32_t) - 1> kPadding = {}; |
| |
| uint32_t name_off = byte_swap(static_cast<uint32_t>(string_block.size())); |
| // String must be null terminated. |
| append(string_block, alias); |
| string_block.push_back('\0'); |
| |
| // Add the null terminator. |
| uint32_t len = static_cast<uint32_t>(absolute_path.size()) + 1; |
| cpp20::span<const uint8_t> padding; |
| if (auto remainder = len % sizeof(uint32_t); remainder != 0) { |
| padding = cpp20::span(kPadding).subspan(0, sizeof(uint32_t) - remainder); |
| } |
| len = byte_swap(len); |
| |
| if (!property_block.empty()) { |
| const uint32_t kFdtPropToken = byte_swap(0x00000003); |
| append(property_block, as_bytes(kFdtPropToken)); |
| } |
| append(property_block, as_bytes(len)); |
| append(property_block, as_bytes(name_off)); |
| append(property_block, absolute_path); |
| property_block.push_back('\0'); |
| append(property_block, padding); |
| } |
| |
| void Add(std::string_view name, cpp20::span<const uint8_t> byte_array) { |
| uint32_t name_off = byte_swap(static_cast<uint32_t>(string_block.size())); |
| // String must be null terminated. |
| append(string_block, name); |
| string_block.push_back('\0'); |
| |
| uint32_t len = byte_swap(static_cast<uint32_t>(byte_array.size())); |
| |
| if (!property_block.empty()) { |
| const uint32_t kFdtPropToken = byte_swap(0x00000003); |
| append(property_block, as_bytes(kFdtPropToken)); |
| } |
| // this are all 32b aliagned, no padding need. |
| append(property_block, as_bytes(len)); |
| append(property_block, as_bytes(name_off)); |
| append(property_block, byte_array); |
| } |
| |
| std::vector<uint8_t> property_block; |
| std::vector<uint8_t> string_block; |
| }; |
| |
| auto check_prop = [](auto& prop, uint32_t val, |
| cpp20::source_location loc = cpp20::source_location::current()) { |
| ASSERT_TRUE(prop, "at %s:%u", loc.file_name(), loc.line()); |
| auto pv = prop->AsUint32(); |
| ASSERT_TRUE(pv, "at %s:%u", loc.file_name(), loc.line()); |
| EXPECT_EQ(*pv, val, "at %s:%u", loc.file_name(), loc.line()); |
| }; |
| |
| TEST(PropertyDecoderTest, FindProperties) { |
| PropertyBuilder builder; |
| builder.Add("property_1", 1); |
| builder.Add("property_2", 2); |
| builder.Add("property_3", 3); |
| auto props = builder.Build(); |
| |
| devicetree::PropertyDecoder decoder(props); |
| |
| auto [p1, p2, p3, u4, rep_p3] = |
| decoder.FindProperties("property_1", "property_2", "property_3", "unknown", "property_3"); |
| |
| check_prop(p1, 1); |
| check_prop(p2, 2); |
| check_prop(p3, 3); |
| ASSERT_FALSE(u4); |
| ASSERT_FALSE(rep_p3); |
| } |
| |
| TEST(PropertyDecoderTest, FindProperty) { |
| PropertyBuilder builder; |
| builder.Add("property_1", 1); |
| builder.Add("property_2", 2); |
| builder.Add("property_3", 3); |
| auto props = builder.Build(); |
| |
| devicetree::PropertyDecoder decoder(props); |
| |
| auto prop = decoder.FindProperty("property_1"); |
| auto not_found = decoder.FindProperty("not in there"); |
| |
| check_prop(prop, 1); |
| ASSERT_FALSE(not_found); |
| } |
| |
| TEST(PropertyDecoderTest, ResolvePathWithAlias) { |
| PropertyBuilder builder; |
| builder.Add("foo", "/foo/bar"); |
| builder.Add("bar", "/bar/baz"); |
| builder.Add("baz", "/baz/foo"); |
| builder.Add("empty", ""); |
| std::optional<devicetree::Properties> aliases(builder.Build()); |
| |
| devicetree::PropertyDecoder decoder(nullptr, devicetree::Properties(), aliases); |
| |
| auto foo_path = decoder.ResolvePath("foo"); |
| ASSERT_TRUE(foo_path.is_ok()); |
| |
| auto [foo_prefix, foo_suffix] = *foo_path; |
| EXPECT_EQ(foo_prefix, "/foo/bar"); |
| EXPECT_TRUE(foo_suffix.empty()); |
| |
| auto bar_path = decoder.ResolvePath("bar/baz"); |
| ASSERT_TRUE(bar_path.is_ok()); |
| |
| auto [bar_prefix, bar_suffix] = *bar_path; |
| EXPECT_EQ(bar_prefix, "/bar/baz"); |
| EXPECT_EQ(bar_suffix, "baz"); |
| |
| auto baz_path = decoder.ResolvePath("baz/baz/foo"); |
| ASSERT_TRUE(baz_path.is_ok()); |
| |
| auto [baz_prefix, baz_suffix] = *baz_path; |
| EXPECT_EQ(baz_prefix, "/baz/foo"); |
| EXPECT_EQ(baz_suffix, "baz/foo"); |
| |
| // Alias not found. |
| auto not_found = decoder.ResolvePath("foobar"); |
| ASSERT_TRUE(not_found.is_error()); |
| EXPECT_EQ(not_found.error_value(), devicetree::PropertyDecoder::PathResolveError::kBadAlias); |
| |
| // Absolute path with alias |
| auto absolute_path = decoder.ResolvePath("/foo"); |
| ASSERT_TRUE(absolute_path.is_ok()); |
| |
| auto [abs_prefix, empty_suffix] = *absolute_path; |
| EXPECT_EQ(abs_prefix, "/foo"); |
| EXPECT_TRUE(empty_suffix.empty()); |
| |
| // empty path |
| auto empty_path = decoder.ResolvePath("empty/baz"); |
| ASSERT_TRUE(empty_path.is_error()); |
| EXPECT_EQ(empty_path.error_value(), devicetree::PropertyDecoder::PathResolveError::kBadAlias); |
| } |
| |
| TEST(PropertyDecoderTest, ResolvePathNoAlias) { |
| std::optional<devicetree::Properties> aliases; |
| |
| devicetree::PropertyDecoder decoder(nullptr, devicetree::Properties(), aliases); |
| auto path_with_alias = decoder.ResolvePath("foo"); |
| |
| ASSERT_TRUE(path_with_alias.is_error()); |
| ASSERT_EQ(path_with_alias.error_value(), |
| devicetree::PropertyDecoder::PathResolveError::kNoAliases); |
| |
| auto absolute_path = decoder.ResolvePath("/foo"); |
| |
| ASSERT_TRUE(absolute_path.is_ok()); |
| auto [abs_prefix, empty_suffix] = *absolute_path; |
| |
| EXPECT_EQ(abs_prefix, "/foo"); |
| EXPECT_TRUE(empty_suffix.empty()); |
| } |
| |
| TEST(PropertyDecoderTest, CellCountsAreCached) { |
| // This test relies on manipulating the underlying data, to verify |
| // at which point are the properties actually cached. |
| PropertyBuilder builder; |
| builder.Add("#address-cells", 1); |
| builder.Add("#size-cells", 2); |
| builder.Add("#interrupt-cells", 3); |
| auto properties = builder.Build(); |
| |
| devicetree::PropertyDecoder decoder(properties); |
| { |
| auto address_cells = decoder.num_address_cells(); |
| ASSERT_TRUE(address_cells); |
| EXPECT_EQ(*address_cells, 1); |
| |
| auto size_cells = decoder.num_size_cells(); |
| ASSERT_TRUE(size_cells); |
| EXPECT_EQ(*size_cells, 2); |
| |
| auto interrupt_cells = decoder.num_interrupt_cells(); |
| ASSERT_TRUE(interrupt_cells); |
| EXPECT_EQ(*interrupt_cells, 3); |
| } |
| // Update the underlying propeties, if the property is not cached, it should |
| // return the updated value. |
| PropertyBuilder mod_builder; |
| mod_builder.Add("#address-cells", 3); |
| mod_builder.Add("#size-cells", 4); |
| mod_builder.Add("#interrupt-cells", 5); |
| std::ignore = mod_builder.Build(); |
| // copy contents to the previous property block. |
| builder.property_block = mod_builder.property_block; |
| |
| // Safe check that the underlying properties have been updated. |
| { |
| auto address_cells = decoder.FindProperty("#address-cells"); |
| ASSERT_TRUE(address_cells); |
| auto val = address_cells->AsUint32(); |
| ASSERT_TRUE(val); |
| EXPECT_EQ(*val, 3); |
| } |
| |
| // Now double check address cell still has the previous value. |
| { |
| auto address_cells = decoder.num_address_cells(); |
| ASSERT_TRUE(address_cells); |
| EXPECT_EQ(*address_cells, 1); |
| |
| auto size_cells = decoder.num_size_cells(); |
| ASSERT_TRUE(size_cells); |
| EXPECT_EQ(*size_cells, 2); |
| |
| auto interrupt_cells = decoder.num_interrupt_cells(); |
| ASSERT_TRUE(interrupt_cells); |
| EXPECT_EQ(*interrupt_cells, 3); |
| } |
| } |
| |
| TEST(PropertyDecoderTest, CellCountsNullOptWhenNotPresent) { |
| PropertyBuilder builder; |
| { |
| auto props = builder.Build(); |
| devicetree::PropertyDecoder decoder(props); |
| EXPECT_FALSE(decoder.num_address_cells()); |
| EXPECT_FALSE(decoder.num_size_cells()); |
| EXPECT_FALSE(decoder.num_interrupt_cells()); |
| } |
| |
| builder.Add("#address-cells", 3); |
| { |
| auto props = builder.Build(); |
| devicetree::PropertyDecoder decoder(props); |
| EXPECT_TRUE(decoder.num_address_cells()); |
| EXPECT_FALSE(decoder.num_size_cells()); |
| EXPECT_FALSE(decoder.num_interrupt_cells()); |
| } |
| |
| builder.Add("#size-cells", 4); |
| { |
| auto props = builder.Build(); |
| devicetree::PropertyDecoder decoder(props); |
| EXPECT_TRUE(decoder.num_address_cells()); |
| EXPECT_TRUE(decoder.num_size_cells()); |
| EXPECT_FALSE(decoder.num_interrupt_cells()); |
| } |
| |
| builder.Add("#interrupt-cells", 5); |
| { |
| auto props = builder.Build(); |
| devicetree::PropertyDecoder decoder(props); |
| EXPECT_TRUE(decoder.num_address_cells()); |
| EXPECT_TRUE(decoder.num_size_cells()); |
| EXPECT_TRUE(decoder.num_interrupt_cells()); |
| } |
| } |
| |
| TEST(PropertyDecoder, AddressTranslation) { |
| struct Range { |
| std::array<uint32_t, 1> child; |
| std::array<uint32_t, 1> parent; |
| std::array<uint32_t, 1> length; |
| }; |
| static_assert(std::has_unique_object_representations_v<Range>); |
| static_assert(sizeof(Range) == 12); |
| |
| std::array<Range, 2> ranges_1 = { |
| Range{ |
| .child = {byte_swap(2000)}, |
| .parent = {byte_swap(1000)}, |
| .length = {byte_swap(50)}, |
| }, |
| Range{ |
| .child = {byte_swap(3000)}, |
| .parent = {byte_swap(1050)}, |
| .length = {byte_swap(50)}, |
| }, |
| }; |
| |
| std::array<Range, 2> ranges_2 = { |
| Range{ |
| .child = {byte_swap(1000)}, |
| .parent = {byte_swap(2000)}, |
| .length = {byte_swap(50)}, |
| }, |
| Range{ |
| .child = {byte_swap(1050)}, |
| .parent = {byte_swap(3000)}, |
| .length = {byte_swap(50)}, |
| }, |
| }; |
| |
| // Creates a structure |
| // root |
| // node_1 ranges_1 |
| // node_2 ranges _2 |
| // node_3 |
| // |
| // The translation should of an arbitrary addres on child of node, should apply two |
| // transformation to reach the view from the root, which are reverse transformations, |
| // meaning |decoder.TranslateAddress(a)| = a. |
| |
| PropertyBuilder root_builder; |
| root_builder.Add("#address-cells", 1); |
| devicetree::PropertyDecoder root_decoder(root_builder.Build()); |
| |
| devicetree::ByteView range_1_view(reinterpret_cast<uint8_t*>(ranges_1.data()), |
| ranges_1.size() * sizeof(Range)); |
| PropertyBuilder node_1_builder; |
| node_1_builder.Add("#address-cells", 1); |
| node_1_builder.Add("ranges", range_1_view); |
| devicetree::PropertyDecoder node_1_decoder(&root_decoder, node_1_builder.Build()); |
| |
| devicetree::ByteView range_2_view(reinterpret_cast<uint8_t*>(ranges_2.data()), |
| ranges_2.size() * sizeof(Range)); |
| PropertyBuilder node_2_builder; |
| node_2_builder.Add("#address-cells", 1); |
| node_2_builder.Add("ranges", range_2_view); |
| devicetree::PropertyDecoder node_2_decoder(&node_1_decoder, node_2_builder.Build()); |
| |
| devicetree::PropertyDecoder node_3_decoder(&node_2_decoder, {}); |
| |
| // node 1 has the same domain as the root so no translation is necessary. |
| EXPECT_EQ(node_1_decoder.TranslateAddress(999), 999); |
| EXPECT_EQ(*node_1_decoder.TranslateAddress(1000), 1000); |
| EXPECT_EQ(*node_1_decoder.TranslateAddress(1001), 1001); |
| EXPECT_EQ(*node_1_decoder.TranslateAddress(1050), 1050); |
| EXPECT_EQ(*node_1_decoder.TranslateAddress(1051), 1051); |
| EXPECT_EQ(node_1_decoder.TranslateAddress(2000), 2000); |
| |
| // node 2 address are subject to node 1's ranges, such that its domain |
| // its properly translated into node 1's domain. Unlike node 1, node 2 translation |
| // is only possible by the the ranges property set in its parent, hence 999 and 2000 |
| // are out of the domain. |
| EXPECT_FALSE(node_2_decoder.TranslateAddress(1999)); |
| EXPECT_EQ(*node_2_decoder.TranslateAddress(2000), 1000); |
| EXPECT_EQ(*node_2_decoder.TranslateAddress(2001), 1001); |
| EXPECT_FALSE(node_2_decoder.TranslateAddress(2051)); |
| EXPECT_FALSE(node_2_decoder.TranslateAddress(2999)); |
| EXPECT_EQ(*node_2_decoder.TranslateAddress(3000), 1050); |
| EXPECT_EQ(*node_2_decoder.TranslateAddress(3001), 1051); |
| EXPECT_FALSE(node_2_decoder.TranslateAddress(3050)); |
| |
| // Just like the relationship node 2 has with node 1, node 3 has to node 2, |
| // address are transformed from one domain to the other. |
| EXPECT_FALSE(node_3_decoder.TranslateAddress(999)); |
| EXPECT_EQ(*node_3_decoder.TranslateAddress(1000), 1000); |
| EXPECT_EQ(*node_3_decoder.TranslateAddress(1001), 1001); |
| EXPECT_EQ(*node_3_decoder.TranslateAddress(1050), 1050); |
| EXPECT_EQ(*node_3_decoder.TranslateAddress(1051), 1051); |
| EXPECT_FALSE(node_3_decoder.TranslateAddress(2000)); |
| } |
| |
| TEST(RegisterBlockPropertyTest, Accessors) { |
| RegisterBlockPropertyBuilder register_block{.address_cells = 1, .size_cells = 1}; |
| register_block.Add(0xACED, 0xD1CE); |
| register_block.Add(0xDEED, 0xFEE7); |
| register_block.Add(0xDEAD, 0xBEEF); |
| |
| PropertyBuilder parent_builder; |
| parent_builder.Add("#address-cells", 1); |
| parent_builder.Add("#size-cells", 1); |
| auto parent_props = parent_builder.Build(); |
| devicetree::PropertyDecoder parent_decoder(parent_props); |
| |
| PropertyBuilder builder; |
| builder.Add("#address-cells", 2); |
| builder.Add("#size-cells", 2); |
| builder.Add("reg", register_block.property_value); |
| auto props = builder.Build(); |
| devicetree::PropertyDecoder decoder(&parent_decoder, props); |
| |
| auto reg = decoder.FindProperty("reg"); |
| ASSERT_TRUE(reg); |
| |
| auto reg_block = reg->AsReg(decoder); |
| ASSERT_TRUE(reg_block); |
| |
| ASSERT_EQ(reg_block->size(), 3); |
| EXPECT_EQ(*(*reg_block)[0].address(), 0xACED); |
| EXPECT_EQ(*(*reg_block)[0].size(), 0xD1CE); |
| |
| EXPECT_EQ(*(*reg_block)[1].address(), 0xDEED); |
| EXPECT_EQ(*(*reg_block)[1].size(), 0xFEE7); |
| |
| EXPECT_EQ(*(*reg_block)[2].address(), 0xDEAD); |
| EXPECT_EQ(*(*reg_block)[2].size(), 0xBEEF); |
| } |
| |
| TEST(RegisterBlockPropertyTest, AccessorsMultipleAddressCells) { |
| RegisterBlockPropertyBuilder register_block{.address_cells = 2, .size_cells = 1}; |
| register_block.Add(0x0000ACED0000ACED, 0xD1CE); |
| register_block.Add(0x0000DEED0000ACED, 0xFEE7); |
| register_block.Add(0x0000DEAD0000ACED, 0xBEEF); |
| |
| PropertyBuilder parent_builder; |
| parent_builder.Add("#address-cells", 2); |
| parent_builder.Add("#size-cells", 1); |
| auto parent_props = parent_builder.Build(); |
| devicetree::PropertyDecoder parent_decoder(parent_props); |
| |
| PropertyBuilder builder; |
| // Random values for cells, should pick parent cells. |
| builder.Add("#address-cells", 1); |
| builder.Add("#size-cells", 2); |
| builder.Add("reg", register_block.property_value); |
| auto props = builder.Build(); |
| devicetree::PropertyDecoder decoder(&parent_decoder, props); |
| |
| auto reg = decoder.FindProperty("reg"); |
| ASSERT_TRUE(reg); |
| |
| auto reg_block = reg->AsReg(decoder); |
| ASSERT_TRUE(reg_block); |
| |
| ASSERT_EQ(reg_block->size(), 3); |
| EXPECT_EQ(*(*reg_block)[0].address(), 0xACED0000ACED); |
| EXPECT_EQ(*(*reg_block)[0].size(), 0xD1CE); |
| |
| EXPECT_EQ(*(*reg_block)[1].address(), 0xACED0000DEED); |
| EXPECT_EQ(*(*reg_block)[1].size(), 0xFEE7); |
| |
| EXPECT_EQ(*(*reg_block)[2].address(), 0xACED0000DEAD); |
| EXPECT_EQ(*(*reg_block)[2].size(), 0xBEEF); |
| } |
| |
| TEST(RangesPropertyTest, Accessors) { |
| struct Range { |
| std::array<uint32_t, 2> child; |
| std::array<uint32_t, 1> parent; |
| std::array<uint32_t, 1> length; |
| }; |
| |
| std::array<Range, 4> data = { |
| Range{ |
| .child = |
| { |
| byte_swap(2), |
| byte_swap(1), |
| }, |
| .parent = |
| { |
| byte_swap(4), |
| }, |
| .length = {byte_swap(6)}, |
| }, |
| Range{ |
| .child = |
| { |
| byte_swap(8), |
| byte_swap(7), |
| }, |
| .parent = |
| { |
| byte_swap(10), |
| }, |
| .length = {byte_swap(12)}, |
| }, |
| Range{ |
| .child = |
| { |
| byte_swap(14), |
| byte_swap(13), |
| }, |
| .parent = |
| { |
| byte_swap(16), |
| }, |
| .length = {byte_swap(18)}, |
| }, |
| }; |
| |
| devicetree::ByteView view(reinterpret_cast<uint8_t*>(data.data()), data.size() * sizeof(Range)); |
| auto ranges_property = devicetree::RangesProperty::Create(2, 1, 1, view); |
| ASSERT_TRUE(ranges_property); |
| auto ranges = *ranges_property; |
| |
| auto range_0 = ranges[0]; |
| EXPECT_EQ(range_0.child_bus_address(), uint64_t(2) << 32 | 1); |
| EXPECT_EQ(range_0.parent_bus_address(), uint64_t(4)); |
| EXPECT_EQ(range_0.length(), 6); |
| |
| auto range_1 = ranges[1]; |
| EXPECT_EQ(range_1.child_bus_address(), uint64_t(8) << 32 | 7); |
| EXPECT_EQ(range_1.parent_bus_address(), uint64_t(10)); |
| EXPECT_EQ(range_1.length(), 12); |
| |
| auto range_2 = ranges[2]; |
| EXPECT_EQ(range_2.child_bus_address(), uint64_t(14) << 32 | 13); |
| EXPECT_EQ(range_2.parent_bus_address(), uint64_t(16)); |
| EXPECT_EQ(range_2.length(), 18); |
| } |
| |
| TEST(RangesPropertyTest, AddressTranslation) { |
| struct Range { |
| std::array<uint32_t, 1> child; |
| std::array<uint32_t, 1> parent; |
| std::array<uint32_t, 1> length; |
| }; |
| static_assert(std::has_unique_object_representations_v<Range>); |
| static_assert(sizeof(Range) == 12); |
| |
| std::array<Range, 2> data = { |
| Range{ |
| .child = {byte_swap(1000)}, |
| .parent = {byte_swap(1111)}, |
| .length = {byte_swap(50)}, |
| }, |
| Range{ |
| .child = {byte_swap(1050)}, |
| .parent = {byte_swap(2222)}, |
| .length = {byte_swap(50)}, |
| }, |
| }; |
| |
| devicetree::ByteView view(reinterpret_cast<uint8_t*>(data.data()), data.size() * sizeof(Range)); |
| auto ranges_property = devicetree::RangesProperty::Create(1, 1, 1, view); |
| ASSERT_TRUE(ranges_property); |
| auto ranges = *ranges_property; |
| |
| EXPECT_FALSE(ranges.TranslateChildAddress(999)); |
| EXPECT_EQ(ranges.TranslateChildAddress(1000), 1111); |
| EXPECT_EQ(ranges.TranslateChildAddress(1001), 1112); |
| |
| EXPECT_EQ(ranges.TranslateChildAddress(1050), 2222); |
| EXPECT_EQ(ranges.TranslateChildAddress(1051), 2223); |
| EXPECT_FALSE(ranges.TranslateChildAddress(2100)); |
| } |
| |
| TEST(RangesPropertyTest, EmptyRanges) { |
| devicetree::ByteView view; |
| auto ranges_property = devicetree::RangesProperty::Create(2, 1, 1, view); |
| ASSERT_TRUE(ranges_property); |
| auto ranges = *ranges_property; |
| |
| EXPECT_EQ(ranges.TranslateChildAddress(999), 999); |
| EXPECT_EQ(ranges.TranslateChildAddress(1000), 1000); |
| EXPECT_EQ(ranges.TranslateChildAddress(1001), 1001); |
| EXPECT_EQ(ranges.TranslateChildAddress(1050), 1050); |
| EXPECT_EQ(ranges.TranslateChildAddress(1051), 1051); |
| EXPECT_EQ(ranges.TranslateChildAddress(2100), 2100); |
| } |
| |
| TEST(PropertyValueTest, AsRegisterBlockWithBadSizeIsNullopt) { |
| RegisterBlockPropertyBuilder register_block{.address_cells = 1, .size_cells = 1}; |
| register_block.Add(0xACED, 0xD1CE); |
| |
| PropertyBuilder builder; |
| builder.Add("reg", register_block.property_value); |
| auto props = builder.Build(); |
| devicetree::PropertyDecoder decoder(props); |
| |
| auto reg = decoder.FindProperty("reg"); |
| ASSERT_TRUE(reg); |
| |
| auto reg_block = reg->AsReg(decoder); |
| ASSERT_FALSE(reg_block); |
| } |
| |
| TEST(PropertyEncodedArrayTest, DecodeFields) { |
| struct Triplet { |
| std::array<uint32_t, 2> field_1; |
| uint32_t field_2; |
| std::array<uint32_t, 2> field_3; |
| }; |
| static_assert(std::has_unique_object_representations_v<Triplet>); |
| |
| std::array<Triplet, 4> raw_data = { |
| Triplet{ |
| // 12 | 32 << 32 |
| .field_1 = |
| { |
| byte_swap(12), |
| byte_swap(32), |
| }, |
| .field_2 = byte_swap(16), |
| .field_3 = |
| { |
| byte_swap(0), |
| byte_swap(1), |
| }, |
| }, |
| Triplet{ |
| .field_1 = |
| { |
| byte_swap(45), |
| byte_swap(3), |
| }, |
| .field_2 = byte_swap(17), |
| .field_3 = |
| { |
| byte_swap(1), |
| byte_swap(21), |
| }, |
| }, |
| Triplet{ |
| .field_1 = |
| { |
| byte_swap(451), |
| byte_swap(31), |
| }, |
| .field_2 = byte_swap(18), |
| .field_3 = |
| { |
| byte_swap(15), |
| byte_swap(22), |
| }, |
| }, |
| Triplet{ |
| .field_1 = |
| { |
| byte_swap(454), |
| byte_swap(34), |
| }, |
| .field_2 = byte_swap(155), |
| .field_3 = |
| { |
| byte_swap(150), |
| byte_swap(220), |
| }, |
| }, |
| }; |
| devicetree::ByteView data(reinterpret_cast<uint8_t*>(raw_data.data()), |
| raw_data.size() * sizeof(Triplet)); |
| devicetree::PropEncodedArray<devicetree::PropEncodedArrayElement<3>> |
| triplet_property_encoded_array(data, /*field_1 num cells*/ 2, /*field_2 num cells*/ 1, |
| /*field_3 num_cells*/ 2); |
| |
| auto encoded_triplet_0 = triplet_property_encoded_array[0]; |
| |
| EXPECT_EQ(*encoded_triplet_0[0], uint64_t(12) << 32 | 32); |
| EXPECT_EQ(*encoded_triplet_0[1], uint64_t(16)); |
| EXPECT_EQ(*encoded_triplet_0[2], uint64_t(0) << 32 | 1); |
| |
| auto encoded_triplet_1 = triplet_property_encoded_array[1]; |
| |
| EXPECT_EQ(*encoded_triplet_1[0], uint64_t(45) << 32 | 3); |
| EXPECT_EQ(*encoded_triplet_1[1], uint64_t(17)); |
| EXPECT_EQ(*encoded_triplet_1[2], uint64_t(1) << 32 | 21); |
| |
| auto encoded_triplet_2 = triplet_property_encoded_array[2]; |
| |
| EXPECT_EQ(*encoded_triplet_2[0], uint64_t(451) << 32 | 31); |
| EXPECT_EQ(*encoded_triplet_2[1], uint64_t(18)); |
| EXPECT_EQ(*encoded_triplet_2[2], uint64_t(15) << 32 | 22); |
| |
| auto encoded_triplet_3 = triplet_property_encoded_array[3]; |
| |
| EXPECT_EQ(*encoded_triplet_3[0], uint64_t(454) << 32 | 34); |
| EXPECT_EQ(*encoded_triplet_3[1], uint64_t(155)); |
| EXPECT_EQ(*encoded_triplet_3[2], uint64_t(150) << 32 | 220); |
| } |
| |
| TEST(PropertyEncodedArrayTest, DecodeFieldsWithZeroSize) { |
| struct Triplet { |
| std::array<uint32_t, 2> field_1; |
| // field_2 0 size. |
| uint32_t field_3; |
| }; |
| |
| std::array<Triplet, 2> raw_data = { |
| Triplet{.field_1 = {byte_swap(456), byte_swap(123)}, .field_3 = byte_swap(2)}, |
| Triplet{.field_1 = {byte_swap(456), byte_swap(124)}, .field_3 = byte_swap(3)}, |
| }; |
| |
| devicetree::ByteView data(reinterpret_cast<uint8_t*>(raw_data.data()), |
| raw_data.size() * sizeof(Triplet)); |
| devicetree::PropEncodedArray<devicetree::PropEncodedArrayElement<3>> |
| triplet_property_encoded_array(data, /*field_1 num cells*/ 2, /*field_2 num cells*/ 0, |
| /*field_3 num_cells*/ 1); |
| |
| auto encoded_triplet_0 = triplet_property_encoded_array[0]; |
| |
| EXPECT_EQ(*encoded_triplet_0[0], uint64_t(456) << 32 | 123); |
| EXPECT_FALSE(encoded_triplet_0[1].has_value()); |
| EXPECT_EQ(*encoded_triplet_0[2], 2); |
| |
| auto encoded_triplet_1 = triplet_property_encoded_array[1]; |
| |
| EXPECT_EQ(*encoded_triplet_1[0], uint64_t(456) << 32 | 124); |
| EXPECT_FALSE(encoded_triplet_1[1].has_value()); |
| EXPECT_EQ(*encoded_triplet_1[2], 3); |
| } |
| |
| } // namespace |