starnix_modules_nanohub/
nanohub.rs1use 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 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 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 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 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 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 if let Err(_) = device_proxy.get_channel(server_end) {
177 continue;
178 }
179
180 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 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}