| // 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 "lib/driver/devicetree/manager/node.h" |
| |
| #include <endian.h> |
| #include <fidl/fuchsia.driver.framework/cpp/fidl.h> |
| #include <fidl/fuchsia.hardware.platform.bus/cpp/fidl.h> |
| #include <lib/driver/component/cpp/composite_node_spec.h> |
| #include <lib/driver/component/cpp/node_add_args.h> |
| #include <lib/driver/logging/cpp/logger.h> |
| #include <zircon/errors.h> |
| |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include <bind/fuchsia/cpp/bind.h> |
| #include <bind/fuchsia/platform/cpp/bind.h> |
| |
| namespace fdf { |
| using namespace fuchsia_driver_framework; |
| } |
| |
| namespace fdf_devicetree { |
| |
| constexpr const char kPhandleProp[] = "phandle"; |
| |
| NodeManager::~NodeManager() = default; |
| |
| Node::Node(Node *parent, const std::string_view name, devicetree::Properties properties, |
| uint32_t id, NodeManager *manager) |
| : parent_(parent), name_(name), id_(id), manager_(manager) { |
| ZX_ASSERT(manager_); |
| |
| if (parent_) { |
| parent_->children_.push_back(this); |
| } else { |
| name_ = "dt-root"; |
| } |
| |
| fdf_name_ = name_; |
| // '@' and ',' are not a valid character in Node names as per driver framework. |
| std::replace(fdf_name_.begin(), fdf_name_.end(), '@', '-'); |
| std::replace(fdf_name_.begin(), fdf_name_.end(), ',', '-'); |
| |
| pbus_node_.did() = bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_DEVICETREE; |
| pbus_node_.vid() = bind_fuchsia_platform::BIND_PLATFORM_DEV_VID_GENERIC; |
| pbus_node_.instance_id() = id; |
| pbus_node_.name() = fdf_name_; |
| |
| for (auto property : properties) { |
| properties_.emplace(property.name, property.value); |
| } |
| |
| // Get phandle if exists. |
| auto phandle = GetProperty<uint32_t>(kPhandleProp); |
| if (phandle.is_ok()) { |
| phandle_ = phandle.value(); |
| } else if (phandle.status_value() != ZX_ERR_NOT_FOUND) { |
| FDF_LOG(WARNING, "Node '%s' has invalid phandle property: %s", name_.c_str(), |
| phandle.status_string()); |
| } |
| } |
| |
| void Node::AddBindProperty(fuchsia_driver_framework::NodeProperty2 prop) { |
| node_properties_.emplace_back(std::move(prop)); |
| } |
| |
| void Node::AddMmio(fuchsia_hardware_platform_bus::Mmio mmio) { |
| if (!pbus_node_.mmio()) { |
| pbus_node_.mmio() = std::vector<fuchsia_hardware_platform_bus::Mmio>(); |
| } |
| pbus_node_.mmio()->emplace_back(std::move(mmio)); |
| add_platform_device_ = true; |
| } |
| |
| void Node::AddBti(fuchsia_hardware_platform_bus::Bti bti) { |
| if (!pbus_node_.bti()) { |
| pbus_node_.bti() = std::vector<fuchsia_hardware_platform_bus::Bti>(); |
| } |
| pbus_node_.bti()->emplace_back(std::move(bti)); |
| add_platform_device_ = true; |
| } |
| |
| void Node::AddIrq(fuchsia_hardware_platform_bus::Irq irq) { |
| if (!pbus_node_.irq()) { |
| pbus_node_.irq() = std::vector<fuchsia_hardware_platform_bus::Irq>(); |
| } |
| pbus_node_.irq()->emplace_back(std::move(irq)); |
| add_platform_device_ = true; |
| } |
| |
| void Node::AddMetadata(fuchsia_hardware_platform_bus::Metadata metadata) { |
| if (!pbus_node_.metadata()) { |
| pbus_node_.metadata() = std::vector<fuchsia_hardware_platform_bus::Metadata>(); |
| } |
| pbus_node_.metadata()->emplace_back(std::move(metadata)); |
| add_platform_device_ = true; |
| } |
| |
| void Node::AddBootMetadata(fuchsia_hardware_platform_bus::BootMetadata boot_metadata) { |
| if (!pbus_node_.boot_metadata()) { |
| pbus_node_.boot_metadata() = std::vector<fuchsia_hardware_platform_bus::BootMetadata>(); |
| } |
| pbus_node_.boot_metadata()->emplace_back(std::move(boot_metadata)); |
| add_platform_device_ = true; |
| } |
| |
| void Node::AddNodeSpec(const fuchsia_driver_framework::ParentSpec2 &spec) { |
| parents_.emplace_back(spec); |
| composite_ = true; |
| } |
| |
| void Node::AddSmc(fuchsia_hardware_platform_bus::Smc smc) { |
| if (!pbus_node_.smc()) { |
| pbus_node_.smc() = std::vector<fuchsia_hardware_platform_bus::Smc>(); |
| } |
| pbus_node_.smc()->emplace_back(std::move(smc)); |
| add_platform_device_ = true; |
| } |
| |
| void Node::AddPowerConfig(fuchsia_hardware_power::PowerElementConfiguration power_config) { |
| if (!pbus_node_.power_config()) { |
| pbus_node_.power_config() = std::vector<fuchsia_hardware_power::PowerElementConfiguration>(); |
| } |
| pbus_node_.power_config()->emplace_back(std::move(power_config)); |
| add_platform_device_ = true; |
| } |
| |
| uint32_t Node::GetPublishIndex() const { return manager_->GetPublishIndex(id()); } |
| |
| zx::result<> Node::ChangePublishOrder(uint32_t new_index) { |
| return manager_->ChangePublishOrder(id(), new_index); |
| } |
| |
| zx::result<> Node::Publish(fdf::WireSyncClient<fuchsia_hardware_platform_bus::PlatformBus> &pbus, |
| fidl::SyncClient<fuchsia_driver_framework::CompositeNodeManager> &mgr, |
| fidl::SyncClient<fuchsia_driver_framework::Node> &fdf_node) { |
| if (node_properties_.empty() && !composite_ && !add_platform_device_) { |
| FDF_LOG( |
| DEBUG, |
| "Not publishing node '%.*s' because it has no node properties, and no platform resources, and it is not a composite node.", |
| static_cast<int>(name().size()), name().data()); |
| return zx::ok(); |
| } |
| |
| auto status_property = GetProperty<std::string>("status"); |
| if (status_property.is_ok() && *status_property != "okay") { |
| fdf::debug("Not publishing node '{}' because its status is {}.", name(), *status_property); |
| return zx::ok(); |
| } |
| |
| // Nodes are published as per below logic - |
| // 1. Node has platform resources |
| // a. Node references other nodes -> PlatformBus.NodeAdd + CompositeNodeManager.AddSpec |
| // b. Node does not reference other nodes -> PlatformBus.NodeAdd |
| // 2. Node does not have platform resources |
| // a. Node has bind properties (i.e. compatible string) |
| // i. Node references other nodes -> Node.AddChild + CompositeNodeManager.AddSpec |
| // ii. Node does not reference other nodes -> Node.AddChild |
| // b. Node has no bind properties |
| // i. Node references other nodes -> CompositeNodeManager.AddSpec |
| // ii. Node does not reference other nodes -> Not published |
| // |
| |
| bool add_non_platform_device = !add_platform_device_ && !node_properties_.empty(); |
| |
| if (add_platform_device_) { |
| FDF_LOG(DEBUG, "Adding node '%s' to pbus with instance id %d.", fdf_name().c_str(), id_); |
| |
| // Pass properties to pbus node directly if we are not adding a composite spec. |
| if (!composite_) { |
| pbus_node_.properties() = node_properties_; |
| } |
| |
| fdf::Arena arena('PBUS'); |
| fidl::Arena fidl_arena; |
| auto result = pbus.buffer(arena)->NodeAdd(fidl::ToWire(fidl_arena, pbus_node_)); |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "NodeAdd request failed: %s", result.FormatDescription().data()); |
| return zx::error(result.status()); |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "NodeAdd failed: %s", zx_status_get_string(result->error_value())); |
| return zx::error(result->error_value()); |
| } |
| } else if (add_non_platform_device) { |
| FDF_LOG(DEBUG, "Adding node '%s' as board driver child.", fdf_name().c_str()); |
| fuchsia_driver_framework::NodeAddArgs node_add_args; |
| node_add_args.name() = fdf_name(); |
| |
| node_add_args.properties2() = node_properties_; |
| |
| auto [client_end, server_end] = |
| fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create(); |
| auto result = fdf_node->AddChild({std::move(node_add_args), std::move(server_end), {}}); |
| if (result.is_error()) { |
| FDF_LOG(ERROR, "AddChild request failed: %s", |
| result.error_value().FormatDescription().c_str()); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| node_controller_.Bind(std::move(client_end)); |
| } |
| |
| // Add composite node spec if composite. |
| if (composite_) { |
| if (add_platform_device_) { |
| // Construct the platform bus node. |
| fdf::ParentSpec2 platform_node; |
| platform_node.properties() = node_properties_; |
| auto additional_node_properties = std::vector<fdf::NodeProperty2>{ |
| fdf::MakeProperty2(bind_fuchsia::PROTOCOL, bind_fuchsia_platform::BIND_PROTOCOL_DEVICE), |
| fdf::MakeProperty2(bind_fuchsia::PLATFORM_DEV_VID, |
| bind_fuchsia_platform::BIND_PLATFORM_DEV_VID_GENERIC), |
| fdf::MakeProperty2(bind_fuchsia::PLATFORM_DEV_DID, |
| bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_DEVICETREE), |
| fdf::MakeProperty2(bind_fuchsia::PLATFORM_DEV_INSTANCE_ID, id_), |
| }; |
| platform_node.properties().insert(platform_node.properties().end(), |
| additional_node_properties.begin(), |
| additional_node_properties.end()); |
| |
| platform_node.bind_rules() = std::vector<fdf::BindRule2>{ |
| fdf::MakeAcceptBindRule2(bind_fuchsia::PROTOCOL, |
| bind_fuchsia_platform::BIND_PROTOCOL_DEVICE), |
| fdf::MakeAcceptBindRule2(bind_fuchsia::PLATFORM_DEV_VID, |
| bind_fuchsia_platform::BIND_PLATFORM_DEV_VID_GENERIC), |
| fdf::MakeAcceptBindRule2(bind_fuchsia::PLATFORM_DEV_DID, |
| bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_DEVICETREE), |
| fdf::MakeAcceptBindRule2(bind_fuchsia::PLATFORM_DEV_INSTANCE_ID, id_), |
| }; |
| parents_.insert(parents_.begin(), std::move(platform_node)); |
| } else if (add_non_platform_device) { |
| // Construct the non platform bus node. |
| fdf::ParentSpec2 non_pbus_node; |
| non_pbus_node.properties() = node_properties_; |
| |
| for (auto &node_property : node_properties_) { |
| fdf::BindRule2 bind_rule = {node_property.key(), |
| fuchsia_driver_framework::Condition::kAccept, |
| {node_property.value()}}; |
| non_pbus_node.bind_rules().emplace_back(std::move(bind_rule)); |
| } |
| parents_.insert(parents_.begin(), std::move(non_pbus_node)); |
| } |
| |
| FDF_LOG(DEBUG, "Adding composite node spec to '%s' with %zu parents.", fdf_name().data(), |
| parents_.size()); |
| |
| fdf::CompositeNodeSpec group{{ |
| .name = fdf_name() + "_group", |
| .parents2 = std::move(parents_), |
| }}; |
| |
| auto devicegroup_result = mgr->AddSpec({std::move(group)}); |
| if (devicegroup_result.is_error()) { |
| FDF_LOG(ERROR, "Failed to create composite node: %s", |
| devicegroup_result.error_value().FormatDescription().data()); |
| return zx::error(devicegroup_result.error_value().is_framework_error() |
| ? devicegroup_result.error_value().framework_error().status() |
| : ZX_ERR_INVALID_ARGS); |
| } |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::result<ReferenceNode> Node::GetReferenceNode(Phandle parent) { |
| return manager_->GetReferenceNode(parent); |
| } |
| |
| ParentNode Node::parent() const { return ParentNode(parent_); } |
| |
| std::vector<ChildNode> Node::children() { |
| std::vector<ChildNode> children; |
| children.reserve(children_.size()); |
| for (Node *child : children_) { |
| children.emplace_back(child); |
| } |
| return children; |
| } |
| |
| ParentNode ReferenceNode::parent() const { return node_->parent(); } |
| |
| template <typename T> |
| typename GetPropertyReturn<T>::type Node::GetProperty(std::string_view property_name) const { |
| auto it = properties_.find(property_name); |
| if constexpr (std::is_same_v<T, bool>) { |
| return it != properties_.end(); |
| } else { |
| if (it == properties_.end()) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| const devicetree::PropertyValue &prop_value = it->second; |
| |
| if constexpr (std::is_same_v<T, std::string>) { |
| auto val = prop_value.AsString(); |
| if (val) { |
| return zx::ok(std::string(*val)); |
| } |
| } else if constexpr (std::is_same_v<T, uint32_t>) { |
| auto val = prop_value.AsUint32(); |
| if (val) { |
| return zx::ok(*val); |
| } |
| } else if constexpr (std::is_same_v<T, uint64_t>) { |
| auto val = prop_value.AsUint64(); |
| if (val) { |
| return zx::ok(*val); |
| } |
| } else if constexpr (std::is_same_v<T, std::vector<uint32_t>>) { |
| auto bytes = prop_value.AsBytes(); |
| if (bytes.size() % sizeof(uint32_t) != 0) { |
| return zx::error(ZX_ERR_WRONG_TYPE); |
| } |
| std::vector<uint32_t> result; |
| result.reserve(bytes.size() / sizeof(uint32_t)); |
| for (size_t i = 0; i < bytes.size(); i += sizeof(uint32_t)) { |
| uint32_t val; |
| memcpy(&val, bytes.data() + i, sizeof(uint32_t)); |
| result.push_back(be32toh(val)); |
| } |
| return zx::ok(result); |
| } else if constexpr (std::is_same_v<T, std::vector<std::string>>) { |
| auto string_list = prop_value.AsStringList(); |
| if (string_list) { |
| std::vector<std::string> result(string_list->begin(), string_list->end()); |
| return zx::ok(result); |
| } |
| } else { |
| static_assert(std::is_void_v<T>, "Invalid type for Node::GetProperty"); |
| } |
| |
| return zx::error(ZX_ERR_WRONG_TYPE); |
| } |
| } |
| |
| template bool Node::GetProperty<bool>(std::string_view property_name) const; |
| template zx::result<std::string> Node::GetProperty<std::string>( |
| std::string_view property_name) const; |
| template zx::result<uint32_t> Node::GetProperty<uint32_t>(std::string_view property_name) const; |
| template zx::result<uint64_t> Node::GetProperty<uint64_t>(std::string_view property_name) const; |
| template zx::result<std::vector<uint32_t>> Node::GetProperty<std::vector<uint32_t>>( |
| std::string_view property_name) const; |
| template zx::result<std::vector<std::string>> Node::GetProperty<std::vector<std::string>>( |
| std::string_view property_name) const; |
| |
| } // namespace fdf_devicetree |