blob: ef5748c8b001fb991c0bcf3c93450c9c808ac7e5 [file] [edit]
// Copyright 2026 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 "src/devices/bin/driver_manager/pending_node_manager.h"
#include <fidl/fuchsia.driver.framework/cpp/wire.h>
#include <lib/fit/defer.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include "src/devices/bin/driver_manager/node.h"
#include "src/devices/bin/driver_manager/node_types.h"
#include "src/devices/bin/driver_manager/resource.h"
#include "src/devices/lib/log/log.h"
namespace driver_manager {
namespace {
struct ResourceWithProperties {
std::shared_ptr<Resource> resource;
std::unordered_map<std::string, fuchsia_driver_framework::NodePropertyValue> properties;
};
bool ResourceMatchesSelector(
const Resource& resource,
const std::unordered_map<std::string, fuchsia_driver_framework::NodePropertyValue>& res_props,
const std::vector<PendingNodeManager::PreParsedOffer>& parsed_offers,
const fuchsia_driver_framework::Selector& selector) {
// Match the offers in the selector.
for (const auto& sel_proc : parsed_offers) {
bool found = false;
for (const auto& res_offer : resource.offers()) {
if (res_offer.service_name == sel_proc.service_name &&
res_offer.transport == sel_proc.transport) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
// Match the include properties in the selector.
if (selector.include_properties().has_value()) {
for (const auto& sel_prop : selector.include_properties().value()) {
auto it = res_props.find(sel_prop.key());
if (it == res_props.end() || it->second != sel_prop.value()) {
return false;
}
}
}
// Reject if the exclude properties are present in the resource.
if (selector.exclude_properties().has_value()) {
for (const auto& sel_prop : selector.exclude_properties().value()) {
auto it = res_props.find(sel_prop.key());
if (it != res_props.end() && it->second == sel_prop.value()) {
return false;
}
}
}
return true;
}
bool TryResolveDependencies(PendingNodeManager::PendingNode& pending,
const std::vector<ResourceWithProperties>& resources) {
if (!pending.node.dependencies().has_value() || pending.node.dependencies()->empty()) {
return true;
}
const auto& deps = pending.node.dependencies().value();
std::vector<std::shared_ptr<Resource>> matched(deps.size(), nullptr);
std::vector<bool> resource_used(resources.size(), false);
auto match_dep = [&](auto& self, size_t dep_idx) -> bool {
if (dep_idx == matched.size()) {
return true;
}
const auto& dep = deps[dep_idx];
if (!dep.selector().has_value()) {
return self(self, dep_idx + 1);
}
for (size_t res_idx = 0; res_idx < resources.size(); ++res_idx) {
if (resource_used[res_idx]) {
continue;
}
if (ResourceMatchesSelector(*resources[res_idx].resource, resources[res_idx].properties,
pending.parsed_offers_per_dependency[dep_idx],
dep.selector().value())) {
resource_used[res_idx] = true;
matched[dep_idx] = resources[res_idx].resource;
if (self(self, dep_idx + 1)) {
return true;
}
resource_used[res_idx] = false;
matched[dep_idx] = nullptr;
}
}
return false;
};
if (match_dep(match_dep, 0)) {
pending.resolved_resources = std::move(matched);
return true;
}
return false;
}
} // namespace
PendingNodeManager::PendingNodeManager(NodeManager* node_manager, async_dispatcher_t* dispatcher)
: node_manager_(node_manager), dispatcher_(dispatcher) {}
fit::result<fuchsia_driver_framework::NodeError> PendingNodeManager::AddNode(
fuchsia_driver_framework::Node2 node,
fidl::ServerEnd<fuchsia_driver_framework::NodeController> controller,
fidl::ServerEnd<fuchsia_driver_framework::Node> node_ref) {
ZX_ASSERT_MSG(node.dependencies().has_value() && !node.dependencies()->empty(),
"Node %s must have dependencies to be added to PendingNodeManager",
node.name().value_or("").c_str());
fidl::Arena arena;
std::string name = node.name().value_or("");
std::vector<fuchsia_driver_framework::ParentSpec2> parents;
std::vector<std::vector<PreParsedOffer>> parsed_offers_per_dependency;
parsed_offers_per_dependency.reserve(node.dependencies()->size());
for (const auto& dep : node.dependencies().value()) {
std::vector<fuchsia_driver_framework::BindRule2> bind_rules;
std::vector<fuchsia_driver_framework::NodeProperty2> properties;
std::vector<PreParsedOffer> parsed_offers;
if (dep.selector().has_value()) {
const auto& selector = dep.selector().value();
if (selector.include_properties().has_value()) {
for (const auto& prop : selector.include_properties().value()) {
bind_rules.push_back(fuchsia_driver_framework::BindRule2{{
.key = prop.key(),
.condition = fuchsia_driver_framework::Condition::kAccept,
.values = {prop.value()},
}});
properties.push_back(fuchsia_driver_framework::NodeProperty2{{
.key = prop.key(),
.value = prop.value(),
}});
}
}
if (selector.exclude_properties().has_value()) {
for (const auto& prop : selector.exclude_properties().value()) {
bind_rules.push_back(fuchsia_driver_framework::BindRule2{{
.key = prop.key(),
.condition = fuchsia_driver_framework::Condition::kReject,
.values = {prop.value()},
}});
}
}
if (selector.offers().has_value()) {
for (const auto& offer : selector.offers().value()) {
auto processed = ProcessNodeOffer(offer, Collection::kNone, "");
if (processed.is_error()) {
return fit::error(processed.error_value());
}
const auto& node_offer = processed.value();
std::string expected_value =
std::format("{}.{}", node_offer.service_name, node_offer.transport);
bind_rules.push_back(fuchsia_driver_framework::BindRule2{{
.key = node_offer.service_name,
.condition = fuchsia_driver_framework::Condition::kAccept,
.values = {fuchsia_driver_framework::NodePropertyValue::WithStringValue(
expected_value)},
}});
properties.push_back(fuchsia_driver_framework::NodeProperty2{{
.key = node_offer.service_name,
.value = fuchsia_driver_framework::NodePropertyValue::WithStringValue(expected_value),
}});
parsed_offers.push_back(PreParsedOffer{
.service_name = node_offer.service_name,
.transport = node_offer.transport,
});
}
}
}
if (dep.tags().has_value()) {
for (const auto& tag : dep.tags().value()) {
properties.push_back(fuchsia_driver_framework::NodeProperty2{{
.key = tag.key(),
.value = tag.value(),
}});
}
}
parents.push_back(fuchsia_driver_framework::ParentSpec2{{
.bind_rules = std::move(bind_rules),
.properties = std::move(properties),
}});
parsed_offers_per_dependency.push_back(std::move(parsed_offers));
}
auto pending = std::make_shared<PendingNode>();
pending->node = std::move(node);
pending->controller = std::move(controller);
pending->node_ref = std::move(node_ref);
pending->parsed_offers_per_dependency = std::move(parsed_offers_per_dependency);
pending->parents = parents;
std::weak_ptr<PendingNode> pending_weak = pending;
auto& pending_ref = *pending;
pending_nodes_.push_back(std::move(pending));
node_manager_->RequestMatchPendingNode(
fidl::ToWire(arena, pending_ref.parents), [this, pending_weak](auto& result) mutable {
auto pending_ptr = pending_weak.lock();
if (!pending_ptr) {
return;
}
if (!result.ok()) {
fdf_log::error("DriverIndex::MatchPendingNode failed: {}", result.status());
} else if (result->is_error()) {
fdf_log::error("DriverIndex::MatchPendingNode returned error: {}",
zx_status_get_string(result->error_value()));
} else {
auto response = result.value();
if (response->has_driver()) {
pending_ptr->matched_driver = fidl::ToNatural(response->driver());
}
}
node_manager_->TryResolvePendingNodes();
});
return fit::ok();
}
void PendingNodeManager::TryResolvePendingNodes(
const std::unordered_map<ResourceId, std::weak_ptr<Resource>>& multibind_resources) {
std::vector<ResourceWithProperties> resources;
resources.reserve(multibind_resources.size());
for (const auto& [id, resource_weak] : multibind_resources) {
if (auto resource = resource_weak.lock()) {
std::unordered_map<std::string, fuchsia_driver_framework::NodePropertyValue> res_props;
res_props.reserve(resource->properties().size());
for (const auto& res_prop : resource->properties()) {
res_props.emplace(res_prop.key(), res_prop.value());
}
resources.push_back(ResourceWithProperties{
.resource = std::move(resource),
.properties = std::move(res_props),
});
}
}
// Resolve dependencies for each pending node. If the node is instantiated,
// remove it from pending_nodes_.
for (auto it = pending_nodes_.begin(); it != pending_nodes_.end();) {
if (TryResolveDependencies(**it, resources)) {
auto result = InstantiateNode(*it);
if (result.is_error()) {
it = pending_nodes_.erase(it);
} else if (result.value()) {
it = pending_nodes_.erase(it);
} else {
++it;
}
} else {
++it;
}
}
}
zx::result<bool> PendingNodeManager::InstantiateNode(std::shared_ptr<PendingNode> pending) {
std::vector<fuchsia_driver_framework::NodePropertyEntry2> parent_properties;
std::vector<std::string> parents_names;
std::vector<std::weak_ptr<Resource>> dependencies;
bool has_matched_driver = pending->matched_driver.has_value();
bool owned_by_parent = pending->node_ref.is_valid();
if (!has_matched_driver && !owned_by_parent) {
return zx::ok(false);
}
std::vector<std::string> matched_parent_names;
uint32_t primary_index = 0;
std::string driver_url;
fuchsia_driver_framework::DriverPackageType package_type =
fuchsia_driver_framework::DriverPackageType::kBase;
if (has_matched_driver) {
const auto& match = pending->matched_driver.value();
if (match.parent_names().has_value()) {
matched_parent_names = match.parent_names().value();
}
primary_index = match.primary_parent_index().value_or(0);
if (match.composite_driver().has_value() &&
match.composite_driver()->driver_info().has_value()) {
const auto& driver_info = match.composite_driver()->driver_info().value();
driver_url = driver_info.url().value_or("");
package_type =
driver_info.package_type().value_or(fuchsia_driver_framework::DriverPackageType::kBase);
}
}
for (size_t i = 0; i < pending->resolved_resources.size(); ++i) {
auto resource = pending->resolved_resources[i];
std::string p_name = has_matched_driver && i < matched_parent_names.size()
? matched_parent_names[i]
: resource->name();
parents_names.push_back(p_name);
dependencies.push_back(resource);
std::vector<fuchsia_driver_framework::NodeProperty2> props = resource->properties();
const auto& dep = pending->node.dependencies().value()[i];
if (dep.tags().has_value()) {
for (const auto& tag : dep.tags().value()) {
fuchsia_driver_framework::NodeProperty2 prop{{
.key = tag.key(),
.value = tag.value(),
}};
props.push_back(std::move(prop));
}
}
parent_properties.push_back(fuchsia_driver_framework::NodePropertyEntry2{{
.name = p_name,
.properties = std::move(props),
}});
}
auto composite_res = Node::CreateCompositeNode(
pending->node.name().value(), std::move(dependencies), std::move(parents_names),
parent_properties, node_manager_, dispatcher_, "", primary_index);
if (composite_res.is_error()) {
fdf_log::error("Failed to create composite node for Node2: {}",
zx_status_get_string(composite_res.error_value()));
return composite_res.take_error();
}
auto node = composite_res.value();
if (pending->controller.is_valid()) {
node->SetController(std::move(pending->controller));
}
if (pending->node_ref.is_valid()) {
node->SetOwnedByParent(std::move(pending->node_ref));
} else {
if (has_matched_driver && !driver_url.empty()) {
auto start_result = node_manager_->StartDriver(*node, driver_url, package_type);
if (start_result.is_error()) {
fdf_log::error("Failed to start driver '{}': {}", node->name(),
zx_status_get_string(start_result.error_value()));
}
}
}
return zx::ok(true);
}
void PendingNodeManager::MatchPendingNodesWithoutDriver() {
for (auto& pending : pending_nodes_) {
if (pending->matched_driver.has_value()) {
continue;
}
std::weak_ptr<PendingNode> pending_weak = pending;
fidl::Arena arena;
node_manager_->RequestMatchPendingNode(
fidl::ToWire(arena, pending->parents), [this, pending_weak](auto& result) mutable {
auto pending_ptr = pending_weak.lock();
if (!pending_ptr) {
return;
}
if (!result.ok()) {
fdf_log::error("DriverIndex::MatchPendingNode failed: {}", result.status());
} else if (result->is_error()) {
fdf_log::info("DriverIndex::MatchPendingNode returned error: {}",
zx_status_get_string(result->error_value()));
} else {
auto response = result.value();
if (response->has_driver()) {
pending_ptr->matched_driver = fidl::ToNatural(response->driver());
}
}
node_manager_->TryResolvePendingNodes();
});
}
}
} // namespace driver_manager