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