Skip to main content

starnix_modules_nanohub/
nanohub.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::datachannel_file::DataChannelDevice;
6use crate::nanohub_comms_directory::{
7    build_display_comms_directory, build_nanohub_comms_directory,
8};
9use crate::nanohub_gpio_stub::register_gpio_chip_device;
10use crate::socket_tunnel_file::register_socket_tunnel_device;
11use fidl_fuchsia_hardware_google_nanohub as fnanohub;
12use fidl_fuchsia_hardware_serial as fserial;
13use fuchsia_async::TimeoutExt as _;
14use futures::TryStreamExt;
15use starnix_core::device::service_watcher::ServiceWatcher;
16use starnix_core::task::Kernel;
17use starnix_logging::{log_error, log_info, log_warn};
18use starnix_modules_serial::SerialDevice;
19use starnix_uapi::auth::FsCred;
20
21use std::sync::Arc;
22
23const SERIAL_DIRECTORY: &str = "/dev/class/serial";
24const GET_IDENTIFIER_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
25
26pub fn nanohub_device_init(kernel: &Arc<Kernel>) {
27    register_gpio_chip_device(kernel, "gpiochipwake0");
28
29    register_socket_tunnel_device(
30        kernel,
31        "/dev/display_comms".into(),
32        "display_comms".into(),
33        "display".into(),
34        build_display_comms_directory,
35    );
36
37    // /dev/nanohub_comms requires a set of additional sysfs nodes, so create this route
38    // with a specialized directory.
39    register_socket_tunnel_device(
40        kernel,
41        "/dev/nanohub_comms".into(),
42        "nanohub_comms".into(),
43        "nanohub".into(),
44        build_nanohub_comms_directory,
45    );
46
47    // Spawn future to bind and configure serial device
48    kernel.kthreads.spawn_future(
49        {
50            let kernel = kernel.clone();
51            move || async move { register_serial_device(kernel).await }
52        },
53        "register_serial_device",
54    );
55
56    register_datachannel_devices(kernel);
57}
58
59fn register_datachannel_devices(kernel: &Kernel) {
60    ServiceWatcher::register_with_probe(
61        kernel,
62        fnanohub::StarnixDataChannelServiceMarker,
63        probe_datachannel_device,
64        |current_task, (data_channel_service_proxy, name), scope| {
65            let kernel = current_task.kernel();
66            let registry = &kernel.device_registry;
67            let device_class = registry.objects.get_or_create_class("nanohub".into());
68            match registry.add_dyn_device(
69                kernel,
70                name.as_bytes().into(),
71                device_class,
72                DataChannelDevice::new(
73                    data_channel_service_proxy,
74                    kernel.suspend_resume_manager.clone(),
75                ),
76            ) {
77                Ok(device) => {
78                    scope.add_scoped_device(device);
79                }
80                Err(e) => log_warn!("Failed to add datachannel device: {:?}", e),
81            }
82        },
83    );
84}
85
86fn is_valid_datachannel_name(name: &str) -> bool {
87    !name.is_empty() && name != "." && name != ".." && !name.contains(['/', '\0'])
88}
89
90async fn probe_datachannel_device(
91    data_channel_service_proxy: Arc<fnanohub::StarnixDataChannelServiceProxy>,
92) -> Option<(Arc<fnanohub::StarnixDataChannelServiceProxy>, String)> {
93    // Connect to `waitable` only for the duration of `GetIdentifier`; keeping an unbound
94    // `waitable` connection open reserves a pooled endpoint in the nanohub driver.
95    let device_proxy = match data_channel_service_proxy.connect_to_waitable() {
96        Ok(proxy) => proxy,
97        Err(e) => {
98            log_error!("Failed to connect to waitable data channel: {:?}", e);
99            return None;
100        }
101    };
102    match async { Some(device_proxy.get_identifier().await) }
103        .on_timeout(GET_IDENTIFIER_TIMEOUT, || None)
104        .await
105    {
106        Some(Ok(fnanohub::UnboundWaitableDataChannelGetIdentifierResponse {
107            name: Some(name),
108            ..
109        })) if is_valid_datachannel_name(&name) => Some((data_channel_service_proxy, name)),
110        Some(Ok(id)) => {
111            log_error!(
112                "Data channel device returned missing or invalid name {:?}, skipping registration",
113                id.name
114            );
115            None
116        }
117        Some(Err(e)) => {
118            log_error!("Failed to get device info: {:?}", e);
119            None
120        }
121        None => {
122            log_error!("Timed out getting data channel device identifier");
123            None
124        }
125    }
126}
127
128async fn register_serial_device(kernel: Arc<Kernel>) {
129    // TODO Move this to expect once test support is enabled
130    let dir =
131        match fuchsia_fs::directory::open_in_namespace(SERIAL_DIRECTORY, fuchsia_fs::PERM_READABLE)
132        {
133            Ok(dir) => dir,
134            Err(e) => {
135                log_error!("Failed to open serial directory: {:}", e);
136                return;
137            }
138        };
139
140    let mut watcher = match fuchsia_fs::directory::Watcher::new(&dir).await {
141        Ok(watcher) => watcher,
142        Err(e) => {
143            log_info!("Failed to create directory watcher for serial device: {:}", e);
144            return;
145        }
146    };
147
148    loop {
149        match watcher.try_next().await {
150            Ok(Some(watch_msg)) => {
151                let filename = watch_msg
152                    .filename
153                    .as_path()
154                    .to_str()
155                    .expect("Failed to convert watch_msg to str");
156                if filename == "." {
157                    continue;
158                }
159                if watch_msg.event == fuchsia_fs::directory::WatchEvent::ADD_FILE
160                    || watch_msg.event == fuchsia_fs::directory::WatchEvent::EXISTING
161                {
162                    let instance_path = format!("{}/{}", SERIAL_DIRECTORY, filename);
163                    let (client_channel, server_channel) = zx::Channel::create();
164                    if let Err(_) = fdio::service_connect(&instance_path, server_channel) {
165                        continue;
166                    }
167
168                    // `fuchsia.hardware.serial` exposes a `DeviceProxy` type used for binding with
169                    // a `Device` type. This should not be confused with the `DeviceProxy` generated
170                    // by FIDL
171                    let device_proxy = fserial::DeviceProxy_SynchronousProxy::new(client_channel);
172                    let (serial_proxy, server_end) =
173                        fidl::endpoints::create_sync_proxy::<fserial::DeviceMarker>();
174
175                    // Instruct the serial driver to bind the connection to the underlying device
176                    if let Err(_) = device_proxy.get_channel(server_end) {
177                        continue;
178                    }
179
180                    // Fetch the device class to see if this is the correct instance
181                    let device_class = match serial_proxy.get_class(zx::MonotonicInstant::INFINITE)
182                    {
183                        Ok(class) => class,
184                        Err(_) => continue,
185                    };
186
187                    if device_class == fserial::Class::Mcu {
188                        let serial_device = SerialDevice::new(
189                            &kernel,
190                            serial_proxy.into_channel().into(),
191                            FsCred::root(),
192                        )
193                        .expect("Can create SerialDevice wrapper");
194
195                        // TODO This will register with an incorrect device number. We should be
196                        // dynamically registering a major device and this should be minor device 1
197                        // of that major device.
198                        let registry = &kernel.device_registry;
199                        registry
200                            .add_dyn_device(
201                                &kernel,
202                                "ttyHS1".into(),
203                                registry.objects.tty_class(),
204                                serial_device,
205                            )
206                            .expect("Can add serial device");
207                        break;
208                    }
209                }
210            }
211            Ok(None) => {
212                break;
213            }
214            Err(e) => {
215                log_error!("Serial driver stream ended with error: {:}", e);
216                break;
217            }
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::is_valid_datachannel_name;
225
226    #[::fuchsia::test]
227    fn validates_datachannel_identifier_names() {
228        assert!(is_valid_datachannel_name("nanohub_ch0"));
229        assert!(is_valid_datachannel_name("test_endpoint1"));
230
231        for invalid_name in ["", ".", "..", "bad/name", "bad\0name"] {
232            assert!(
233                !is_valid_datachannel_name(invalid_name),
234                "expected {invalid_name:?} to be rejected"
235            );
236        }
237    }
238}