| // Copyright 2022 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/node.h" |
| |
| #include <lib/driver/component/cpp/internal/start_args.h> |
| #include <lib/driver/component/cpp/node_add_args.h> |
| |
| #include <deque> |
| #include <optional> |
| #include <unordered_set> |
| #include <utility> |
| |
| #include <bind/fuchsia/platform/cpp/bind.h> |
| |
| #include "src/devices/bin/driver_manager/controller_allowlist_passthrough.h" |
| #include "src/devices/bin/driver_manager/shutdown/node_removal_tracker.h" |
| #include "src/devices/lib/log/log.h" |
| #include "src/lib/fxl/strings/join_strings.h" |
| |
| namespace fdf { |
| using namespace fuchsia_driver_framework; |
| } // namespace fdf |
| namespace fdecl = fuchsia_component_decl; |
| namespace fcomponent = fuchsia_component; |
| |
| namespace driver_manager { |
| |
| namespace { |
| |
| const std::string kUnboundUrl = "unbound"; |
| |
| // TODO(https://fxbug.dev/42075799): Remove this flag once composite node spec rebind once all |
| // clients are updated to the new Rebind() behavior and this is fully implemented on both DFv1 and |
| // DFv2. |
| constexpr bool kEnableCompositeNodeSpecRebind = false; |
| |
| // Return a clone of `node_properties`. The data referenced by the clone is owned by `arena`. |
| std::vector<fuchsia_driver_framework::wire::NodeProperty> CloneNodeProperties( |
| fidl::AnyArena& arena, |
| const std::vector<::fuchsia_driver_framework::NodeProperty>& node_properties) { |
| std::vector<fuchsia_driver_framework::wire::NodeProperty> clone; |
| clone.reserve(node_properties.size()); |
| for (const auto& node_property : node_properties) { |
| clone.emplace_back(fidl::ToWire(arena, node_property)); |
| } |
| return clone; |
| } |
| |
| // Return a clone of the node properties of `parents`. The data referenced by the clone is owned by |
| // `arena`. |
| std::vector<fuchsia_driver_framework::wire::NodePropertyEntry> GetParentNodePropertyEntries( |
| fidl::AnyArena& arena, |
| const std::vector<fuchsia_driver_framework::NodePropertyEntry>& parent_properties) { |
| std::vector<fuchsia_driver_framework::wire::NodePropertyEntry> entries; |
| for (const auto& parent : parent_properties) { |
| std::vector<fuchsia_driver_framework::wire::NodeProperty> properties_clone = |
| CloneNodeProperties(arena, parent.properties()); |
| |
| entries.emplace_back(fuchsia_driver_framework::wire::NodePropertyEntry{ |
| .name = fidl::StringView(arena, parent.name()), |
| .properties = fuchsia_driver_framework::wire::NodePropertyVector(arena, properties_clone)}); |
| } |
| return entries; |
| } |
| |
| template <typename R, typename F> |
| std::optional<R> VisitOffer(fdecl::Offer& offer, F apply) { |
| // Note, we access each field of the union as mutable, so that `apply` can |
| // modify the field if necessary. |
| switch (offer.Which()) { |
| case fdecl::Offer::Tag::kService: |
| return apply(offer.service()); |
| case fdecl::Offer::Tag::kProtocol: |
| return apply(offer.protocol()); |
| case fdecl::Offer::Tag::kDirectory: |
| return apply(offer.directory()); |
| case fdecl::Offer::Tag::kStorage: |
| return apply(offer.storage()); |
| case fdecl::Offer::Tag::kRunner: |
| return apply(offer.runner()); |
| case fdecl::Offer::Tag::kResolver: |
| return apply(offer.resolver()); |
| case fdecl::Offer::Tag::kEventStream: |
| return apply(offer.event_stream()); |
| default: |
| return {}; |
| } |
| } |
| |
| const char* CollectionName(Collection collection) { |
| switch (collection) { |
| case Collection::kNone: |
| return ""; |
| case Collection::kBoot: |
| return "boot-drivers"; |
| case Collection::kPackage: |
| return "pkg-drivers"; |
| case Collection::kFullPackage: |
| return "full-pkg-drivers"; |
| } |
| } |
| |
| // Processes the offer by validating it has a source_name and adding a source ref to it. |
| // Returns the offer back out. |
| fit::result<fdf::wire::NodeError, fdecl::Offer> ProcessNodeOffer(fdecl::Offer add_offer, |
| fdecl::Ref source) { |
| auto has_source_name = |
| VisitOffer<bool>(add_offer, [](const auto& decl) { return decl->source_name().has_value(); }); |
| if (!has_source_name.value_or(false)) { |
| return fit::as_error(fdf::wire::NodeError::kOfferSourceNameMissing); |
| } |
| |
| auto has_ref = VisitOffer<bool>(add_offer, [](const auto& decl) { |
| return decl->source().has_value() || decl->target().has_value(); |
| }); |
| if (has_ref.value_or(false)) { |
| return fit::as_error(fdf::wire::NodeError::kOfferRefExists); |
| } |
| |
| // Assign the source of the offer. |
| VisitOffer<bool>(add_offer, [source = std::move(source)](auto decl) mutable { |
| decl->source(std::move(source)); |
| return true; |
| }); |
| |
| return fit::ok(std::move(add_offer)); |
| } |
| |
| // Processes the offer by validating it has a source_name and adding a source ref to it. |
| // Returns a tuple containing the offer as well as node property that provides transport |
| // information for the offer. |
| fit::result<fdf::wire::NodeError, std::tuple<fdecl::Offer, fdf::NodeProperty>> |
| ProcessNodeOfferWithTransportProperty(fdecl::Offer add_offer, fdecl::Ref source, |
| const std::string& transport_for_property) { |
| auto result = ProcessNodeOffer(std::move(add_offer), std::move(source)); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| |
| auto processed_offer = std::move(result.value()); |
| |
| std::optional<fdf::NodeProperty> node_property = std::nullopt; |
| VisitOffer<bool>(processed_offer, [&node_property, &transport_for_property](const auto& decl) { |
| auto& name = decl->source_name(); |
| if (name.has_value()) { |
| const std::string& name_str = name.value(); |
| node_property.emplace(fdf::MakeProperty(name_str, name_str + "." + transport_for_property)); |
| } |
| |
| return true; |
| }); |
| |
| return fit::ok(std::make_tuple(std::move(processed_offer), std::move(node_property.value()))); |
| } |
| |
| bool IsDefaultOffer(std::string_view target_name) { |
| return std::string_view("default").compare(target_name) == 0; |
| } |
| |
| template <typename T> |
| void CloseIfExists(std::optional<fidl::ServerBinding<T>>& ref) { |
| if (ref) { |
| ref->Close(ZX_OK); |
| } |
| } |
| |
| fit::result<fdf::wire::NodeError> ValidateSymbols(std::vector<fdf::NodeSymbol>& symbols) { |
| std::unordered_set<std::string_view> names; |
| for (auto& symbol : symbols) { |
| if (!symbol.name().has_value()) { |
| LOGF(ERROR, "SymbolError: a symbol is missing a name"); |
| return fit::error(fdf::wire::NodeError::kSymbolNameMissing); |
| } |
| if (!symbol.address().has_value()) { |
| LOGF(ERROR, "SymbolError: symbol '%s' is missing an address", symbol.name().value().c_str()); |
| return fit::error(fdf::wire::NodeError::kSymbolAddressMissing); |
| } |
| auto [_, inserted] = names.emplace(symbol.name().value()); |
| if (!inserted) { |
| LOGF(ERROR, "SymbolError: symbol '%s' already exists", symbol.name().value().c_str()); |
| return fit::error(fdf::wire::NodeError::kSymbolAlreadyExists); |
| } |
| } |
| return fit::ok(); |
| } |
| |
| } // namespace |
| |
| std::optional<fdecl::wire::Offer> CreateCompositeServiceOffer(fidl::AnyArena& arena, |
| fdecl::wire::Offer& offer, |
| std::string_view parents_name, |
| bool primary_parent) { |
| if (!offer.is_service() || !offer.service().has_source_instance_filter() || |
| !offer.service().has_renamed_instances()) { |
| return std::nullopt; |
| } |
| |
| size_t new_instance_count = offer.service().renamed_instances().count(); |
| if (primary_parent) { |
| for (auto& instance : offer.service().renamed_instances()) { |
| if (IsDefaultOffer(instance.target_name.get())) { |
| new_instance_count++; |
| } |
| } |
| } |
| |
| size_t new_filter_count = offer.service().source_instance_filter().count(); |
| if (primary_parent) { |
| for (auto& filter : offer.service().source_instance_filter()) { |
| if (IsDefaultOffer(filter.get())) { |
| new_filter_count++; |
| } |
| } |
| } |
| |
| // We have to create a new offer so we aren't manipulating our parent's offer. |
| auto service = fdecl::wire::OfferService::Builder(arena); |
| if (offer.service().has_source_name()) { |
| service.source_name(offer.service().source_name()); |
| } |
| if (offer.service().has_target_name()) { |
| service.target_name(offer.service().target_name()); |
| } |
| if (offer.service().has_source()) { |
| service.source(offer.service().source()); |
| } |
| if (offer.service().has_target()) { |
| service.target(offer.service().target()); |
| } |
| |
| size_t index = 0; |
| fidl::VectorView<fdecl::wire::NameMapping> mappings(arena, new_instance_count); |
| for (auto instance : offer.service().renamed_instances()) { |
| // The instance is not "default", so copy it over. |
| if (!IsDefaultOffer(instance.target_name.get())) { |
| mappings[index].source_name = fidl::StringView(arena, instance.source_name.get()); |
| mappings[index].target_name = fidl::StringView(arena, instance.target_name.get()); |
| index++; |
| continue; |
| } |
| |
| // We are the primary parent, so add the "default" offer. |
| if (primary_parent) { |
| mappings[index].source_name = fidl::StringView(arena, instance.source_name.get()); |
| mappings[index].target_name = fidl::StringView(arena, instance.target_name.get()); |
| index++; |
| } |
| |
| // Rename the instance to match the parent's name. |
| mappings[index].source_name = fidl::StringView(arena, instance.source_name.get()); |
| mappings[index].target_name = fidl::StringView(arena, parents_name); |
| index++; |
| } |
| ZX_ASSERT(index == new_instance_count); |
| |
| index = 0; |
| fidl::VectorView<fidl::StringView> filters(arena, new_instance_count); |
| for (auto filter : offer.service().source_instance_filter()) { |
| // The filter is not "default", so copy it over. |
| if (!IsDefaultOffer(filter.get())) { |
| filters[index] = fidl::StringView(arena, filter.get()); |
| index++; |
| continue; |
| } |
| |
| // We are the primary parent, so add the "default" filter. |
| if (primary_parent) { |
| filters[index] = fidl::StringView(arena, "default"); |
| index++; |
| } |
| |
| // Rename the filter to match the parent's name. |
| filters[index] = fidl::StringView(arena, parents_name); |
| index++; |
| } |
| ZX_ASSERT(index == new_filter_count); |
| |
| service.renamed_instances(mappings); |
| service.source_instance_filter(filters); |
| |
| return fdecl::wire::Offer::WithService(arena, service.Build()); |
| } |
| |
| std::optional<fdecl::wire::Offer> CreateCompositeOffer(fidl::AnyArena& arena, |
| fdecl::wire::Offer& offer, |
| std::string_view parents_name, |
| bool primary_parent) { |
| // We route 'service' capabilities based on the parent's name. |
| if (offer.is_service()) { |
| return CreateCompositeServiceOffer(arena, offer, parents_name, primary_parent); |
| } |
| |
| // Other capabilities we can simply forward unchanged, but allocated on the new arena. |
| return fidl::ToWire(arena, fidl::ToNatural(offer)); |
| } |
| |
| Node::Node(std::string_view name, std::vector<std::weak_ptr<Node>> parents, |
| NodeManager* node_manager, async_dispatcher_t* dispatcher, DeviceInspect inspect, |
| uint32_t primary_index, NodeType type) |
| : name_(name), |
| type_(type), |
| parents_(std::move(parents)), |
| primary_index_(primary_index), |
| node_manager_(node_manager), |
| dispatcher_(dispatcher), |
| inspect_(std::move(inspect)) { |
| if (type == NodeType::kNormal) { |
| ZX_ASSERT(parents_.size() <= 1); |
| } |
| |
| ZX_ASSERT(primary_index_ == 0 || primary_index_ < parents_.size()); |
| if (auto primary_parent = GetPrimaryParent()) { |
| // By default, we set `driver_host_` to match the primary parent's |
| // `driver_host_`. If the node is then subsequently bound to a driver in a |
| // different driver host, this value will be updated to match. |
| driver_host_ = primary_parent->driver_host_; |
| } |
| } |
| |
| zx::result<std::shared_ptr<Node>> Node::CreateCompositeNode( |
| std::string_view node_name, std::vector<std::weak_ptr<Node>> parents, |
| std::vector<std::string> parents_names, |
| const std::vector<fuchsia_driver_framework::NodePropertyEntry>& parent_properties, |
| NodeManager* driver_binder, async_dispatcher_t* dispatcher, bool is_legacy, |
| uint32_t primary_index) { |
| ZX_ASSERT(!parents.empty()); |
| |
| if (parents.size() != parent_properties.size()) { |
| LOGF(ERROR, |
| "Missing parent properties. Expected %d entries, equal to the number of parents %d.", |
| parents.size(), parent_properties.size()); |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| if (primary_index >= parents.size()) { |
| LOGF(ERROR, "Primary node index is out of bounds"); |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| auto primary_node_ptr = parents[primary_index].lock(); |
| if (!primary_node_ptr) { |
| LOGF(ERROR, "Primary node freed before use"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| DeviceInspect inspect = |
| primary_node_ptr->inspect_.CreateChild(std::string(node_name), zx::vmo(), 0); |
| std::shared_ptr composite = std::make_shared<Node>( |
| node_name, std::move(parents), driver_binder, dispatcher, std::move(inspect), primary_index, |
| is_legacy ? NodeType::kLegacyComposite : NodeType::kComposite); |
| composite->parents_names_ = std::move(parents_names); |
| |
| composite->SetCompositeParentProperties(parent_properties); |
| composite->SetAndPublishInspect(); |
| |
| Node* primary = composite->GetPrimaryParent(); |
| // We know that our device has a parent because we're creating it. |
| ZX_ASSERT(primary); |
| |
| // Copy the symbols from the primary parent. |
| composite->symbols_.reserve(primary->symbols_.size()); |
| for (auto& symbol : primary->symbols_) { |
| composite->symbols_.emplace_back(fdf::wire::NodeSymbol::Builder(composite->arena_) |
| .name(composite->arena_, symbol.name().get()) |
| .address(symbol.address()) |
| .Build()); |
| } |
| |
| // Copy the offers from each parent. |
| std::vector<fdecl::wire::Offer> node_offers; |
| size_t parent_index = 0; |
| for (const std::weak_ptr<Node> parent : composite->parents_) { |
| auto parent_ptr = parent.lock(); |
| if (!parent_ptr) { |
| LOGF(ERROR, "Composite parent node freed before use"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| auto parent_offers = parent_ptr->offers(); |
| node_offers.reserve(node_offers.size() + parent_offers.count()); |
| |
| for (auto& parent_offer : parent_offers) { |
| auto offer = CreateCompositeOffer(composite->arena_, parent_offer, |
| composite->parents_names_[parent_index], |
| parent_index == primary_index); |
| if (offer) { |
| node_offers.push_back(*offer); |
| } |
| } |
| parent_index++; |
| } |
| composite->offers_ = std::move(node_offers); |
| |
| composite->AddToParents(); |
| ZX_ASSERT_MSG(primary->devfs_device_.topological_node().has_value(), "%s", |
| composite->MakeTopologicalPath().c_str()); |
| |
| // TODO(https://fxbug.dev/331779666): disable controller access for composite nodes |
| primary->devfs_device_.topological_node().value().add_child( |
| composite->name_, std::nullopt, |
| composite->CreateDevfsPassthrough(std::nullopt, std::nullopt, true, ""), |
| composite->devfs_device_); |
| composite->devfs_device_.publish(); |
| return zx::ok(std::move(composite)); |
| } |
| |
| Node::~Node() { |
| // TODO(https://fxbug.dev/42085057): Notify the NodeRemovalTracker if the node is deallocated |
| // before shutdown is complete. |
| if (GetNodeState() != NodeState::kStopped) { |
| LOGF(INFO, "Node %s deallocating while at state %s", MakeComponentMoniker().c_str(), |
| GetShutdownHelper().NodeStateAsString()); |
| } |
| |
| CloseIfExists(controller_ref_); |
| CloseIfExists(node_ref_); |
| |
| for (auto& completer : unbinding_children_completers_) { |
| completer.Reply(zx::error(ZX_ERR_CANCELED)); |
| } |
| |
| if (pending_bind_completer_.has_value()) { |
| pending_bind_completer_.value()(zx::error(ZX_ERR_CANCELED)); |
| pending_bind_completer_.reset(); |
| } |
| |
| if (composite_rebind_completer_.has_value() && composite_rebind_completer_.value()) { |
| LOGF(WARNING, "Unable to rebind node %s since it deallocated before completing shutdown", |
| MakeComponentMoniker().c_str()); |
| composite_rebind_completer_.value()(zx::error(ZX_ERR_CANCELED)); |
| composite_rebind_completer_.reset(); |
| } |
| } |
| |
| const std::string& Node::driver_url() const { |
| if (driver_component_) { |
| return driver_component_->driver_url; |
| } |
| return kUnboundUrl; |
| } |
| |
| std::string Node::MakeTopologicalPath() const { |
| std::deque<std::string_view> names; |
| for (auto node = this; node != nullptr; node = node->GetPrimaryParent()) { |
| names.push_front(node->name()); |
| } |
| return fxl::JoinStrings(names, "/"); |
| } |
| |
| std::string Node::MakeComponentMoniker() const { |
| std::string topo_path = MakeTopologicalPath(); |
| |
| // The driver's component name is based on the node name, which means that the |
| // node name cam only have [a-z0-9-_.] characters. DFv1 composites contain ':' |
| // which is not allowed, so replace those characters. |
| // TODO(https://fxbug.dev/42062456): Migrate driver names to only use CF valid characters. |
| std::replace(topo_path.begin(), topo_path.end(), ':', '_'); |
| std::replace(topo_path.begin(), topo_path.end(), '/', '.'); |
| return topo_path; |
| } |
| |
| void Node::OnBind() const { |
| if (controller_ref_) { |
| fidl::Status result = fidl::WireSendEvent(*controller_ref_)->OnBind(); |
| if (!result.ok()) { |
| LOGF(ERROR, "Failed to send OnBind event: %s", result.FormatDescription().data()); |
| } |
| } |
| } |
| |
| void Node::Stop(StopCompleter::Sync& completer) { |
| LOGF(DEBUG, "Calling Remove on %s because of Stop() from component framework.", name().c_str()); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::Kill(KillCompleter::Sync& completer) { |
| LOGF(DEBUG, "Calling Remove on %s because of Kill() from component framework.", name().c_str()); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::CompleteBind(zx::result<> result) { |
| if (result.is_error()) { |
| LOGF(WARNING, "Bind failed for node '%s'", MakeComponentMoniker().c_str()); |
| driver_component_.reset(); |
| } |
| |
| if (driver_component_) { |
| ZX_ASSERT_MSG(driver_component_->state == DriverState::kBinding, |
| "Node %s CompleteBind() invoked at invalid state", name().c_str()); |
| driver_component_->state = DriverState::kRunning; |
| } |
| |
| auto completer = std::move(pending_bind_completer_); |
| pending_bind_completer_.reset(); |
| if (completer.has_value()) { |
| completer.value()(result); |
| } |
| |
| GetShutdownHelper().CheckNodeState(); |
| } |
| |
| void Node::AddToParents() { |
| auto this_node = shared_from_this(); |
| for (auto& parent : parents_) { |
| if (auto ptr = parent.lock(); ptr) { |
| ptr->children_.push_back(this_node); |
| continue; |
| } |
| LOGF(WARNING, "Parent freed before child %s could be added to it", name().c_str()); |
| } |
| } |
| |
| ShutdownHelper& Node::GetShutdownHelper() { |
| if (!shutdown_helper_) { |
| bool is_shutdown_test_delay_enabled = |
| node_manager_.has_value() && node_manager_.value()->IsTestShutdownDelayEnabled(); |
| auto shutdown_rng = node_manager_.has_value() ? node_manager_.value()->GetShutdownTestRng() |
| : std::weak_ptr<std::mt19937>(); |
| shutdown_helper_ = std::make_unique<ShutdownHelper>( |
| this, dispatcher_, is_shutdown_test_delay_enabled, shutdown_rng); |
| } |
| return *shutdown_helper_.get(); |
| } |
| |
| // TODO(https://fxbug.dev/42075799): If the node invoking this function cannot multibind to |
| // composites, is parenting one composite node, and is not in a state for removal, then it should |
| // attempt to bind to something else. |
| void Node::RemoveChild(const std::shared_ptr<Node>& child) { |
| LOGF(DEBUG, "RemoveChild %s from parent %s", child->name().c_str(), name().c_str()); |
| children_.erase(std::find(children_.begin(), children_.end(), child)); |
| if (!unbinding_children_completers_.empty() && children_.empty()) { |
| for (auto& completer : unbinding_children_completers_) { |
| completer.ReplySuccess(); |
| } |
| unbinding_children_completers_.clear(); |
| } |
| GetShutdownHelper().CheckNodeState(); |
| } |
| |
| void Node::FinishShutdown(fit::callback<void()> shutdown_callback) { |
| ZX_ASSERT_MSG(GetNodeState() == NodeState::kWaitingOnDriverComponent, |
| "FinishShutdown called in invalid node state: %s", |
| GetShutdownHelper().NodeStateAsString()); |
| LOGF(INFO, "Node: %s finishing shutdown", name().c_str()); |
| |
| if (shutdown_intent() == ShutdownIntent::kRestart) { |
| shutdown_callback(); |
| FinishRestart(); |
| return; |
| } |
| |
| LOGF(DEBUG, "Node: %s unbinding and resetting", name().c_str()); |
| CloseIfExists(controller_ref_); |
| CloseIfExists(node_ref_); |
| devfs_device_.unpublish(); |
| |
| // Store a shared_ptr to ourselves so we won't be freed halfway through this function. |
| std::shared_ptr this_node = shared_from_this(); |
| driver_component_.reset(); |
| for (auto& parent : parents()) { |
| if (auto ptr = parent.lock(); ptr) { |
| ptr->RemoveChild(this_node); |
| continue; |
| } |
| LOGF(WARNING, "Parent freed before child %s could be removed from it", name().c_str()); |
| } |
| parents_.clear(); |
| |
| shutdown_callback(); |
| |
| if (remove_complete_callback_) { |
| remove_complete_callback_(); |
| } |
| |
| if (shutdown_intent() == ShutdownIntent::kRebindComposite && composite_rebind_completer_ && |
| composite_rebind_completer_.value()) { |
| composite_rebind_completer_.value()(zx::ok()); |
| composite_rebind_completer_.reset(); |
| } |
| } |
| |
| void Node::FinishRestart() { |
| ZX_ASSERT_MSG(shutdown_intent() == ShutdownIntent::kRestart, |
| "FinishRestart called when node is not restarting."); |
| |
| GetShutdownHelper().ResetShutdown(); |
| |
| // Store previous url before we reset the driver_component_. |
| std::string previous_url = driver_url(); |
| |
| // Perform cleanups for previous driver before we try to start the next driver. |
| driver_component_.reset(); |
| CloseIfExists(node_ref_); |
| |
| if (restart_driver_url_suffix_.has_value()) { |
| auto tracker = CreateBindResultTracker(); |
| node_manager_.value()->BindToUrl(*this, restart_driver_url_suffix_.value(), std::move(tracker)); |
| restart_driver_url_suffix_.reset(); |
| return; |
| } |
| |
| zx::result start_result = |
| node_manager_.value()->StartDriver(*this, previous_url, driver_package_type_); |
| if (start_result.is_error()) { |
| LOGF(ERROR, "Failed to start driver '%s': %s", name().c_str(), start_result.status_string()); |
| } |
| } |
| |
| void Node::ClearHostDriver() { |
| if (driver_component_) { |
| driver_component_->driver = {}; |
| } |
| } |
| |
| // State table for package driver: |
| // Initial States |
| // Running | Prestop| WoC | WoDriver | Stopping |
| // Remove(kPkg) WoC | WoC | Ignore | Error! | Error! |
| // Remove(kAll) WoC | WoC | WoC | Error! | Error! |
| // children empty N/A | N/A |WoDriver| Error! | Error! |
| // Driver exit WoC | WoC | WoC | Stopping | Error! |
| // |
| // State table for boot driver: |
| // Initial States |
| // Running | Prestop | WoC | WoDriver | Stopping |
| // Remove(kPkg) Prestop | Ignore | Ignore | Ignore | Ignore |
| // Remove(kAll) WoC | WoC | Ignore | Ignore | Ignore |
| // children empty N/A | N/A |WoDriver| Ignore | Ignore |
| // Driver exit WoC | WoC | WoC | Stopping | Ignore |
| // Boot drivers go into the Prestop state when Remove(kPackage) is set, to signify that |
| // a removal is taking place, but this node will not be removed yet, even if all its children |
| // are removed. |
| void Node::Remove(RemovalSet removal_set, NodeRemovalTracker* removal_tracker) { |
| GetShutdownHelper().Remove(shared_from_this(), removal_set, removal_tracker); |
| } |
| |
| void Node::RestartNode() { |
| GetShutdownHelper().set_shutdown_intent(ShutdownIntent::kRestart); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| // TODO(https://fxbug.dev/42082343): Handle the case in which this function is called during node |
| // removal. |
| void Node::RestartNodeWithRematch(std::optional<std::string> restart_driver_url_suffix, |
| fit::callback<void(zx::result<>)> completer) { |
| if (pending_bind_completer_.has_value()) { |
| completer(zx::error(ZX_ERR_ALREADY_EXISTS)); |
| return; |
| } |
| |
| pending_bind_completer_ = std::move(completer); |
| restart_driver_url_suffix_ = std::move(restart_driver_url_suffix); |
| RestartNode(); |
| } |
| |
| void Node::RestartNodeWithRematch() { |
| RestartNodeWithRematch("", [](zx::result<> result) {}); |
| } |
| |
| // TODO(https://fxbug.dev/42082343): Handle the case in which this function is called during node |
| // removal. |
| void Node::RemoveCompositeNodeForRebind(fit::callback<void(zx::result<>)> completer) { |
| if (composite_rebind_completer_.has_value()) { |
| completer(zx::error(ZX_ERR_ALREADY_EXISTS)); |
| return; |
| } |
| |
| if (type_ != NodeType::kComposite) { |
| completer(zx::error(ZX_ERR_NOT_SUPPORTED)); |
| return; |
| } |
| |
| composite_rebind_completer_ = std::move(completer); |
| GetShutdownHelper().set_shutdown_intent(ShutdownIntent::kRebindComposite); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| std::shared_ptr<BindResultTracker> Node::CreateBindResultTracker() { |
| return std::make_shared<BindResultTracker>( |
| 1, [weak_self = weak_from_this()]( |
| fidl::VectorView<fuchsia_driver_development::wire::NodeBindingInfo> info) { |
| std::shared_ptr self = weak_self.lock(); |
| if (!self) { |
| return; |
| } |
| // We expect a single successful "bind". If we don't get it, we can assume the bind |
| // request failed. If we do get it, we will continue to wait for the driver's start hook |
| // to complete, which will only occur after the successful bind. The remaining flow will |
| // be similar to the RestartNode flow. |
| if (info.count() < 1) { |
| self->CompleteBind(zx::error(ZX_ERR_NOT_FOUND)); |
| } else if (info.count() > 1) { |
| LOGF(ERROR, "Unexpectedly bound multiple drivers to a single node"); |
| self->CompleteBind(zx::error(ZX_ERR_BAD_STATE)); |
| } |
| }); |
| } |
| |
| void Node::SetNonCompositeProperties( |
| cpp20::span<const fuchsia_driver_framework::NodeProperty> properties) { |
| std::vector<fuchsia_driver_framework::wire::NodeProperty> wire; |
| wire.reserve(properties.size() + 1); // + 1 for DFv2 prop. |
| for (const auto& property : properties) { |
| wire.emplace_back(fidl::ToWire(arena_, property)); |
| } |
| wire.emplace_back(fdf::MakeProperty(arena_, bind_fuchsia_platform::DRIVER_FRAMEWORK_VERSION, |
| static_cast<uint32_t>(2))); |
| |
| std::vector<fuchsia_driver_framework::wire::NodePropertyEntry> entries; |
| entries.emplace_back(fuchsia_driver_framework::wire::NodePropertyEntry{ |
| .name = "default", |
| .properties = fuchsia_driver_framework::wire::NodePropertyVector(arena_, wire)}); |
| |
| properties_ = fuchsia_driver_framework::wire::NodePropertyDictionary(arena_, entries); |
| SynchronizePropertiesDict(); |
| } |
| |
| void Node::SetCompositeParentProperties( |
| const std::vector<fuchsia_driver_framework::NodePropertyEntry>& parent_properties) { |
| auto entries = GetParentNodePropertyEntries(arena_, parent_properties); |
| |
| ZX_ASSERT(primary_index_ < parents_.size()); |
| const auto default_node_properties = entries[primary_index_].properties.get(); |
| entries.emplace_back(fuchsia_driver_framework::wire::NodePropertyEntry{ |
| .name = "default", |
| .properties = fuchsia_driver_framework::wire::NodePropertyVector::FromExternal( |
| default_node_properties.data(), default_node_properties.size())}); |
| |
| properties_ = fuchsia_driver_framework::wire::NodePropertyDictionary(arena_, entries); |
| SynchronizePropertiesDict(); |
| } |
| |
| void Node::SynchronizePropertiesDict() { |
| properties_dict_.clear(); |
| for (const auto& entry : properties_) { |
| properties_dict_[std::string(entry.name.get())] = entry.properties.get(); |
| } |
| } |
| |
| fit::result<fuchsia_driver_framework::wire::NodeError, std::shared_ptr<Node>> Node::AddChildHelper( |
| fuchsia_driver_framework::NodeAddArgs args, |
| fidl::ServerEnd<fuchsia_driver_framework::NodeController> controller, |
| fidl::ServerEnd<fuchsia_driver_framework::Node> node) { |
| if (!unbinding_children_completers_.empty()) { |
| LOGF(ERROR, "Failed to add node: Node is currently unbinding all of its children"); |
| return fit::as_error(fdf::wire::NodeError::kUnbindChildrenInProgress); |
| } |
| if (node_manager_ == nullptr) { |
| LOGF(WARNING, "Failed to add Node, as this Node '%s' was removed", name().data()); |
| return fit::as_error(fdf::wire::NodeError::kNodeRemoved); |
| } |
| if (GetShutdownHelper().IsShuttingDown()) { |
| LOGF(WARNING, "Failed to add Node, as this Node '%s' is being removed", name().c_str()); |
| return fit::as_error(fdf::wire::NodeError::kNodeRemoved); |
| } |
| if (!args.name().has_value()) { |
| LOGF(ERROR, "Failed to add Node, a name must be provided"); |
| return fit::as_error(fdf::wire::NodeError::kNameMissing); |
| } |
| std::string_view name = args.name().value(); |
| for (auto& child : children_) { |
| if (child->name() == name) { |
| LOGF(ERROR, "Failed to add Node '%.*s', name already exists among siblings", |
| static_cast<int>(name.size()), name.data()); |
| return fit::as_error(fdf::wire::NodeError::kNameAlreadyExists); |
| } |
| }; |
| zx::vmo inspect_vmo; |
| if (args.devfs_args().has_value() && args.devfs_args()->inspect().has_value()) { |
| inspect_vmo = std::move(args.devfs_args()->inspect().value()); |
| } |
| DeviceInspect inspect = inspect_.CreateChild(std::string(name), std::move(inspect_vmo), 0); |
| std::shared_ptr child = |
| std::make_shared<Node>(name, std::vector<std::weak_ptr<Node>>{weak_from_this()}, |
| *node_manager_, dispatcher_, std::move(inspect)); |
| |
| auto& deprecated_offers = args.offers(); |
| auto& fdf_offers = args.offers2(); |
| std::vector<fuchsia_driver_framework::NodeProperty> properties; |
| const auto& arg_properties = args.properties(); |
| if (arg_properties.has_value()) { |
| properties = arg_properties.value(); |
| } |
| if (deprecated_offers.has_value() || fdf_offers.has_value()) { |
| size_t n = 0; |
| if (deprecated_offers.has_value()) { |
| n += deprecated_offers.value().size(); |
| } |
| if (fdf_offers.has_value()) { |
| n += fdf_offers.value().size(); |
| } |
| child->offers_.reserve(n); |
| |
| // Find a parent node with a collection. This indicates that a driver has |
| // been bound to the node, and the driver is running within the collection. |
| Node* source_node = this; |
| while (source_node && source_node->collection_ == Collection::kNone) { |
| source_node = source_node->GetPrimaryParent(); |
| } |
| fdecl::Ref source_ref = |
| fdecl::Ref::WithChild(fdecl::ChildRef() |
| .name(source_node->MakeComponentMoniker()) |
| .collection(CollectionName(source_node->collection_))); |
| |
| if (deprecated_offers.has_value()) { |
| for (auto& offer : deprecated_offers.value()) { |
| fit::result new_offer = ProcessNodeOffer(offer, source_ref); |
| if (new_offer.is_error()) { |
| LOGF(ERROR, "Failed to add Node '%s': Bad add offer: %d", |
| child->MakeTopologicalPath().c_str(), new_offer.error_value()); |
| return new_offer.take_error(); |
| } |
| |
| child->offers_.emplace_back(fidl::ToWire(child->arena_, new_offer.value())); |
| } |
| } |
| |
| if (fdf_offers.has_value()) { |
| for (auto& fdf_offer : fdf_offers.value()) { |
| std::optional<fuchsia_component_decl::Offer> offer; |
| std::optional<std::string> transport; |
| |
| switch (fdf_offer.Which()) { |
| case fdf::Offer::Tag::kZirconTransport: |
| offer.emplace(fdf_offer.zircon_transport().value()); |
| transport.emplace("ZirconTransport"); |
| break; |
| case fdf::Offer::Tag::kDriverTransport: |
| offer.emplace(fdf_offer.driver_transport().value()); |
| transport.emplace("DriverTransport"); |
| break; |
| default: |
| LOGF(ERROR, "Unknown offer transport type %d", fdf_offer.Which()); |
| return fit::error(fdf::NodeError::kInternal); |
| } |
| |
| fit::result new_offer = |
| ProcessNodeOfferWithTransportProperty(offer.value(), source_ref, transport.value()); |
| if (new_offer.is_error()) { |
| LOGF(ERROR, "Failed to add Node '%s': Bad add offer: %d", |
| child->MakeTopologicalPath().c_str(), new_offer.error_value()); |
| return new_offer.take_error(); |
| } |
| auto [processed_offer, property] = std::move(new_offer.value()); |
| child->offers_.emplace_back(fidl::ToWire(child->arena_, processed_offer)); |
| properties.emplace_back(property); |
| } |
| } |
| } |
| |
| child->SetNonCompositeProperties(properties); |
| |
| child->SetAndPublishInspect(); |
| |
| if (args.symbols().has_value()) { |
| auto is_valid = ValidateSymbols(args.symbols().value()); |
| if (is_valid.is_error()) { |
| LOGF(ERROR, "Failed to add Node '%.*s', bad symbols", static_cast<int>(name.size()), |
| name.data()); |
| return fit::as_error(is_valid.error_value()); |
| } |
| |
| child->symbols_.reserve(args.symbols().value().size()); |
| for (auto& symbol : args.symbols().value()) { |
| child->symbols_.emplace_back(fdf::wire::NodeSymbol::Builder(child->arena_) |
| .name(child->arena_, symbol.name().value()) |
| .address(symbol.address().value()) |
| .Build()); |
| } |
| } |
| |
| Devnode::Target devfs_target; |
| std::optional<std::string_view> devfs_class_path; |
| std::string class_name = "Unknown_Class_name"; |
| auto& devfs_args = args.devfs_args(); |
| if (devfs_args.has_value()) { |
| if (devfs_args->class_name().has_value()) { |
| devfs_class_path = devfs_args->class_name(); |
| class_name = std::string(devfs_args->class_name().value()); |
| } |
| // We do not populate the connection to the controller unless it is specifically |
| // supported through the connector_supports argument. |
| bool allow_controller_connection = (devfs_args->connector_supports().has_value() && |
| (devfs_args->connector_supports().value() & |
| fuchsia_device_fs::ConnectionType::kController)); |
| if (allow_controller_connection && !devfs_args->class_name().has_value()) { |
| class_name = "No_class_name_but_driver_url_is_" + driver_url(); |
| } |
| |
| devfs_target = child->CreateDevfsPassthrough(std::move(devfs_args->connector()), |
| std::move(devfs_args->controller_connector()), |
| allow_controller_connection, class_name); |
| } else { |
| devfs_target = child->CreateDevfsPassthrough(std::nullopt, std::nullopt, false, class_name); |
| } |
| ZX_ASSERT(devfs_device_.topological_node().has_value()); |
| zx_status_t status = devfs_device_.topological_node()->add_child( |
| child->name_, devfs_class_path, std::move(devfs_target), child->devfs_device_); |
| ZX_ASSERT_MSG(status == ZX_OK, "%s failed to export: %s", child->MakeTopologicalPath().c_str(), |
| zx_status_get_string(status)); |
| ZX_ASSERT(child->devfs_device_.topological_node().has_value()); |
| child->devfs_device_.publish(); |
| |
| if (controller.is_valid()) { |
| child->controller_ref_.emplace(dispatcher_, std::move(controller), child.get(), |
| fidl::kIgnoreBindingClosure); |
| } |
| if (node.is_valid()) { |
| child->node_ref_.emplace(dispatcher_, std::move(node), child.get(), [](Node* node, auto) { |
| node->node_ref_.reset(); |
| LOGF(WARNING, "Removing node %s because of binding closed", node->name().c_str()); |
| node->Remove(RemovalSet::kAll, nullptr); |
| }); |
| } else { |
| // We don't care about tracking binds here, sending nullptr is fine. |
| (*node_manager_)->Bind(*child, nullptr); |
| } |
| |
| child->AddToParents(); |
| return fit::ok(child); |
| } |
| |
| void Node::WaitForChildToExit( |
| std::string_view name, |
| fit::callback<void(fit::result<fuchsia_driver_framework::wire::NodeError>)> callback) { |
| for (auto& child : children_) { |
| if (child->name() != name) { |
| continue; |
| } |
| if (!child->GetShutdownHelper().IsShuttingDown()) { |
| LOGF(ERROR, "Failed to add Node '%.*s', name already exists among siblings", |
| static_cast<int>(name.size()), name.data()); |
| callback(fit::as_error(fdf::wire::NodeError::kNameAlreadyExists)); |
| return; |
| } |
| if (child->remove_complete_callback_) { |
| LOGF(ERROR, |
| "Failed to add Node '%.*s': Node with name already exists and is marked to be replaced.", |
| static_cast<int>(name.size()), name.data()); |
| callback(fit::as_error(fdf::wire::NodeError::kNameAlreadyExists)); |
| return; |
| } |
| child->remove_complete_callback_ = [callback = std::move(callback)]() mutable { |
| callback(fit::success()); |
| }; |
| return; |
| }; |
| callback(fit::success()); |
| } |
| |
| void Node::AddChild(fuchsia_driver_framework::NodeAddArgs args, |
| fidl::ServerEnd<fuchsia_driver_framework::NodeController> controller, |
| fidl::ServerEnd<fuchsia_driver_framework::Node> node, |
| AddNodeResultCallback callback) { |
| if (!args.name().has_value()) { |
| LOGF(ERROR, "Failed to add Node, a name must be provided"); |
| callback(fit::as_error(fdf::wire::NodeError::kNameMissing)); |
| return; |
| } |
| std::string name = args.name().value(); |
| WaitForChildToExit( |
| name, [self = shared_from_this(), args = std::move(args), controller = std::move(controller), |
| node = std::move(node), callback = std::move(callback)]( |
| fit::result<fuchsia_driver_framework::wire::NodeError> result) mutable { |
| if (result.is_error()) { |
| callback(result.take_error()); |
| return; |
| } |
| callback(self->AddChildHelper(std::move(args), std::move(controller), std::move(node))); |
| }); |
| } |
| |
| void Node::Remove(RemoveCompleter::Sync& completer) { |
| LOGF(DEBUG, "Remove() Fidl call for %s", name().c_str()); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::RequestBind(RequestBindRequestView request, RequestBindCompleter::Sync& completer) { |
| bool force_rebind = false; |
| if (request->has_force_rebind()) { |
| force_rebind = request->force_rebind(); |
| } |
| |
| std::optional<std::string> driver_url_suffix; |
| if (request->has_driver_url_suffix()) { |
| driver_url_suffix = std::string(request->driver_url_suffix().get()); |
| } |
| |
| BindHelper(force_rebind, std::move(driver_url_suffix), |
| [completer = completer.ToAsync()](zx_status_t status) mutable { |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| }); |
| } |
| |
| void Node::BindHelper(bool force_rebind, std::optional<std::string> driver_url_suffix, |
| fit::callback<void(zx_status_t)> on_bind_complete) { |
| if (driver_component_.has_value() && !force_rebind) { |
| on_bind_complete(ZX_ERR_ALREADY_BOUND); |
| return; |
| } |
| |
| if (pending_bind_completer_.has_value()) { |
| on_bind_complete(ZX_ERR_ALREADY_EXISTS); |
| return; |
| } |
| |
| auto completer_wrapper = [on_bind_complete = |
| std::move(on_bind_complete)](zx::result<> result) mutable { |
| on_bind_complete(result.status_value()); |
| }; |
| |
| if (driver_component_.has_value()) { |
| RestartNodeWithRematch(driver_url_suffix, std::move(completer_wrapper)); |
| return; |
| } |
| |
| pending_bind_completer_ = std::move(completer_wrapper); |
| auto tracker = CreateBindResultTracker(); |
| if (driver_url_suffix.has_value()) { |
| node_manager_.value()->BindToUrl(*this, driver_url_suffix.value(), std::move(tracker)); |
| } else { |
| node_manager_.value()->Bind(*this, std::move(tracker)); |
| } |
| } |
| |
| void Node::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_driver_framework::NodeController> 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; |
| }; |
| |
| LOGF(WARNING, "fdf::NodeController received unknown %s method. Ordinal: %lu", method_type.c_str(), |
| metadata.method_ordinal); |
| } |
| |
| void Node::AddChild(AddChildRequestView request, AddChildCompleter::Sync& completer) { |
| AddChild(fidl::ToNatural(request->args), std::move(request->controller), std::move(request->node), |
| [completer = completer.ToAsync()]( |
| fit::result<fuchsia_driver_framework::wire::NodeError, std::shared_ptr<Node>> |
| result) mutable { |
| if (result.is_error()) { |
| completer.Reply(result.take_error()); |
| } else { |
| completer.ReplySuccess(); |
| } |
| }); |
| } |
| |
| void Node::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_driver_framework::Node> 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; |
| }; |
| |
| LOGF(WARNING, "fdf::Node received unknown %s method. Ordinal: %lu", method_type.c_str(), |
| metadata.method_ordinal); |
| } |
| |
| void Node::StartDriver(fuchsia_component_runner::wire::ComponentStartInfo start_info, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> controller, |
| fit::callback<void(zx::result<>)> cb) { |
| auto url = start_info.resolved_url().get(); |
| bool colocate = |
| fdf_internal::ProgramValue(start_info.program(), "colocate").value_or("") == "true"; |
| bool host_restart_on_crash = |
| fdf_internal::ProgramValue(start_info.program(), "host_restart_on_crash").value_or("") == |
| "true"; |
| bool use_next_vdso = |
| fdf_internal::ProgramValue(start_info.program(), "use_next_vdso").value_or("") == "true"; |
| |
| if (host_restart_on_crash && colocate) { |
| LOGF(ERROR, |
| "Failed to start driver '%.*s'. Both host_restart_on_crash and colocate cannot be true.", |
| static_cast<int>(url.size()), url.data()); |
| cb(zx::error(ZX_ERR_INVALID_ARGS)); |
| return; |
| } |
| |
| host_restart_on_crash_ = host_restart_on_crash; |
| |
| if (colocate && !driver_host_) { |
| LOGF(ERROR, |
| "Failed to start driver '%.*s', driver is colocated but does not have a prent with a " |
| "driver host", |
| static_cast<int>(url.size()), url.data()); |
| cb(zx::error(ZX_ERR_INVALID_ARGS)); |
| return; |
| } |
| |
| auto symbols = fidl::VectorView<fdf::wire::NodeSymbol>(); |
| if (colocate) { |
| symbols = this->symbols(); |
| } |
| |
| // Launch a driver host if we are not colocated. |
| if (!colocate) { |
| auto result = (*node_manager_)->CreateDriverHost(use_next_vdso); |
| if (result.is_error()) { |
| cb(result.take_error()); |
| return; |
| } |
| driver_host_ = result.value(); |
| } |
| |
| // Bind the Node associated with the driver. |
| auto [client_end, server_end] = fidl::Endpoints<fdf::Node>::Create(); |
| node_ref_.emplace( |
| dispatcher_, std::move(server_end), this, [](Node* node, fidl::UnbindInfo info) { |
| node->node_ref_.reset(); |
| // If the unbind is initiated from us, we don't need to do anything to handle |
| // the closure. |
| if (info.is_user_initiated()) { |
| return; |
| } |
| |
| // IF the driver fails to bind to the node, don't remove the node. |
| if (node->driver_component_->state == DriverState::kBinding) { |
| LOGF(WARNING, "The driver for node %s failed to bind.", node->name().c_str()); |
| return; |
| } |
| |
| if (node->GetNodeState() == NodeState::kRunning) { |
| // If the node is running but this node closure has happened, then we want to restart |
| // the node if it has the host_restart_on_crash_ enabled on it. |
| if (node->host_restart_on_crash_) { |
| LOGF(INFO, "Restarting node %s due to node closure while running.", |
| node->name().c_str()); |
| node->RestartNode(); |
| return; |
| } |
| |
| LOGF(WARNING, "fdf::Node binding for node %s closed while the node was running: %s", |
| node->name().c_str(), info.FormatDescription().c_str()); |
| } |
| |
| node->Remove(RemovalSet::kAll, nullptr); |
| }); |
| |
| LOGF(INFO, "Binding %.*s to %s", static_cast<int>(url.size()), url.data(), name().c_str()); |
| // Start the driver within the driver host. |
| auto driver_endpoints = fidl::Endpoints<fuchsia_driver_host::Driver>::Create(); |
| driver_component_.emplace(*this, std::string(url), std::move(controller), |
| std::move(driver_endpoints.client)); |
| driver_host_.value()->Start(std::move(client_end), name_, properties_, symbols, start_info, |
| std::move(driver_endpoints.server), |
| [weak_self = weak_from_this(), name = name_, |
| cb = std::move(cb)](zx::result<> result) mutable { |
| auto node_ptr = weak_self.lock(); |
| if (!node_ptr) { |
| LOGF(WARNING, "Node '%s' freed before it is used", name.c_str()); |
| cb(result); |
| return; |
| } |
| |
| if (result.is_error()) { |
| LOGF(WARNING, "Failed to start driver host for %s", |
| node_ptr->MakeComponentMoniker().c_str()); |
| node_ptr->driver_component_.reset(); |
| node_ptr->GetShutdownHelper().CheckNodeState(); |
| } |
| cb(result); |
| |
| // If the node set in the process of shutting down, shut down now. |
| }); |
| } |
| |
| bool Node::EvaluateRematchFlags(fuchsia_driver_development::RestartRematchFlags rematch_flags, |
| std::string_view requested_url) { |
| if (type_ == NodeType::kLegacyComposite && |
| !(rematch_flags & fuchsia_driver_development::RestartRematchFlags::kLegacyComposite)) { |
| return false; |
| } |
| |
| if (type_ == NodeType::kComposite && |
| !(rematch_flags & fuchsia_driver_development::RestartRematchFlags::kCompositeSpec)) { |
| return false; |
| } |
| |
| if (driver_url() == requested_url && |
| !(rematch_flags & fuchsia_driver_development::RestartRematchFlags::kRequested)) { |
| return false; |
| } |
| |
| if (driver_url() != requested_url && |
| !(rematch_flags & fuchsia_driver_development::RestartRematchFlags::kNonRequested)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::pair<std::string, Collection> Node::GetRemovalTrackerInfo() { |
| return {MakeComponentMoniker(), collection_}; |
| } |
| |
| void Node::StopDriver() { |
| ZX_ASSERT_MSG(GetNodeState() == NodeState::kWaitingOnChildren, |
| "StopDriverComponent called in invalid node state: %s", |
| GetShutdownHelper().NodeStateAsString()); |
| if (!HasDriver()) { |
| return; |
| } |
| |
| if (driver_component_->state == DriverState::kBinding) { |
| LOGF(WARNING, "Stopping driver '%s' for node '%s' while bind is in process", |
| driver_component_->driver_url.c_str(), MakeComponentMoniker().c_str()); |
| return; |
| } |
| |
| fidl::OneWayStatus result = driver_component_->driver->Stop(); |
| if (result.ok()) { |
| return; // We'll now wait for the channel to close |
| } |
| |
| LOGF(ERROR, "Node: %s failed to stop driver: %s", name().c_str(), |
| result.FormatDescription().data()); |
| // Continue to clear out the driver, since we can't talk to it. |
| ClearHostDriver(); |
| } |
| |
| void Node::StopDriverComponent() { |
| ZX_ASSERT_MSG(GetNodeState() == NodeState::kWaitingOnDriver, |
| "StopDriverComponent called in invalid node state: %s", |
| GetShutdownHelper().NodeStateAsString()); |
| |
| if (!driver_component_) { |
| return; |
| } |
| |
| // Send an epitaph to the component manager and close the connection. The |
| // server of a `ComponentController` protocol is expected to send an epitaph |
| // before closing the associated connection. |
| auto this_node = shared_from_this(); |
| driver_component_->component_controller_ref.Close(ZX_OK); |
| if (!node_manager_.has_value()) { |
| return; |
| } |
| node_manager_.value()->DestroyDriverComponent( |
| *this_node, |
| [self = this_node](fidl::WireUnownedResult<fcomponent::Realm::DestroyChild>& result) { |
| if (!result.ok()) { |
| auto error = result.error().FormatDescription(); |
| LOGF(ERROR, "Node: %s: Failed to send request to destroy component: %.*s", |
| self->name_.c_str(), static_cast<int>(error.size()), error.data()); |
| } |
| if (result->is_error() && |
| result->error_value() != fcomponent::wire::Error::kInstanceNotFound) { |
| LOGF(ERROR, "Node: %.*s: Failed to destroy driver component: %u", |
| static_cast<int>(self->name_.size()), self->name_.data(), result->error_value()); |
| } |
| |
| LOGF(INFO, "Destroyed driver component for %s", self->MakeComponentMoniker().c_str()); |
| self->driver_component_->state = DriverState::kStopped; |
| self->GetShutdownHelper().CheckNodeState(); |
| }); |
| } |
| |
| void Node::on_fidl_error(fidl::UnbindInfo info) { |
| ClearHostDriver(); |
| |
| // The only valid way a driver host should shut down the Driver channel |
| // is with the ZX_OK epitaph. |
| // TODO(b/322235974): Increase the log severity to ERROR once we resolve the component shutdown |
| // order in DriverTestRealm. |
| if (info.reason() != fidl::Reason::kPeerClosedWhileReading || info.status() != ZX_OK) { |
| LOGF(WARNING, "Node: %s: driver channel shutdown with: %s", name().c_str(), |
| info.FormatDescription().data()); |
| } |
| |
| if (GetNodeState() == NodeState::kWaitingOnDriver) { |
| LOGF(INFO, "Node: %s: realm channel had expected shutdown.", MakeComponentMoniker().c_str()); |
| GetShutdownHelper().CheckNodeState(); |
| return; |
| } |
| |
| if (GetNodeState() == NodeState::kWaitingOnDriverComponent) { |
| LOGF(DEBUG, "Node: %s: driver channel had expected shutdown.", name().c_str()); |
| if (driver_component_) { |
| driver_component_->state = DriverState::kStopped; |
| } |
| GetShutdownHelper().CheckNodeState(); |
| return; |
| } |
| |
| if (host_restart_on_crash_) { |
| LOGF(WARNING, "Restarting node %s because of unexpected driver channel shutdown.", |
| name().c_str()); |
| RestartNode(); |
| return; |
| } |
| |
| LOGF(WARNING, "Removing node %s because of unexpected driver channel shutdown.", name().c_str()); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| std::optional<cpp20::span<const fuchsia_driver_framework::wire::NodeProperty>> |
| Node::GetNodeProperties(std::string_view parent_name) const { |
| auto it = properties_dict_.find(std::string(parent_name)); |
| if (it == properties_dict_.end()) { |
| return std::nullopt; |
| } |
| return {it->second}; |
| } |
| |
| Node::DriverComponent::DriverComponent( |
| Node& node, std::string url, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> controller, |
| fidl::ClientEnd<fuchsia_driver_host::Driver> driver) |
| : component_controller_ref( |
| node.dispatcher_, std::move(controller), &node, |
| [](Node* node, fidl::UnbindInfo info) { |
| if (!info.is_user_initiated()) { |
| LOGF(WARNING, "Removing node %s because of ComponentController binding closed: %s", |
| node->name().c_str(), info.FormatDescription().c_str()); |
| node->Remove(RemovalSet::kAll, nullptr); |
| } |
| }), |
| driver(std::move(driver), node.dispatcher_, &node), |
| driver_url(std::move(url)) {} |
| |
| void Node::SetAndPublishInspect() { |
| constexpr char kDeviceTypeString[] = "Device"; |
| constexpr char kCompositeDeviceTypeString[] = "Composite Device"; |
| |
| std::vector<zx_device_prop_t> property_vector; |
| uint32_t protocol_id = 0; |
| if (type_ == NodeType::kNormal) { |
| const auto node_properties = GetNodeProperties(); |
| ZX_ASSERT_MSG(node_properties.has_value(), "Non-composite node \"%s\" missing node properties", |
| name_.c_str()); |
| for (auto& node_property : node_properties.value()) { |
| if (node_property.key.is_int_value() && node_property.value.is_int_value()) { |
| auto key = node_property.key.int_value(); |
| auto value = node_property.value.int_value(); |
| property_vector.push_back(zx_device_prop_t{ |
| .id = static_cast<uint16_t>(key), |
| .value = value, |
| }); |
| if (key == BIND_PROTOCOL) { |
| protocol_id = value; |
| } |
| } |
| } |
| } |
| |
| inspect_.SetStaticValues(MakeTopologicalPath(), protocol_id, |
| IsComposite() ? kCompositeDeviceTypeString : kDeviceTypeString, |
| property_vector, |
| driver_component_.has_value() ? driver_component_->driver_url : ""); |
| if (zx::result result = inspect_.Publish(); result.is_error()) { |
| LOGF(ERROR, "%s: Failed to publish inspect: %s", MakeTopologicalPath().c_str(), |
| result.status_string()); |
| } |
| } |
| |
| void Node::ConnectToDeviceFidl(ConnectToDeviceFidlRequestView request, |
| ConnectToDeviceFidlCompleter::Sync& completer) { |
| zx_status_t status = ConnectDeviceInterface(std::move(request->server)); |
| if (status != ZX_OK) { |
| LOGF(ERROR, "%s: Failed to connect to device fidl: ", zx_status_get_string(status)); |
| } |
| } |
| |
| void Node::ConnectToController(ConnectToControllerRequestView request, |
| ConnectToControllerCompleter::Sync& completer) { |
| ConnectControllerInterface( |
| fidl::ServerEnd<fuchsia_device::Controller>{std::move(request->server)}); |
| } |
| |
| void Node::Bind(BindRequestView request, BindCompleter::Sync& completer) { |
| BindHelper(false, {{request->driver.data(), request->driver.size()}}, |
| [completer = completer.ToAsync()](zx_status_t status) mutable { |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| }); |
| } |
| |
| void Node::Rebind(RebindRequestView request, RebindCompleter::Sync& completer) { |
| std::optional<std::string> url; |
| if (!request->driver.is_null() && !request->driver.empty()) { |
| url = std::string(request->driver.get()); |
| } |
| |
| auto rebind_callback = [completer = completer.ToAsync()](zx::result<> result) mutable { |
| if (result.is_ok()) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(result.error_value()); |
| } |
| }; |
| |
| if (kEnableCompositeNodeSpecRebind && type_ == NodeType::kComposite) { |
| node_manager_.value()->RebindComposite(name_, url, std::move(rebind_callback)); |
| return; |
| } |
| |
| RestartNodeWithRematch(url, std::move(rebind_callback)); |
| } |
| |
| void Node::UnbindChildren(UnbindChildrenCompleter::Sync& completer) { |
| if (children_.empty()) { |
| completer.ReplySuccess(); |
| return; |
| } |
| |
| unbinding_children_completers_.emplace_back(completer.ToAsync()); |
| if (unbinding_children_completers_.size() == 1) { |
| // Iterate over a copy of `children_` because `children_` may be modified during `Node::Remove` |
| // which would mess up the for loop. |
| std::vector<std::shared_ptr<Node>> children{children_.begin(), children_.end()}; |
| for (const auto& child : children) { |
| child->Remove(RemovalSet::kAll, nullptr); |
| } |
| } |
| } |
| |
| void Node::ScheduleUnbind(ScheduleUnbindCompleter::Sync& completer) { |
| Remove(RemovalSet::kAll, nullptr); |
| completer.ReplySuccess(); |
| } |
| void Node::GetTopologicalPath(GetTopologicalPathCompleter::Sync& completer) { |
| completer.ReplySuccess(fidl::StringView::FromExternal("/" + MakeTopologicalPath())); |
| } |
| |
| zx_status_t Node::ConnectControllerInterface( |
| fidl::ServerEnd<fuchsia_device::Controller> server_end) { |
| // This should never be called |
| ZX_ASSERT_MSG(false, |
| "Connect To controller should never be called in node.cc," |
| " as it is intercepted by the ControllerAllowlistPassthrough"); |
| return ZX_OK; |
| } |
| |
| zx_status_t Node::ConnectDeviceInterface(zx::channel channel) { |
| if (!devfs_connector_.has_value()) { |
| return ZX_ERR_INTERNAL; |
| } |
| return fidl::WireCall(devfs_connector_.value())->Connect(std::move(channel)).status(); |
| } |
| |
| Devnode::Target Node::CreateDevfsPassthrough( |
| std::optional<fidl::ClientEnd<fuchsia_device_fs::Connector>> connector, |
| std::optional<fidl::ClientEnd<fuchsia_device_fs::Connector>> controller_connector, |
| bool allow_controller_connection, const std::string& class_name) { |
| controller_allowlist_passthrough_ = ControllerAllowlistPassthrough::Create( |
| std::move(controller_connector), weak_from_this(), dispatcher_, class_name); |
| devfs_connector_ = std::move(connector); |
| return Devnode::PassThrough( |
| [node = weak_from_this(), node_name = name_](zx::channel server_end) { |
| std::shared_ptr locked_node = node.lock(); |
| if (!locked_node) { |
| LOGF(ERROR, "Node was freed before it was used for %s.", node_name.c_str()); |
| return ZX_ERR_BAD_STATE; |
| } |
| return locked_node->ConnectDeviceInterface(std::move(server_end)); |
| }, |
| [node = weak_from_this(), allow_controller_connection, |
| node_name = name_](fidl::ServerEnd<fuchsia_device::Controller> server_end) { |
| if (!allow_controller_connection) { |
| LOGF(ERROR, |
| "Connection to %s controller interface failed, as that node did not" |
| " include controller support in its DevAddArgs", |
| node_name.c_str()); |
| return ZX_ERR_PROTOCOL_NOT_SUPPORTED; |
| } |
| std::shared_ptr locked_node = node.lock(); |
| if (!locked_node) { |
| LOGF(ERROR, "Node was freed before it was used for %s.", node_name.c_str()); |
| return ZX_ERR_BAD_STATE; |
| } |
| return locked_node->controller_allowlist_passthrough_->Connect(std::move(server_end)); |
| }); |
| } |
| |
| } // namespace driver_manager |