blob: 28621bb02bc95ccb0b930e1aee4143f547b291ab [file]
// 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/manager.h"
#ifdef __Fuchsia__
#include <fidl/fuchsia.hardware.platform.bus/cpp/driver/fidl.h>
#endif
#include <lib/devicetree/devicetree.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zx/result.h>
#include <zircon/status.h>
#include <algorithm>
#include <cstddef>
namespace fdf {
using namespace fuchsia_driver_framework;
}
namespace fhpb = fuchsia_hardware_platform_bus;
namespace {
std::string GetPath(const devicetree::NodePath& node_path) {
std::string path;
for (std::string_view p : node_path) {
// Skip adding '/' for the root node.
if (path.length() != 1) {
path.append("/");
}
path.append(p);
}
return path;
}
std::string GetParentPath(const devicetree::NodePath& node_path) {
if (node_path.size() <= 1) {
// root node.
return "";
}
std::string path;
auto it = node_path.begin();
for (size_t i = 0; i < (node_path.size() - 1); i++, it++) {
// Skip adding '/' for the root node.
if (path.length() != 1) {
path.append("/");
}
path.append(it->data());
}
return path;
}
} // namespace
namespace fdf_devicetree {
#ifdef __Fuchsia__
zx::result<Manager> Manager::CreateFromNamespace(fdf::Namespace& ns) {
zx::result client = ns.Connect<fhpb::Service::Firmware>();
if (client.is_error()) {
fdf::error("Failed to connect to fuchsia.hardware.platform.bus.Firmware: {}", client);
return client.take_error();
}
fdf::Arena arena('dtdt');
auto result =
fdf::WireCall(*client).buffer(arena)->GetFirmware(fhpb::wire::FirmwareType::kDeviceTree);
if (!result.ok()) {
fdf::error("Failed to send GetFirmware request: {}", result.FormatDescription());
return zx::error(result.status());
}
if (result->is_error()) {
fdf::error("Failed to GetFirmware: {}", result->error_value());
return zx::error(result->error_value());
}
auto& [vmo, length] = result->value()->blobs[0];
std::vector<uint8_t> data;
data.resize(length);
zx_status_t status = vmo.read(data.data(), 0, length);
if (status != ZX_OK) {
fdf::error("Failed to read {} bytes from the devicetree: {}", length, status);
return zx::error(status);
}
return zx::ok(Manager(std::move(data)));
}
#endif
zx::result<> Manager::Walk(Visitor& visitor) {
// Walk the tree and create all nodes before calling the visitor. This is required for
// |GetReferenceNode| method to work properly.
tree_.Walk(
[&, this](const devicetree::NodePath& path, const devicetree::PropertyDecoder& decoder) {
fdf::debug("Found node - {}", static_cast<std::string_view>(path.back()));
Node* parent = nullptr;
if (path != "/") {
parent = nodes_by_path_[GetParentPath(path)];
}
// Create a node.
const devicetree::Properties& properties = decoder.properties();
auto node = std::make_unique<Node>(parent, path.back(), properties, node_id_++, this);
Node* ptr = node.get();
nodes_publish_order_.emplace_back(std::move(node));
fdf::debug("Node[{}] - {} added for publishing", node_id_,
static_cast<std::string_view>(path.back()));
if (ptr->phandle()) {
nodes_by_phandle_.emplace(*(ptr->phandle()), ptr);
}
nodes_by_path_.emplace(GetPath(path), ptr);
return true;
});
zx::result<> visit_status = zx::ok();
tree_.Walk(
[&, this](const devicetree::NodePath& path, const devicetree::PropertyDecoder& decoder) {
fdf::debug("Visit node - {}", static_cast<std::string_view>(path.back()));
auto node = nodes_by_path_[GetPath(path)];
zx::result<> status = visitor.Visit(*node, decoder);
if (status.is_error()) {
fdf::error("Node visit failed. node_name: {}, status_str: {}", node->name(),
status.status_value());
visit_status = status;
}
return true;
});
if (visit_status.is_error()) {
fdf::error("Devicetree walk failed. status_str: {}", visit_status.status_value());
return visit_status;
}
// Call |FinalizeNode| method of the visitor on all nodes to complete the parsing. At this point
// all references to the node is known and so the visitor can use that information to update any
// Node properties if needed.
for (auto& node : nodes_publish_order_) {
fdf::debug("Finalize node - {}", node->name());
zx::result finalize_status = visitor.FinalizeNode(*node);
if (finalize_status.is_error()) {
fdf::error("Node finalize failed. node_name: {}, status_str: {}", node->name(),
finalize_status.status_value());
return finalize_status;
}
}
return zx::ok();
}
zx::result<> Manager::PublishDevices(PublisherInterface& publisher) {
for (const auto& [iommu_id, iommu] : iommus_) {
zx::result<> result = publisher.RegisterIommu(iommu_id, iommu);
if (result.is_error()) {
fdf::error("Failed to register IOMMU for node ID {}: {}", iommu_id, result.status_value());
}
}
for (auto& node : nodes_publish_order_) {
zx::result<> status = node->Publish(publisher);
if (status.is_error()) {
fdf::error("Failed to publish device for node ID {}: {}", node->id(), status.status_value());
}
}
return zx::ok();
}
zx::result<ReferenceNode> Manager::GetReferenceNode(Phandle id) {
auto node = nodes_by_phandle_.find(id);
if (node == nodes_by_phandle_.end()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok(ReferenceNode(node->second));
}
uint32_t Manager::GetPublishIndex(uint32_t node_id) {
for (uint32_t index = 0; index < nodes_publish_order_.size(); index++) {
if (nodes_publish_order_[index]->id() == node_id) {
return index;
}
}
ZX_ASSERT_MSG(false, "Should not reach here. Node id should always be valid.");
return 0;
}
zx::result<> Manager::ChangePublishOrder(uint32_t node_id, uint32_t new_index) {
if (new_index >= nodes_publish_order_.size()) {
fdf::error(
"The change publish order request index ({}) is out of range. The list only contains {} items.",
new_index, nodes_publish_order_.size());
return zx::error(ZX_ERR_INVALID_ARGS);
}
std::swap(nodes_publish_order_[new_index], nodes_publish_order_[GetPublishIndex(node_id)]);
return zx::ok();
}
zx::result<> Manager::RegisterIommu(uint32_t iommu_id, fhpb::Iommu iommu) {
auto [_, inserted] = iommus_.insert({iommu_id, iommu});
if (!inserted) {
return zx::error(ZX_ERR_ALREADY_EXISTS);
}
return zx::ok();
}
bool Manager::IsNodeForceEnabled(std::string_view path) {
return std::find(force_enabled_nodes_.begin(), force_enabled_nodes_.end(), path) !=
force_enabled_nodes_.end();
}
std::optional<Node*> Manager::FindNode(std::string_view name) {
for (auto& node : nodes_publish_order_) {
if (node->name() == name) {
return node.get();
}
}
return std::nullopt;
}
zx::result<> Manager::AddMetadata(std::string_view path,
const fuchsia_hardware_platform_bus::Metadata& metadata) {
auto node = nodes_by_path_.find(std::string(path));
if (node == nodes_by_path_.end()) {
fdf::error("Manager::AddMetadata: Node path '{}' not found", path);
return zx::error(ZX_ERR_NOT_FOUND);
}
node->second->AddMetadata(metadata);
return zx::ok();
}
} // namespace fdf_devicetree