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::socket_tunnel_file::register_socket_tunnel_device;
10use fuchsia_component::client::Service;
11use futures::TryStreamExt;
12use starnix_core::device::serial::SerialDevice;
13use starnix_core::fs::sysfs::build_device_directory;
14use starnix_core::task::{CurrentTask, Kernel};
15use starnix_logging::{log_error, log_info, log_warn};
16use starnix_sync::{Locked, Unlocked};
17use std::ops::DerefMut;
18use std::sync::Arc;
19use {fidl_fuchsia_hardware_google_nanohub as fnanohub, fidl_fuchsia_hardware_serial as fserial};
20
21const SERIAL_DIRECTORY: &str = "/dev/class/serial";
22
23pub fn nanohub_device_init(locked: &mut Locked<Unlocked>, current_task: &CurrentTask) {
24    register_socket_tunnel_device(
25        locked,
26        current_task,
27        "/dev/display_comms".into(),
28        "display_comms".into(),
29        "display".into(),
30        build_display_comms_directory,
31    );
32
33    // /dev/nanohub_comms requires a set of additional sysfs nodes, so create this route
34    // with a specialized directory.
35    register_socket_tunnel_device(
36        locked,
37        current_task,
38        "/dev/nanohub_comms".into(),
39        "nanohub_comms".into(),
40        "nanohub".into(),
41        build_nanohub_comms_directory,
42    );
43
44    // Spawn future to bind and configure serial device
45    current_task.kernel().kthreads.spawn_future(
46        {
47            let kernel = current_task.kernel().clone();
48            move || async move { register_serial_device(kernel).await }
49        },
50        "register_serial_device",
51    );
52
53    current_task.kernel().kthreads.spawn_future(
54        {
55            let kernel = current_task.kernel().clone();
56            move || async move { register_datachannel_devices(kernel).await }
57        },
58        "register_datachannel_devices",
59    );
60}
61
62async fn register_datachannel_devices(kernel: Arc<Kernel>) {
63    let current_task = kernel.kthreads.system_task();
64    let service = match Service::open(fnanohub::StarnixDataChannelServiceMarker) {
65        Ok(service) => service,
66        Err(e) => {
67            log_warn!("Failed to open DriverService: {:?}", e);
68            return;
69        }
70    };
71    let mut watcher = match service.watch().await {
72        Ok(watcher) => watcher,
73        Err(e) => {
74            log_warn!("Failed to create watcher: {:?}", e);
75            return;
76        }
77    };
78
79    while let Ok(Some(data_channel_service_proxy)) = watcher.try_next().await {
80        let name = match (|| {
81            let device_proxy = data_channel_service_proxy.connect_to_waitable_sync()?;
82            let id = device_proxy.get_identifier(zx::MonotonicInstant::INFINITE)?;
83            Ok::<std::option::Option<std::string::String>, fidl::Error>(id.name)
84        })() {
85            Ok(Some(name)) => name,
86            Ok(None) => {
87                log_error!("Data channel device has no name, skipping registration");
88                continue;
89            }
90            Err(e) => {
91                log_error!("Failed to get device info: {:?}", e);
92                continue;
93            }
94        };
95
96        let registry = &kernel.device_registry;
97
98        let device_class =
99            registry.objects.get_or_create_class("nanohub".into(), registry.objects.virtual_bus());
100
101        if let Err(e) = registry.register_dyn_device_with_dir(
102            current_task.kernel().kthreads.unlocked_for_async().deref_mut(),
103            current_task,
104            name.as_bytes().into(),
105            device_class,
106            build_device_directory,
107            DataChannelDevice::new(
108                data_channel_service_proxy,
109                current_task.kernel().suspend_resume_manager.clone(),
110            ),
111        ) {
112            log_warn!("Failed to register datachannel device: {:?}", e);
113        }
114    }
115}
116
117async fn register_serial_device(kernel: Arc<Kernel>) {
118    // TODO Move this to expect once test support is enabled
119    let dir =
120        match fuchsia_fs::directory::open_in_namespace(SERIAL_DIRECTORY, fuchsia_fs::PERM_READABLE)
121        {
122            Ok(dir) => dir,
123            Err(e) => {
124                log_error!("Failed to open serial directory: {:}", e);
125                return;
126            }
127        };
128
129    let mut watcher = match fuchsia_fs::directory::Watcher::new(&dir).await {
130        Ok(watcher) => watcher,
131        Err(e) => {
132            log_info!("Failed to create directory watcher for serial device: {:}", e);
133            return;
134        }
135    };
136
137    loop {
138        match watcher.try_next().await {
139            Ok(Some(watch_msg)) => {
140                let current_task = kernel.kthreads.system_task();
141                let mut locked = kernel.kthreads.unlocked_for_async();
142                let locked = &mut locked;
143                let filename = watch_msg
144                    .filename
145                    .as_path()
146                    .to_str()
147                    .expect("Failed to convert watch_msg to str");
148                if filename == "." {
149                    continue;
150                }
151                if watch_msg.event == fuchsia_fs::directory::WatchEvent::ADD_FILE
152                    || watch_msg.event == fuchsia_fs::directory::WatchEvent::EXISTING
153                {
154                    let instance_path = format!("{}/{}", SERIAL_DIRECTORY, filename);
155                    let (client_channel, server_channel) = zx::Channel::create();
156                    if let Err(_) = fdio::service_connect(&instance_path, server_channel) {
157                        continue;
158                    }
159
160                    // `fuchsia.hardware.serial` exposes a `DeviceProxy` type used for binding with
161                    // a `Device` type. This should not be confused with the `DeviceProxy` generated
162                    // by FIDL
163                    let device_proxy = fserial::DeviceProxy_SynchronousProxy::new(client_channel);
164                    let (serial_proxy, server_end) =
165                        fidl::endpoints::create_sync_proxy::<fserial::DeviceMarker>();
166
167                    // Instruct the serial driver to bind the connection to the underlying device
168                    if let Err(_) = device_proxy.get_channel(server_end) {
169                        continue;
170                    }
171
172                    // Fetch the device class to see if this is the correct instance
173                    let device_class = match serial_proxy.get_class(zx::MonotonicInstant::INFINITE)
174                    {
175                        Ok(class) => class,
176                        Err(_) => continue,
177                    };
178
179                    if device_class == fserial::Class::Mcu {
180                        let serial_device = SerialDevice::new(
181                            locked,
182                            current_task,
183                            serial_proxy.into_channel().into(),
184                        )
185                        .expect("Can create SerialDevice wrapper");
186
187                        // TODO This will register with an incorrect device number. We should be
188                        // dynamically registering a major device and this should be minor device 1
189                        // of that major device.
190                        let registry = &current_task.kernel().device_registry;
191                        registry
192                            .register_dyn_device(
193                                locked,
194                                current_task,
195                                "ttyHS1".into(),
196                                registry.objects.tty_class(),
197                                serial_device,
198                            )
199                            .expect("Can register serial device");
200                        break;
201                    }
202                }
203            }
204            Ok(None) => {
205                break;
206            }
207            Err(e) => {
208                log_error!("Serial driver stream ended with error: {:}", e);
209                break;
210            }
211        }
212    }
213}