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