1#![recursion_limit = "256"]
6
7use fidl_fuchsia_hardware_usb_policy::DeviceState;
8use starnix_core::task::dynamic_thread_spawner::SpawnRequestBuilder;
9use starnix_core::task::{CurrentTask, Kernel};
10use starnix_logging::log_error;
11
12use starnix_uapi::errors::Errno;
13use starnix_uapi::{errno, error};
14
15use fidl_fuchsia_usb_policy::{
16 ConfigurationMarker, ConfigurationSynchronousProxy, PolicyProviderMarker,
17};
18use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_sync};
19use starnix_core::device::kobject::{Device, UEventAction};
20use starnix_core::device::mem::DevNull;
21use starnix_core::device::simple_device_ops;
22use starnix_core::fs::sysfs::build_device_directory;
23use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
24use starnix_core::vfs::{FsStr, FsString};
25use starnix_sync::{AndroidUsbCcToggleEnableLock, LockDepMutex};
26use starnix_uapi::file_mode::mode;
27use std::borrow::Cow;
28
29use std::future::Future;
30use std::sync::Arc;
31use std::sync::atomic::{AtomicU8, Ordering};
32
33#[repr(u8)]
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub enum UsbGadgetState {
36 Disconnected = 0,
37 Configured = 1,
38 Connected = 2,
39 Unknown = 3,
40}
41
42impl From<u8> for UsbGadgetState {
43 fn from(val: u8) -> Self {
44 match val {
45 0 => UsbGadgetState::Disconnected,
46 1 => UsbGadgetState::Configured,
47 2 => UsbGadgetState::Connected,
48 _ => UsbGadgetState::Unknown,
49 }
50 }
51}
52
53impl UsbGadgetState {
54 pub fn to_fs_str(&self) -> &'static FsStr {
55 match self {
56 UsbGadgetState::Disconnected => b"DISCONNECTED".into(),
57 UsbGadgetState::Configured => b"CONFIGURED".into(),
58 UsbGadgetState::Connected => b"CONNECTED".into(),
59 UsbGadgetState::Unknown => b"UNKNOWN".into(),
60 }
61 }
62
63 pub fn map_from_device_state(state: DeviceState, previous: Option<Self>) -> Option<Self> {
64 match state {
65 DeviceState::NotAttached | DeviceState::Attached | DeviceState::Powered => {
66 Some(UsbGadgetState::Disconnected)
67 }
68 DeviceState::Default | DeviceState::Address => Some(UsbGadgetState::Connected),
69 DeviceState::Configured => Some(UsbGadgetState::Configured),
70 DeviceState::Suspended => previous, _ => {
72 log_error!("Unexpected DeviceState: {:?}", state);
73 previous
74 }
75 }
76 }
77}
78
79#[derive(Clone)]
80struct UsbStateSysfsFile {
81 state: Arc<AtomicU8>,
82}
83
84impl BytesFileOps for UsbStateSysfsFile {
85 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
86 let state_val = self.state.load(Ordering::Relaxed);
87 let state = UsbGadgetState::from(state_val);
88 let mut content = state.to_fs_str().to_vec();
89 content.push(b'\n'); Ok(Cow::Owned(content))
91 }
92}
93
94struct CcToggleEnableState {
95 enabled: bool,
97 config_proxy: Option<ConfigurationSynchronousProxy>,
100}
101
102struct CcToggleEnableSysfsFile {
103 state: LockDepMutex<CcToggleEnableState, AndroidUsbCcToggleEnableLock>,
106}
107
108impl CcToggleEnableSysfsFile {
109 fn new() -> Self {
110 Self { state: CcToggleEnableState { enabled: true, config_proxy: None }.into() }
111 }
112
113 #[cfg(test)]
114 fn new_with_proxy(config_proxy: ConfigurationSynchronousProxy) -> Self {
115 Self {
116 state: CcToggleEnableState { enabled: true, config_proxy: Some(config_proxy) }.into(),
117 }
118 }
119}
120
121impl BytesFileOps for CcToggleEnableSysfsFile {
122 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
123 let content: &[u8] = if self.state.lock().enabled { b"1\n" } else { b"0\n" };
124 Ok(Cow::Borrowed(content))
125 }
126
127 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
128 let input = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
129 let enable = match input.trim_matches(|c: char| c.is_whitespace() || c == '\0') {
130 "0" => false,
131 "1" => true,
132 _ => return error!(EINVAL),
133 };
134
135 let mut state = self.state.lock();
136 if state.config_proxy.is_none() {
137 state.config_proxy =
138 Some(connect_to_protocol_sync::<ConfigurationMarker>().map_err(|e| {
139 log_error!("Failed to connect to fuchsia.usb.policy.Configuration: {:?}", e);
140 errno!(EIO)
141 })?);
142 }
143 let Some(proxy) = state.config_proxy.as_ref() else {
144 return error!(EIO);
145 };
146
147 let (method, result) = if enable {
148 ("ConnectCable", proxy.connect_cable(zx::MonotonicInstant::INFINITE))
149 } else {
150 ("DisconnectCable", proxy.disconnect_cable(0, zx::MonotonicInstant::INFINITE))
151 };
152 match result {
153 Ok(Ok(())) => {}
154 Ok(Err(status)) => {
155 log_error!("{method} returned error: {:?}", zx::Status::err_from_raw(status));
156 return error!(EIO);
157 }
158 Err(e) => {
159 log_error!("{method} FIDL error: {:?}", e);
160 state.config_proxy = None;
162 return error!(EIO);
163 }
164 }
165
166 state.enabled = enable;
167 Ok(())
168 }
169}
170
171pub fn usb_device_init(kernel: &Arc<Kernel>) -> Result<Device, Errno> {
172 let registry = &kernel.device_registry;
173
174 registry.objects.get_or_create_class("udc".into());
176 let android_usb_class = registry.objects.get_or_create_class("android_usb".into());
177
178 let shared_state = Arc::new(AtomicU8::new(UsbGadgetState::Disconnected as u8));
179 let state_clone = shared_state.clone();
180
181 let device = registry.add_dyn_device_with_dir(
182 kernel,
183 "android0".into(),
184 android_usb_class,
185 |device, dir| {
186 build_device_directory(device, dir);
187 dir.entry(
188 "state",
189 BytesFile::new_node(UsbStateSysfsFile { state: state_clone }),
190 mode!(IFREG, 0o444),
191 );
192 },
193 simple_device_ops::<DevNull>,
194 )?;
195
196 let platform_bus = registry.objects.platform_bus();
203 let i2c_bus = registry.objects.get_or_create_bus("i2c".into());
204 let hsi2c = registry.add_bus_device(
205 "10d50000.hsi2c".into(),
206 Some(registry.objects.platform_device()),
207 platform_bus,
208 build_device_directory,
209 );
210 let i2c_6 = registry.add_bus_device(
211 "i2c-6".into(),
212 Some(hsi2c),
213 i2c_bus.clone(),
214 build_device_directory,
215 );
216 registry.add_bus_device("i2c-max77759tcpc".into(), Some(i2c_6), i2c_bus, |device, dir| {
217 build_device_directory(device, dir);
218 dir.entry(
219 "cc_toggle_enable",
220 BytesFile::new_node(CcToggleEnableSysfsFile::new()),
221 mode!(IFREG, 0o644),
222 );
223 });
224
225 let kernel_clone = Arc::clone(kernel);
226 let device_clone = device.clone();
227 kernel.kthreads.spawn_future(
228 move || async move {
229 monitor_usb_device_state(kernel_clone, device_clone, shared_state).await;
230 },
231 "usb_device_state_monitor",
232 );
233
234 Ok(device)
235}
236
237fn dispatch_usb_state_change(
239 kernel: &Arc<Kernel>,
240 device: &Device,
241 usb_state: FsString,
242) -> impl Future<Output = Result<(), Errno>> {
243 let spawner = kernel.kthreads.spawner();
244 let device_clone = device.clone();
245 let closure = move |current_task: &CurrentTask| {
246 if let Some(metadata) = device_clone.metadata() {
247 metadata.properties.insert("USB_STATE".into(), usb_state);
248 }
249
250 current_task.kernel.device_registry.dispatch_uevent(UEventAction::Change, device_clone);
251 };
252 let (result, request) =
253 SpawnRequestBuilder::new().with_sync_closure(closure).build_with_async_result();
254 spawner.spawn_from_request(request);
255 result
256}
257
258async fn monitor_usb_device_state(
261 kernel: Arc<Kernel>,
262 device: Device,
263 shared_state: Arc<AtomicU8>,
264) {
265 let provider = connect_to_protocol::<PolicyProviderMarker>()
266 .expect("USB Failed to connect to PolicyProvider");
267 let mut previous_mapped_state: Option<UsbGadgetState> = None;
268 loop {
269 match provider.watch_device_state().await {
270 Ok(Ok(update)) => {
271 let state = update.state.unwrap_or_else(DeviceState::unknown);
272
273 let mapped = UsbGadgetState::map_from_device_state(state, previous_mapped_state);
275
276 if previous_mapped_state != mapped {
277 if let Some(val) = mapped {
278 previous_mapped_state = Some(val);
279 shared_state.store(val as u8, Ordering::Relaxed);
280 if let Err(e) =
281 dispatch_usb_state_change(&kernel, &device, val.to_fs_str().to_owned())
282 .await
283 {
284 log_error!("Failed to dispatch USB state change: {:?}", e);
285 }
286 }
287 }
288 }
289 Ok(Err(err)) => {
290 log_error!("USB PolicyProvider returned error: {:?}", err);
291 break;
292 }
293 Err(e) => {
294 log_error!("USB PolicyProvider watch failed: {:?}", e);
295 break;
296 }
297 }
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use fidl_fuchsia_usb_policy::ConfigurationRequest;
305 use futures::StreamExt;
306 use starnix_core::testing::spawn_kernel_and_run;
307 use starnix_rcu::RcuReadScope;
308
309 #[::fuchsia::test]
310 async fn test_usb_gadget_state_map_from_device_state() {
311 assert_eq!(
312 UsbGadgetState::map_from_device_state(DeviceState::NotAttached, None),
313 Some(UsbGadgetState::Disconnected)
314 );
315 assert_eq!(
316 UsbGadgetState::map_from_device_state(DeviceState::Configured, None),
317 Some(UsbGadgetState::Configured)
318 );
319 assert_eq!(
320 UsbGadgetState::map_from_device_state(DeviceState::Attached, None),
321 Some(UsbGadgetState::Disconnected)
322 );
323 assert_eq!(
324 UsbGadgetState::map_from_device_state(DeviceState::Powered, None),
325 Some(UsbGadgetState::Disconnected)
326 );
327 assert_eq!(
328 UsbGadgetState::map_from_device_state(DeviceState::Default, None),
329 Some(UsbGadgetState::Connected)
330 );
331 assert_eq!(
332 UsbGadgetState::map_from_device_state(DeviceState::Address, None),
333 Some(UsbGadgetState::Connected)
334 );
335 assert_eq!(
336 UsbGadgetState::map_from_device_state(
337 DeviceState::Suspended,
338 Some(UsbGadgetState::Configured)
339 ),
340 Some(UsbGadgetState::Configured)
341 );
342 assert_eq!(UsbGadgetState::map_from_device_state(DeviceState::Suspended, None), None);
343 assert_eq!(UsbGadgetState::map_from_device_state(DeviceState::unknown(), None), None);
344 assert_eq!(
345 UsbGadgetState::map_from_device_state(
346 DeviceState::unknown(),
347 Some(UsbGadgetState::Configured)
348 ),
349 Some(UsbGadgetState::Configured)
350 );
351 }
352
353 #[::fuchsia::test]
354 async fn test_usb_sysfs_state_reads() {
355 spawn_kernel_and_run(async |current_task| {
356 let shared_state = Arc::new(AtomicU8::new(UsbGadgetState::Disconnected as u8));
357
358 shared_state.store(UsbGadgetState::Configured as u8, Ordering::Relaxed);
359
360 let sysfs_file = UsbStateSysfsFile { state: shared_state };
361 let content = sysfs_file.read(current_task).expect("read failed");
362 assert_eq!(content.as_ref(), b"CONFIGURED\n");
363 })
364 .await;
365 }
366
367 #[::fuchsia::test]
368 async fn test_usb_device_state_to_fs_str() {
369 assert_eq!(UsbGadgetState::Disconnected.to_fs_str(), b"DISCONNECTED");
370 assert_eq!(UsbGadgetState::Configured.to_fs_str(), b"CONFIGURED");
371 assert_eq!(UsbGadgetState::Connected.to_fs_str(), b"CONNECTED");
372 assert_eq!(UsbGadgetState::Unknown.to_fs_str(), b"UNKNOWN");
373 }
374
375 #[::fuchsia::test]
376 async fn test_usb_device_state_from_u8() {
377 assert_eq!(UsbGadgetState::from(0), UsbGadgetState::Disconnected);
378 assert_eq!(UsbGadgetState::from(1), UsbGadgetState::Configured);
379 assert_eq!(UsbGadgetState::from(2), UsbGadgetState::Connected);
380 assert_eq!(UsbGadgetState::from(255), UsbGadgetState::Unknown);
381 }
382
383 #[::fuchsia::test]
384 async fn test_usb_device_init_sysfs() {
385 spawn_kernel_and_run(async |current_task| {
386 let device = usb_device_init(current_task.kernel()).expect("usb_device_init failed");
387
388 assert_eq!(device.name(), "android0");
389 let metadata = device.metadata().expect("metadata not found");
390 assert_eq!(metadata.devname.as_slice(), b"android0");
391
392 let root = ¤t_task.kernel().device_registry.objects.root;
393 assert!(root.lookup("class/udc".into()).is_some());
394 assert!(
395 root.lookup(
396 "devices/platform/10d50000.hsi2c/i2c-6/i2c-max77759tcpc/cc_toggle_enable"
397 .into()
398 )
399 .is_some()
400 );
401 })
402 .await;
403 }
404
405 #[::fuchsia::test]
406 async fn test_usb_device_metadata_property_injection() {
407 spawn_kernel_and_run(async |current_task| {
408 let device = usb_device_init(current_task.kernel()).expect("usb_device_init failed");
409
410 let kernel = current_task.kernel();
411
412 dispatch_usb_state_change(
414 kernel,
415 &device,
416 UsbGadgetState::Connected.to_fs_str().to_owned(),
417 )
418 .await
419 .unwrap();
420
421 let metadata = device.metadata().expect("metadata not found");
422 let scope = RcuReadScope::new();
423 let value = metadata
424 .properties
425 .get(&scope, FsStr::new(b"USB_STATE"))
426 .expect("property not found");
427 assert_eq!(value.as_slice(), b"CONNECTED");
428 })
429 .await;
430 }
431
432 #[::fuchsia::test]
433 async fn test_cc_toggle_enable_sysfs_read_and_write() {
434 let (config_proxy, mut config_stream) =
435 fidl::endpoints::create_sync_proxy_and_stream::<ConfigurationMarker>();
436
437 let server_task = fuchsia_async::Task::spawn(async move {
438 let mut requests = Vec::new();
439 while let Some(Ok(req)) = config_stream.next().await {
440 match req {
441 ConfigurationRequest::DisconnectCable { duration, responder } => {
442 requests.push((false, duration));
443 let _ = responder.send(Ok(()));
444 }
445 ConfigurationRequest::ConnectCable { responder } => {
446 requests.push((true, 0));
447 let _ = responder.send(Ok(()));
448 }
449 _ => panic!("unexpected request: {:?}", req),
450 }
451 if requests.len() == 4 {
452 break;
453 }
454 }
455 requests
456 });
457
458 spawn_kernel_and_run(async move |current_task| {
459 let sysfs_file = CcToggleEnableSysfsFile::new_with_proxy(config_proxy);
460 assert_eq!(sysfs_file.read(current_task).expect("read failed").as_ref(), b"1\n");
461
462 sysfs_file.write(current_task, b"0\n".to_vec()).expect("write 0 failed");
463 assert_eq!(sysfs_file.read(current_task).expect("read failed").as_ref(), b"0\n");
464
465 sysfs_file.write(current_task, b"1".to_vec()).expect("write 1 failed");
466 assert_eq!(sysfs_file.read(current_task).expect("read failed").as_ref(), b"1\n");
467
468 sysfs_file.write(current_task, b"0\0".to_vec()).expect("write 0\\0 failed");
469 assert_eq!(sysfs_file.read(current_task).expect("read failed").as_ref(), b"0\n");
470
471 sysfs_file.write(current_task, b"1\n\0".to_vec()).expect("write 1\\n\\0 failed");
472 assert_eq!(sysfs_file.read(current_task).expect("read failed").as_ref(), b"1\n");
473
474 assert_eq!(sysfs_file.write(current_task, b"invalid".to_vec()), error!(EINVAL));
475 })
476 .await;
477
478 assert_eq!(server_task.await, vec![(false, 0), (true, 0), (false, 0), (true, 0)]);
479 }
480
481 #[::fuchsia::test]
482 async fn test_cc_toggle_enable_sysfs_write_error_keeps_state() {
483 let (config_proxy, mut config_stream) =
484 fidl::endpoints::create_sync_proxy_and_stream::<ConfigurationMarker>();
485
486 let server_task = fuchsia_async::Task::spawn(async move {
487 let mut num_requests = 0;
488 while let Some(Ok(req)) = config_stream.next().await {
489 match req {
490 ConfigurationRequest::DisconnectCable { responder, .. } => {
491 let _ = responder.send(Err(zx::Status::UNAVAILABLE.into_raw()));
492 }
493 ConfigurationRequest::ConnectCable { responder } => {
494 let _ = responder.send(Err(zx::Status::INTERNAL.into_raw()));
495 }
496 _ => panic!("unexpected request: {:?}", req),
497 }
498 num_requests += 1;
499 if num_requests == 2 {
500 break;
501 }
502 }
503 num_requests
504 });
505
506 spawn_kernel_and_run(async move |current_task| {
507 let sysfs_file = CcToggleEnableSysfsFile::new_with_proxy(config_proxy);
508
509 assert_eq!(sysfs_file.write(current_task, b"0".to_vec()), error!(EIO));
511 assert_eq!(sysfs_file.read(current_task).expect("read failed").as_ref(), b"1\n");
512
513 sysfs_file.state.lock().enabled = false;
515 assert_eq!(sysfs_file.write(current_task, b"1".to_vec()), error!(EIO));
516 assert_eq!(sysfs_file.read(current_task).expect("read failed").as_ref(), b"0\n");
517 })
518 .await;
519
520 assert_eq!(server_task.await, 2);
521 }
522}