Skip to main content

starnix_modules_usbfs/
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
7pub(crate) mod device;
8pub(crate) mod sysfs;
9
10use crate::device::{
11    USB_SPEED_FULL, USB_SPEED_HIGH, USB_SPEED_LOW, USB_SPEED_SUPER, USB_SPEED_SUPER_PLUS,
12    USB_SPEED_UNKNOWN, UsbDevice, UsbDeviceFile,
13};
14use crate::sysfs::build_usb_device_directory;
15use fidl::endpoints::Proxy;
16use fidl_fuchsia_hardware_usb as fhardware_usb;
17use fidl_fuchsia_hardware_usb_descriptor as fusb_descriptor;
18use fuchsia_component::client::Service;
19use futures::TryStreamExt;
20use starnix_core::device::DeviceMode;
21use starnix_core::device::kobject::{Bus, Device, DeviceMetadata};
22use starnix_core::task::dynamic_thread_spawner::SpawnRequestBuilder;
23use starnix_core::task::{CurrentTask, Kernel};
24use starnix_core::vfs::NamespaceNode;
25use starnix_logging::{log_error, log_info};
26use starnix_sync::Mutex;
27use starnix_uapi::device_id::{DeviceId, USB_DEVICE_MAJOR};
28use starnix_uapi::errors::Errno;
29use starnix_uapi::open_flags::OpenFlags;
30use starnix_uapi::{errno, usb_device_descriptor};
31use std::collections::{BTreeMap, HashSet};
32use std::sync::Arc;
33use std::sync::atomic::Ordering;
34
35pub(crate) fn fuchsia_speed_to_linux_speed(speed: fusb_descriptor::UsbSpeed) -> u32 {
36    match speed {
37        fusb_descriptor::UsbSpeed::Full => USB_SPEED_FULL,
38        fusb_descriptor::UsbSpeed::Low => USB_SPEED_LOW,
39        fusb_descriptor::UsbSpeed::High => USB_SPEED_HIGH,
40        fusb_descriptor::UsbSpeed::Super => USB_SPEED_SUPER,
41        fusb_descriptor::UsbSpeed::EnhancedSuper => USB_SPEED_SUPER_PLUS,
42        fusb_descriptor::UsbSpeed::Undefined => USB_SPEED_UNKNOWN,
43    }
44}
45
46pub(crate) fn fidl_device_descriptor_to_linux(
47    desc: &fusb_descriptor::UsbDeviceDescriptor,
48) -> usb_device_descriptor {
49    usb_device_descriptor {
50        bLength: desc.b_length,
51        bDescriptorType: desc.b_descriptor_type,
52        bcdUSB: desc.bcd_usb.to_le(),
53        bDeviceClass: desc.b_device_class,
54        bDeviceSubClass: desc.b_device_sub_class,
55        bDeviceProtocol: desc.b_device_protocol,
56        bMaxPacketSize0: desc.b_max_packet_size0,
57        idVendor: desc.id_vendor.to_le(),
58        idProduct: desc.id_product.to_le(),
59        bcdDevice: desc.bcd_device.to_le(),
60        iManufacturer: desc.i_manufacturer,
61        iProduct: desc.i_product,
62        iSerialNumber: desc.i_serial_number,
63        bNumConfigurations: desc.b_num_configurations,
64    }
65}
66
67struct DevNumAllocator {
68    allocated: HashSet<u16>,
69    next: u16,
70}
71
72impl DevNumAllocator {
73    fn new() -> Self {
74        Self { allocated: HashSet::new(), next: 2 }
75    }
76
77    fn allocate(&mut self) -> Option<u16> {
78        let start = self.next;
79        for i in 0..126 {
80            let num = ((start - 2 + i) % 126) + 2;
81            if self.allocated.insert(num) {
82                self.next = if num >= 127 { 2 } else { num + 1 };
83                return Some(num);
84            }
85        }
86        None
87    }
88
89    fn release(&mut self, dev_num: u16) {
90        self.allocated.remove(&dev_num);
91    }
92}
93
94#[derive(Clone)]
95pub struct UsbDeviceManager {
96    pub(crate) bus: Bus,
97    pub(crate) devices: Arc<Mutex<BTreeMap<DeviceId, Arc<UsbDevice>>>>,
98    pub(crate) allocator: Arc<Mutex<DevNumAllocator>>,
99}
100
101impl UsbDeviceManager {
102    pub(crate) fn new(bus: Bus) -> Self {
103        Self {
104            bus,
105            devices: Arc::new(Mutex::new(BTreeMap::new())),
106            allocator: Arc::new(Mutex::new(DevNumAllocator::new())),
107        }
108    }
109
110    pub(crate) fn allocate_dev_num(&self) -> Option<u16> {
111        self.allocator.lock().allocate()
112    }
113
114    pub(crate) fn release_dev_num(&self, dev_num: u16) {
115        self.allocator.lock().release(dev_num);
116    }
117
118    pub(crate) fn get_device(&self, id: DeviceId) -> Option<Arc<UsbDevice>> {
119        self.devices.lock().get(&id).cloned()
120    }
121
122    pub(crate) fn add_device(
123        &self,
124        kernel: &Kernel,
125        device: Arc<UsbDevice>,
126    ) -> Result<Device, Errno> {
127        let bus_str = format!("{:03}", device.bus_num);
128        let dev_str = format!("{:03}", device.dev_num);
129        let devname = format!("bus/usb/{}/{}", bus_str, dev_str);
130        let sysname = format!("{}-{}", device.bus_num, device.dev_num);
131
132        let devt = DeviceId::new(USB_DEVICE_MAJOR, device.minor());
133
134        let desc = &device.device_descriptor;
135        let id_vendor = u16::from_le(desc.idVendor);
136        let id_product = u16::from_le(desc.idProduct);
137        let bcd_device = u16::from_le(desc.bcdDevice);
138        let b_device_class = desc.bDeviceClass;
139        let b_device_sub_class = desc.bDeviceSubClass;
140        let b_device_protocol = desc.bDeviceProtocol;
141
142        let metadata =
143            DeviceMetadata::new(devname.into(), devt, DeviceMode::Char).with_devtype("usb_device");
144
145        metadata.properties.insert(
146            b"PRODUCT".into(),
147            format!("{:x}/{:x}/{:x}", id_vendor, id_product, bcd_device).into(),
148        );
149        metadata.properties.insert(
150            b"TYPE".into(),
151            format!("{}/{}/{}", b_device_class, b_device_sub_class, b_device_protocol).into(),
152        );
153        metadata.properties.insert(b"BUSNUM".into(), format!("{:03}", device.bus_num).into());
154        metadata.properties.insert(b"DEVNUM".into(), format!("{:03}", device.dev_num).into());
155
156        self.devices.lock().insert(devt, device.clone());
157
158        let device_clone = device.clone();
159        let registered_device = kernel.device_registry.add_major_device(
160            kernel,
161            sysname.as_bytes().into(),
162            metadata,
163            None,
164            self.bus.clone(),
165            move |device_obj, dir| {
166                build_usb_device_directory(device_obj, dir, &device_clone);
167            },
168        );
169
170        Ok(registered_device)
171    }
172
173    pub(crate) fn remove_device(
174        &self,
175        current_task: &CurrentTask,
176        registered_device: Device,
177    ) -> Result<(), Errno> {
178        if let Some(metadata) = registered_device.metadata() {
179            if let Some(device) = self.devices.lock().remove(&metadata.devt) {
180                self.release_dev_num(device.dev_num);
181                device.on_disconnect();
182            }
183        }
184        current_task.kernel().device_registry.remove_device(current_task, registered_device);
185        Ok(())
186    }
187}
188
189async fn run_in_kernel_task_async<F, R>(
190    kernel: &Kernel,
191    debug_name: &'static str,
192    f: F,
193) -> Result<R, Errno>
194where
195    F: FnOnce(&CurrentTask) -> R + Send + 'static,
196    R: Send + 'static,
197{
198    let closure = move |current_task: &CurrentTask| {
199        current_task.override_creds(
200            starnix_core::security::creds_start_internal_operation(current_task),
201            || f(current_task),
202        )
203    };
204    let (result_fut, req) = SpawnRequestBuilder::new()
205        .with_debug_name(debug_name)
206        .with_sync_closure(closure)
207        .build_with_async_result();
208    kernel.kthreads.spawner().spawn_from_request(req);
209    result_fut.await
210}
211
212pub fn usbfs_init(kernel: &Arc<Kernel>) -> Result<UsbDeviceManager, Errno> {
213    let registry = &kernel.device_registry;
214
215    let usb_bus = registry.objects.get_or_create_bus("usb".into());
216    let manager = UsbDeviceManager::new(usb_bus);
217    let manager_clone = manager.clone();
218
219    registry.register_major(
220        "usb".into(),
221        DeviceMode::Char,
222        USB_DEVICE_MAJOR,
223        move |_current_task: &CurrentTask,
224              id: DeviceId,
225              _node: &NamespaceNode,
226              _flags: OpenFlags|
227              -> Result<Box<dyn starnix_core::vfs::FileOps>, Errno> {
228            let device = manager_clone.get_device(id).ok_or_else(|| errno!(ENODEV))?;
229            Ok(Box::new(UsbDeviceFile::new(device)))
230        },
231    )?;
232
233    let kernel_clone = Arc::clone(kernel);
234    let manager_for_watcher = manager.clone();
235    kernel.kthreads.spawn_future(
236        move || async move {
237            watch_usb_devices(kernel_clone, manager_for_watcher).await;
238        },
239        "usbfs_watcher",
240    );
241
242    Ok(manager)
243}
244
245async fn watch_usb_devices(kernel: Arc<Kernel>, manager: UsbDeviceManager) {
246    log_info!("Starting Starnix usbfs USB device watcher.");
247
248    let service = match Service::open(fhardware_usb::UsbServiceMarker) {
249        Ok(service) => service,
250        Err(e) => {
251            log_info!("Physical USB service not available: {:?}", e);
252            return;
253        }
254    };
255    watch_usb_devices_from_service(kernel, manager, service).await;
256}
257
258async fn watch_usb_devices_from_service(
259    kernel: Arc<Kernel>,
260    manager: UsbDeviceManager,
261    service: Service<fhardware_usb::UsbServiceMarker>,
262) {
263    let mut watcher = match service.watch().await {
264        Ok(watcher) => watcher,
265        Err(e) => {
266            log_info!("Failed to create USB service watcher: {:?}", e);
267            return;
268        }
269    };
270
271    let active_hw_ids = Arc::new(Mutex::new(HashSet::<u32>::new()));
272    while let Ok(Some(instance)) = watcher.try_next().await {
273        let device_proxy = match instance.connect_to_device() {
274            Ok(proxy) => proxy,
275            Err(e) => {
276                log_error!("Failed to connect to USB device: {:?}", e);
277                continue;
278            }
279        };
280
281        let first_config_desc = match device_proxy.get_configuration_descriptor(1).await {
282            Err(fidl::Error::ClientChannelClosed { .. }) => continue,
283            Ok(Err(zx::sys::ZX_ERR_NOT_SUPPORTED)) => {
284                // Child interface node from usb-composite, not parent UsbDevice; skip it.
285                continue;
286            }
287            Ok(Ok(desc)) if !desc.is_empty() => Some(desc),
288            _ => None,
289        };
290
291        let hw_device_id = match device_proxy.get_device_id().await {
292            Ok(id) => id,
293            Err(e) => {
294                log_error!("Failed to get device ID: {:?}", e);
295                continue;
296            }
297        };
298        if !active_hw_ids.lock().insert(hw_device_id) {
299            continue;
300        }
301
302        let fidl_desc = match device_proxy.get_device_descriptor().await {
303            Ok(desc) => desc,
304            Err(e) => {
305                active_hw_ids.lock().remove(&hw_device_id);
306                log_error!("Failed to get device descriptor: {:?}", e);
307                continue;
308            }
309        };
310
311        let device_descriptor = fidl_device_descriptor_to_linux(&fidl_desc);
312
313        let mut config_descriptors = Vec::new();
314        if let Some(desc) = first_config_desc {
315            config_descriptors.extend_from_slice(&desc);
316        }
317        for cfg in 2..=device_descriptor.bNumConfigurations {
318            match device_proxy.get_configuration_descriptor(cfg).await {
319                Ok(Ok(desc)) if !desc.is_empty() => {
320                    config_descriptors.extend_from_slice(&desc);
321                }
322                _ => {}
323            }
324        }
325
326        let speed = match device_proxy.get_device_speed().await {
327            Ok(s) => fuchsia_speed_to_linux_speed(s),
328            Err(_) => USB_SPEED_FULL,
329        };
330
331        let active_config = device_proxy.get_configuration().await.unwrap_or(1);
332
333        let assigned_dev_num = match manager.allocate_dev_num() {
334            Some(n) => n,
335            None => {
336                active_hw_ids.lock().remove(&hw_device_id);
337                log_error!("Exhausted USB device numbers (2..127) for incoming device.");
338                continue;
339            }
340        };
341
342        let sync_proxy = match instance.connect_to_device() {
343            Ok(client_proxy) => {
344                let client_end = client_proxy.into_client_end().ok();
345                client_end
346                    .map(|c| Arc::new(fhardware_usb::UsbSynchronousProxy::new(c.into_channel())))
347            }
348            Err(_) => None,
349        };
350
351        let mut usb_device =
352            UsbDevice::new(1, assigned_dev_num, speed, device_descriptor, config_descriptors);
353        usb_device.active_config.store(active_config, Ordering::Relaxed);
354        usb_device.sync_proxy = sync_proxy;
355        let usb_device = Arc::new(usb_device);
356
357        let add_res = manager.add_device(&kernel, Arc::clone(&usb_device));
358
359        match add_res {
360            Ok(registered_device) => {
361                log_info!(
362                    "Registered USB device in Starnix usbfs (/dev/bus/usb/001/{:03}) with speed {}",
363                    assigned_dev_num,
364                    speed
365                );
366
367                let kernel_for_close = Arc::clone(&kernel);
368                let manager_for_close = manager.clone();
369                let active_hw_ids_for_close = Arc::clone(&active_hw_ids);
370                let proxy_for_close = device_proxy.clone();
371                kernel.kthreads.spawn_future(
372                    move || async move {
373                        let mut event_stream = proxy_for_close.take_event_stream();
374                        let _ = event_stream.try_next().await;
375                        active_hw_ids_for_close.lock().remove(&hw_device_id);
376                        let _ = run_in_kernel_task_async(
377                            &kernel_for_close,
378                            "usbfs-device-disconnect",
379                            move |current_task| {
380                                if manager_for_close
381                                    .remove_device(current_task, registered_device)
382                                    .is_ok()
383                                {
384                                    log_info!(
385                                        "Removed disconnected USB device from Starnix usbfs (/dev/bus/usb/001/{:03})",
386                                        assigned_dev_num
387                                    );
388                                }
389                            },
390                        )
391                        .await;
392                    },
393                    "usbfs-device-lifecycle",
394                );
395            }
396            Err(e) => {
397                manager.release_dev_num(assigned_dev_num);
398                active_hw_ids.lock().remove(&hw_device_id);
399                log_error!("Failed to register USB device in Starnix usbfs: {:?}", e);
400            }
401        }
402    }
403}
404
405#[cfg(test)]
406mod tests {
407    use super::*;
408    use starnix_core::device::DeviceListener;
409    use starnix_core::device::kobject::{UEventAction, UEventContext};
410    use starnix_core::task::KernelFeatures;
411    use starnix_core::testing::{spawn_kernel_and_run, spawn_kernel_with_features_and_run};
412    use starnix_core::vfs::{FsStr, FsString};
413    use starnix_uapi::{
414        error, usb_config_descriptor, usb_endpoint_descriptor, usb_interface_descriptor,
415    };
416    use std::sync::atomic::{AtomicUsize, Ordering};
417    use zerocopy::IntoBytes as _;
418
419    fn create_mock_hid_descriptors() -> (usb_device_descriptor, Vec<u8>) {
420        let device_desc = usb_device_descriptor {
421            bLength: 18,
422            bDescriptorType: 1,
423            bcdUSB: 0x0210u16.to_le(),
424            bDeviceClass: 0,
425            bDeviceSubClass: 0,
426            bDeviceProtocol: 0,
427            bMaxPacketSize0: 64,
428            idVendor: 0x1234u16.to_le(),
429            idProduct: 0x5678u16.to_le(),
430            bcdDevice: 0x0543u16.to_le(),
431            iManufacturer: 1,
432            iProduct: 2,
433            iSerialNumber: 3,
434            bNumConfigurations: 1,
435        };
436
437        let config_desc = usb_config_descriptor {
438            bLength: 9,
439            bDescriptorType: 2,
440            wTotalLength: ((9 + 9 + 7 + 7) as u16).to_le(),
441            bNumInterfaces: 1,
442            bConfigurationValue: 1,
443            iConfiguration: 0,
444            bmAttributes: 0x80, // Bus powered
445            bMaxPower: 50,      // 100mA
446        };
447
448        let if_desc = usb_interface_descriptor {
449            bLength: 9,
450            bDescriptorType: 4,
451            bInterfaceNumber: 0,
452            bAlternateSetting: 0,
453            bNumEndpoints: 2,
454            bInterfaceClass: 3, // HID class
455            bInterfaceSubClass: 0,
456            bInterfaceProtocol: 0,
457            iInterface: 0,
458        };
459
460        let ep_in_desc = usb_endpoint_descriptor {
461            bLength: 7,
462            bDescriptorType: 5,
463            bEndpointAddress: 0x81, // EP 1 IN
464            bmAttributes: 3,        // Interrupt
465            wMaxPacketSize: 64u16.to_le(),
466            bInterval: 5,
467            bRefresh: 0,
468            bSynchAddress: 0,
469        };
470
471        let ep_out_desc = usb_endpoint_descriptor {
472            bLength: 7,
473            bDescriptorType: 5,
474            bEndpointAddress: 0x01, // EP 1 OUT
475            bmAttributes: 3,        // Interrupt
476            wMaxPacketSize: 64u16.to_le(),
477            bInterval: 5,
478            bRefresh: 0,
479            bSynchAddress: 0,
480        };
481
482        let mut config_bytes = Vec::new();
483        config_bytes.extend_from_slice(config_desc.as_bytes());
484        config_bytes.extend_from_slice(if_desc.as_bytes());
485        config_bytes.extend_from_slice(&ep_in_desc.as_bytes()[..7]);
486        config_bytes.extend_from_slice(&ep_out_desc.as_bytes()[..7]);
487
488        (device_desc, config_bytes)
489    }
490
491    fn open_dev_node(
492        current_task: &CurrentTask,
493        path: &FsStr,
494        flags: OpenFlags,
495    ) -> Result<starnix_core::vfs::FileHandle, Errno> {
496        let dev_root = NamespaceNode::new_anonymous(
497            starnix_core::fs::devtmpfs::DevTmpFs::from_kernel(current_task.kernel()).root().clone(),
498        );
499        let mut context = starnix_core::vfs::LookupContext::default();
500        let node = current_task.lookup_path(&mut context, dev_root, path)?;
501        node.open(current_task, flags)
502    }
503
504    #[::fuchsia::test]
505    async fn test_usb_device_descriptor_layout() {
506        assert_eq!(std::mem::size_of::<usb_device_descriptor>(), 18);
507        assert_eq!(std::mem::size_of::<usb_config_descriptor>(), 9);
508        assert_eq!(std::mem::size_of::<usb_interface_descriptor>(), 9);
509        assert_eq!(std::mem::size_of::<usb_endpoint_descriptor>(), 9);
510
511        let (device_desc, config_bytes) = create_mock_hid_descriptors();
512        let device = UsbDevice::new(1, 2, USB_SPEED_HIGH, device_desc, config_bytes);
513
514        assert_eq!(device.bus_num, 1);
515        assert_eq!(device.dev_num, 2);
516        assert_eq!(device.minor(), 1);
517        assert_eq!(device.speed, USB_SPEED_HIGH);
518        assert_eq!(device.raw_descriptors.len(), 18 + 9 + 9 + 7 + 7);
519    }
520
521    #[::fuchsia::test]
522    async fn test_usbfs_registration_and_devtmpfs() {
523        spawn_kernel_with_features_and_run(
524            async |current_task| {
525                let kernel = current_task.kernel();
526                let manager = usbfs_init(kernel).expect("usbfs_init failed");
527
528                let (device_desc, config_bytes) = create_mock_hid_descriptors();
529                let usb_dev =
530                    Arc::new(UsbDevice::new(1, 2, USB_SPEED_HIGH, device_desc, config_bytes));
531
532                let registered = manager.add_device(kernel, usb_dev).expect("add_device failed");
533                let metadata = registered.metadata().expect("metadata present");
534                assert_eq!(metadata.devname.as_slice(), b"bus/usb/001/002");
535                assert_eq!(metadata.devt.major(), USB_DEVICE_MAJOR);
536                assert_eq!(metadata.devt.minor(), 1);
537
538                let node = open_dev_node(current_task, b"bus/usb/001/002".into(), OpenFlags::RDWR)
539                    .expect("open_dev_node failed");
540                assert!(node.ops().is_seekable());
541
542                // Verify device appears under /sys/bus/usb/devices/1-2.
543                assert!(
544                    kernel
545                        .device_registry
546                        .objects
547                        .root
548                        .lookup("bus/usb/devices/1-2".into())
549                        .is_some()
550                );
551
552                manager.remove_device(current_task, registered).expect("remove_device failed");
553
554                // Verify device is removed from /sys/bus/usb/devices/1-2.
555                assert!(
556                    kernel
557                        .device_registry
558                        .objects
559                        .root
560                        .lookup("bus/usb/devices/1-2".into())
561                        .is_none()
562                );
563            },
564            KernelFeatures { devtmpfs: true, ..Default::default() },
565        )
566        .await;
567    }
568
569    #[::fuchsia::test]
570    async fn test_usbfs_read_descriptors() {
571        spawn_kernel_with_features_and_run(
572            async |current_task| {
573                let kernel = current_task.kernel();
574                let manager = usbfs_init(kernel).expect("usbfs_init failed");
575
576                let (device_desc, config_bytes) = create_mock_hid_descriptors();
577                let mut expected_raw = Vec::new();
578                expected_raw.extend_from_slice(device_desc.as_bytes());
579                expected_raw.extend_from_slice(&config_bytes);
580
581                let usb_dev = Arc::new(UsbDevice::new(
582                    1,
583                    3,
584                    USB_SPEED_FULL,
585                    device_desc,
586                    config_bytes.clone(),
587                ));
588                let registered = manager.add_device(kernel, usb_dev).unwrap();
589
590                let file =
591                    open_dev_node(current_task, b"bus/usb/001/003".into(), OpenFlags::RDONLY)
592                        .expect("open_dev_node failed");
593
594                let mut output_buf = starnix_core::vfs::buffers::VecOutputBuffer::new(128);
595                let bytes_read = file.ops().read(&file, current_task, 0, &mut output_buf).unwrap();
596
597                assert_eq!(bytes_read, expected_raw.len());
598                assert_eq!(Vec::from(output_buf), expected_raw);
599
600                manager.remove_device(current_task, registered).unwrap();
601            },
602            KernelFeatures { devtmpfs: true, ..Default::default() },
603        )
604        .await;
605    }
606
607    struct MockUEventListener {
608        add_count: Arc<AtomicUsize>,
609        remove_count: Arc<AtomicUsize>,
610    }
611
612    impl DeviceListener for MockUEventListener {
613        fn on_device_event(&self, action: UEventAction, device: Device, _context: UEventContext) {
614            if let Some(metadata) = device.metadata() {
615                if metadata.devname.as_slice() == b"bus/usb/001/005" {
616                    match action {
617                        UEventAction::Add => {
618                            self.add_count.fetch_add(1, Ordering::SeqCst);
619                        }
620                        UEventAction::Remove => {
621                            self.remove_count.fetch_add(1, Ordering::SeqCst);
622                        }
623                        _ => {}
624                    }
625                }
626            }
627        }
628    }
629
630    #[::fuchsia::test]
631    async fn test_usbfs_uevent_emission() {
632        spawn_kernel_and_run(async |current_task| {
633            let kernel = current_task.kernel();
634            let manager = usbfs_init(kernel).expect("usbfs_init failed");
635
636            let add_count = Arc::new(AtomicUsize::new(0));
637            let remove_count = Arc::new(AtomicUsize::new(0));
638            let listener = MockUEventListener {
639                add_count: add_count.clone(),
640                remove_count: remove_count.clone(),
641            };
642
643            let _listener_key = kernel.device_registry.register_listener(listener);
644
645            let (device_desc, config_bytes) = create_mock_hid_descriptors();
646            let usb_dev = Arc::new(UsbDevice::new(1, 5, USB_SPEED_HIGH, device_desc, config_bytes));
647            let registered = manager.add_device(kernel, usb_dev).unwrap();
648
649            let props: std::collections::HashMap<FsString, FsString> =
650                registered.get_uevent_properties_list().into_iter().collect();
651            assert_eq!(props.get(&FsString::from(b"SUBSYSTEM")).unwrap().as_slice(), b"usb");
652            assert_eq!(props.get(&FsString::from(b"DEVTYPE")).unwrap().as_slice(), b"usb_device");
653            assert_eq!(props.get(&FsString::from(b"BUSNUM")).unwrap().as_slice(), b"001");
654            assert_eq!(props.get(&FsString::from(b"DEVNUM")).unwrap().as_slice(), b"005");
655            assert_eq!(
656                props.get(&FsString::from(b"PRODUCT")).unwrap().as_slice(),
657                b"1234/5678/543"
658            );
659
660            assert_eq!(add_count.load(Ordering::SeqCst), 1);
661            assert_eq!(remove_count.load(Ordering::SeqCst), 0);
662
663            manager.remove_device(current_task, registered).unwrap();
664            assert_eq!(remove_count.load(Ordering::SeqCst), 1);
665        })
666        .await;
667    }
668
669    #[::fuchsia::test]
670    async fn test_multidevice_minor_allocation_and_cleanup() {
671        spawn_kernel_and_run(async |current_task| {
672            let kernel = current_task.kernel();
673            let manager = usbfs_init(kernel).expect("usbfs_init failed");
674
675            let dev1 = manager.allocate_dev_num().expect("alloc 1");
676            let dev2 = manager.allocate_dev_num().expect("alloc 2");
677            assert_eq!(dev1, 2);
678            assert_eq!(dev2, 3);
679
680            manager.release_dev_num(dev1);
681            let dev3 = manager.allocate_dev_num().expect("alloc 3");
682            assert_eq!(dev3, 4, "Monotonic device number allocation must increment");
683
684            manager.release_dev_num(dev3);
685            manager.release_dev_num(dev2);
686        })
687        .await;
688    }
689
690    #[::fuchsia::test]
691    async fn test_watch_usb_devices_missing_service_handled_gracefully() {
692        spawn_kernel_and_run(async |current_task| {
693            let kernel = current_task.kernel();
694            let manager = usbfs_init(kernel).expect("usbfs_init failed");
695            assert!(manager.allocate_dev_num().is_some());
696        })
697        .await;
698    }
699
700    #[::fuchsia::test]
701    async fn test_barebones_ioctl_returns_enotty() {
702        spawn_kernel_with_features_and_run(
703            async |current_task| {
704                let kernel = current_task.kernel();
705                let manager = usbfs_init(kernel).expect("usbfs_init failed");
706
707                let (device_desc, config_bytes) = create_mock_hid_descriptors();
708                let usb_dev =
709                    Arc::new(UsbDevice::new(1, 2, USB_SPEED_HIGH, device_desc, config_bytes));
710                let registered = manager.add_device(kernel, usb_dev.clone()).unwrap();
711
712                let file = open_dev_node(current_task, b"bus/usb/001/002".into(), OpenFlags::RDWR)
713                    .expect("open_dev_node failed");
714
715                // Calling any ioctl in barebones returns ENOTTY
716                let res = file.ops().ioctl(&file, current_task, 0x1234, 0u64.into());
717                assert_eq!(res, error!(ENOTTY));
718
719                // Disconnecting the device causes ioctl to return ENODEV
720                usb_dev.on_disconnect();
721                let res_disconn = file.ops().ioctl(&file, current_task, 0x1234, 0u64.into());
722                assert_eq!(res_disconn, error!(ENODEV));
723
724                manager.remove_device(current_task, registered).unwrap();
725            },
726            KernelFeatures { devtmpfs: true, ..Default::default() },
727        )
728        .await;
729    }
730}