Skip to main content

starnix_modules_android_usb/
lib.rs

1// Copyright 2026 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
5#![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;
11use starnix_sync::{Locked, Unlocked};
12use starnix_uapi::errors::Errno;
13
14use fidl_fuchsia_usb_policy::PolicyProviderMarker;
15use fuchsia_component::client::connect_to_protocol;
16use starnix_core::device::kobject::{Device, UEventAction};
17use starnix_core::device::mem::DevNull;
18use starnix_core::device::simple_device_ops;
19use starnix_core::fs::sysfs::build_device_directory;
20use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
21use starnix_core::vfs::{FsStr, FsString};
22use starnix_uapi::file_mode::mode;
23use std::borrow::Cow;
24
25use std::future::Future;
26use std::sync::Arc;
27use std::sync::atomic::{AtomicU8, Ordering};
28
29#[repr(u8)]
30#[derive(Copy, Clone, Debug, PartialEq, Eq)]
31pub enum UsbGadgetState {
32    Disconnected = 0,
33    Configured = 1,
34    Connected = 2,
35    Unknown = 3,
36}
37
38impl From<u8> for UsbGadgetState {
39    fn from(val: u8) -> Self {
40        match val {
41            0 => UsbGadgetState::Disconnected,
42            1 => UsbGadgetState::Configured,
43            2 => UsbGadgetState::Connected,
44            _ => UsbGadgetState::Unknown,
45        }
46    }
47}
48
49impl UsbGadgetState {
50    pub fn to_fs_str(&self) -> &'static FsStr {
51        match self {
52            UsbGadgetState::Disconnected => b"DISCONNECTED".into(),
53            UsbGadgetState::Configured => b"CONFIGURED".into(),
54            UsbGadgetState::Connected => b"CONNECTED".into(),
55            UsbGadgetState::Unknown => b"UNKNOWN".into(),
56        }
57    }
58
59    pub fn map_from_device_state(state: DeviceState, previous: Option<Self>) -> Option<Self> {
60        match state {
61            DeviceState::NotAttached => Some(UsbGadgetState::Disconnected),
62            DeviceState::Attached => Some(UsbGadgetState::Connected),
63            DeviceState::Powered => Some(UsbGadgetState::Connected),
64            DeviceState::Default => Some(UsbGadgetState::Connected),
65            DeviceState::Address => Some(UsbGadgetState::Connected),
66            DeviceState::Configured => Some(UsbGadgetState::Configured),
67            DeviceState::Suspended => previous, // No change when suspended
68            _ => {
69                log_error!("Unexpected DeviceState: {:?}", state);
70                previous
71            }
72        }
73    }
74}
75
76#[derive(Clone)]
77struct UsbStateSysfsFile {
78    state: Arc<AtomicU8>,
79}
80
81impl BytesFileOps for UsbStateSysfsFile {
82    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
83        let state_val = self.state.load(Ordering::Relaxed);
84        let state = UsbGadgetState::from(state_val);
85        let mut content = state.to_fs_str().to_vec();
86        content.push(b'\n'); // standard sysfs newline
87        Ok(Cow::Owned(content))
88    }
89}
90
91pub fn usb_device_init(
92    locked: &mut Locked<Unlocked>,
93    current_task: &CurrentTask,
94) -> Result<Device, Errno> {
95    let kernel = current_task.kernel();
96    let registry = &kernel.device_registry;
97
98    let android_usb_class =
99        registry.objects.get_or_create_class("android_usb".into(), registry.objects.virtual_bus());
100
101    let shared_state = Arc::new(AtomicU8::new(UsbGadgetState::Disconnected as u8));
102    let state_clone = shared_state.clone();
103
104    let device = registry.register_dyn_device_with_dir(
105        locked,
106        current_task,
107        "android0".into(),
108        android_usb_class,
109        |device, dir| {
110            build_device_directory(device, dir);
111            dir.entry(
112                "state",
113                BytesFile::new_node(UsbStateSysfsFile { state: state_clone }),
114                mode!(IFREG, 0o444),
115            );
116        },
117        simple_device_ops::<DevNull>,
118    )?;
119
120    let kernel_clone = Arc::clone(kernel);
121    let device_clone = device.clone();
122    kernel.kthreads.spawn_future(
123        move || async move {
124            monitor_usb_device_state(kernel_clone, device_clone, shared_state).await;
125        },
126        "usb_device_state_monitor",
127    );
128
129    Ok(device)
130}
131
132// Prepare a request to broadcast a USB state change and dispatch it on a kernel thread.
133fn dispatch_usb_state_change(
134    kernel: &Arc<Kernel>,
135    device: &Device,
136    usb_state: FsString,
137) -> impl Future<Output = Result<(), Errno>> {
138    let spawner = kernel.kthreads.spawner();
139    let device_clone = device.clone();
140    let closure = move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| {
141        if let Some(metadata) = &device_clone.metadata {
142            metadata.properties.insert("USB_STATE".into(), usb_state);
143        }
144
145        current_task.kernel.device_registry.dispatch_uevent(
146            locked,
147            UEventAction::Change,
148            device_clone,
149        );
150    };
151    let (result, request) =
152        SpawnRequestBuilder::new().with_sync_closure(closure).build_with_async_result();
153    spawner.spawn_from_request(request);
154    result
155}
156
157/// Monitor the USB state via FIDL messages from the PolicyProvider and dispatch uevents when the
158/// state changes.
159async fn monitor_usb_device_state(
160    kernel: Arc<Kernel>,
161    device: Device,
162    shared_state: Arc<AtomicU8>,
163) {
164    let provider = connect_to_protocol::<PolicyProviderMarker>()
165        .expect("USB Failed to connect to PolicyProvider");
166    let mut previous_mapped_state: Option<UsbGadgetState> = None;
167    loop {
168        match provider.watch_device_state().await {
169            Ok(Ok(update)) => {
170                let state = update.state.unwrap_or_else(DeviceState::unknown);
171
172                // Map the incoming device state onto the new UsbGadgetState.
173                let mapped = UsbGadgetState::map_from_device_state(state, previous_mapped_state);
174
175                if previous_mapped_state != mapped {
176                    if let Some(val) = mapped {
177                        previous_mapped_state = Some(val);
178                        shared_state.store(val as u8, Ordering::Relaxed);
179                        if let Err(e) =
180                            dispatch_usb_state_change(&kernel, &device, val.to_fs_str().to_owned())
181                                .await
182                        {
183                            log_error!("Failed to dispatch USB state change: {:?}", e);
184                        }
185                    }
186                }
187            }
188            Ok(Err(err)) => {
189                log_error!("USB PolicyProvider returned error: {:?}", err);
190                break;
191            }
192            Err(e) => {
193                log_error!("USB PolicyProvider watch failed: {:?}", e);
194                break;
195            }
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use starnix_core::testing::spawn_kernel_and_run;
204    use starnix_rcu::RcuReadScope;
205
206    #[::fuchsia::test]
207    async fn test_usb_gadget_state_map_from_device_state() {
208        assert_eq!(
209            UsbGadgetState::map_from_device_state(DeviceState::NotAttached, None),
210            Some(UsbGadgetState::Disconnected)
211        );
212        assert_eq!(
213            UsbGadgetState::map_from_device_state(DeviceState::Configured, None),
214            Some(UsbGadgetState::Configured)
215        );
216        assert_eq!(
217            UsbGadgetState::map_from_device_state(DeviceState::Attached, None),
218            Some(UsbGadgetState::Connected)
219        );
220        assert_eq!(
221            UsbGadgetState::map_from_device_state(DeviceState::Powered, None),
222            Some(UsbGadgetState::Connected)
223        );
224        assert_eq!(
225            UsbGadgetState::map_from_device_state(DeviceState::Default, None),
226            Some(UsbGadgetState::Connected)
227        );
228        assert_eq!(
229            UsbGadgetState::map_from_device_state(DeviceState::Address, None),
230            Some(UsbGadgetState::Connected)
231        );
232        assert_eq!(
233            UsbGadgetState::map_from_device_state(
234                DeviceState::Suspended,
235                Some(UsbGadgetState::Configured)
236            ),
237            Some(UsbGadgetState::Configured)
238        );
239        assert_eq!(UsbGadgetState::map_from_device_state(DeviceState::Suspended, None), None);
240        assert_eq!(UsbGadgetState::map_from_device_state(DeviceState::unknown(), None), None);
241        assert_eq!(
242            UsbGadgetState::map_from_device_state(
243                DeviceState::unknown(),
244                Some(UsbGadgetState::Configured)
245            ),
246            Some(UsbGadgetState::Configured)
247        );
248    }
249
250    #[::fuchsia::test]
251    async fn test_usb_sysfs_state_reads() {
252        spawn_kernel_and_run(async |_locked, current_task| {
253            let shared_state = Arc::new(AtomicU8::new(UsbGadgetState::Disconnected as u8));
254
255            shared_state.store(UsbGadgetState::Configured as u8, Ordering::Relaxed);
256
257            let sysfs_file = UsbStateSysfsFile { state: shared_state };
258            let content = sysfs_file.read(current_task).expect("read failed");
259            assert_eq!(content.as_ref(), b"CONFIGURED\n");
260        })
261        .await;
262    }
263
264    #[::fuchsia::test]
265    async fn test_usb_device_state_to_fs_str() {
266        assert_eq!(UsbGadgetState::Disconnected.to_fs_str(), b"DISCONNECTED");
267        assert_eq!(UsbGadgetState::Configured.to_fs_str(), b"CONFIGURED");
268        assert_eq!(UsbGadgetState::Connected.to_fs_str(), b"CONNECTED");
269        assert_eq!(UsbGadgetState::Unknown.to_fs_str(), b"UNKNOWN");
270    }
271
272    #[::fuchsia::test]
273    async fn test_usb_device_state_from_u8() {
274        assert_eq!(UsbGadgetState::from(0), UsbGadgetState::Disconnected);
275        assert_eq!(UsbGadgetState::from(1), UsbGadgetState::Configured);
276        assert_eq!(UsbGadgetState::from(2), UsbGadgetState::Connected);
277        assert_eq!(UsbGadgetState::from(255), UsbGadgetState::Unknown);
278    }
279
280    #[::fuchsia::test]
281    async fn test_usb_device_init_sysfs() {
282        spawn_kernel_and_run(async |locked, current_task| {
283            let device = usb_device_init(locked, current_task).expect("usb_device_init failed");
284
285            assert_eq!(device.name.as_slice(), b"android0");
286            let metadata = device.metadata.as_ref().expect("metadata not found");
287            assert_eq!(metadata.devname.as_slice(), b"android0");
288        })
289        .await;
290    }
291
292    #[::fuchsia::test]
293    async fn test_usb_device_metadata_property_injection() {
294        spawn_kernel_and_run(async |locked, current_task| {
295            let device = usb_device_init(locked, current_task).expect("usb_device_init failed");
296
297            let kernel = current_task.kernel();
298
299            // Send the state change and wait for the background task to execute.
300            dispatch_usb_state_change(
301                kernel,
302                &device,
303                UsbGadgetState::Connected.to_fs_str().to_owned(),
304            )
305            .await
306            .unwrap();
307
308            let metadata = device.metadata.as_ref().expect("metadata not found");
309            let scope = RcuReadScope::new();
310            let value = metadata
311                .properties
312                .get(&scope, FsStr::new(b"USB_STATE"))
313                .expect("property not found");
314            assert_eq!(value.as_slice(), b"CONNECTED");
315        })
316        .await;
317    }
318}