Skip to main content

strat9_kernel/hardware/usb/
uhci.rs

1// USB UHCI (Universal Host Controller Interface) Driver
2// Reference: UHCI spec (USB 1.1)
3//
4// Features:
5// - UHCI controller initialization
6// - Port management
7// - Frame list and TD/QH management
8// - Low-speed USB 1.1 support
9
10#![allow(dead_code)]
11
12use crate::{
13    arch::x86_64::io::{inw, outw},
14    hardware::pci_client::{self as pci, Bar, ProbeCriteria},
15    memory::{allocate_zeroed_frame, phys_to_virt},
16    x86_crate_shim::instructions::port::Port,
17};
18use alloc::{sync::Arc, vec::Vec};
19use core::sync::atomic::{AtomicBool, Ordering};
20use spin::Mutex;
21
22const UHCI_USBCMD: u16 = 0x00;
23const UHCI_USBSTS: u16 = 0x02;
24const UHCI_USBINTR: u16 = 0x04;
25const UHCI_FRNUM: u16 = 0x06;
26const UHCI_FRBASEADDR: u16 = 0x08;
27const UHCI_SOFMOD: u16 = 0x0C;
28const UHCI_PORTSC: u16 = 0x10;
29
30const USBCMD_RUN_STOP: u16 = 1 << 0;
31const USBCMD_HCRESET: u16 = 1 << 1;
32const USBCMD_EGSM: u16 = 1 << 3;
33const USBCMD_FGSM: u16 = 1 << 4;
34const USBCMD_CONFIGURE: u16 = 1 << 6;
35const USBCMD_MAX_PACKET: u16 = 1 << 7;
36
37const USBSTS_USBINT: u16 = 1 << 0;
38const USBSTS_USBERR: u16 = 1 << 1;
39const USBSTS_RD: u16 = 1 << 2;
40const USBSTS_HSE: u16 = 1 << 3;
41const USBSTS_HCPE: u16 = 1 << 4;
42const USBSTS_HCH: u16 = 1 << 5;
43
44const PORTSC_CCS: u16 = 1 << 0;
45const PORTSC_CSC: u16 = 1 << 1;
46const PORTSC_PE: u16 = 1 << 2;
47const PORTSC_PEC: u16 = 1 << 3;
48const PORTSC_LSDA: u16 = 1 << 8;
49const PORTSC_PR: u16 = 1 << 9;
50
51const TD_TOKEN_ACTIVE: u32 = 1 << 23;
52const TD_TOKEN_IOC: u32 = 1 << 24;
53const TD_TOKEN_LS: u32 = 1 << 26;
54const TD_TOKEN_ERRCNT_SHIFT: u32 = 27;
55const TD_TOKEN_TOGGLE: u32 = 1 << 19;
56const TD_TOKEN_MAXPKT_SHIFT: u32 = 16;
57const TD_TOKEN_MAXPKT_MASK: u32 = 0x7FF << 16;
58const TD_TOKEN_DEVADDR_SHIFT: u32 = 9;
59const TD_TOKEN_ENDPT_SHIFT: u32 = 6;
60
61const TD_LINK_PTR_MASK: u32 = 0xFFFFFFF0;
62const TD_LINK_VF: u32 = 1 << 0;
63const TD_LINK_QH: u32 = 1 << 1;
64
65#[repr(C)]
66struct UhciTD {
67    link_ptr: u32,
68    ctrl_status: u32,
69    token: u32,
70    buffer: u32,
71}
72
73#[repr(C)]
74struct UhciQH {
75    head_link: u32,
76    element_link: u32,
77}
78
79pub struct UhciPort {
80    port_num: usize,
81    enabled: bool,
82    connected: bool,
83    low_speed: bool,
84}
85
86pub struct UhciController {
87    io_base: u16,
88    usbcmd: Port<u16>,
89    usbsts: Port<u16>,
90    usbintr: Port<u16>,
91    frnum: Port<u16>,
92    frbaseaddr: Port<u32>,
93    sofmod: Port<u16>,
94    max_ports: usize,
95    ports: Vec<UhciPort>,
96    frame_list: *mut u32,
97    frame_list_phys: u64,
98}
99
100unsafe impl Send for UhciController {}
101unsafe impl Sync for UhciController {}
102
103impl UhciController {
104    /// Creates a new instance.
105    pub unsafe fn new(pci_dev: pci::PciDevice) -> Result<Arc<Self>, &'static str> {
106        let io_base = match pci_dev.read_bar(4) {
107            Some(Bar::Io { port }) => port as u16,
108            _ => return Err("Invalid BAR4"),
109        };
110
111        let mut controller = Self {
112            io_base,
113            usbcmd: Port::new(io_base + UHCI_USBCMD),
114            usbsts: Port::new(io_base + UHCI_USBSTS),
115            usbintr: Port::new(io_base + UHCI_USBINTR),
116            frnum: Port::new(io_base + UHCI_FRNUM),
117            frbaseaddr: Port::new(io_base + UHCI_FRBASEADDR),
118            sofmod: Port::new(io_base + UHCI_SOFMOD),
119            max_ports: 2, // UHCI typically has 2 ports
120            ports: Vec::new(),
121            frame_list: core::ptr::null_mut(),
122            frame_list_phys: 0,
123        };
124
125        controller.init()?;
126        Ok(Arc::new(controller))
127    }
128
129    /// Performs the init operation.
130    fn init(&mut self) -> Result<(), &'static str> {
131        unsafe {
132            let mut cmd = self.usbcmd.read();
133            cmd &= !USBCMD_RUN_STOP;
134            self.usbcmd.write(cmd);
135            let mut timeout = 10000;
136            while self.usbsts.read() & USBSTS_HCH == 0 {
137                core::hint::spin_loop();
138                timeout -= 1;
139                if timeout == 0 {
140                    return Err("UHCI: controller did not halt");
141                }
142            }
143
144            cmd = self.usbcmd.read();
145            cmd |= USBCMD_HCRESET;
146            self.usbcmd.write(cmd);
147            let mut reset_ok = false;
148            for _ in 0..10000 {
149                if self.usbcmd.read() & USBCMD_HCRESET == 0 {
150                    reset_ok = true;
151                    break;
152                }
153                core::hint::spin_loop();
154            }
155            if !reset_ok {
156                return Err("UHCI: controller reset timed out");
157            }
158
159            // Initialize ports
160            for i in 0..self.max_ports {
161                let portsc = self.read_portsc(i);
162                self.ports.push(UhciPort {
163                    port_num: i,
164                    enabled: (portsc & PORTSC_PE) != 0,
165                    connected: (portsc & PORTSC_CCS) != 0,
166                    low_speed: (portsc & PORTSC_LSDA) != 0,
167                });
168            }
169
170            // Initialize frame list
171            self.init_frame_list()?;
172
173            // Enable interrupts
174            self.usbintr
175                .write(USBSTS_USBINT | USBSTS_USBERR | USBSTS_RD);
176
177            // Start the controller
178            cmd = self.usbcmd.read();
179            cmd |= USBCMD_RUN_STOP | USBCMD_CONFIGURE | USBCMD_MAX_PACKET;
180            self.usbcmd.write(cmd);
181        }
182        Ok(())
183    }
184
185    /// Initializes frame list.
186    unsafe fn init_frame_list(&mut self) -> Result<(), &'static str> {
187        // Allocate frame list (4KB aligned, 1024 entries for 1ms frames)
188        let frame = allocate_zeroed_frame().ok_or("Failed to allocate frame list")?;
189        self.frame_list_phys = frame.start_address.as_u64();
190        self.frame_list = phys_to_virt(self.frame_list_phys) as *mut u32;
191        core::ptr::write_bytes(self.frame_list as *mut u8, 0, 4096);
192
193        // Set up frame list (all entries point to termination)
194        for i in 0..1024 {
195            *self.frame_list.add(i) = 0x0001; // Terminate bit
196        }
197
198        self.frbaseaddr
199            .write((self.frame_list_phys & 0xFFFFF000) as u32);
200
201        Ok(())
202    }
203
204    /// Reads portsc.
205    unsafe fn read_portsc(&self, port: usize) -> u16 {
206        let mut port_reg = Port::new(self.io_base + UHCI_PORTSC + (port as u16) * 2);
207        port_reg.read()
208    }
209
210    /// Writes portsc.
211    unsafe fn write_portsc(&self, port: usize, val: u16) {
212        let mut port_reg = Port::new(self.io_base + UHCI_PORTSC + (port as u16) * 2);
213        port_reg.write(val);
214    }
215
216    /// Performs the port count operation.
217    pub fn port_count(&self) -> usize {
218        self.max_ports
219    }
220
221    /// Returns whether port connected.
222    pub fn is_port_connected(&self, port: usize) -> bool {
223        if port >= self.ports.len() {
224            return false;
225        }
226        self.ports[port].connected
227    }
228
229    /// Returns whether low speed.
230    pub fn is_low_speed(&self, port: usize) -> bool {
231        if port >= self.ports.len() {
232            return false;
233        }
234        self.ports[port].low_speed
235    }
236
237    /// Reset a port and wait for enable.
238    unsafe fn reset_port(&self, port: usize) -> bool {
239        let mut portsc = self.read_portsc(port);
240        if portsc & PORTSC_CCS == 0 {
241            return false;
242        }
243
244        // Port reset
245        self.write_portsc(port, portsc | PORTSC_PR);
246        for _ in 0..10_000u32 {
247            core::hint::spin_loop();
248        }
249        self.write_portsc(port, self.read_portsc(port) & !PORTSC_PR);
250        for _ in 0..10_000u32 {
251            core::hint::spin_loop();
252        }
253
254        // Wait for port enable
255        for _ in 0..100_000u32 {
256            portsc = self.read_portsc(port);
257            if portsc & PORTSC_PE != 0 {
258                return true;
259            }
260            if portsc & PORTSC_CCS == 0 {
261                return false;
262            }
263            core::hint::spin_loop();
264        }
265        false
266    }
267
268    /// Execute a USB control transfer via a frame list slot.
269    ///
270    /// Temporarily replaces a frame list entry with our QH, waits for
271    /// completion, then restores the original entry.
272    unsafe fn ctrl_transfer(
273        &self,
274        port: usize,
275        setup_data: &[u8; 8],
276        data_buf: Option<&mut [u8]>,
277        data_len: usize,
278        device_addr: u8,
279        max_packet: u32,
280        low_speed: bool,
281    ) -> Result<usize, &'static str> {
282        // Allocate QH
283        let qh_frame = allocate_zeroed_frame().ok_or("UHCI: QH alloc failed")?;
284        let qh_phys = qh_frame.start_address.as_u64();
285        let qh_virt = phys_to_virt(qh_phys) as *mut UhciQH;
286
287        // Allocate setup TD
288        let td_setup_frame = allocate_zeroed_frame().ok_or("UHCI: TD alloc failed")?;
289        let td_setup_phys = td_setup_frame.start_address.as_u64();
290        let td_setup_virt = phys_to_virt(td_setup_phys) as *mut UhciTD;
291
292        // Allocate setup buffer
293        let setup_buf_frame = allocate_zeroed_frame().ok_or("UHCI: setup buf alloc failed")?;
294        let setup_buf_phys = setup_buf_frame.start_address.as_u64();
295        let setup_buf_virt = phys_to_virt(setup_buf_phys) as *mut u8;
296        core::ptr::copy_nonoverlapping(setup_data.as_ptr(), setup_buf_virt, 8);
297
298        let dir_in = (setup_data[0] & 0x80) != 0;
299        let has_data = data_buf.is_some();
300
301        // UHCI TD token layout (32-bit, spec ยง7.1.3):
302        //   31:29 = reserved
303        //   28:27 = error count
304        //   26    = low-speed device
305        //   25    = reserved
306        //   24    = IOC
307        //   23    = Active
308        //   22:20 = reserved
309        //   19    = Data Toggle
310        //   18:16 = total bytes (bits 10:0 of count, max 0x7FF=2047)
311        //   15    = reserved
312        //   14:9  = device address
313        //   8:6   = endpoint
314        //   5:0   = PID code
315
316        // ctrl_status field: Active + Error Count + Low Speed
317        let ctrl_base = TD_TOKEN_ACTIVE
318            | (3u32 << TD_TOKEN_ERRCNT_SHIFT)
319            | (if low_speed { TD_TOKEN_LS } else { 0 });
320
321        // token field: IOC + Data Toggle + Bytes + DevAddr + EndPt + PID
322        // Setup TD: PID=0x2D (SETUP), 8 bytes, DATA0 (toggle=0)
323        let setup_token = (8u32 << TD_TOKEN_MAXPKT_SHIFT)
324            | ((device_addr as u32 & 0x7F) << TD_TOKEN_DEVADDR_SHIFT)
325            | (0u32 << TD_TOKEN_ENDPT_SHIFT)
326            | 0x2Du32; // PID = SETUP
327        (*td_setup_virt).link_ptr = 0;
328        (*td_setup_virt).ctrl_status = ctrl_base;
329        (*td_setup_virt).token = setup_token;
330        (*td_setup_virt).buffer = setup_buf_phys as u32;
331
332        // Allocate status TD (always needed)
333        let td_status_frame = allocate_zeroed_frame().ok_or("UHCI: status TD alloc failed")?;
334        let td_status_phys = td_status_frame.start_address.as_u64();
335        let td_status_virt = phys_to_virt(td_status_phys) as *mut UhciTD;
336
337        if has_data && data_len > 0 {
338            // Allocate data buffer
339            let data_buf_frame = allocate_zeroed_frame().ok_or("UHCI: data buf alloc failed")?;
340            let data_buf_phys_addr = data_buf_frame.start_address.as_u64();
341            let data_buf_virt_addr = phys_to_virt(data_buf_phys_addr) as *mut u8;
342
343            if !dir_in {
344                if let Some(ref buf) = data_buf {
345                    core::ptr::copy_nonoverlapping(buf.as_ptr(), data_buf_virt_addr, data_len);
346                }
347            }
348
349            // Data TD: toggle=1 (DATA1), PID IN/OUT
350            let td_data_frame = allocate_zeroed_frame().ok_or("UHCI: data TD alloc failed")?;
351            let td_data_phys = td_data_frame.start_address.as_u64();
352            let td_data_virt = phys_to_virt(td_data_phys) as *mut UhciTD;
353
354            let data_pid: u32 = if dir_in { 0x69 } else { 0xE1 };
355            let data_token = TD_TOKEN_TOGGLE
356                | (((data_len as u32) & 0x7FF) << TD_TOKEN_MAXPKT_SHIFT)
357                | ((device_addr as u32 & 0x7F) << TD_TOKEN_DEVADDR_SHIFT)
358                | (0u32 << TD_TOKEN_ENDPT_SHIFT)
359                | data_pid;
360            (*td_data_virt).link_ptr = 0;
361            (*td_data_virt).ctrl_status = ctrl_base;
362            (*td_data_virt).token = data_token;
363            (*td_data_virt).buffer = data_buf_phys_addr as u32;
364
365            // Status TD: toggle=0, 0 bytes, IOC=1, PID opposite direction
366            let status_pid: u32 = if dir_in { 0xE1 } else { 0x69 };
367            let status_token = TD_TOKEN_IOC
368                | (0u32 << TD_TOKEN_MAXPKT_SHIFT)
369                | ((device_addr as u32 & 0x7F) << TD_TOKEN_DEVADDR_SHIFT)
370                | (0u32 << TD_TOKEN_ENDPT_SHIFT)
371                | status_pid;
372            (*td_status_virt).link_ptr = 0;
373            (*td_status_virt).ctrl_status = ctrl_base;
374            (*td_status_virt).token = status_token;
375            (*td_status_virt).buffer = 0;
376
377            // Chain TDs
378            (*td_setup_virt).link_ptr = (td_data_phys as u32) | TD_LINK_VF;
379            (*td_data_virt).link_ptr = (td_status_phys as u32) | TD_LINK_VF;
380        } else {
381            // Status-only TD: toggle=1, 0 bytes, IOC=1
382            let status_token = TD_TOKEN_IOC
383                | TD_TOKEN_TOGGLE
384                | (0u32 << TD_TOKEN_MAXPKT_SHIFT)
385                | ((device_addr as u32 & 0x7F) << TD_TOKEN_DEVADDR_SHIFT)
386                | (0u32 << TD_TOKEN_ENDPT_SHIFT)
387                | 0x69u32; // PID = IN
388            (*td_status_virt).link_ptr = 0;
389            (*td_status_virt).ctrl_status = ctrl_base;
390            (*td_status_virt).token = status_token;
391            (*td_status_virt).buffer = 0;
392
393            (*td_setup_virt).link_ptr = (td_status_phys as u32) | TD_LINK_VF;
394        }
395
396        // Set up QH
397        (*qh_virt).head_link = 0x0000_0002; // terminate
398        (*qh_virt).element_link = td_setup_phys as u32;
399
400        // Compute status TD physical address for completion polling
401        let status_td_phys = if has_data && data_len > 0 {
402            td_setup_phys + 64 // setup(32) + data(32) + status(32) at +64
403        } else {
404            td_setup_phys + 32 // setup(32) + status(32) at +32
405        };
406
407        // Point current frame to QH
408        let frame_idx = unsafe { inw(self.io_base + UHCI_FRNUM) } as usize % 1024;
409        let old_frame = core::ptr::read_volatile(self.frame_list.add(frame_idx));
410        core::ptr::write_volatile(
411            self.frame_list.add(frame_idx),
412            (qh_phys as u32 & 0xFFFFFFFE) | TD_LINK_QH,
413        );
414
415        // Wait for completion: poll the status TD
416        let status_td_virt = phys_to_virt(status_td_phys) as *const UhciTD;
417        let mut transferred = 0;
418        for _ in 0..1_000_000u32 {
419            let token = core::ptr::read_volatile(core::ptr::addr_of!((*status_td_virt).token));
420            if token & TD_TOKEN_ACTIVE == 0 {
421                if dir_in && has_data && data_len > 0 {
422                    if let Some(buf) = data_buf {
423                        // Data is in the data TD's buffer (setup + 32 bytes)
424                        let data_td_virt = phys_to_virt(td_setup_phys + 32) as *const UhciTD;
425                        let data_buf_ptr = phys_to_virt((*data_td_virt).buffer as u64) as *const u8;
426                        core::ptr::copy_nonoverlapping(data_buf_ptr, buf.as_mut_ptr(), data_len);
427                        transferred = data_len;
428                    }
429                }
430                break;
431            }
432            core::hint::spin_loop();
433        }
434
435        // Restore frame list
436        core::ptr::write_volatile(self.frame_list.add(frame_idx), old_frame);
437
438        Ok(transferred)
439    }
440
441    /// Enumerate connected ports and hand off HID devices.
442    fn enumerate_all_ports(&self) {
443        unsafe {
444            core::arch::asm!("out 0xe9, al", in("al") b'W', options(nomem, nostack));
445        }
446        let mut usb_address: u8 = 1;
447
448        for port in 0..self.max_ports {
449            let portsc = unsafe { self.read_portsc(port) };
450            if portsc & PORTSC_CCS == 0 {
451                continue;
452            }
453
454            log::info!("[UHCI] Port {} connected, resetting...", port);
455
456            if !unsafe { self.reset_port(port) } {
457                log::warn!("[UHCI] Port {} reset failed", port);
458                continue;
459            }
460
461            let low_speed = unsafe { self.is_low_speed(port) };
462            let max_packet: u32 = if low_speed { 8 } else { 64 };
463            log::info!(
464                "[UHCI] Port {} low_speed={} max_pkt={}",
465                port,
466                low_speed,
467                max_packet
468            );
469
470            // Set address
471            let addr = usb_address;
472            let ctrl_dev_addr = [0x00u8, 0x05, addr, 0x00, 0x00, 0x00, 0x00, 0x00];
473            if unsafe {
474                self.ctrl_transfer(port, &ctrl_dev_addr, None, 0, 0, max_packet, low_speed)
475            }
476            .is_err()
477            {
478                log::warn!("[UHCI] Port {} set address failed", port);
479                continue;
480            }
481
482            // Get descriptor (8 bytes) to learn max_packet0
483            let get_desc_8 = [0x80u8, 0x06, 0x00, 0x01, 0x00, 0x00, 8, 0x00];
484            let mut desc8 = [0u8; 8];
485            if unsafe {
486                self.ctrl_transfer(
487                    port,
488                    &get_desc_8,
489                    Some(&mut desc8),
490                    8,
491                    addr,
492                    max_packet,
493                    low_speed,
494                )
495            }
496            .is_err()
497            {
498                log::warn!("[UHCI] Port {} get desc (8) failed", addr);
499                usb_address += 1;
500                continue;
501            }
502
503            let vid = u16::from_le_bytes([desc8[2], desc8[3]]);
504            let pid = u16::from_le_bytes([desc8[4], desc8[5]]);
505            let max_pkt0 = desc8[7] as u16;
506            log::info!(
507                "[UHCI] Port {} device VID={:04x} PID={:04x} max_pkt0={}",
508                port,
509                vid,
510                pid,
511                max_pkt0
512            );
513
514            // Get full 18-byte device descriptor
515            let get_desc_18 = [0x80u8, 0x06, 0x00, 0x01, 0x00, 0x00, 18, 0x00];
516            let mut dev_desc = [0u8; 18];
517            let _ = unsafe {
518                self.ctrl_transfer(
519                    port,
520                    &get_desc_18,
521                    Some(&mut dev_desc),
522                    18,
523                    addr,
524                    max_pkt0 as u32,
525                    low_speed,
526                )
527            };
528
529            // Set configuration (value=1)
530            let set_config = [0x00u8, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
531            let _ = unsafe {
532                self.ctrl_transfer(port, &set_config, None, 0, addr, max_pkt0 as u32, low_speed)
533            };
534
535            crate::hardware::usb::hid::enumerate_device(port, addr as u8, &dev_desc);
536
537            usb_address += 1;
538        }
539    }
540}
541
542static UHCI_CONTROLLERS: Mutex<Vec<Arc<UhciController>>> = Mutex::new(Vec::new());
543static UHCI_INITIALIZED: AtomicBool = AtomicBool::new(false);
544
545/// Performs the init operation.
546pub fn init() {
547    log::info!("[UHCI] Scanning for UHCI controllers...");
548
549    let candidates = pci::probe_all(ProbeCriteria {
550        vendor_id: None,
551        device_id: None,
552        class_code: Some(0x0C),
553        subclass: Some(0x03),
554        prog_if: Some(0x00),
555    });
556
557    unsafe {
558        core::arch::asm!("out 0xe9, al", in("al") b'q', options(nomem, nostack));
559        core::arch::asm!("out 0xe9, al", in("al") (b'0' + candidates.len() as u8), options(nomem, nostack));
560        core::arch::asm!("out 0xe9, al", in("al") b'\n', options(nomem, nostack));
561    }
562    for pci_dev in candidates.into_iter() {
563        log::info!(
564            "UHCI: Found controller at {:?} (VEN:{:04x} DEV:{:04x})",
565            pci_dev.address,
566            pci_dev.vendor_id,
567            pci_dev.device_id
568        );
569
570        pci_dev.enable_bus_master();
571
572        match unsafe { UhciController::new(pci_dev) } {
573            Ok(controller) => {
574                log::info!("[UHCI] Initialized with {} ports", controller.port_count());
575                unsafe {
576                    core::arch::asm!("out 0xe9, al", in("al") b'Q', options(nomem, nostack));
577                    core::arch::asm!("out 0xe9, al", in("al") (b'0' + controller.port_count() as u8), options(nomem, nostack));
578                    core::arch::asm!("out 0xe9, al", in("al") b'\n', options(nomem, nostack));
579                }
580                controller.enumerate_all_ports();
581                UHCI_CONTROLLERS.lock().push(controller);
582            }
583            Err(e) => {
584                log::warn!("UHCI: Failed to initialize controller: {}", e);
585            }
586        }
587    }
588
589    UHCI_INITIALIZED.store(true, Ordering::SeqCst);
590    log::info!(
591        "[UHCI] Found {} controller(s)",
592        UHCI_CONTROLLERS.lock().len()
593    );
594}
595
596/// Returns controller.
597pub fn get_controller(index: usize) -> Option<Arc<UhciController>> {
598    UHCI_CONTROLLERS.lock().get(index).cloned()
599}
600
601/// Returns whether available.
602pub fn is_available() -> bool {
603    UHCI_INITIALIZED.load(Ordering::Relaxed)
604}