Skip to main content

driver_manager_node/
devfs.rs

1// Copyright 2026 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::node::Node;
6use driver_manager_devfs::{Connector, ConnectorMsg, TopologicalDevnode};
7use fidl::endpoints::{ClientEnd, ServerEnd};
8use futures::channel::mpsc;
9use futures::{StreamExt, TryStreamExt};
10use log::{error, warn};
11use phf::{Map, Set, phf_map, phf_set};
12use std::rc::{Rc, Weak};
13use {fidl_fuchsia_device as fdevice, fidl_fuchsia_device_fs as fdevfs, fuchsia_async as fasync};
14
15const ALLOW_ALL_USES: &str = "Allow_all_uses";
16
17// LINT.IfChange
18static CONTROLLER_ALLOWLISTS: Map<&'static str, Set<&'static str>> = phf_map! {
19    "ConnectToController" => phf_set! {
20        "block",
21        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/sample_driver.cm",
22        "driver_runner_test",
23    },
24    "ConnectToDeviceFidl" => phf_set! {
25        "block",
26        "driver_runner_test",
27        "nand",
28        "skip-block",
29        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/fvm.cm",
30        "No_class_name_but_driver_url_is_fuchsia-boot:///gpt#meta/gpt.cm",
31        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/gpt.cm",
32        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/nand-broker.cm",
33        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/sample_driver.cm",
34    },
35    "Bind" => phf_set! {
36        "block",
37        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/test.cm",
38        "driver_runner_test",
39    },
40    "Rebind" => phf_set! {
41        "block",
42        "driver_runner_test",
43        "No_class_name_but_driver_url_is_owned by parent",
44        "nand",
45        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/ddk-lifecycle-test.cm",
46    },
47    "UnbindChildren" => phf_set! {
48        "block",
49        "driver_runner_test",
50    },
51    "ScheduleUnbind" => phf_set! {
52        "bt-emulator",
53        "driver_runner_test",
54        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/ddk-lifecycle-test.cm",
55        "No_class_name_but_driver_url_is_fuchsia-boot:///dtr#meta/fvm.cm",
56        "No_class_name_but_driver_url_is_fuchsia-boot:///fvm#meta/fvm.cm",
57        "No_class_name_but_driver_url_is_owned by parent",
58    },
59    "GetTopologicalPath" => phf_set! {
60        "Allow_all_uses",
61    },
62};
63// LINT.ThenChange(//src/devices/bin/driver_manager/controller_allowlist_passthrough.cc)
64
65pub struct ControllerAllowlistPassthrough {
66    node: Weak<Node>,
67    class_name: String,
68    compat_client: Option<fdevice::ControllerProxy>,
69    scope: fasync::Scope,
70}
71
72impl ControllerAllowlistPassthrough {
73    pub fn new(
74        node: Weak<Node>,
75        class_name: String,
76        controller_connector: Option<ClientEnd<fdevfs::ConnectorMarker>>,
77        parent_scope: &fasync::ScopeHandle,
78    ) -> Rc<Self> {
79        let compat_client = controller_connector.and_then(|connector| {
80            let (client, server) = fidl::endpoints::create_proxy::<fdevice::ControllerMarker>();
81            let connector_proxy = connector.into_proxy();
82            connector_proxy.connect(server.into()).ok()?;
83            Some(client)
84        });
85
86        let scope = parent_scope.new_child_with_name("controller passthrough");
87        Rc::new(Self { node, class_name, compat_client, scope })
88    }
89
90    fn check_allowlist(&self, function_name: &str) {
91        let allowlist = CONTROLLER_ALLOWLISTS.get(function_name).unwrap();
92        if allowlist.contains(ALLOW_ALL_USES) {
93            return;
94        }
95        assert!(
96            allowlist.contains(self.class_name.as_str()),
97            "\nUndeclared DEVFS_USAGE detected: {} is using {}.\n",
98            self.class_name,
99            function_name
100        );
101    }
102
103    pub fn serve(
104        self: &Rc<Self>,
105        server_end: ServerEnd<fdevice::ControllerMarker>,
106    ) -> Result<(), zx::Status> {
107        let mut stream = server_end.into_stream();
108        let this = self.clone();
109        self.scope.spawn_local(async move {
110            while let Ok(Some(request)) = stream.try_next().await {
111                this.handle_request(request).await.unwrap_or_else(|e| {
112                    error!("Error handling controller request: {}", e);
113                });
114            }
115        });
116        Ok(())
117    }
118
119    async fn handle_request(
120        self: &Rc<Self>,
121        request: fdevice::ControllerRequest,
122    ) -> Result<(), fidl::Error> {
123        match request {
124            fdevice::ControllerRequest::ConnectToDeviceFidl { server, .. } => {
125                self.check_allowlist("ConnectToDeviceFidl");
126                if let Some(client) = &self.compat_client {
127                    client.connect_to_device_fidl(server)?;
128                } else if let Some(node) = self.node.upgrade() {
129                    node.connect_to_device_fidl(server);
130                }
131            }
132            fdevice::ControllerRequest::ConnectToController { server, .. } => {
133                self.check_allowlist("ConnectToController");
134                self.serve(server).unwrap();
135            }
136            fdevice::ControllerRequest::Bind { driver, responder } => {
137                self.check_allowlist("Bind");
138                if let Some(client) = &self.compat_client {
139                    let result = client.bind(driver.as_str()).await?;
140                    responder.send(result)?;
141                } else if let Some(node) = self.node.upgrade() {
142                    responder.send(node.bind(driver).await.map_err(zx::Status::into_raw))?;
143                } else {
144                    responder.send(Err(zx::Status::INTERNAL.into_raw()))?;
145                }
146            }
147            fdevice::ControllerRequest::Rebind { driver, responder } => {
148                self.check_allowlist("Rebind");
149                if let Some(client) = &self.compat_client {
150                    let result = client.rebind(driver.as_ref()).await?;
151                    responder.send(result)?;
152                } else if let Some(node) = self.node.upgrade() {
153                    responder
154                        .send(node.rebind(Some(driver)).await.map_err(zx::Status::into_raw))?;
155                } else {
156                    responder.send(Err(zx::Status::INTERNAL.into_raw()))?;
157                }
158            }
159            fdevice::ControllerRequest::UnbindChildren { responder } => {
160                self.check_allowlist("UnbindChildren");
161                if let Some(client) = &self.compat_client {
162                    let result = client.unbind_children().await?;
163                    responder.send(result)?;
164                } else if let Some(node) = self.node.upgrade() {
165                    let result = node.unbind_children().await.map_err(zx::Status::into_raw);
166                    responder.send(result)?;
167                } else {
168                    responder.send(Err(zx::Status::INTERNAL.into_raw()))?;
169                }
170            }
171            fdevice::ControllerRequest::ScheduleUnbind { responder } => {
172                self.check_allowlist("ScheduleUnbind");
173                if let Some(client) = &self.compat_client {
174                    let result = client.schedule_unbind().await?;
175                    responder.send(result)?;
176                } else if let Some(node) = self.node.upgrade() {
177                    node.schedule_unbind();
178                    responder.send(Ok(()))?;
179                } else {
180                    responder.send(Err(zx::Status::INTERNAL.into_raw()))?;
181                }
182            }
183            fdevice::ControllerRequest::GetTopologicalPath { responder } => {
184                self.check_allowlist("GetTopologicalPath");
185                if let Some(node) = self.node.upgrade() {
186                    let path = format!("/{}", node.make_topological_path(false));
187                    responder.send(Ok(&path))?;
188                } else {
189                    responder.send(Err(zx::Status::INTERNAL.into_raw()))?;
190                }
191            }
192        }
193        Ok(())
194    }
195}
196
197impl Node {
198    pub fn setup_devfs_for_root_node(&self, root: TopologicalDevnode) {
199        let mut device = self.device();
200        device.topological = Some(root);
201        self.set_device(device);
202    }
203
204    pub(crate) fn connect_to_device_fidl(&self, server: zx::Channel) {
205        if let Some(connector) = self.protocol_connector()
206            && let Err(e) = connector.connect(server)
207        {
208            error!("Failed to connect to device fidl: {}", e);
209        }
210    }
211
212    pub(crate) fn connect_to_controller(&self, server_end: ServerEnd<fdevice::ControllerMarker>) {
213        if let Some(passthrough) = self.controller_allowlist_passthrough() {
214            let _ = passthrough.serve(server_end);
215        } else {
216            warn!(
217                concat!(
218                    "Connection to {} controller interface failed, as that node ",
219                    "did not include controller support in its DevAddArgs"
220                ),
221                self.name()
222            );
223        }
224    }
225
226    pub(crate) fn create_devfs_passthrough(
227        self: &Rc<Self>,
228        protocol_connector: Option<ClientEnd<fdevfs::ConnectorMarker>>,
229        controller_connector: Option<ClientEnd<fdevfs::ConnectorMarker>>,
230        allow_controller_connection: bool,
231        class_name: String,
232    ) -> Connector {
233        if allow_controller_connection {
234            let passthrough = ControllerAllowlistPassthrough::new(
235                self.weak_self.clone(),
236                class_name,
237                controller_connector,
238                self.scope.as_handle(),
239            );
240            self.set_controller_allowlist_passthrough(passthrough);
241        }
242        if let Some(c) = protocol_connector {
243            self.set_protocol_connector(c.into_proxy());
244        }
245        let weak_node = self.weak_self.clone();
246        let node_name = self.name().to_string();
247        let (tx, mut rx) = mpsc::unbounded::<ConnectorMsg>();
248        self.scope.spawn_local(async move {
249            while let Some(msg) = rx.next().await {
250                if let Some(node) = weak_node.upgrade() {
251                    match msg {
252                        ConnectorMsg::Controller(server_end) => {
253                            node.connect_to_controller(server_end);
254                        }
255                        ConnectorMsg::Protocol(server_end) => {
256                            node.connect_to_device_fidl(server_end);
257                        }
258                    }
259                } else {
260                    error!("Node was freed before it was used for {}.", node_name);
261                }
262            }
263        });
264        tx
265    }
266}