blob: e033c368bc585db13d964da2a5402459d80630bd [file] [log] [blame]
// 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