blob: f617db7b2878db18c22bfd01e691569a10131284 [file] [log] [blame] [edit]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "manager.h"
#include <fidl/fuchsia.driver.framework/cpp/fidl.h>
#include <lib/driver/component/cpp/composite_node_spec.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <lib/driver/devicetree/visitors/default/default.h>
#include <lib/driver/devicetree/visitors/driver-visitor.h>
#include <lib/driver/devicetree/visitors/registry.h>
#include <zircon/errors.h>
#include <cstddef>
#include <memory>
#include <optional>
#include <unordered_set>
#include <utility>
#include <vector>
#include <bind/fuchsia/cpp/bind.h>
#include <bind/fuchsia/devicetree/cpp/bind.h>
#include <bind/fuchsia/platform/cpp/bind.h>
#include <gtest/gtest.h>
#include "manager-test-helper.h"
#include "test-data/basic-properties.h"
#include "test-data/simple.h"
#include "visitor.h"
namespace fdf_devicetree {
namespace {
class ManagerTest : public testing::ManagerTestHelper, public ::testing::Test {
public:
ManagerTest() : ManagerTestHelper("ManagerTest") {}
};
TEST_F(ManagerTest, TestFindsNodes) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/simple.dtb"));
class EmptyVisitor : public Visitor {
public:
zx::result<> Visit(Node& node, const devicetree::PropertyDecoder& decoder) override {
return zx::ok();
}
};
EmptyVisitor visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
ASSERT_EQ(3lu, manager.nodes().size());
// Root node is always first, and has no name.
Node* node = manager.nodes()[0].get();
ASSERT_STREQ("dt-root", node->name().data());
// example-device node should be next.
node = manager.nodes()[1].get();
ASSERT_STREQ("example-device", node->name().data());
// another-device should be last.
node = manager.nodes()[2].get();
ASSERT_STREQ("another-device", node->name().data());
}
TEST_F(ManagerTest, TestPropertyCallback) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/simple.dtb"));
class TestVisitor : public Visitor {
public:
zx::result<> Visit(Node& node, const devicetree::PropertyDecoder& decoder) override {
for (auto& [name, _] : node.properties()) {
if (node.name() == "example-device") {
auto iter = expected.find(std::string(name));
EXPECT_NE(expected.end(), iter) << "Property " << name << " was unexpected.";
if (iter != expected.end()) {
expected.erase(iter);
}
}
}
return zx::ok();
}
std::unordered_set<std::string> expected{
"compatible",
"phandle",
};
};
TestVisitor visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
EXPECT_EQ(0lu, visitor.expected.size());
}
TEST_F(ManagerTest, TestPublishesSimpleNode) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/simple.dtb"));
DefaultVisitors<> default_visitors;
ASSERT_EQ(ZX_OK, manager.Walk(default_visitors).status_value());
ASSERT_TRUE(DoPublish(manager).is_ok());
ASSERT_EQ(0lu, env().SyncCall(&testing::FakeEnvWrapper::pbus_node_size));
ASSERT_EQ(2lu, env().SyncCall(&testing::FakeEnvWrapper::non_pbus_node_size));
ASSERT_EQ(0lu, env().SyncCall(&testing::FakeEnvWrapper::mgr_requests_size));
auto non_pbus_node_0 = env().SyncCall(&testing::FakeEnvWrapper::non_pbus_nodes_at, 0);
ASSERT_TRUE(non_pbus_node_0->args().name().has_value());
ASSERT_EQ(non_pbus_node_0->args().name(), "dt-root");
ASSERT_TRUE(non_pbus_node_0->args().properties2().has_value());
ASSERT_TRUE(testing::CheckHasProperties(
{{{
.key = std::string(bind_fuchsia_devicetree::FIRST_COMPATIBLE),
.value =
fuchsia_driver_framework::NodePropertyValue::WithStringValue("fuchsia,sample-dt"),
}}},
*non_pbus_node_0->args().properties2(), false));
auto non_pbus_node_1 = env().SyncCall(&testing::FakeEnvWrapper::non_pbus_nodes_at, 1);
ASSERT_TRUE(non_pbus_node_1->args().name().has_value());
ASSERT_NE(nullptr, strstr("example-device", non_pbus_node_1->args().name()->data()));
ASSERT_TRUE(non_pbus_node_1->args().properties2().has_value());
ASSERT_TRUE(testing::CheckHasProperties(
{{{
.key = std::string(bind_fuchsia_devicetree::FIRST_COMPATIBLE),
.value =
fuchsia_driver_framework::NodePropertyValue::WithStringValue("fuchsia,sample-device"),
}}},
*non_pbus_node_1->args().properties2(), false));
}
TEST_F(ManagerTest, DriverVisitorTest) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/basic-properties.dtb"));
class TestDriverVisitor final : public DriverVisitor {
public:
TestDriverVisitor() : DriverVisitor({"wrong-string", "fuchsia,sample-device"}) {}
zx::result<> DriverVisit(Node& node, const devicetree::PropertyDecoder& decoder) override {
visited = true;
return zx::ok();
}
bool visited = false;
};
TestDriverVisitor visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
ASSERT_TRUE(DoPublish(manager).is_ok());
ASSERT_TRUE(visitor.visited);
}
TEST_F(ManagerTest, TestMetadata) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/basic-properties.dtb"));
class MetadataVisitor : public DriverVisitor {
public:
MetadataVisitor() : DriverVisitor({"fuchsia,sample-device"}) {}
zx::result<> DriverVisit(Node& node, const devicetree::PropertyDecoder& decoder) override {
auto prop = node.GetProperty<uint32_t>("device_specific_prop");
EXPECT_TRUE(prop.is_ok()) << "Property device_specific_prop was unexpected.";
device_specific_prop = prop.value_or(ZX_ERR_INVALID_ARGS);
EXPECT_EQ(device_specific_prop, (uint32_t)DEVICE_SPECIFIC_PROP_VALUE);
fuchsia_hardware_platform_bus::Metadata metadata = {
{.data = std::vector<uint8_t>(reinterpret_cast<const uint8_t*>(&device_specific_prop),
reinterpret_cast<const uint8_t*>(&device_specific_prop) +
sizeof(device_specific_prop))}};
node.AddMetadata(metadata);
return zx::ok();
}
uint32_t device_specific_prop = 0;
};
DefaultVisitors<MetadataVisitor> visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
ASSERT_TRUE(DoPublish(manager).is_ok());
ASSERT_EQ(1lu, env().SyncCall(&testing::FakeEnvWrapper::pbus_node_size));
ASSERT_EQ(10lu, env().SyncCall(&testing::FakeEnvWrapper::non_pbus_node_size));
// Check metadata of sample-device.
auto metadata = env().SyncCall(&testing::FakeEnvWrapper::pbus_nodes_at, 0).metadata();
// Test Metadata properties.
ASSERT_TRUE(metadata);
ASSERT_EQ(1lu, metadata->size());
ASSERT_EQ((uint32_t)DEVICE_SPECIFIC_PROP_VALUE,
*reinterpret_cast<uint32_t*>((*(*metadata)[0].data()).data()));
}
TEST_F(ManagerTest, TestReferences) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/basic-properties.dtb"));
class ReferenceParentVisitor final : public DriverVisitor {
public:
using Property1Specifier = devicetree::PropEncodedArrayElement<PROPERTY1_CELLS>;
ReferenceParentVisitor() : DriverVisitor({"fuchsia,reference-parent"}) {
Properties props = {};
props.emplace_back(std::make_unique<ReferenceProperty>("property1", "#property1-cells"));
props.emplace_back(std::make_unique<ReferenceProperty>("property2", "#property2-cells"));
props.emplace_back(
std::make_unique<StringListProperty>("property2-names", /* required */ false));
parser_ = std::make_unique<PropertyParser>(std::move(props));
}
zx::result<> Visit(Node& node, const devicetree::PropertyDecoder& decoder) override {
auto parser_output = parser_->Parse(node);
if (parser_output.is_error()) {
return parser_output.take_error();
}
if (auto property1 = parser_output->Get<References>("property1"); property1) {
for (auto& reference : *property1) {
if (is_match(reference.reference_node().properties())) {
reference1_specifier() =
devicetree::PropEncodedArray<Property1Specifier>(reference.property_cells(), 1);
reference1_count()++;
}
}
}
auto property2 = parser_output->Get<References>("property2");
auto property2_names = parser_output->Get<std::vector<std::string>>("property2-names");
if (property2 && property2_names) {
size_t index = 0;
for (auto& reference : *property2) {
if (is_match(reference.reference_node().properties())) {
auto name = (*property2_names)[index];
reference2_names().emplace_back(name);
reference2_parent_names().push_back(reference.reference_node().name());
reference2_count()++;
}
index++;
}
}
return DriverVisitor::Visit(node, decoder);
}
zx::result<> DriverVisit(Node& node, const devicetree::PropertyDecoder& decoder) override {
visit_called()++;
return zx::ok();
}
zx::result<> DriverFinalizeNode(Node& node) override {
ZX_ASSERT(reference1_count() == 1u);
ZX_ASSERT(reference2_count() == 3u);
finalize_called()++;
return zx::ok();
}
size_t& visit_called() { return visit_called_; }
size_t& finalize_called() { return finalize_called_; }
size_t& reference1_count() { return reference1_count_; }
size_t& reference2_count() { return reference2_count_; }
devicetree::PropEncodedArray<Property1Specifier>& reference1_specifier() {
return reference1_specifier_;
}
std::vector<std::string>& reference2_names() { return reference2_names_; }
std::vector<std::string>& reference2_parent_names() { return reference2_parent_names_; }
private:
size_t visit_called_ = 0;
size_t finalize_called_ = 0;
size_t reference1_count_ = 0;
size_t reference2_count_ = 0;
devicetree::PropEncodedArray<Property1Specifier> reference1_specifier_;
std::vector<std::string> reference2_names_;
std::vector<std::string> reference2_parent_names_;
std::unique_ptr<PropertyParser> parser_;
};
auto parent_visitor = std::make_unique<ReferenceParentVisitor>();
ReferenceParentVisitor* parent_visitor_ptr = parent_visitor.get();
VisitorRegistry visitors;
ASSERT_TRUE(visitors.RegisterVisitor(std::move(parent_visitor)).is_ok());
ASSERT_EQ(ZX_OK, manager.Walk(visitors).status_value());
ASSERT_EQ(parent_visitor_ptr->visit_called(), 3u);
ASSERT_EQ(parent_visitor_ptr->finalize_called(), 3u);
ASSERT_EQ(parent_visitor_ptr->reference1_specifier().size(), 1u);
ASSERT_EQ(parent_visitor_ptr->reference1_specifier()[0][0], PROPERTY1_SPECIFIER);
ASSERT_EQ(parent_visitor_ptr->reference2_parent_names()[0], "reference-parent-1");
ASSERT_EQ(parent_visitor_ptr->reference2_parent_names()[1], "reference-parent-2");
ASSERT_EQ(parent_visitor_ptr->reference2_parent_names()[2], "reference-parent-3");
ASSERT_EQ(parent_visitor_ptr->reference2_names()[0], PROPERTY2_NAME1);
ASSERT_EQ(parent_visitor_ptr->reference2_names()[1], PROPERTY2_NAME2);
ASSERT_EQ(parent_visitor_ptr->reference2_names()[2], PROPERTY2_NAME3);
ASSERT_TRUE(DoPublish(manager).is_ok());
}
TEST_F(ManagerTest, TestParentChild) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/basic-properties.dtb"));
class ParentVisitor final : public DriverVisitor {
public:
ParentVisitor() : DriverVisitor({"fuchsia,parent"}) {}
zx::result<> DriverVisit(Node& node, const devicetree::PropertyDecoder& decoder) override {
auto children = node.children();
child_count = children.size();
for (ChildNode& child : children) {
child_names.push_back(child.name());
}
name = node.name();
return zx::ok();
}
size_t child_count = 0;
std::vector<std::string_view> child_names;
std::string_view name;
};
class ChildVisitor final : public DriverVisitor {
public:
ChildVisitor() : DriverVisitor({"fuchsia,child"}) {}
zx::result<> DriverVisit(Node& node, const devicetree::PropertyDecoder& decoder) override {
count++;
if (!parent_name.empty() && parent_name != node.parent().name()) {
return zx::error(ZX_ERR_INTERNAL);
}
parent_name = node.parent().name();
names.push_back(node.name());
return zx::ok();
}
size_t count = 0;
std::vector<std::string_view> names;
std::string_view parent_name;
};
auto parent_visitor = std::make_unique<ParentVisitor>();
ParentVisitor* parent_visitor_ptr = parent_visitor.get();
auto child_visitor = std::make_unique<ChildVisitor>();
ChildVisitor* child_visitor_ptr = child_visitor.get();
VisitorRegistry visitors;
ASSERT_TRUE(visitors.RegisterVisitor(std::move(parent_visitor)).is_ok());
ASSERT_TRUE(visitors.RegisterVisitor(std::move(child_visitor)).is_ok());
ASSERT_EQ(ZX_OK, manager.Walk(visitors).status_value());
EXPECT_EQ(parent_visitor_ptr->child_count, child_visitor_ptr->count);
EXPECT_EQ(child_visitor_ptr->count, 2u);
for (auto child_name : parent_visitor_ptr->child_names) {
bool matched = false;
for (auto name : child_visitor_ptr->names) {
if (name == child_name) {
matched = true;
break;
}
}
EXPECT_TRUE(matched);
}
EXPECT_EQ(child_visitor_ptr->parent_name, parent_visitor_ptr->name);
ASSERT_TRUE(DoPublish(manager).is_ok());
}
TEST_F(ManagerTest, TestSkipDisabledNodes) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/status-disabled.dtb"));
DefaultVisitors<> default_visitors;
ASSERT_EQ(ZX_OK, manager.Walk(default_visitors).status_value());
ASSERT_TRUE(DoPublish(manager).is_ok());
ASSERT_EQ(0lu, env().SyncCall(&testing::FakeEnvWrapper::pbus_node_size));
ASSERT_EQ(3lu, env().SyncCall(&testing::FakeEnvWrapper::non_pbus_node_size));
auto non_pbus_node0 = env().SyncCall(&testing::FakeEnvWrapper::non_pbus_nodes_at, 0);
ASSERT_TRUE(non_pbus_node0->args().name().has_value());
ASSERT_EQ(non_pbus_node0->args().name(), "dt-root");
auto non_pbus_node1 = env().SyncCall(&testing::FakeEnvWrapper::non_pbus_nodes_at, 1);
ASSERT_TRUE(non_pbus_node1->args().name().has_value());
ASSERT_NE(nullptr, strstr("status-okay-device", non_pbus_node1->args().name()->data()));
auto non_pbus_node2 = env().SyncCall(&testing::FakeEnvWrapper::non_pbus_nodes_at, 2);
ASSERT_TRUE(non_pbus_node2->args().name().has_value());
ASSERT_NE(nullptr, strstr("status-none-device", non_pbus_node2->args().name()->data()));
}
TEST_F(ManagerTest, TestNonPbusCompositeSpec) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/simple.dtb"));
static const std::string kTestKey = "test-key";
static const std::string kTestProperty = "test-property";
class TestDriverVisitor final : public DriverVisitor {
public:
TestDriverVisitor() : DriverVisitor({SAMPLE_DEVICE_COMPATIBILITY}) {}
zx::result<> DriverVisit(Node& node, const devicetree::PropertyDecoder& decoder) override {
visited = true;
parent_spec.bind_rules({fdf::MakeAcceptBindRule2(kTestKey, kTestProperty)});
parent_spec.properties({fdf::MakeProperty2(kTestKey, kTestProperty)});
node.AddNodeSpec(parent_spec);
return zx::ok();
}
bool visited = false;
fuchsia_driver_framework::ParentSpec2 parent_spec;
};
DefaultVisitors<TestDriverVisitor> visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
ASSERT_TRUE(DoPublish(manager).is_ok());
ASSERT_EQ(0lu, env().SyncCall(&testing::FakeEnvWrapper::pbus_node_size));
ASSERT_EQ(2lu, env().SyncCall(&testing::FakeEnvWrapper::non_pbus_node_size));
ASSERT_EQ(1lu, env().SyncCall(&testing::FakeEnvWrapper::mgr_requests_size));
auto mgr_request = env().SyncCall(&testing::FakeEnvWrapper::mgr_requests_at, 0);
ASSERT_TRUE(mgr_request.parents2().has_value());
ASSERT_EQ(2lu, mgr_request.parents2()->size());
EXPECT_TRUE(
testing::CheckHasProperties({{
fdf::MakeProperty2(bind_fuchsia_devicetree::FIRST_COMPATIBLE,
SAMPLE_DEVICE_COMPATIBILITY),
}},
(*mgr_request.parents2())[0].properties(), true));
EXPECT_TRUE(testing::CheckHasBindRules(
{
fdf::MakeAcceptBindRule2(bind_fuchsia_devicetree::FIRST_COMPATIBLE,
SAMPLE_DEVICE_COMPATIBILITY),
},
(*mgr_request.parents2())[0].bind_rules(), true));
EXPECT_TRUE(testing::CheckHasProperties({{fdf::MakeProperty2(kTestKey, kTestProperty)}},
(*mgr_request.parents2())[1].properties(), false));
EXPECT_TRUE(testing::CheckHasBindRules({{fdf::MakeAcceptBindRule2(kTestKey, kTestProperty)}},
(*mgr_request.parents2())[1].bind_rules(), false));
}
TEST_F(ManagerTest, TestPbusCompositeSpec) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/simple.dtb"));
static const std::string kTestKey = "test-key";
static const std::string kTestProperty = "test-property";
class TestDriverVisitor final : public DriverVisitor {
public:
TestDriverVisitor() : DriverVisitor({SAMPLE_DEVICE_COMPATIBILITY}) {}
zx::result<> DriverVisit(Node& node, const devicetree::PropertyDecoder& decoder) override {
visited = true;
parent_spec.bind_rules({fdf::MakeAcceptBindRule2(kTestKey, kTestProperty)});
parent_spec.properties({fdf::MakeProperty2(kTestKey, kTestProperty)});
node.AddNodeSpec(parent_spec);
// This adds a pbus resource, making the one of the parent of the composite to be platform
// device.
node.AddBootMetadata({});
return zx::ok();
}
bool visited = false;
fuchsia_driver_framework::ParentSpec2 parent_spec;
};
DefaultVisitors<TestDriverVisitor> visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
ASSERT_TRUE(DoPublish(manager).is_ok());
ASSERT_EQ(1lu, env().SyncCall(&testing::FakeEnvWrapper::pbus_node_size));
ASSERT_EQ(1lu, env().SyncCall(&testing::FakeEnvWrapper::non_pbus_node_size));
ASSERT_EQ(1lu, env().SyncCall(&testing::FakeEnvWrapper::mgr_requests_size));
auto mgr_request = env().SyncCall(&testing::FakeEnvWrapper::mgr_requests_at, 0);
ASSERT_TRUE(mgr_request.parents2().has_value());
ASSERT_EQ(2lu, mgr_request.parents2()->size());
EXPECT_TRUE(testing::CheckHasProperties(
{{
fdf::MakeProperty2(bind_fuchsia::PROTOCOL, bind_fuchsia_platform::BIND_PROTOCOL_DEVICE),
}},
(*mgr_request.parents2())[0].properties(), true));
EXPECT_TRUE(testing::CheckHasBindRules(
{
fdf::MakeAcceptBindRule2(bind_fuchsia::PROTOCOL,
bind_fuchsia_platform::BIND_PROTOCOL_DEVICE),
},
(*mgr_request.parents2())[0].bind_rules(), true));
EXPECT_TRUE(testing::CheckHasProperties({{fdf::MakeProperty2(kTestKey, kTestProperty)}},
(*mgr_request.parents2())[1].properties(), false));
EXPECT_TRUE(testing::CheckHasBindRules({{fdf::MakeAcceptBindRule2(kTestKey, kTestProperty)}},
(*mgr_request.parents2())[1].bind_rules(), false));
}
TEST_F(ManagerTest, TestPublishOrder) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/simple.dtb"));
DefaultVisitors<> visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
auto& first_node = manager.nodes()[0];
auto first_node_id = first_node->id();
auto& second_node = manager.nodes()[1];
auto second_node_id = second_node->id();
EXPECT_EQ(first_node->GetPublishIndex(), 0u);
EXPECT_EQ(second_node->GetPublishIndex(), 1u);
EXPECT_TRUE(first_node->ChangePublishOrder(1u).is_ok());
EXPECT_EQ(manager.nodes()[0]->id(), second_node_id);
EXPECT_EQ(manager.nodes()[1]->id(), first_node_id);
ASSERT_TRUE(DoPublish(manager).is_ok());
}
TEST_F(ManagerTest, GetPropertyTest) {
Manager manager(testing::LoadTestBlob("/pkg/test-data/basic-properties.dtb"));
DefaultVisitors<> visitor;
ASSERT_EQ(ZX_OK, manager.Walk(visitor).status_value());
auto test_device = manager.FindNode("test-properties-device");
ASSERT_TRUE(test_device.has_value());
// Test bool.
ASSERT_TRUE((*test_device)->GetProperty<bool>("bool-property"));
ASSERT_FALSE((*test_device)->GetProperty<bool>("non-existent-property"));
// Test string.
auto string_prop = (*test_device)->GetProperty<std::string>("string-property");
ASSERT_TRUE(string_prop.is_ok());
ASSERT_EQ(string_prop.value(), "hello");
// Test uint32.
auto uint32_prop = (*test_device)->GetProperty<uint32_t>("device_specific_prop");
ASSERT_TRUE(uint32_prop.is_ok());
ASSERT_EQ(uint32_prop.value(), static_cast<uint32_t>(DEVICE_SPECIFIC_PROP_VALUE));
// Test uint64.
auto uint64_prop = (*test_device)->GetProperty<uint64_t>("uint64-property");
ASSERT_TRUE(uint64_prop.is_ok());
ASSERT_EQ(uint64_prop.value(), 0x123456789abcdef0ULL);
// Test uint32 vector.
auto uint32_vector_prop =
(*test_device)->GetProperty<std::vector<uint32_t>>("uint32-vector-property");
ASSERT_TRUE(uint32_vector_prop.is_ok());
ASSERT_EQ(uint32_vector_prop.value().size(), 2u);
ASSERT_EQ(uint32_vector_prop.value()[0], 1u);
ASSERT_EQ(uint32_vector_prop.value()[1], 2u);
// Test string vector.
auto string_vector_prop =
(*test_device)->GetProperty<std::vector<std::string>>("string-list-property");
ASSERT_TRUE(string_vector_prop.is_ok());
ASSERT_EQ(string_vector_prop.value().size(), 2u);
ASSERT_EQ(string_vector_prop.value()[0], "string1");
ASSERT_EQ(string_vector_prop.value()[1], "string2");
// Test wrong type.
auto wrong_type_prop = (*test_device)->GetProperty<uint64_t>("string-property");
ASSERT_TRUE(wrong_type_prop.is_error());
ASSERT_EQ(wrong_type_prop.status_value(), ZX_ERR_WRONG_TYPE);
// Test not found.
auto not_found_prop = (*test_device)->GetProperty<std::string>("non-existent-property");
ASSERT_TRUE(not_found_prop.is_error());
ASSERT_EQ(not_found_prop.status_value(), ZX_ERR_NOT_FOUND);
}
} // namespace
} // namespace fdf_devicetree