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