⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/drivers/usb/
hotplug.rs

1//! USB Hotplug Detection
2//!
3//! Monitors xHCI port status change bits (PORTSC registers) to detect
4//! USB device attach and detach events.  Events are queued in a ring
5//! buffer and can be consumed by a userland udev daemon.
6
7#![allow(dead_code)]
8
9#[cfg(feature = "alloc")]
10extern crate alloc;
11
12use core::sync::atomic::{AtomicBool, Ordering};
13
14use spin::Mutex;
15
16use crate::error::KernelError;
17
18// ---------------------------------------------------------------------------
19// Constants
20// ---------------------------------------------------------------------------
21
22/// Maximum number of USB ports to monitor
23const MAX_PORTS: usize = 16;
24
25/// Event ring buffer capacity
26const EVENT_RING_CAPACITY: usize = 16;
27
28/// PORTSC register offsets (xHCI spec section 5.4.8)
29/// Connect Status Change bit
30const PORTSC_CSC: u32 = 1 << 17;
31/// Current Connect Status bit
32const PORTSC_CCS: u32 = 1 << 0;
33/// Port Enabled/Disabled bit
34const PORTSC_PED: u32 = 1 << 1;
35/// Port Reset bit
36const PORTSC_PR: u32 = 1 << 4;
37/// Port Link State bits [8:5]
38const PORTSC_PLS_MASK: u32 = 0xF << 5;
39/// Port Speed bits [13:10]
40const PORTSC_SPEED_MASK: u32 = 0xF << 10;
41/// Port Speed shift
42const PORTSC_SPEED_SHIFT: u32 = 10;
43/// Port Power bit
44const PORTSC_PP: u32 = 1 << 9;
45/// Port Enable/Disable Change bit
46const PORTSC_PEC: u32 = 1 << 18;
47/// Warm Port Reset Change bit
48const PORTSC_WRC: u32 = 1 << 19;
49/// Over-current Change bit
50const PORTSC_OCC: u32 = 1 << 20;
51/// Port Reset Change bit
52const PORTSC_PRC: u32 = 1 << 21;
53/// Port Link State Change bit
54const PORTSC_PLC: u32 = 1 << 22;
55/// Port Config Error Change bit
56const PORTSC_CEC: u32 = 1 << 23;
57
58/// Write-1-to-clear status change bits (must be preserved when writing PORTSC)
59const PORTSC_CHANGE_BITS: u32 =
60    PORTSC_CSC | PORTSC_PEC | PORTSC_WRC | PORTSC_OCC | PORTSC_PRC | PORTSC_PLC | PORTSC_CEC;
61
62/// USB device class codes
63const USB_CLASS_HID: u8 = 0x03;
64const USB_CLASS_MASS_STORAGE: u8 = 0x08;
65const USB_CLASS_HUB: u8 = 0x09;
66const USB_CLASS_AUDIO: u8 = 0x01;
67const USB_CLASS_VIDEO: u8 = 0x0E;
68const USB_CLASS_PRINTER: u8 = 0x07;
69const USB_CLASS_CDC: u8 = 0x02;
70
71// ---------------------------------------------------------------------------
72// Types
73// ---------------------------------------------------------------------------
74
75/// USB device connection speed
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum UsbDeviceSpeed {
78    /// 1.5 Mbps (USB 1.0)
79    Low,
80    /// 12 Mbps (USB 1.1)
81    Full,
82    /// 480 Mbps (USB 2.0)
83    High,
84    /// 5 Gbps (USB 3.0)
85    Super,
86}
87
88impl UsbDeviceSpeed {
89    /// Decode speed from xHCI PORTSC speed field
90    pub(crate) fn from_portsc(speed_field: u32) -> Self {
91        match speed_field {
92            1 => UsbDeviceSpeed::Full,
93            2 => UsbDeviceSpeed::Low,
94            3 => UsbDeviceSpeed::High,
95            4 => UsbDeviceSpeed::Super,
96            _ => UsbDeviceSpeed::Full, // default
97        }
98    }
99
100    /// Human-readable name
101    pub fn name(&self) -> &'static str {
102        match self {
103            UsbDeviceSpeed::Low => "Low Speed (1.5 Mbps)",
104            UsbDeviceSpeed::Full => "Full Speed (12 Mbps)",
105            UsbDeviceSpeed::High => "High Speed (480 Mbps)",
106            UsbDeviceSpeed::Super => "SuperSpeed (5 Gbps)",
107        }
108    }
109}
110
111/// USB hotplug event
112#[derive(Debug, Clone)]
113pub enum UsbHotplugEvent {
114    /// A USB device was physically attached to a port
115    DeviceAttached {
116        /// Port number (0-based)
117        port: u8,
118        /// Connection speed
119        speed: UsbDeviceSpeed,
120        /// USB vendor ID (from device descriptor, 0 if not yet read)
121        vendor_id: u16,
122        /// USB product ID (from device descriptor, 0 if not yet read)
123        product_id: u16,
124        /// USB device class code (from device descriptor)
125        device_class: u8,
126    },
127    /// A USB device was physically detached from a port
128    DeviceDetached {
129        /// Port number (0-based)
130        port: u8,
131    },
132}
133
134/// Per-port status tracking
135#[derive(Debug, Clone, Copy)]
136pub struct UsbPortStatus {
137    /// Whether a device is currently connected
138    pub connected: bool,
139    /// Whether the port is enabled
140    pub enabled: bool,
141    /// Current connection speed
142    pub speed: UsbDeviceSpeed,
143    /// Connect status changed since last poll
144    pub connect_changed: bool,
145    /// Enable status changed since last poll
146    pub enable_changed: bool,
147    /// Vendor ID of attached device (0 if none)
148    pub vendor_id: u16,
149    /// Product ID of attached device (0 if none)
150    pub product_id: u16,
151    /// Device class of attached device (0 if none)
152    pub device_class: u8,
153}
154
155impl Default for UsbPortStatus {
156    fn default() -> Self {
157        Self {
158            connected: false,
159            enabled: false,
160            speed: UsbDeviceSpeed::Full,
161            connect_changed: false,
162            enable_changed: false,
163            vendor_id: 0,
164            product_id: 0,
165            device_class: 0,
166        }
167    }
168}
169
170/// Hotplug callback function type
171pub type HotplugCallback = fn(UsbHotplugEvent);
172
173/// Event ring buffer for hotplug events
174struct EventRingBuffer {
175    events: [Option<UsbHotplugEvent>; EVENT_RING_CAPACITY],
176    head: usize,
177    tail: usize,
178    count: usize,
179}
180
181impl EventRingBuffer {
182    const fn new() -> Self {
183        // Initialize all slots to None using const array init
184        const NONE: Option<UsbHotplugEvent> = None;
185        Self {
186            events: [NONE; EVENT_RING_CAPACITY],
187            head: 0,
188            tail: 0,
189            count: 0,
190        }
191    }
192
193    fn push(&mut self, event: UsbHotplugEvent) -> bool {
194        if self.count >= EVENT_RING_CAPACITY {
195            return false; // ring full, drop event
196        }
197        self.events[self.tail] = Some(event);
198        self.tail = (self.tail + 1) % EVENT_RING_CAPACITY;
199        self.count += 1;
200        true
201    }
202
203    fn pop(&mut self) -> Option<UsbHotplugEvent> {
204        if self.count == 0 {
205            return None;
206        }
207        let event = self.events[self.head].take();
208        self.head = (self.head + 1) % EVENT_RING_CAPACITY;
209        self.count -= 1;
210        event
211    }
212
213    fn is_empty(&self) -> bool {
214        self.count == 0
215    }
216
217    fn len(&self) -> usize {
218        self.count
219    }
220
221    fn clear(&mut self) {
222        self.head = 0;
223        self.tail = 0;
224        self.count = 0;
225        for slot in self.events.iter_mut() {
226            *slot = None;
227        }
228    }
229}
230
231/// USB Hotplug Manager
232///
233/// Tracks port status for up to MAX_PORTS ports and generates
234/// attach/detach events by polling PORTSC change bits.
235pub struct UsbHotplugManager {
236    /// Per-port status
237    ports: [UsbPortStatus; MAX_PORTS],
238    /// Number of ports on this controller
239    num_ports: u8,
240    /// Event ring buffer
241    event_ring: EventRingBuffer,
242    /// Registered callbacks
243    callbacks: [Option<HotplugCallback>; 4],
244    /// Number of registered callbacks
245    num_callbacks: usize,
246    /// Base address of xHCI operational registers (for PORTSC access)
247    portsc_base: usize,
248    /// Whether hotplug monitoring is initialized
249    initialized: bool,
250    /// Total attach events since init
251    total_attach_events: u32,
252    /// Total detach events since init
253    total_detach_events: u32,
254}
255
256impl Default for UsbHotplugManager {
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262impl UsbHotplugManager {
263    /// Create a new hotplug manager
264    pub const fn new() -> Self {
265        const DEFAULT_PORT: UsbPortStatus = UsbPortStatus {
266            connected: false,
267            enabled: false,
268            speed: UsbDeviceSpeed::Full,
269            connect_changed: false,
270            enable_changed: false,
271            vendor_id: 0,
272            product_id: 0,
273            device_class: 0,
274        };
275        const NONE_CB: Option<HotplugCallback> = None;
276        Self {
277            ports: [DEFAULT_PORT; MAX_PORTS],
278            num_ports: 0,
279            event_ring: EventRingBuffer::new(),
280            callbacks: [NONE_CB; 4],
281            num_callbacks: 0,
282            portsc_base: 0,
283            initialized: false,
284            total_attach_events: 0,
285            total_detach_events: 0,
286        }
287    }
288
289    /// Initialize with the xHCI operational register base and port count
290    pub fn init(&mut self, portsc_base: usize, num_ports: u8) {
291        let capped = if num_ports as usize > MAX_PORTS {
292            MAX_PORTS as u8
293        } else {
294            num_ports
295        };
296        self.portsc_base = portsc_base;
297        self.num_ports = capped;
298        self.initialized = true;
299        self.event_ring.clear();
300        self.total_attach_events = 0;
301        self.total_detach_events = 0;
302
303        // Clear all port status
304        for port in self.ports.iter_mut() {
305            *port = UsbPortStatus::default();
306        }
307    }
308
309    /// Poll all ports for status changes
310    ///
311    /// Reads PORTSC registers, detects CSC (Connect Status Change),
312    /// and generates appropriate events.
313    pub fn poll(&mut self) {
314        if !self.initialized || self.portsc_base == 0 {
315            return;
316        }
317
318        for port_idx in 0..self.num_ports as usize {
319            let portsc = self.read_portsc(port_idx);
320
321            // Check Connect Status Change bit
322            if portsc & PORTSC_CSC != 0 {
323                let connected = portsc & PORTSC_CCS != 0;
324                let was_connected = self.ports[port_idx].connected;
325
326                if connected && !was_connected {
327                    // Device attached
328                    let speed_field = (portsc & PORTSC_SPEED_MASK) >> PORTSC_SPEED_SHIFT;
329                    let speed = UsbDeviceSpeed::from_portsc(speed_field);
330                    let enabled = portsc & PORTSC_PED != 0;
331
332                    // Read device descriptor to get vendor/product/class
333                    let (vendor_id, product_id, device_class) =
334                        self.read_device_descriptor(port_idx);
335
336                    self.ports[port_idx] = UsbPortStatus {
337                        connected: true,
338                        enabled,
339                        speed,
340                        connect_changed: true,
341                        enable_changed: false,
342                        vendor_id,
343                        product_id,
344                        device_class,
345                    };
346
347                    let event = UsbHotplugEvent::DeviceAttached {
348                        port: port_idx as u8,
349                        speed,
350                        vendor_id,
351                        product_id,
352                        device_class,
353                    };
354
355                    self.event_ring.push(event.clone());
356                    self.total_attach_events = self.total_attach_events.saturating_add(1);
357                    self.notify_callbacks(event);
358                } else if !connected && was_connected {
359                    // Device detached
360                    self.ports[port_idx] = UsbPortStatus {
361                        connected: false,
362                        enabled: false,
363                        speed: UsbDeviceSpeed::Full,
364                        connect_changed: true,
365                        enable_changed: false,
366                        vendor_id: 0,
367                        product_id: 0,
368                        device_class: 0,
369                    };
370
371                    let event = UsbHotplugEvent::DeviceDetached {
372                        port: port_idx as u8,
373                    };
374
375                    self.event_ring.push(event.clone());
376                    self.total_detach_events = self.total_detach_events.saturating_add(1);
377                    self.notify_callbacks(event);
378                }
379
380                // Clear CSC by writing 1 to it (preserve other bits, clear change bits)
381                self.clear_portsc_csc(port_idx, portsc);
382            }
383
384            // Also check enable change
385            if portsc & PORTSC_PEC != 0 {
386                let enabled = portsc & PORTSC_PED != 0;
387                self.ports[port_idx].enabled = enabled;
388                self.ports[port_idx].enable_changed = true;
389
390                // Clear PEC
391                self.clear_portsc_change(port_idx, portsc, PORTSC_PEC);
392            }
393        }
394    }
395
396    /// Read PORTSC register for a given port
397    fn read_portsc(&self, port_idx: usize) -> u32 {
398        if self.portsc_base == 0 {
399            return 0;
400        }
401        // Each PORTSC is at offset 0x400 + (port_idx * 0x10) from operational base
402        let addr = self.portsc_base + 0x400 + port_idx * 0x10;
403
404        // SAFETY: portsc_base was validated at init time and addr is within
405        // the xHCI MMIO region.  Port index is bounded by num_ports.
406        #[cfg(all(target_arch = "x86_64", target_os = "none"))]
407        unsafe {
408            core::ptr::read_volatile(addr as *const u32)
409        }
410        #[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
411        {
412            let _ = addr;
413            0
414        }
415    }
416
417    /// Clear Connect Status Change bit in PORTSC
418    fn clear_portsc_csc(&self, port_idx: usize, current: u32) {
419        self.clear_portsc_change(port_idx, current, PORTSC_CSC);
420    }
421
422    /// Clear a specific change bit in PORTSC
423    ///
424    /// xHCI PORTSC is special: writing 1 to a change bit clears it,
425    /// but we must NOT write 1 to other change bits (would clear them too).
426    fn clear_portsc_change(&self, port_idx: usize, current: u32, change_bit: u32) {
427        if self.portsc_base == 0 {
428            return;
429        }
430        let addr = self.portsc_base + 0x400 + port_idx * 0x10;
431
432        // Preserve non-change bits, write 1 only to the target change bit
433        let write_val = (current & !PORTSC_CHANGE_BITS) | change_bit;
434
435        // SAFETY: addr is within xHCI MMIO region, validated at init.
436        #[cfg(all(target_arch = "x86_64", target_os = "none"))]
437        unsafe {
438            core::ptr::write_volatile(addr as *mut u32, write_val);
439        }
440        #[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
441        {
442            let _ = (addr, write_val);
443        }
444    }
445
446    /// Attempt to read device descriptor from a newly attached device
447    ///
448    /// Returns (vendor_id, product_id, device_class).
449    /// Returns (0, 0, 0) if the descriptor cannot be read yet.
450    fn read_device_descriptor(&self, _port_idx: usize) -> (u16, u16, u8) {
451        // In a full implementation this would issue a GET_DESCRIPTOR
452        // control transfer via the xHCI command ring.  For now we
453        // return zeros; the udev daemon can read descriptors later
454        // via the USB sysfs interface.
455        (0, 0, 0)
456    }
457
458    /// Get the next pending hotplug event
459    pub fn get_event(&mut self) -> Option<UsbHotplugEvent> {
460        self.event_ring.pop()
461    }
462
463    /// Check if there are pending events
464    pub fn has_events(&self) -> bool {
465        !self.event_ring.is_empty()
466    }
467
468    /// Get the number of pending events
469    pub fn pending_event_count(&self) -> usize {
470        self.event_ring.len()
471    }
472
473    /// Register a callback for hotplug events
474    pub fn register_callback(&mut self, callback: HotplugCallback) -> Result<(), KernelError> {
475        if self.num_callbacks >= self.callbacks.len() {
476            return Err(KernelError::ResourceExhausted {
477                resource: "hotplug callbacks",
478            });
479        }
480        self.callbacks[self.num_callbacks] = Some(callback);
481        self.num_callbacks += 1;
482        Ok(())
483    }
484
485    /// Notify all registered callbacks about an event
486    fn notify_callbacks(&self, event: UsbHotplugEvent) {
487        for cb in self.callbacks.iter().flatten() {
488            cb(event.clone());
489        }
490    }
491
492    /// Get port status for a specific port
493    pub fn port_status(&self, port: u8) -> Option<&UsbPortStatus> {
494        if port < self.num_ports {
495            Some(&self.ports[port as usize])
496        } else {
497            None
498        }
499    }
500
501    /// Get the number of monitored ports
502    pub fn num_ports(&self) -> u8 {
503        self.num_ports
504    }
505
506    /// Get total attach event count
507    pub fn total_attach_events(&self) -> u32 {
508        self.total_attach_events
509    }
510
511    /// Get total detach event count
512    pub fn total_detach_events(&self) -> u32 {
513        self.total_detach_events
514    }
515
516    /// Check if the hotplug manager is initialized
517    pub fn is_initialized(&self) -> bool {
518        self.initialized
519    }
520}
521
522// ---------------------------------------------------------------------------
523// Global Hotplug Manager
524// ---------------------------------------------------------------------------
525
526static HOTPLUG_MANAGER: Mutex<UsbHotplugManager> = Mutex::new(UsbHotplugManager::new());
527
528/// Whether hotplug subsystem has been initialized
529static HOTPLUG_INITIALIZED: AtomicBool = AtomicBool::new(false);
530
531/// Initialize USB hotplug detection
532///
533/// Should be called after xHCI controller enumeration, with the PORTSC
534/// base address and number of root hub ports.
535pub fn usb_hotplug_init(portsc_base: usize, num_ports: u8) {
536    let mut manager = HOTPLUG_MANAGER.lock();
537    manager.init(portsc_base, num_ports);
538    HOTPLUG_INITIALIZED.store(true, Ordering::Release);
539    crate::println!(
540        "[USB-HOTPLUG] Initialized, monitoring {} ports (PORTSC base: {:#x})",
541        num_ports,
542        portsc_base
543    );
544}
545
546/// Poll for USB hotplug events
547///
548/// Called periodically (e.g., from a timer interrupt or polling thread)
549/// to check xHCI port status change bits.
550pub fn usb_hotplug_poll() {
551    if !HOTPLUG_INITIALIZED.load(Ordering::Acquire) {
552        return;
553    }
554    HOTPLUG_MANAGER.lock().poll();
555}
556
557/// Get the next pending hotplug event
558pub fn usb_hotplug_get_event() -> Option<UsbHotplugEvent> {
559    if !HOTPLUG_INITIALIZED.load(Ordering::Acquire) {
560        return None;
561    }
562    HOTPLUG_MANAGER.lock().get_event()
563}
564
565/// Register a callback for hotplug events
566pub fn usb_hotplug_register_callback(callback: HotplugCallback) -> Result<(), KernelError> {
567    HOTPLUG_MANAGER.lock().register_callback(callback)
568}
569
570/// Check if there are pending hotplug events
571pub fn usb_hotplug_has_events() -> bool {
572    if !HOTPLUG_INITIALIZED.load(Ordering::Acquire) {
573        return false;
574    }
575    HOTPLUG_MANAGER.lock().has_events()
576}
577
578/// Get port status
579pub fn usb_hotplug_port_status(port: u8) -> Option<UsbPortStatus> {
580    if !HOTPLUG_INITIALIZED.load(Ordering::Acquire) {
581        return None;
582    }
583    HOTPLUG_MANAGER.lock().port_status(port).copied()
584}
585
586/// Get the number of monitored ports
587pub fn usb_hotplug_num_ports() -> u8 {
588    if !HOTPLUG_INITIALIZED.load(Ordering::Acquire) {
589        return 0;
590    }
591    HOTPLUG_MANAGER.lock().num_ports()
592}
593
594// ---------------------------------------------------------------------------
595// Tests
596// ---------------------------------------------------------------------------
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601
602    #[test]
603    fn test_usb_device_speed_from_portsc() {
604        assert_eq!(UsbDeviceSpeed::from_portsc(1), UsbDeviceSpeed::Full);
605        assert_eq!(UsbDeviceSpeed::from_portsc(2), UsbDeviceSpeed::Low);
606        assert_eq!(UsbDeviceSpeed::from_portsc(3), UsbDeviceSpeed::High);
607        assert_eq!(UsbDeviceSpeed::from_portsc(4), UsbDeviceSpeed::Super);
608        assert_eq!(UsbDeviceSpeed::from_portsc(0), UsbDeviceSpeed::Full);
609    }
610
611    #[test]
612    fn test_usb_device_speed_name() {
613        assert!(UsbDeviceSpeed::Low.name().contains("1.5"));
614        assert!(UsbDeviceSpeed::Full.name().contains("12"));
615        assert!(UsbDeviceSpeed::High.name().contains("480"));
616        assert!(UsbDeviceSpeed::Super.name().contains("5"));
617    }
618
619    #[test]
620    fn test_event_ring_buffer_empty() {
621        let ring = EventRingBuffer::new();
622        assert!(ring.is_empty());
623        assert_eq!(ring.len(), 0);
624    }
625
626    #[test]
627    fn test_event_ring_buffer_push_pop() {
628        let mut ring = EventRingBuffer::new();
629        let event = UsbHotplugEvent::DeviceDetached { port: 0 };
630        assert!(ring.push(event));
631        assert!(!ring.is_empty());
632        assert_eq!(ring.len(), 1);
633
634        let popped = ring.pop();
635        assert!(popped.is_some());
636        assert!(ring.is_empty());
637    }
638
639    #[test]
640    fn test_event_ring_buffer_overflow() {
641        let mut ring = EventRingBuffer::new();
642        for i in 0..EVENT_RING_CAPACITY {
643            let event = UsbHotplugEvent::DeviceDetached { port: i as u8 };
644            assert!(ring.push(event));
645        }
646        assert_eq!(ring.len(), EVENT_RING_CAPACITY);
647
648        // Ring is full, should fail
649        let event = UsbHotplugEvent::DeviceDetached { port: 99 };
650        assert!(!ring.push(event));
651    }
652
653    #[test]
654    fn test_event_ring_buffer_wrap() {
655        let mut ring = EventRingBuffer::new();
656
657        // Fill and drain partially
658        for i in 0..8 {
659            ring.push(UsbHotplugEvent::DeviceDetached { port: i });
660        }
661        for _ in 0..4 {
662            ring.pop();
663        }
664        assert_eq!(ring.len(), 4);
665
666        // Add more to wrap around
667        for i in 0..8 {
668            ring.push(UsbHotplugEvent::DeviceDetached { port: 10 + i });
669        }
670        assert_eq!(ring.len(), 12);
671    }
672
673    #[test]
674    fn test_event_ring_buffer_clear() {
675        let mut ring = EventRingBuffer::new();
676        for i in 0..5 {
677            ring.push(UsbHotplugEvent::DeviceDetached { port: i });
678        }
679        ring.clear();
680        assert!(ring.is_empty());
681        assert_eq!(ring.len(), 0);
682    }
683
684    #[test]
685    fn test_hotplug_manager_new() {
686        let manager = UsbHotplugManager::new();
687        assert!(!manager.is_initialized());
688        assert_eq!(manager.num_ports(), 0);
689    }
690
691    #[test]
692    fn test_hotplug_manager_init() {
693        let mut manager = UsbHotplugManager::new();
694        manager.init(0x1000, 4);
695        assert!(manager.is_initialized());
696        assert_eq!(manager.num_ports(), 4);
697    }
698
699    #[test]
700    fn test_hotplug_manager_port_capping() {
701        let mut manager = UsbHotplugManager::new();
702        manager.init(0x1000, 255); // over MAX_PORTS
703        assert_eq!(manager.num_ports(), MAX_PORTS as u8);
704    }
705
706    #[test]
707    fn test_hotplug_manager_port_status() {
708        let mut manager = UsbHotplugManager::new();
709        manager.init(0x1000, 4);
710
711        assert!(manager.port_status(0).is_some());
712        assert!(manager.port_status(3).is_some());
713        assert!(manager.port_status(4).is_none());
714
715        let status = manager.port_status(0).unwrap();
716        assert!(!status.connected);
717        assert!(!status.enabled);
718    }
719
720    #[test]
721    fn test_hotplug_manager_event_counters() {
722        let manager = UsbHotplugManager::new();
723        assert_eq!(manager.total_attach_events(), 0);
724        assert_eq!(manager.total_detach_events(), 0);
725    }
726
727    #[test]
728    fn test_hotplug_manager_no_events_when_uninit() {
729        let mut manager = UsbHotplugManager::new();
730        assert!(!manager.has_events());
731        assert!(manager.get_event().is_none());
732    }
733
734    #[test]
735    fn test_port_status_default() {
736        let status = UsbPortStatus::default();
737        assert!(!status.connected);
738        assert!(!status.enabled);
739        assert_eq!(status.vendor_id, 0);
740        assert_eq!(status.product_id, 0);
741        assert_eq!(status.device_class, 0);
742    }
743
744    #[test]
745    fn test_portsc_constants() {
746        // Verify bit positions match xHCI spec
747        assert_eq!(PORTSC_CCS, 0x0000_0001);
748        assert_eq!(PORTSC_PED, 0x0000_0002);
749        assert_eq!(PORTSC_CSC, 0x0002_0000);
750        assert_eq!(PORTSC_PEC, 0x0004_0000);
751    }
752
753    #[test]
754    fn test_register_callback_limit() {
755        let mut manager = UsbHotplugManager::new();
756        fn dummy(_: UsbHotplugEvent) {}
757
758        for _ in 0..4 {
759            assert!(manager.register_callback(dummy).is_ok());
760        }
761        // 5th should fail
762        assert!(manager.register_callback(dummy).is_err());
763    }
764}