| // 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. |
| |
| #ifndef SRC_DEVICES_BIN_DRIVER_MANAGER_NODE_H_ |
| #define SRC_DEVICES_BIN_DRIVER_MANAGER_NODE_H_ |
| |
| #include <fidl/fuchsia.component.runner/cpp/wire.h> |
| #include <fidl/fuchsia.driver.development/cpp/fidl.h> |
| #include <fidl/fuchsia.driver.framework/cpp/fidl.h> |
| #include <fidl/fuchsia.driver.host/cpp/wire.h> |
| #include <fidl/fuchsia.driver.index/cpp/wire.h> |
| #include <zircon/assert.h> |
| |
| #include <list> |
| #include <memory> |
| #include <utility> |
| |
| #include "lib/fidl/cpp/wire/client.h" |
| #include "lib/fidl/cpp/wire/internal/transport_channel.h" |
| #include "src/devices/bin/driver_manager/bind/bind_result_tracker.h" |
| #include "src/devices/bin/driver_manager/controller_allowlist_passthrough.h" |
| #include "src/devices/bin/driver_manager/devfs/devfs.h" |
| #include "src/devices/bin/driver_manager/driver_host.h" |
| #include "src/devices/bin/driver_manager/inspect.h" |
| #include "src/devices/bin/driver_manager/shutdown/shutdown_helper.h" |
| |
| namespace driver_manager { |
| |
| // This function creates a composite offer based on a service offer. |
| std::optional<fuchsia_component_decl::wire::Offer> CreateCompositeServiceOffer( |
| fidl::AnyArena& arena, fuchsia_component_decl::wire::Offer& offer, |
| std::string_view parents_name, bool primary_parent); |
| |
| class Node; |
| class NodeRemovalTracker; |
| |
| using AddNodeResultCallback = fit::callback<void( |
| fit::result<fuchsia_driver_framework::wire::NodeError, std::shared_ptr<Node>>)>; |
| |
| using DestroyDriverComponentCallback = |
| fit::callback<void(fidl::WireUnownedResult<fuchsia_component::Realm::DestroyChild>& result)>; |
| |
| class NodeManager { |
| public: |
| virtual ~NodeManager() = default; |
| |
| // Attempt to bind `node`. |
| // A nullptr for result_tracker is acceptable if the caller doesn't intend to |
| // track the results. |
| virtual void Bind(Node& node, std::shared_ptr<BindResultTracker> result_tracker) = 0; |
| |
| virtual void BindToUrl(Node& node, std::string_view driver_url_suffix, |
| std::shared_ptr<BindResultTracker> result_tracker) { |
| ZX_PANIC("Unimplemented BindToUrl"); |
| } |
| |
| virtual zx::result<> StartDriver(Node& node, std::string_view url, |
| fuchsia_driver_framework::DriverPackageType package_type) { |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| virtual zx::result<DriverHost*> CreateDriverHost(bool use_next_vdso) = 0; |
| |
| // DriverHost lifetimes are managed through a linked list, and they will delete themselves |
| // when the FIDL connection is closed. Currently in the Node class we store a raw pointer to the |
| // DriverHost object, and do not have a way to remove it from the class when the underlying |
| // DriverHost object is deallocated. This function will return true if the underlying DriverHost |
| // object is still alive and in the linked list. Otherwise returns false. |
| virtual bool IsDriverHostValid(DriverHost* driver_host) const { return true; } |
| |
| // Destroys the dynamic child component that runs the driver associated with |
| // `node`. |
| virtual void DestroyDriverComponent(Node& node, DestroyDriverComponentCallback callback) = 0; |
| |
| virtual void RebindComposite(std::string spec, std::optional<std::string> driver_url, |
| fit::callback<void(zx::result<>)> callback) {} |
| |
| virtual bool IsTestShutdownDelayEnabled() const { return false; } |
| virtual std::weak_ptr<std::mt19937> GetShutdownTestRng() const { |
| return std::weak_ptr<std::mt19937>(); |
| } |
| }; |
| |
| enum class Collection : uint8_t { |
| kNone, |
| // Collection for boot drivers. |
| kBoot, |
| // Collection for package drivers. |
| kPackage, |
| // Collection for universe package drivers. |
| kFullPackage, |
| }; |
| |
| enum class NodeType { |
| kNormal, // Normal non-composite node. |
| kLegacyComposite, // Composite node created from the legacy system. |
| kComposite, // Composite node created from composite node specs. |
| }; |
| |
| enum class DriverState : uint8_t { |
| kStopped, // Driver is not running. |
| kBinding, // Driver's bind hook is scheduled and running. |
| kRunning, // Driver finished binding and is running. |
| }; |
| |
| class Node : public fidl::WireServer<fuchsia_driver_framework::NodeController>, |
| public fidl::WireServer<fuchsia_driver_framework::Node>, |
| public fidl::WireServer<fuchsia_component_runner::ComponentController>, |
| public fidl::WireServer<fuchsia_device::Controller>, |
| public fidl::WireAsyncEventHandler<fuchsia_driver_host::Driver>, |
| public std::enable_shared_from_this<Node>, |
| public NodeShutdownBridge { |
| public: |
| 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 = 0, |
| NodeType type = NodeType::kNormal); |
| |
| ~Node() override; |
| |
| static zx::result<std::shared_ptr<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 = 0); |
| |
| // NodeShutdownBridge |
| // Exposed for testing. |
| bool HasDriverComponent() const override { |
| return driver_component_.has_value() && driver_component_->state != DriverState::kStopped; |
| } |
| |
| void OnBind() const; |
| |
| bool is_bound() const { return driver_component_.has_value(); } |
| |
| // Begin the removal process for a Node. This function ensures that a Node is |
| // only removed after all of its children are removed. It also ensures that |
| // a Node is only removed after the driver that is bound to it has been stopped. |
| // This is safe to call multiple times. |
| // There are multiple reasons a Node's removal will be started: |
| // - The system is being stopped. |
| // - The Node had an unexpected error or disconnect |
| // During a system stop, Remove is expected to be called twice: |
| // once with |removal_set| == kPackage, and once with |removal_set| == kAll. |
| // Errors and disconnects that are unrecoverable should call Remove(kAll, nullptr). |
| void Remove(RemovalSet removal_set, NodeRemovalTracker* removal_tracker); |
| |
| // `callback` is invoked once the node has finished being added or an error |
| // has occurred. |
| void AddChild(fuchsia_driver_framework::NodeAddArgs args, |
| fidl::ServerEnd<fuchsia_driver_framework::NodeController> controller, |
| fidl::ServerEnd<fuchsia_driver_framework::Node> node, |
| AddNodeResultCallback callback); |
| |
| // Add this Node to its parents. This should be called when the node is created. Exposed for |
| // testing. |
| void AddToParents(); |
| |
| // Begins the process of restarting the node. Restarting a node includes stopping and removing |
| // all children nodes, stopping the driver that is bound to the node, and asking the NodeManager |
| // to bind the node again. The restart operation is very similar to the Remove operation, the |
| // difference being once the children are removed, and the driver stopped, we don't remove the |
| // node from the topology but instead bind the node again. |
| void RestartNode(); |
| |
| void RemoveCompositeNodeForRebind(fit::callback<void(zx::result<>)> completer); |
| |
| // Restarting a node WithRematch, means that instead of re-using the currently bound driver, |
| // another MatchDriver call will be made into the driver index to find a new driver to bind. |
| void RestartNodeWithRematch(std::optional<std::string> restart_driver_url_suffix, |
| fit::callback<void(zx::result<>)> completer); |
| void RestartNodeWithRematch(); |
| |
| void StartDriver(fuchsia_component_runner::wire::ComponentStartInfo start_info, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> controller, |
| fit::callback<void(zx::result<>)> cb); |
| |
| bool IsComposite() const { |
| return type_ == NodeType::kLegacyComposite || type_ == NodeType::kComposite; |
| } |
| |
| // Exposed for testing. |
| // Set properties to non-composite node properties containing a clone of `properties` and |
| // "DRIVER_FRAMEWORK_VERSION == 2". |
| void SetNonCompositeProperties( |
| cpp20::span<const fuchsia_driver_framework::NodeProperty> properties); |
| |
| // Evaluates the given rematch_flags against the node. Returns true if rematch should take place, |
| // false otherwise. Rematching is done based on the node type and url both matching: |
| // For node type, if the node is a composite, the rematch flags must contain the flag |
| // for the composite variant that the node is. No validation for non-composites. |
| // For the url, rematch takes place if either: |
| // - the url matches the requested_url and the 'requested' flag is available. |
| // - the url does not match and the 'non_requested' flag is available. |
| bool EvaluateRematchFlags(fuchsia_driver_development::RestartRematchFlags rematch_flags, |
| std::string_view requested_url); |
| |
| // Creates the node's topological path by combining each primary parent's name together, |
| // separated by '/'. |
| // E.g: dev/sys/topo/path |
| std::string MakeTopologicalPath() const; |
| |
| // Make the node's component moniker by making the topological path and then replacing |
| // characters not allowed by the component framework. |
| // E.g: dev.sys.topo.path |
| std::string MakeComponentMoniker() const; |
| |
| // Exposed for testing. |
| Node* GetPrimaryParent() const { |
| return parents_.empty() ? nullptr : parents_[primary_index_].lock().get(); |
| } |
| |
| // This should be used on the root node. Install the root node at the top of the devfs filesystem. |
| void SetupDevfsForRootNode(std::optional<Devfs>& devfs) { |
| devfs.emplace(devfs_device_.topological_node()); |
| } |
| |
| // This is exposed for testing. Setup this node's devfs nodes. |
| void AddToDevfsForTesting(Devnode& parent) { |
| parent.add_child(name_, std::nullopt, Devnode::Target(), devfs_device_); |
| } |
| |
| // Invoked when a bind sequence has been completed. It allows us to reply to outstanding bind |
| // requests that may have originated from the node. |
| void CompleteBind(zx::result<> result); |
| |
| ShutdownHelper& GetShutdownHelper(); |
| |
| NodeState GetNodeState() { return GetShutdownHelper().node_state(); } |
| |
| const std::string& name() const { return name_; } |
| |
| NodeType type() const { return type_; } |
| |
| const DriverHost* driver_host() const { |
| if (node_manager_.has_value() && node_manager_.value()->IsDriverHostValid(*driver_host_)) { |
| return *driver_host_; |
| } |
| |
| return nullptr; |
| } |
| |
| const std::string& driver_url() const; |
| |
| const std::vector<std::weak_ptr<Node>>& parents() const { return parents_; } |
| |
| const std::list<std::shared_ptr<Node>>& children() const { return children_; } |
| |
| fidl::ArenaBase& arena() { return arena_; } |
| |
| // TODO(https://fxbug.dev/42144926): Once FIDL wire types support a Clone() method, |
| // remove the const_cast. |
| fidl::VectorView<fuchsia_component_decl::wire::Offer> offers() const { |
| return fidl::VectorView<fuchsia_component_decl::wire::Offer>::FromExternal( |
| const_cast<decltype(offers_)&>(offers_)); |
| } |
| |
| // TODO(https://fxbug.dev/42160282): Remove const_cast once VectorView supports const. |
| fidl::VectorView<fuchsia_driver_framework::wire::NodeSymbol> symbols() const { |
| return fidl::VectorView<fuchsia_driver_framework::wire::NodeSymbol>::FromExternal( |
| const_cast<decltype(symbols_)&>(symbols_)); |
| } |
| |
| // Returns the node properties of the node and its parents if the node is a composite node. |
| // See `properties_` property for more info. |
| const fuchsia_driver_framework::wire::NodePropertyDictionary& properties() const { |
| return properties_; |
| } |
| |
| // Returns the node properties of the node or the node's parent if the node is a composite node. |
| // Returns std::nullopt if the node is a non-composite and `parent_name` is not "default". |
| // Returns std::nullopt if the parent node cannot be found. |
| // See `properties_` property for more info. |
| std::optional<cpp20::span<const fuchsia_driver_framework::wire::NodeProperty>> GetNodeProperties( |
| std::string_view parent_name = "default") const; |
| |
| const Collection& collection() const { return collection_; } |
| |
| const fuchsia_driver_framework::DriverPackageType& driver_package_type() const { |
| return driver_package_type_; |
| } |
| |
| DevfsDevice& devfs_device() { return devfs_device_; } |
| |
| bool can_multibind_composites() const { return can_multibind_composites_; } |
| |
| void set_collection(Collection collection) { collection_ = collection; } |
| |
| void set_driver_package_type(fuchsia_driver_framework::DriverPackageType driver_package_type) { |
| driver_package_type_ = driver_package_type; |
| } |
| |
| void set_offers(std::vector<fuchsia_component_decl::wire::Offer> offers) { |
| offers_ = std::move(offers); |
| } |
| void set_symbols(std::vector<fuchsia_driver_framework::wire::NodeSymbol> symbols) { |
| symbols_ = std::move(symbols); |
| } |
| |
| void set_can_multibind_composites(bool can_multibind_composites) { |
| can_multibind_composites_ = can_multibind_composites; |
| } |
| |
| ShutdownIntent shutdown_intent() { return GetShutdownHelper().shutdown_intent(); } |
| |
| private: |
| struct DriverComponent { |
| DriverComponent(Node& node, std::string url, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> controller, |
| fidl::ClientEnd<fuchsia_driver_host::Driver> driver); |
| |
| // This represents the Driver Component within the Component Framework. |
| // When this is closed with an epitaph it signals to the Component Framework |
| // that this driver component has stopped. |
| fidl::ServerBinding<fuchsia_component_runner::ComponentController> component_controller_ref; |
| fidl::WireClient<fuchsia_driver_host::Driver> driver; |
| std::string driver_url; |
| |
| DriverState state = DriverState::kBinding; |
| }; |
| |
| // fidl::WireServer<fuchsia_device::Controller> |
| void ConnectToDeviceFidl(ConnectToDeviceFidlRequestView request, |
| ConnectToDeviceFidlCompleter::Sync& completer) override; |
| void ConnectToController(ConnectToControllerRequestView request, |
| ConnectToControllerCompleter::Sync& completer) override; |
| void Bind(BindRequestView request, BindCompleter::Sync& completer) override; |
| void Rebind(RebindRequestView request, RebindCompleter::Sync& completer) override; |
| void UnbindChildren(UnbindChildrenCompleter::Sync& completer) override; |
| void ScheduleUnbind(ScheduleUnbindCompleter::Sync& completer) override; |
| void GetTopologicalPath(GetTopologicalPathCompleter::Sync& completer) override; |
| |
| // This is called when fuchsia_driver_host::Driver is closed. |
| void on_fidl_error(fidl::UnbindInfo info) override; |
| |
| // fidl::WireServer<fuchsia_component_runner::ComponentController> |
| // We ignore these signals. |
| void Stop(StopCompleter::Sync& completer) override; |
| void Kill(KillCompleter::Sync& completer) override; |
| |
| // fidl::WireServer<fuchsia_driver_framework::NodeController> |
| void Remove(RemoveCompleter::Sync& completer) override; |
| void RequestBind(RequestBindRequestView request, RequestBindCompleter::Sync& completer) override; |
| void handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_driver_framework::NodeController> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) override; |
| |
| // fidl::WireServer<fuchsia_driver_framework::Node> |
| void AddChild(AddChildRequestView request, AddChildCompleter::Sync& completer) override; |
| void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_driver_framework::Node> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) override; |
| |
| // NodeShutdownBridge |
| std::pair<std::string, Collection> GetRemovalTrackerInfo() override; |
| void StopDriver() override; |
| void StopDriverComponent() override; |
| void FinishShutdown(fit::callback<void()> shutdown_callback) override; |
| bool HasChildren() const override { return !children_.empty(); } |
| bool HasDriver() const override { |
| return driver_component_.has_value() && driver_component_->driver; |
| } |
| |
| bool IsPendingBind() const override { |
| if (!driver_component_) { |
| return false; |
| } |
| return driver_component_->driver && driver_component_->state == DriverState::kBinding; |
| } |
| |
| void BindHelper(bool force_rebind, std::optional<std::string> driver_url_suffix, |
| fit::callback<void(zx_status_t)> on_bind_complete); |
| |
| // Shutdown helpers: |
| // Remove a child from this parent |
| void RemoveChild(const std::shared_ptr<Node>& child); |
| |
| // Start the node's driver back up. |
| void FinishRestart(); |
| |
| // Clear out the values associated with the driver on the driver host. |
| void ClearHostDriver(); |
| |
| // Call `callback` once child node with the name `name` has been removed. |
| // Returns an error if a child node with the name `name` exists and is not in |
| // the process of being removed. |
| void WaitForChildToExit( |
| std::string_view name, |
| fit::callback<void(fit::result<fuchsia_driver_framework::wire::NodeError>)> callback); |
| |
| std::shared_ptr<BindResultTracker> CreateBindResultTracker(); |
| |
| fit::result<fuchsia_driver_framework::wire::NodeError, std::shared_ptr<Node>> AddChildHelper( |
| fuchsia_driver_framework::NodeAddArgs args, |
| fidl::ServerEnd<fuchsia_driver_framework::NodeController> controller, |
| fidl::ServerEnd<fuchsia_driver_framework::Node> node); |
| |
| // Set the inspect data and publish it. This should be called once as the node is being created. |
| void SetAndPublishInspect(); |
| |
| // Creates a passthrough for the associated devfs node that will connect to |
| // the device controller of this node if no connector provided, or the connection |
| // type is not supported. |
| Devnode::Target 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); |
| |
| zx_status_t ConnectControllerInterface(fidl::ServerEnd<fuchsia_device::Controller> server_end); |
| zx_status_t ConnectDeviceInterface(zx::channel channel); |
| |
| // Set properties to composite node properties containing a clone of the node properties of |
| // `parents_`. |
| void SetCompositeParentProperties( |
| const std::vector<fuchsia_driver_framework::NodePropertyEntry>& parent_properties); |
| |
| // Update `properties_dict_` to identify the contents of `properties_`. |
| void SynchronizePropertiesDict(); |
| |
| std::string name_; |
| |
| NodeType type_; |
| |
| // If this is a composite device, this stores the list of each parent's names. |
| std::vector<std::string> parents_names_; |
| std::vector<std::weak_ptr<Node>> parents_; |
| uint32_t primary_index_ = 0; |
| std::list<std::shared_ptr<Node>> children_; |
| fit::nullable<NodeManager*> node_manager_; |
| async_dispatcher_t* const dispatcher_; |
| |
| // TODO(https://fxbug.dev/42073492): Set this flag from NodeAddArgs. |
| bool can_multibind_composites_ = true; |
| |
| bool host_restart_on_crash_ = false; |
| |
| fidl::Arena<128> arena_; |
| std::vector<fuchsia_component_decl::wire::Offer> offers_; |
| std::vector<fuchsia_driver_framework::wire::NodeSymbol> symbols_; |
| |
| // Contains the properties of the node or its parents if the node is a composite or legacy |
| // composite node. "default" entry refers to the node's properties if the node is a |
| // non-composite. "default" entry refers to the primary parent node's properties if the node is a |
| // composite. All referenced data is owned by `arena_`. Make sure to call |
| // `Node::SynchronizePropertiesDict()` when modified. |
| fuchsia_driver_framework::wire::NodePropertyDictionary properties_; |
| |
| // Maps the node properties of the entries of `properties_` by their name. |
| std::unordered_map<std::string, cpp20::span<const fuchsia_driver_framework::wire::NodeProperty>> |
| properties_dict_; |
| |
| Collection collection_ = Collection::kNone; |
| fuchsia_driver_framework::DriverPackageType driver_package_type_; |
| fit::nullable<DriverHost*> driver_host_; |
| |
| // An outstanding rebind request. |
| std::optional<fit::callback<void(zx::result<>)>> pending_bind_completer_; |
| |
| // Set by RemoveCompositeNodeForRebind(). Invoked when the node finished shutting down. |
| std::optional<fit::callback<void(zx::result<>)>> composite_rebind_completer_; |
| |
| std::optional<std::string> restart_driver_url_suffix_; |
| |
| // Invoked when the node has been fully removed. |
| fit::callback<void()> remove_complete_callback_; |
| |
| std::optional<DriverComponent> driver_component_; |
| std::optional<fidl::ServerBinding<fuchsia_driver_framework::Node>> node_ref_; |
| std::optional<fidl::ServerBinding<fuchsia_driver_framework::NodeController>> controller_ref_; |
| |
| // The device's inspect information. |
| DeviceInspect inspect_; |
| |
| std::unique_ptr<ShutdownHelper> shutdown_helper_; |
| |
| // This represents the node's presence in devfs, both it's topological path and it's class path. |
| DevfsDevice devfs_device_; |
| |
| // Connector to service exported to devfs |
| std::optional<fidl::ClientEnd<fuchsia_device_fs::Connector>> devfs_connector_; |
| // Connector to fuchsia_device/Controller exported to devfs. |
| // This is only used by the compat shim to override the node's own controller |
| // implementation. |
| fidl::ServerBindingGroup<fuchsia_device::Controller> dev_controller_bindings_; |
| std::unique_ptr<ControllerAllowlistPassthrough> controller_allowlist_passthrough_; |
| |
| // Completers that are waiting for the node to unbind all of its children. |
| std::vector<UnbindChildrenCompleter::Async> unbinding_children_completers_; |
| }; |
| |
| } // namespace driver_manager |
| |
| #endif // SRC_DEVICES_BIN_DRIVER_MANAGER_NODE_H_ |