Skip to main content

fdf_component/
context.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{Incoming, Node};
6use fuchsia_async::ScopeHandle;
7use fuchsia_component::server::{ServiceFs, ServiceObjTrait};
8use fuchsia_component_config::Config;
9use fuchsia_inspect::Inspector;
10use inspect_runtime::PublishOptions;
11use log::error;
12use namespace::Namespace;
13use zx::Status;
14
15use fdf::DispatcherRef;
16use fidl_fuchsia_driver_framework::DriverStartArgs;
17
18pub use fuchsia_inspect_contrib::content_publisher as inspect_publisher;
19
20/// The context arguments passed to the driver in its start arguments.
21#[non_exhaustive]
22pub struct DriverContext {
23    /// A reference to the root [`fdf::Dispatcher`] for this driver.
24    pub root_dispatcher: DispatcherRef<'static>,
25    /// The original [`DriverStartArgs`] passed in as start arguments, minus any parts that were
26    /// used to construct other elements of [`Self`].
27    pub start_args: DriverStartArgs,
28    /// The incoming namespace constructed from [`DriverStartArgs::incoming`]. Since producing this
29    /// consumes the incoming namespace from [`Self::start_args`], that will always be [`None`].
30    pub incoming: Incoming,
31}
32
33impl DriverContext {
34    /// Binds the node proxy client end from the start args into a [`NodeProxy`] that can be used
35    /// to add child nodes. Dropping this proxy will result in the node being removed and the
36    /// driver starting shutdown, so it should be bound and stored in your driver object in its
37    /// [`crate::Driver::start`] method.
38    ///
39    /// After calling this, [`DriverStartArgs::node`] in [`Self::start_args`] will be `None`.
40    ///
41    /// Returns [`Status::INVALID_ARGS`] if the node client end is not present in the start
42    /// arguments.
43    pub fn take_node(&mut self) -> Result<Node, Status> {
44        let node_client = self.start_args.node.take().ok_or(Status::INVALID_ARGS)?;
45        Ok(Node::from(node_client.into_proxy()))
46    }
47
48    /// Returns the component config.
49    ///
50    /// After calling this, [`DriverStartArgs::config`] in [`Self::start_args`] will be `None`.
51    ///
52    /// Returns [`Status::INVALID_ARGS`] if the config is not present in the start arguments.
53    pub fn take_config<C: Config>(&mut self) -> Result<C, Status> {
54        let vmo = self.start_args.config.take().ok_or(Status::INVALID_ARGS)?;
55        Ok(Config::from_vmo(&vmo).expect("Config VMO handle must be valid."))
56    }
57
58    /// Serves the given [`ServiceFs`] on the node's outgoing directory. This can only be called
59    /// once, and after this the [`DriverStartArgs::outgoing_dir`] member will be [`None`].
60    ///
61    /// Logs an error and returns [`Status::INVALID_ARGS`] if the outgoing directory server end is
62    /// not present in the start arguments, or [`Status::INTERNAL`] if serving the connection
63    /// failed.
64    pub fn serve_outgoing<O: ServiceObjTrait>(
65        &mut self,
66        outgoing_fs: &mut ServiceFs<O>,
67    ) -> Result<(), Status> {
68        let Some(outgoing_dir) = self.start_args.outgoing_dir.take() else {
69            error!("Tried to serve on outgoing directory but it wasn't available");
70            return Err(Status::INVALID_ARGS);
71        };
72        outgoing_fs.serve_connection(outgoing_dir).map_err(|err| {
73            error!("Failed to serve outgoing directory: {err}");
74            Status::INTERNAL
75        })?;
76
77        Ok(())
78    }
79
80    /// Spawns a server handling `fuchsia.inspect.Tree` requests and a handle
81    /// to the `fuchsia.inspect.Tree` is published using `fuchsia.inspect.InspectSink`.
82    ///
83    /// Whenever the client wishes to stop publishing Inspect, the Controller may be dropped.
84    pub fn publish_inspect(&self, inspector: &Inspector, scope: ScopeHandle) -> Result<(), Status> {
85        let client = self.incoming.connect_protocol().map_err(|err| {
86            error!("Error connecting to inspect : {err}");
87            Status::INTERNAL
88        })?;
89
90        let task = inspect_runtime::publish(
91            inspector,
92            PublishOptions::default().on_inspect_sink_client(client),
93        )
94        .ok_or(Status::INTERNAL)?;
95
96        scope.spawn_local(task);
97
98        Ok(())
99    }
100
101    /// Returns an inspect content publisher that acts as a stream for requests for inspect data.
102    pub fn inspect_content_publisher(&self) -> Result<inspect_publisher::ContentPublisher, Status> {
103        let inspect_sink_client = self.incoming.connect_protocol().map_err(|err| {
104            error!("Error connecting to inspect : {err}");
105            Status::INTERNAL
106        })?;
107
108        let options = inspect_publisher::PublishOptions { inspect_sink_client };
109        inspect_publisher::content_publisher(options).map_err(|err| {
110            error!("Error creating inspect content publisher: {err}");
111            Status::INTERNAL
112        })
113    }
114
115    /// Returns the VMAR which the driver can use to map memory.
116    ///
117    /// If the driver was not provided with an explicit VMAR in its start arguments, the root VMAR
118    /// is returned.
119    pub fn vmar(&self) -> zx::Unowned<'_, zx::Vmar> {
120        // NB: We can't use `map_or_else` here, because the compiler gets confused about lifetimes
121        // when attempting to unify the types of the two function arguments.
122        if let Some(vmar) = self.start_args.vmar.as_ref().map(zx::Unowned::new) {
123            vmar
124        } else {
125            fuchsia_runtime::vmar_root_self()
126        }
127    }
128
129    pub(crate) fn new(
130        root_dispatcher: DispatcherRef<'static>,
131        mut start_args: DriverStartArgs,
132    ) -> Result<Self, Status> {
133        let incoming_namespace: Namespace = start_args
134            .incoming
135            .take()
136            .unwrap_or_default()
137            .try_into()
138            .map_err(|_| Status::INVALID_ARGS)?;
139        let incoming = Incoming::from(incoming_namespace);
140        Ok(DriverContext { root_dispatcher, start_args, incoming })
141    }
142
143    pub(crate) fn start_logging(&self, driver_name: &str) -> Result<(), Status> {
144        let log_client = match self.incoming.connect_protocol() {
145            Ok(log_client) => log_client,
146            Err(err) => {
147                eprintln!(
148                    "Error connecting to log sink proxy at driver startup: {err}. Continuing without logging."
149                );
150                return Ok(());
151            }
152        };
153
154        if let Err(e) = driver_diagnostics_log::initialize(
155            driver_diagnostics_log::PublishOptions::default()
156                .use_log_sink(log_client)
157                .tags(&["driver", driver_name]),
158        ) {
159            eprintln!("Error initializing logging at driver startup: {e}");
160        }
161        Ok(())
162    }
163}