blob: 338e1a033cbba937ff57a2a40e9e1a6f31a74f8d [file] [log] [blame] [edit]
// Copyright 2020 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/driver_runner.h"
#include <fidl/fuchsia.component.sandbox/cpp/common_types_format.h>
#include <fidl/fuchsia.driver.development/cpp/wire.h>
#include <fidl/fuchsia.driver.host/cpp/wire.h>
#include <fidl/fuchsia.driver.index/cpp/wire.h>
#include <fidl/fuchsia.power.broker/cpp/fidl.h>
#include <fidl/fuchsia.process/cpp/wire.h>
#include <lib/async/cpp/task.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/directory.h>
#include <lib/fit/defer.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/status.h>
#include <forward_list>
#include <queue>
#include <random>
#include <stack>
#include <utility>
#include "src/devices/bin/driver_manager/async_sharder.h"
#include "src/devices/bin/driver_manager/composite/composite_node_spec.h"
#include "src/devices/bin/driver_manager/node_property_conversion.h"
#include "src/devices/lib/log/log.h"
#include "src/lib/fxl/strings/join_strings.h"
namespace fdf {
using namespace fuchsia_driver_framework;
}
namespace fdh = fuchsia_driver_host;
namespace fdd = fuchsia_driver_development;
namespace fdi = fuchsia_driver_index;
namespace fio = fuchsia_io;
namespace frunner = fuchsia_component_runner;
namespace fcomponent = fuchsia_component;
namespace fdecl = fuchsia_component_decl;
using InspectStack = std::stack<std::pair<inspect::Node*, const driver_manager::Node*>>;
namespace driver_manager {
namespace {
constexpr auto kBootScheme = "fuchsia-boot://";
constexpr std::string_view kRootDeviceName = "dev";
void InspectNode(inspect::Inspector& inspector, InspectStack& stack) {
std::forward_list<inspect::Node> roots;
std::unordered_set<const Node*> unique_nodes;
while (!stack.empty()) {
// Pop the current root and node to operate on.
auto [root, node] = stack.top();
stack.pop();
auto [_, inserted] = unique_nodes.insert(node);
if (!inserted) {
// Only insert unique nodes from the DAG.
continue;
}
// Populate root with data from node.
if (const auto& offers = node->offers(); !offers.empty()) {
auto array = root->CreateStringArray("offers", offers.size());
for (size_t i = 0; i < offers.size(); i++) {
array.Set(i, offers[i].service_name);
}
root->Record(std::move(array));
}
if (auto symbols = node->symbols(); !symbols.empty()) {
auto array = root->CreateStringArray("symbols", symbols.size());
for (size_t i = 0; i < symbols.size(); i++) {
array.Set(i, symbols[i].name().value());
}
root->Record(std::move(array));
}
if (auto properties = node->GetNodeProperties(); properties && !properties->empty()) {
root->RecordChild("properties", [&](inspect::Node& properties_array) {
for (uint32_t i = 0; i < properties->size(); ++i) {
properties_array.RecordChild(std::to_string(i), [&](inspect::Node& inspect_property) {
auto& property = properties.value()[i];
inspect_property.RecordString("key", property.key());
if (const auto& str_prop = property.value().string_value(); str_prop.has_value()) {
inspect_property.RecordString("value", str_prop.value());
} else if (const auto& int_prop = property.value().int_value(); int_prop.has_value()) {
inspect_property.RecordUint("value", int_prop.value());
} else if (const auto& enum_prop = property.value().enum_value();
enum_prop.has_value()) {
inspect_property.RecordString("value", enum_prop.value());
} else if (const auto& bool_prop = property.value().bool_value();
bool_prop.has_value()) {
inspect_property.RecordBool("value", bool_prop.value());
} else {
inspect_property.RecordString("value", "UNKNOWN VALUE TYPE");
}
});
}
});
}
root->RecordString("type", node->IsComposite() ? "Composite Device" : "Device");
root->RecordString("topological_path", node->MakeTopologicalPath());
root->RecordString("driver", node->driver_url());
// Push children of this node onto the stack. We do this in reverse order to
// ensure the children are handled in order, from first to last.
auto& children = node->children();
for (auto child = children.rbegin(), end = children.rend(); child != end; ++child) {
auto& name = (*child)->name();
auto& root_for_child = roots.emplace_front(root->CreateChild(name));
stack.emplace(&root_for_child, child->get());
}
}
// Store all of the roots in the inspector.
for (auto& root : roots) {
inspector.GetRoot().Record(std::move(root));
}
}
fidl::StringView CollectionName(Collection collection) {
switch (collection) {
case Collection::kNone:
return {};
case Collection::kBoot:
return "boot-drivers";
case Collection::kPackage:
return "base-drivers";
case Collection::kFullPackage:
return "full-drivers";
}
}
Collection ToCollection(fdf::DriverPackageType package) {
switch (package) {
case fdf::DriverPackageType::kBoot:
return Collection::kBoot;
case fdf::DriverPackageType::kBase:
return Collection::kPackage;
case fdf::DriverPackageType::kCached:
case fdf::DriverPackageType::kUniverse:
return Collection::kFullPackage;
default:
return Collection::kNone;
}
}
// Choose the highest ranked collection between `collection` and `node`'s
// parents. If one of `node`'s parent's collection is none then check the
// parent's parents and so on.
Collection GetHighestRankingCollection(const Node& node, Collection collection) {
std::stack<std::weak_ptr<Node>> ancestors;
for (const auto& parent : node.parents()) {
ancestors.emplace(parent);
}
// Find the highest ranked collection out of `node`'s parent nodes. If a
// node's collection is none then check that node's parents and so on.
while (!ancestors.empty()) {
auto ancestor = ancestors.top();
ancestors.pop();
auto ancestor_ptr = ancestor.lock();
if (!ancestor_ptr) {
fdf_log::warn("Ancestor node released");
continue;
}
auto ancestor_collection = ancestor_ptr->collection();
if (ancestor_collection == Collection::kNone) {
// Check ancestor's parents to see what the collection of the ancestor
// should be.
for (const auto& parent : ancestor_ptr->parents()) {
ancestors.emplace(parent);
}
} else if (ancestor_collection > collection) {
collection = ancestor_collection;
}
}
return collection;
}
// Perform a Breadth-First-Search (BFS) over the node topology, applying the visitor function on
// the node being visited.
// The return value of the visitor function is a boolean for whether the children of the node
// should be visited. If it returns false, the children will be skipped.
void PerformBFS(const std::shared_ptr<Node>& starting_node,
fit::function<bool(const std::shared_ptr<driver_manager::Node>&)> visitor) {
std::unordered_set<std::shared_ptr<const Node>> visited;
std::queue<std::shared_ptr<Node>> node_queue;
visited.insert(starting_node);
node_queue.push(starting_node);
while (!node_queue.empty()) {
auto current = node_queue.front();
node_queue.pop();
bool visit_children = visitor(current);
if (!visit_children) {
continue;
}
for (const auto& child : current->children()) {
if (child->GetPrimaryParent() != current.get()) {
continue;
}
if (auto [_, inserted] = visited.insert(child); inserted) {
node_queue.push(child);
}
}
}
}
void CallStartDriverOnRunner(Runner& runner, Node& node, const std::string& moniker,
std::string_view url,
const std::shared_ptr<BootupTracker>& bootup_tracker) {
if (!node.HasDriverComponentController()) {
auto [controller_client, controller_request] =
fidl::Endpoints<fcomponent::Controller>::Create();
node.SetController(std::move(controller_client));
runner.CreateDriverComponent(node.shared_from_this(), std::move(controller_request), moniker,
url, CollectionName(node.collection()).get(), node.offers());
} else {
runner.StartDriverComponent(moniker);
}
}
} // namespace
Collection ToCollection(const Node& node, fdf::DriverPackageType package_type) {
Collection collection = ToCollection(package_type);
return GetHighestRankingCollection(node, collection);
}
DriverRunner::DriverRunner(
fidl::ClientEnd<fcomponent::Realm> realm,
fidl::ClientEnd<fcomponent::Introspector> introspector,
fidl::ClientEnd<fuchsia_component_sandbox::CapabilityStore> capability_store,
fidl::ClientEnd<fdi::DriverIndex> driver_index, inspect::ComponentInspector& inspect,
LoaderServiceFactory loader_service_factory, async_dispatcher_t* dispatcher,
bool enable_test_shutdown_delays, OfferInjector offer_injector,
fidl::ClientEnd<fuchsia_power_broker::Topology> topology_client,
std::optional<DynamicLinkerArgs> dynamic_linker_args)
: driver_index_(std::move(driver_index), dispatcher),
loader_service_factory_(std::move(loader_service_factory)),
dictionary_util_(std::move(capability_store), dispatcher),
dispatcher_(dispatcher),
root_node_(std::make_shared<Node>(kRootDeviceName, std::weak_ptr<Node>{}, this, dispatcher)),
composite_node_spec_manager_(this),
bind_manager_(this, this, dispatcher),
runner_(dispatcher, fidl::WireClient(std::move(realm), dispatcher),
fidl::WireClient(std::move(introspector), dispatcher), offer_injector),
removal_tracker_(dispatcher),
enable_test_shutdown_delays_(enable_test_shutdown_delays),
dynamic_linker_args_(std::move(dynamic_linker_args)),
memory_attributor_(dispatcher_) {
if (enable_test_shutdown_delays_) {
// TODO(https://fxbug.dev/42084497): Allow the seed to be set from the configuration.
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
fdf_log::info("Shutdown test delays enabled. Using seed {}", seed);
shutdown_test_delay_rng_ = std::make_shared<std::mt19937>(static_cast<uint32_t>(seed));
}
inspect.root().RecordLazyNode("driver_runner", [this] { return Inspect(); });
// Pick a non-zero starting id so that folks cannot rely on the driver host process names being
// stable.
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distrib(0, 1000);
next_driver_host_id_ = distrib(gen);
bootup_tracker_ = std::make_shared<BootupTracker>(&bind_manager_, dispatcher);
runner_.SetBootupTracker(bootup_tracker_);
if (topology_client.is_valid()) {
power_topology_ =
fidl::Client<fuchsia_power_broker::Topology>(std::move(topology_client), dispatcher);
}
// Setup the driver notifier.
auto [notifier_client, notifier_server] =
fidl::Endpoints<fuchsia_driver_index::DriverNotifier>::Create();
driver_notifier_bindings_.AddBinding(dispatcher_, std::move(notifier_server), this,
fidl::kIgnoreBindingClosure);
fidl::OneWayStatus status = driver_index_->SetNotifier(std::move(notifier_client));
if (!status.ok()) {
fdf_log::warn("Failed to set the driver notifier: {}", status.status_string());
}
}
void DriverRunner::BindNodesForCompositeNodeSpec() { TryBindAllAvailable(); }
void DriverRunner::AddSpec(AddSpecRequestView request, AddSpecCompleter::Sync& completer) {
if (!request->has_name() || (!request->has_parents() && !request->has_parents2())) {
completer.Reply(fit::error(fdf::CompositeNodeSpecError::kMissingArgs));
return;
}
if (!request->has_parents() && !request->has_parents2()) {
completer.Reply(fit::error(fdf::CompositeNodeSpecError::kDuplicateParents));
return;
}
std::vector<fuchsia_driver_framework::ParentSpec2> parents;
if (request->has_parents()) {
if (request->parents().empty()) {
completer.Reply(fit::error(fdf::CompositeNodeSpecError::kEmptyNodes));
return;
}
auto to_parent_spec2 = [](const auto& parent) {
auto parent_spec = fidl::ToNatural(parent);
std::vector<fuchsia_driver_framework::BindRule2> bind_rules;
std::transform(parent_spec.bind_rules().begin(), parent_spec.bind_rules().end(),
std::back_inserter(bind_rules), ToBindRule2);
std::vector<fuchsia_driver_framework::NodeProperty2> properties;
std::transform(parent_spec.properties().begin(), parent_spec.properties().end(),
std::back_inserter(properties),
[](const auto& prop) { return ToProperty2(prop); });
return fuchsia_driver_framework::ParentSpec2{{
.bind_rules = std::move(bind_rules),
.properties = std::move(properties),
}};
};
std::transform(request->parents().cbegin(), request->parents().cend(),
std::back_inserter(parents), to_parent_spec2);
}
if (request->has_parents2()) {
if (request->parents2().empty()) {
completer.Reply(fit::error(fdf::CompositeNodeSpecError::kEmptyNodes));
return;
}
parents = fidl::ToNatural(request->parents2()).value();
}
auto spec = std::make_unique<CompositeNodeSpec>(
CompositeNodeSpecCreateInfo{
.name = std::string(request->name().get()),
.parents = std::move(parents),
.driver_host_name_for_colocation = request->has_driver_host()
? std::string(request->driver_host().get())
: std::string(),
},
dispatcher_, this);
composite_node_spec_manager_.AddSpec(
*request, std::move(spec),
[completer = completer.ToAsync()](
fit::result<fuchsia_driver_framework::CompositeNodeSpecError> result) mutable {
completer.Reply(result);
});
}
void DriverRunner::FindDriverCrash(FindDriverCrashRequestView request,
FindDriverCrashCompleter::Sync& completer) {
for (const DriverHostComponent& host : driver_hosts_) {
zx::result process_koid = host.GetProcessKoid();
if (process_koid.is_ok() && process_koid.value() == request->process_koid) {
host.GetCrashInfo(
request->thread_koid,
[this, async_completer = completer.ToAsync()](
zx::result<fuchsia_driver_host::DriverCrashInfo> info_result) mutable {
if (info_result.is_error()) {
async_completer.ReplyError(info_result.error_value());
return;
}
fuchsia_driver_host::DriverCrashInfo& found = info_result.value();
zx_info_handle_basic_t info;
zx_status_t status = found.node_token()->get_info(ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
async_completer.ReplyError(ZX_ERR_INTERNAL);
return;
}
const Node* node = nullptr;
PerformBFS(root_node_, [&node, token_koid = info.koid](
const std::shared_ptr<driver_manager::Node>& current) {
if (node != nullptr) {
// Already found it.
return false;
}
std::optional current_koid = current->token_koid();
if (current_koid && current_koid.value() == token_koid) {
node = current.get();
return false;
}
return true;
});
if (node == nullptr) {
async_completer.ReplyError(ZX_ERR_NOT_FOUND);
return;
}
fidl::Arena arena;
async_completer.ReplySuccess(fuchsia_driver_crash::wire::DriverCrashInfo::Builder(arena)
.node_moniker(arena, node->MakeComponentMoniker())
.url(arena, found.url().value())
.Build());
});
return;
}
}
completer.ReplyError(ZX_ERR_NOT_FOUND);
}
void DriverRunner::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_driver_framework::CompositeNodeManager> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
std::string method_type;
switch (metadata.unknown_method_type) {
case fidl::UnknownMethodType::kOneWay:
method_type = "one-way";
break;
case fidl::UnknownMethodType::kTwoWay:
method_type = "two-way";
break;
};
fdf_log::warn("CompositeNodeManager received unknown {} method. Ordinal: {}", method_type,
metadata.method_ordinal);
}
void DriverRunner::Get(GetRequest& request,
fidl::Completer<fidl::internal::NaturalCompleterBase<
fuchsia_driver_token::NodeBusTopology::Get>>::Sync& completer) {
zx_info_handle_basic_t info;
zx_status_t status =
request.token().get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
completer.Reply(zx::error(status));
return;
}
const Node* node = nullptr;
PerformBFS(root_node_,
[&node, token_koid = info.koid](const std::shared_ptr<driver_manager::Node>& current) {
if (node != nullptr) {
// Already found it.
return false;
}
std::optional current_koid = current->token_koid();
if (current_koid && current_koid.value() == token_koid) {
node = current.get();
return false;
}
return true;
});
if (node == nullptr) {
completer.Reply(zx::error(ZX_ERR_NOT_FOUND));
return;
}
completer.Reply(zx::ok(node->GetBusTopology()));
}
void DriverRunner::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_driver_token::NodeBusTopology> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
std::string method_type;
switch (metadata.unknown_method_type) {
case fidl::UnknownMethodType::kOneWay:
method_type = "one-way";
break;
case fidl::UnknownMethodType::kTwoWay:
method_type = "two-way";
break;
};
fdf_log::warn("NodeBusTopology received unknown {} method. Ordinal: {}", method_type,
metadata.method_ordinal);
}
void DriverRunner::AddSpecToDriverIndex(fuchsia_driver_framework::wire::CompositeNodeSpec group,
AddToIndexCallback callback) {
driver_index_->AddCompositeNodeSpec(group).Then(
[callback = std::move(callback)](
fidl::WireUnownedResult<fdi::DriverIndex::AddCompositeNodeSpec>& result) mutable {
if (!result.ok()) {
fdf_log::error("DriverIndex::AddCompositeNodeSpec failed {}", result.status());
callback(zx::error(result.status()));
return;
}
if (result->is_error()) {
callback(result->take_error());
return;
}
callback(zx::ok());
});
}
// TODO(https://fxbug.dev/42072971): Add information for composite node specs.
fpromise::promise<inspect::Inspector> DriverRunner::Inspect() const {
// Create our inspector.
// The default maximum size was too small, and so this is double the default size.
// If a device loads too much inspect data, this can be increased in the future.
inspect::Inspector inspector(inspect::InspectSettings{.maximum_size = 2 * 256 * 1024});
// Make the device tree inspect nodes.
auto device_tree = inspector.GetRoot().CreateChild("node_topology");
auto root = device_tree.CreateChild(root_node_->name());
InspectStack stack{{std::make_pair(&root, root_node_.get())}};
InspectNode(inspector, stack);
device_tree.Record(std::move(root));
inspector.GetRoot().Record(std::move(device_tree));
bind_manager_.RecordInspect(inspector);
return fpromise::make_ok_promise(inspector);
}
std::vector<fdd::wire::CompositeNodeInfo> DriverRunner::GetCompositeListInfo(
fidl::AnyArena& arena) const {
auto spec_composite_list = composite_node_spec_manager_.GetCompositeInfo(arena);
auto list = bind_manager_.GetCompositeListInfo(arena);
list.reserve(list.size() + spec_composite_list.size());
list.insert(list.end(), std::make_move_iterator(spec_composite_list.begin()),
std::make_move_iterator(spec_composite_list.end()));
return list;
}
void DriverRunner::WaitForBootup(fit::callback<void()> callback) {
bootup_tracker_->WaitForBootup(std::move(callback));
}
void DriverRunner::PublishComponentRunner(component::OutgoingDirectory& outgoing) {
zx::result result = runner_.Publish(outgoing);
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
result = memory_attributor_.Publish(outgoing);
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
result = outgoing.AddUnmanagedProtocol<fdf::CompositeNodeManager>(
manager_bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure));
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
result = outgoing.AddUnmanagedProtocol<fuchsia_driver_token::NodeBusTopology>(
bus_topo_bindings_.CreateHandler(this, dispatcher_, [](fidl::UnbindInfo info) {
if (info.is_user_initiated() || info.is_peer_closed()) {
return;
}
fdf_log::warn("Unexpected closure of NodeBusTopology: {}", info.FormatDescription());
}));
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
result = outgoing.AddUnmanagedProtocol<fuchsia_driver_crash::CrashIntrospect>(
crash_introspect_bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure));
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
}
zx::result<> DriverRunner::StartRootDriver(std::string_view url) {
fdf::DriverPackageType package = cpp20::starts_with(url, kBootScheme)
? fdf::DriverPackageType::kBoot
: fdf::DriverPackageType::kBase;
bootup_tracker_->Start();
WaitForBootup([this]() { this->OnBootupComplete(); });
return StartDriver(*root_node_, url, package);
}
void DriverRunner::StartDevfsDriver(std::shared_ptr<driver_manager::Devfs>& devfs) {
auto [controller_client, controller_request] = fidl::Endpoints<fcomponent::Controller>::Create();
devfs->SetController(std::move(controller_client));
std::vector<NodeOffer> offers;
runner_.CreateDriverComponent(devfs, std::move(controller_request), "devfs_driver",
"fuchsia-boot:///devfs-driver#meta/devfs-driver.cm",
CollectionName(Collection::kBoot).get(), offers);
}
void DriverRunner::NewDriverAvailable(NewDriverAvailableCompleter::Sync& completer) {
TryBindAllAvailable();
}
void DriverRunner::TryBindAllAvailable(NodeBindingInfoResultCallback result_callback) {
bind_manager_.TryBindAllAvailable(std::move(result_callback));
}
zx::result<> DriverRunner::StartDriver(Node& node, std::string_view url,
fdf::DriverPackageType package_type) {
// Ensure `node`'s collection is equal to or higher ranked than its ancestor
// nodes' collections. This is to avoid node components having a dependency
// cycle with each other. For example, node components in the boot driver
// collection depend on the devfs component which ultimately depends on all
// components within the package driver collection. If a package driver
// component depended on a component in the boot driver collection (a lower
// ranked collection than the package driver collection) then a cyclic
// dependency would occur.
node.set_collection(ToCollection(node, package_type));
node.set_driver_package_type(package_type);
std::weak_ptr node_weak = node.shared_from_this();
std::string url_string(url.data(), url.size());
auto moniker = node.MakeComponentMoniker();
bootup_tracker_->NotifyNewStartRequest(moniker, url_string);
node.PrepareDictionary([this, node_weak, moniker, url_string](zx::result<> result) {
if (!result.is_ok()) {
return;
}
std::shared_ptr node = node_weak.lock();
if (!node) {
return;
}
CallStartDriverOnRunner(runner_, *node, moniker, url_string, bootup_tracker_);
});
return zx::ok();
}
void DriverRunner::Bind(Node& node, std::shared_ptr<BindResultTracker> result_tracker) {
BindToUrl(node, {}, std::move(result_tracker));
}
void DriverRunner::BindToUrl(Node& node, std::string_view driver_url_suffix,
std::shared_ptr<BindResultTracker> result_tracker) {
bind_manager_.Bind(node, driver_url_suffix, std::move(result_tracker));
}
void DriverRunner::AddLeaseControlChannel(
fidl::ClientEnd<fuchsia_power_broker::LeaseControl> lease) {
// We only hold leases for drivers added before we consider boot-up complete.
// After boot-up is complete, just drop the lease immediately.
if (bootup_tracker_->BootupComplete()) {
return;
}
leases_.push_back(std::move(lease));
}
void DriverRunner::RebindComposite(std::string spec, std::optional<std::string> driver_url,
fit::callback<void(zx::result<>)> callback) {
composite_node_spec_manager_.Rebind(spec, driver_url, std::move(callback));
}
void DriverRunner::RebindCompositesWithDriver(const std::string& url,
fit::callback<void(size_t)> complete_callback) {
std::unordered_set<std::string> names;
PerformBFS(root_node_, [&names, url](const std::shared_ptr<driver_manager::Node>& current) {
if (current->type() == driver_manager::NodeType::kComposite && current->driver_url() == url) {
fdf_log::debug("RebindCompositesWithDriver rebinding composite {}",
current->MakeComponentMoniker());
names.insert(current->name());
return false;
}
return true;
});
if (names.empty()) {
complete_callback(0);
return;
}
auto complete_wrapper = [complete_callback = std::move(complete_callback), count = names.size()](
zx::result<>) mutable { complete_callback(count); };
std::shared_ptr<AsyncSharder> sharder =
std::make_shared<AsyncSharder>(names.size(), std::move(complete_wrapper));
for (const auto& name : names) {
RebindComposite(name, std::nullopt,
[sharder](zx::result<>) mutable { sharder->CompleteShard(); });
}
}
DriverHost* DriverRunner::GetDriverHost(std::string_view driver_host_name_for_colocation) {
if (driver_host_name_for_colocation.empty()) {
return nullptr;
}
for (auto& driver_host : driver_hosts_) {
if (driver_host.name_for_colocation() == driver_host_name_for_colocation) {
return &driver_host;
}
}
return nullptr;
}
zx::result<DriverHost*> DriverRunner::CreateDriverHost(
bool use_next_vdso, std::string_view driver_host_name_for_colocation) {
auto endpoints = fidl::Endpoints<fio::Directory>::Create();
std::string name = "driver-host-" + std::to_string(next_driver_host_id_++);
std::shared_ptr<bool> connected = std::make_shared<bool>(false);
auto create =
CreateDriverHostComponent(name, std::move(endpoints.server), connected, use_next_vdso);
if (create.is_error()) {
return create.take_error();
}
auto client_end = component::ConnectAt<fdh::DriverHost>(endpoints.client);
if (client_end.is_error()) {
fdf_log::error("Failed to connect to service '{}': {}",
fidl::DiscoverableProtocolName<fdh::DriverHost>, client_end.status_string());
return client_end.take_error();
}
auto loader_service_client = loader_service_factory_();
if (loader_service_client.is_error()) {
fdf_log::error("Failed to connect to service fuchsia.ldsvc/Loader: {}",
loader_service_client.status_string());
return loader_service_client.take_error();
}
auto driver_host =
std::make_unique<DriverHostComponent>(std::move(*client_end), dispatcher_, &driver_hosts_,
connected, driver_host_name_for_colocation);
auto result = driver_host->InstallLoader(std::move(*loader_service_client));
if (result.is_error()) {
fdf_log::error("Failed to install loader service: {}", result);
return result.take_error();
}
auto driver_host_ptr = driver_host.get();
driver_hosts_.push_back(std::move(driver_host));
return zx::ok(driver_host_ptr);
}
void DriverRunner::CreateDriverHostDynamicLinker(
std::string_view driver_host_name_for_colocation,
fit::callback<void(zx::result<DriverHost*>)> completion_cb) {
if (!dynamic_linker_args_.has_value()) {
fdf_log::error("Dynamic linker was not available");
completion_cb(zx::error(ZX_ERR_NOT_SUPPORTED));
return;
}
auto endpoints = fidl::Endpoints<fio::Directory>::Create();
auto client_end = component::ConnectAt<fdh::DriverHost>(endpoints.client);
if (client_end.is_error()) {
fdf_log::error("Failed to connect to service '{}': {}",
fidl::DiscoverableProtocolName<fdh::DriverHost>, client_end.status_string());
completion_cb(client_end.take_error());
return;
}
// TODO(https://fxbug.dev/349831408): for now we use the same driver host launcher client
// channel for each driver host.
if (!driver_host_launcher_.has_value()) {
auto client = dynamic_linker_args_->linker_service_factory();
if (client.is_error()) {
fdf_log::error("Failed to create driver host launcher client");
completion_cb(client.take_error());
return;
}
driver_host_launcher_ = fidl::WireSharedClient<fuchsia_driver_loader::DriverHostLauncher>(
std::move(*client), dispatcher_);
}
std::shared_ptr<bool> connected = std::make_shared<bool>(false);
dynamic_linker_args_->driver_host_runner->StartDriverHost(
driver_host_launcher_->Clone(), std::move(endpoints.server), connected,
[this, completion_cb = std::move(completion_cb), client_end = std::move(client_end),
connected = std::move(connected), name = std::string(driver_host_name_for_colocation)](
zx::result<fidl::ClientEnd<fuchsia_driver_loader::DriverHost>> result) mutable {
if (result.is_error()) {
completion_cb(result.take_error());
return;
}
auto driver_host = std::make_unique<DriverHostComponent>(
std::move(*client_end), dispatcher_, &driver_hosts_, connected, name,
std::move(*result));
auto driver_host_ptr = driver_host.get();
driver_hosts_.push_back(std::move(driver_host));
completion_cb(zx::ok(driver_host_ptr));
});
}
bool DriverRunner::IsDriverHostValid(DriverHost* driver_host) const {
return driver_hosts_.find_if([driver_host](const DriverHostComponent& host) {
return &host == driver_host;
}) != driver_hosts_.end();
}
zx::result<std::string> DriverRunner::StartDriver(
Node& node, fuchsia_driver_framework::wire::DriverInfo driver_info) {
if (!driver_info.has_url()) {
fdf_log::error("Failed to start driver for node '{}', the driver URL is missing", node.name());
return zx::error(ZX_ERR_INTERNAL);
}
auto pkg_type =
driver_info.has_package_type() ? driver_info.package_type() : fdf::DriverPackageType::kBase;
auto result = StartDriver(node, driver_info.url().get(), pkg_type);
if (result.is_error()) {
return result.take_error();
}
return zx::ok(std::string(driver_info.url().get()));
}
zx::result<BindSpecResult> DriverRunner::BindToParentSpec(fidl::AnyArena& arena,
CompositeParents composite_parents,
std::weak_ptr<Node> node,
bool enable_multibind) {
return this->composite_node_spec_manager_.BindParentSpec(arena, composite_parents, node,
enable_multibind);
}
void DriverRunner::CreatePowerElement(std::string_view name,
fuchsia_power_broker::DependencyToken element_token,
std::span<fuchsia_power_broker::DependencyToken>& deps,
fidl::ServerEnd<fuchsia_power_broker::ElementControl> control,
fidl::ClientEnd<fuchsia_power_broker::ElementRunner> runner,
fidl::ServerEnd<fuchsia_power_broker::Lessor> lessor,
fit::callback<void(zx::result<bool>)> cb) {
if (!power_topology_.is_valid()) {
cb(zx::ok(false));
return;
}
fidl::Arena arena;
std::vector<fuchsia_power_broker::LevelDependency> level_deps;
for (fuchsia_power_broker::DependencyToken& dep : deps) {
fuchsia_power_broker::DependencyToken clone;
zx_status_t dupe_result = dep.duplicate(ZX_RIGHT_SAME_RIGHTS, &clone);
if (dupe_result != ZX_OK) {
cb(zx::error(dupe_result));
return;
}
std::vector<fuchsia_power_broker::PowerLevel> reqs_by_pref;
reqs_by_pref.push_back(static_cast<fuchsia_power_broker::PowerLevel>(1));
fuchsia_power_broker::LevelDependency level_dep;
level_dep.dependency_type() = fuchsia_power_broker::DependencyType::kAssertive,
level_dep.dependent_level() = static_cast<fuchsia_power_broker::PowerLevel>(1),
level_dep.requires_token() = std::move(clone),
level_dep.requires_level_by_preference() = reqs_by_pref;
level_deps.push_back(std::move(level_dep));
}
fuchsia_power_broker::ElementSchema schema;
schema.element_name() = name;
schema.initial_current_level() = static_cast<fuchsia_power_broker::PowerLevel>(1);
schema.valid_levels() = std::vector<fuchsia_power_broker::PowerLevel>{
static_cast<fuchsia_power_broker::PowerLevel>(0),
static_cast<fuchsia_power_broker::PowerLevel>(1),
};
schema.dependencies() = std::move(level_deps);
schema.lessor_channel() = std::move(lessor);
schema.element_control() = std::move(control);
schema.element_runner() = std::move(runner);
power_topology_->AddElement(std::move(schema))
.Then([cb = std::move(cb)](
fidl::Result<fuchsia_power_broker::Topology::AddElement>& add_result) mutable {
if (add_result.is_error() && add_result.error_value().is_framework_error()) {
cb(zx::error(add_result.error_value().framework_error().status()));
return;
}
if (add_result.is_error()) {
// This is a protocol error.
switch (add_result.error_value().domain_error()) {
case fuchsia_power_broker::AddElementError::kInvalid:
cb(zx::error(ZX_ERR_INVALID_ARGS));
return;
case fuchsia_power_broker::AddElementError::kNotAuthorized:
cb(zx::error(ZX_ERR_ACCESS_DENIED));
return;
default:
cb(zx::error(ZX_ERR_INTERNAL));
return;
}
}
cb(zx::ok(true));
});
}
void DriverRunner::OnBootupComplete() { leases_.clear(); }
void DriverRunner::RequestMatchFromDriverIndex(
fuchsia_driver_index::wire::MatchDriverArgs args,
fit::callback<void(fidl::WireUnownedResult<fdi::DriverIndex::MatchDriver>&)> match_callback) {
driver_index()->MatchDriver(args).Then(std::move(match_callback));
}
void DriverRunner::RequestRebindFromDriverIndex(std::string spec,
std::optional<std::string> driver_url_suffix,
fit::callback<void(zx::result<>)> callback) {
fidl::Arena allocator;
fidl::StringView fidl_driver_url = driver_url_suffix == std::nullopt
? fidl::StringView()
: fidl::StringView(allocator, driver_url_suffix.value());
driver_index_->RebindCompositeNodeSpec(fidl::StringView(allocator, spec), fidl_driver_url)
.Then(
[callback = std::move(callback)](
fidl::WireUnownedResult<fdi::DriverIndex::RebindCompositeNodeSpec>& result) mutable {
if (!result.ok()) {
fdf_log::error(
"Failed to send a composite rebind request to the Driver Index failed {}",
result.error().FormatDescription());
callback(zx::error(result.status()));
return;
}
if (result->is_error()) {
callback(result->take_error());
return;
}
callback(zx::ok());
});
}
zx::result<> DriverRunner::CreateDriverHostComponent(
std::string moniker, fidl::ServerEnd<fuchsia_io::Directory> exposed_dir,
std::shared_ptr<bool> exposed_dir_connected, bool use_next_vdso) {
constexpr std::string_view kUrl = "fuchsia-boot:///driver_host#meta/driver_host.cm";
constexpr std::string_view kNextUrl = "fuchsia-boot:///driver_host#meta/driver_host_next.cm";
fidl::Arena arena;
auto child_decl_builder = fdecl::wire::Child::Builder(arena)
.name(moniker)
.url(use_next_vdso ? kNextUrl : kUrl)
.startup(fdecl::wire::StartupMode::kLazy);
auto child_args_builder = fcomponent::wire::CreateChildArgs::Builder(arena);
auto open_callback =
[moniker](fidl::WireUnownedResult<fcomponent::Realm::OpenExposedDir>& result) {
if (!result.ok()) {
fdf_log::error("Failed to open exposed directory for driver host: '{}': {}", moniker,
result.FormatDescription());
return;
}
if (result->is_error()) {
fdf_log::error("Failed to open exposed directory for driver host: '{}': {}", moniker,
static_cast<uint32_t>(result->error_value()));
}
};
auto create_callback =
[this, moniker, exposed_dir = std::move(exposed_dir),
exposed_dir_connected = std::move(exposed_dir_connected),
open_callback = std::move(open_callback)](
fidl::WireUnownedResult<fcomponent::Realm::CreateChild>& result) mutable {
if (!result.ok()) {
fdf_log::error("Failed to create driver host '{}': {}", moniker,
result.error().FormatDescription());
return;
}
if (result->is_error()) {
fdf_log::error("Failed to create driver host '{}': {}", moniker,
static_cast<uint32_t>(result->error_value()));
return;
}
fdecl::wire::ChildRef child_ref{
.name = fidl::StringView::FromExternal(moniker),
.collection = "driver-hosts",
};
runner_.realm()
->OpenExposedDir(child_ref, std::move(exposed_dir))
.ThenExactlyOnce(std::move(open_callback));
*exposed_dir_connected = true;
};
runner_.realm()
->CreateChild(
fdecl::wire::CollectionRef{
.name = "driver-hosts",
},
child_decl_builder.Build(), child_args_builder.Build())
.Then(std::move(create_callback));
return zx::ok();
}
zx::result<uint32_t> DriverRunner::RestartNodesColocatedWithDriverUrl(
std::string_view url, fdd::RestartRematchFlags rematch_flags) {
auto driver_hosts = DriverHostsWithDriverUrl(url);
// Perform a BFS over the node topology, if the current node's host is one of the driver_hosts
// we collected, then restart that node and skip its children since they will go away
// as part of it's restart.
//
// The BFS ensures that we always find the topmost node of a driver host.
// This node will by definition have colocated set to false, so when we call StartDriver
// on this node we will always create a new driver host. The old driver host will go away
// on its own asynchronously since it is drained from all of its drivers.
PerformBFS(root_node_, [this, &driver_hosts, rematch_flags,
url](const std::shared_ptr<driver_manager::Node>& current) {
if (driver_hosts.find(current->driver_host()) == driver_hosts.end()) {
// Not colocated with one of the restarting hosts. Continue to visit the children.
return true;
}
if (current->EvaluateRematchFlags(rematch_flags, url)) {
if (current->type() == driver_manager::NodeType::kComposite) {
// Composites need to go through a different flow that will fully remove the
// node and empty out the composite spec management layer.
fdf_log::debug("RestartNodesColocatedWithDriverUrl rebinding composite {}",
current->MakeComponentMoniker());
RebindComposite(current->name(), std::nullopt, [](zx::result<>) {});
return false;
}
// Non-composite nodes use the restart with rematch flow.
fdf_log::debug("RestartNodesColocatedWithDriverUrl restarting node with rematch {}",
current->MakeComponentMoniker());
current->RestartNodeWithRematch();
return false;
}
// Not rematching, plain node restart.
fdf_log::debug("RestartNodesColocatedWithDriverUrl restarting node {}",
current->MakeComponentMoniker());
current->RestartNode();
return false;
});
return zx::ok(static_cast<uint32_t>(driver_hosts.size()));
}
void DriverRunner::RestartWithDictionary(fidl::StringView moniker,
fuchsia_component_sandbox::wire::DictionaryRef dictionary,
zx::eventpair reset_eventpair) {
dictionary_util_.ImportDictionaryWire(std::move(dictionary), [this,
moniker =
std::string(moniker.get()),
reset_eventpair =
std::move(reset_eventpair)](
zx::result<
fuchsia_component_sandbox::
NewCapabilityId>
result) mutable {
if (result.is_error()) {
return;
}
std::shared_ptr<driver_manager::Node> restarted_node = nullptr;
PerformBFS(root_node_, [&](const std::shared_ptr<driver_manager::Node>& current) {
if (current->MakeComponentMoniker() == moniker && current->HasDriverComponent()) {
if (current->HasSubtreeDictionaryRef()) {
fdf_log::error(
"RestartWithDictionary requested node id already contains a dictionary_ref from another RestartWithDictionary operation.");
return false;
}
ZX_ASSERT_MSG(restarted_node == nullptr, "Multiple nodes with same moniker not possible.");
restarted_node = current;
current->SetSubtreeDictionaryRef(result.value());
current->RestartNode();
return false;
}
return true;
});
if (restarted_node != nullptr) {
std::unique_ptr<async::WaitOnce> wait = std::make_unique<async::WaitOnce>(
reset_eventpair.release(), ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED);
async::WaitOnce* wait_ptr = wait.get();
zx_status_t status = wait_ptr->Begin(
dispatcher_, [restarted_node = std::move(restarted_node), moved_wait = std::move(wait)](
async_dispatcher_t* dispatcher, async::WaitOnce* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
fdf_log::info("RestartWithDictionary operation released.");
restarted_node->SetSubtreeDictionaryRef(std::nullopt);
restarted_node->RestartNode();
});
if (status != ZX_OK) {
fdf_log::error("Failed to Begin async::Wait for RestartWithDictionary.");
}
}
});
}
std::unordered_set<const DriverHost*> DriverRunner::DriverHostsWithDriverUrl(std::string_view url) {
std::unordered_set<const DriverHost*> result_hosts;
// Perform a BFS over the node topology, if the current node's driver url is the url we are
// interested in, add the driver host it is in to the result set.
PerformBFS(root_node_,
[&result_hosts, url](const std::shared_ptr<driver_manager::Node>& current) {
if (current->driver_url() == url) {
result_hosts.insert(current->driver_host());
}
return true;
});
return result_hosts;
}
} // namespace driver_manager