| // 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" |
| |
| #include <lib/devicetree/devicetree.h> |
| #include <lib/driver/logging/cpp/structured_logger.h> |
| #include <lib/zbi-format/zbi.h> |
| #include <lib/zx/result.h> |
| |
| #include <algorithm> |
| #include <cstddef> |
| |
| namespace fdf { |
| using namespace fuchsia_driver_framework; |
| } |
| |
| 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 { |
| |
| zx::result<Manager> Manager::CreateFromNamespace(fdf::Namespace& ns) { |
| zx::result client = ns.Connect<fuchsia_hardware_platform_bus::Service::Firmware>(); |
| if (client.is_error()) { |
| FDF_LOG(ERROR, "Failed to connect to fuchsia.hardware.platform.bus.Firmware: %s", |
| client.status_string()); |
| return client.take_error(); |
| } |
| |
| fdf::Arena arena('dtdt'); |
| auto result = fdf::WireCall(*client).buffer(arena)->GetFirmware( |
| fuchsia_hardware_platform_bus::wire::FirmwareType::kDeviceTree); |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "Failed to send GetFirmware request: %s", result.FormatDescription().data()); |
| return zx::error(result.status()); |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "Failed to GetFirmware: %s", zx_status_get_string(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_LOG(ERROR, "Failed to read %lu bytes from the devicetree: %s", length, |
| zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| |
| return zx::ok(Manager(std::move(data))); |
| } |
| |
| 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_LOG(DEBUG, "Found node - %.*s", static_cast<int>(path.back().length()), path.back().data()); |
| |
| 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_LOG(DEBUG, "Node[%d] - %s added for publishing", node_id_, path.back().data()); |
| |
| 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_LOG(DEBUG, "Visit node - %.*s", static_cast<int>(path.back().length()), path.back().data()); |
| auto node = nodes_by_path_[GetPath(path)]; |
| zx::result<> status = visitor.Visit(*node, decoder); |
| if (status.is_error()) { |
| FDF_SLOG(ERROR, "Node visit failed.", KV("node_name", node->name()), |
| KV("status_str", status.status_string())); |
| visit_status = status; |
| } |
| return true; |
| }); |
| |
| if (visit_status.is_error()) { |
| FDF_SLOG(ERROR, "Devicetree walk failed.", KV("status_str", visit_status.status_string())); |
| 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_LOG(DEBUG, "Finalize node - %s", node->name().c_str()); |
| zx::result finalize_status = visitor.FinalizeNode(*node); |
| if (finalize_status.is_error()) { |
| FDF_SLOG(ERROR, "Node finalize failed.", KV("node_name", node->name()), |
| KV("status_str", finalize_status.status_string())); |
| return finalize_status; |
| } |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::result<> Manager::PublishDevices( |
| fdf::WireSyncClient<fuchsia_hardware_platform_bus::PlatformBus>& pbus_client, |
| fidl::ClientEnd<fuchsia_driver_framework::CompositeNodeManager> mgr, |
| fidl::SyncClient<fuchsia_driver_framework::Node>& fdf_node) { |
| auto mgr_client = fidl::SyncClient(std::move(mgr)); |
| |
| for (auto& node : nodes_publish_order_) { |
| zx::result<> status = node->Publish(pbus_client, mgr_client, fdf_node); |
| if (status.is_error()) { |
| FDF_LOG(ERROR, "Failed to publish node: %s: %s", node->name().c_str(), |
| status.status_string()); |
| } |
| } |
| |
| 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_LOG( |
| ERROR, |
| "The change publish order request index (%d) is out of range. The list only contains %zu 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(); |
| } |
| |
| 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; |
| } |
| |
| } // namespace fdf_devicetree |