diff --git a/local/recipes/drivers/ohcid/source/src/main.rs b/local/recipes/drivers/ohcid/source/src/main.rs index 16af5532c5..51a5e7f935 100644 --- a/local/recipes/drivers/ohcid/source/src/main.rs +++ b/local/recipes/drivers/ohcid/source/src/main.rs @@ -8,6 +8,9 @@ use std::thread; use log::{info, error, warn, LevelFilter}; use redox_driver_sys::dma::DmaBuffer; use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion}; +use usb_core::scheme::{UsbError, UsbHostController}; +use usb_core::types::{PortStatus, SetupPacket, TransferDirection}; + use registers::*; // ---- DMA helpers ---- @@ -18,11 +21,20 @@ fn alloc_dma(size: usize, align: usize) -> (*mut u8, usize) { } // ---- Controller state ---- -struct OhciController { mmio: MmioRegion, port_count: usize } +struct OhciController { + name: String, + mmio: MmioRegion, + port_count: usize, +} struct PortDevice { - address: u8, vendor_id: u16, product_id: u16, - device_class: u8, device_subclass: u8, device_protocol: u8, low_speed: bool, + address: u8, + vendor_id: u16, + product_id: u16, + device_class: u8, + device_subclass: u8, + device_protocol: u8, + low_speed: bool, } impl OhciController { @@ -69,118 +81,216 @@ impl OhciController { self.reg_write(o, PORT_PPS); } } + + // ---- Control transfer method — was a free function ---- + // Linux 7.1 ohci-q.c PIPE_CONTROL pattern: 3-TD chain (setup → data → status) + // plus a dummy TD at the ED tail. See Linux source for full reference. + fn do_control_transfer( + &self, + dev_addr: u8, + ep: u8, + low_speed: bool, + setup_buf: &[u8; 8], + data: Option<(&[u8], bool)>, + mut out_buf: Option<&mut [u8]>, + ) -> Result { + let is_out = data.map(|(_, io)| !io).unwrap_or(true); + let data_len = data.map(|(b, _)| b.len()).unwrap_or(0); + + // Allocate ED + let (ed_ptr, ed_phys) = alloc_dma(core::mem::size_of::(), 16); + let (dummy_ptr, dummy_phys) = alloc_dma(core::mem::size_of::(), 16); + let ed = unsafe { &mut *(ed_ptr as *mut EndpointDescriptor) }; + let dummy = unsafe { &mut *(dummy_ptr as *mut TransferDescriptor) }; + + let spd = if low_speed { ED_LOW_SPEED } else { 0 }; + ed.hw_info = spd | ED_SKIP | ((dev_addr as u32) << ED_FUNC_ADDR_SHIFT) + | ((ep as u32) << 7) | ED_DIR_IN | (8u32 << ED_MAX_PKT_SHIFT); + dummy.hw_info = 0; dummy.hw_cbp = 0; dummy.hw_next_td = 0; dummy.hw_be = 0; + ed.hw_tail_p = dummy_phys as u32; + ed.hw_head_p = ED_HALTED; + ed.hw_next_ed = 0; + + let (stp_ptr, stp_phys) = alloc_dma(core::mem::size_of::(), 16); + let (stp_bf, stp_bf_phys) = alloc_dma(8, 16); + unsafe { core::ptr::copy_nonoverlapping(setup_buf.as_ptr(), stp_bf, 8); } + let stp = unsafe { &mut *(stp_ptr as *mut TransferDescriptor) }; + stp.hw_info = TD_CC_NO_ERROR | TD_DP_SETUP | TD_TOGGLE_0 | TD_DELAY_INT; + stp.hw_cbp = stp_bf_phys as u32; + stp.hw_be = if 8 > 0 { (stp_bf_phys + 7) as u32 } else { 0 }; + stp.hw_next_td = 0; + + let dt_ptr: *mut u8; + let dt_bf: *mut u8; + let dt_phys: usize; + if data_len > 0 { + let (ptr, phys) = alloc_dma(core::mem::size_of::(), 16); + let (bf, bf_phys) = alloc_dma(data_len, 16); + let td = unsafe { &mut *(ptr as *mut TransferDescriptor) }; + let dir = if is_out { TD_DP_OUT } else { TD_DP_IN }; + td.hw_info = TD_CC_NO_ERROR | TD_ROUND | TD_TOGGLE_1 | TD_DELAY_INT | dir; + td.hw_cbp = bf_phys as u32; + td.hw_next_td = 0; + td.hw_be = if data_len > 0 { (bf_phys.wrapping_add(data_len).wrapping_sub(1)) as u32 } else { 0 }; + if is_out { + let src = data.unwrap().0; + unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), bf, data_len); } + } + stp.hw_next_td = phys as u32; + dt_ptr = ptr; dt_bf = bf; dt_phys = bf_phys; + } else { + dt_ptr = core::ptr::null_mut(); dt_bf = core::ptr::null_mut(); dt_phys = 0; + } + + let (sta_ptr, sta_phys) = alloc_dma(core::mem::size_of::(), 16); + let sta = unsafe { &mut *(sta_ptr as *mut TransferDescriptor) }; + let sta_dir = if is_out || data_len == 0 { TD_DP_IN } else { TD_DP_OUT }; + sta.hw_info = TD_CC_NO_ERROR | TD_TOGGLE_1 | TD_DELAY_INT | sta_dir; + sta.hw_cbp = 0; sta.hw_next_td = dummy_phys as u32; sta.hw_be = 0; + + if data_len > 0 { + let dt = unsafe { &mut *(dt_ptr as *mut TransferDescriptor) }; + dt.hw_next_td = sta_phys as u32; + } + + ed.hw_head_p = stp_phys as u32; + ed.hw_tail_p = dummy_phys as u32; + + self.mmio.write32(HC_CONTROL_HEAD_ED, ed_phys as u32); + self.mmio.write32(HC_CMD_STATUS, CMD_HCR | (1 << 1)); + + let start = Instant::now(); + loop { + let done = self.mmio.read32(HC_DONE_HEAD); + if done != 0 { + self.mmio.write32(HC_DONE_HEAD, 0); + let cc = (stp.hw_info >> 28) as u32; + if cc != 0 { return Err("setup TD error"); } + + let actual = if data_len > 0 && !is_out { + let dt = unsafe { &*(dt_ptr as *const TransferDescriptor) }; + let cc_data = (dt.hw_info >> 28) as u32; + if cc_data != 0 { return Err("data TD error"); } + let len = (dt.hw_be.wrapping_sub(dt.hw_cbp) as usize).wrapping_add(1); + if let Some(ob) = out_buf.as_mut() { + let n = len.min(ob.len()); + unsafe { + let src = core::slice::from_raw_parts(dt_bf, n); + ob[..n].copy_from_slice(src); + } + } + len + } else { + 0 + }; + return Ok(actual); + } + if start.elapsed() > Duration::from_secs(2) { return Err("control transfer timeout"); } + thread::sleep(Duration::from_micros(100)); + } + } } -// ---- Control transfer — following Linux 7.1 ohci-q.c PIPE_CONTROL pattern ---- -fn control_transfer( - mmio: &MmioRegion, dev_addr: u8, ep: u8, low_speed: bool, - setup_buf: &[u8; 8], data: Option<(&[u8], bool)>, - mut out_buf: Option<&mut [u8]>, -) -> Result { - let is_out = data.map(|(_, io)| !io).unwrap_or(true); - let data_len = data.map(|(b, _)| b.len()).unwrap_or(0); - - // Allocate ED (Linux: ed, 16-byte aligned) - let (ed_ptr, ed_phys) = alloc_dma(core::mem::size_of::(), 16); - let (dummy_ptr, dummy_phys) = alloc_dma(core::mem::size_of::(), 16); - let ed = unsafe { &mut *(ed_ptr as *mut EndpointDescriptor) }; - let dummy = unsafe { &mut *(dummy_ptr as *mut TransferDescriptor) }; - - // ED init (Linux: ed->hwINFO, ed->hwTailP = dummy, ed->hwHeadP = halted) - let spd = if low_speed { ED_LOW_SPEED } else { 0 }; - ed.hw_info = spd | ED_SKIP | ((dev_addr as u32) << ED_FUNC_ADDR_SHIFT) - | ((ep as u32) << 7) | ED_DIR_IN | (8u32 << ED_MAX_PKT_SHIFT); - dummy.hw_info = 0; dummy.hw_cbp = 0; dummy.hw_next_td = 0; dummy.hw_be = 0; - ed.hw_tail_p = dummy_phys as u32; // tail → dummy (Linux: ed->hwTailP) - ed.hw_head_p = ED_HALTED; // halted until queue is set up - ed.hw_next_ed = 0; - - // Allocate 3 TDs: setup, data (optional), status (Linux: td_fill × 3) - let (stp_ptr, stp_phys) = alloc_dma(core::mem::size_of::(), 16); - let (stp_bf, stp_bf_phys) = alloc_dma(8, 16); - unsafe { core::ptr::copy_nonoverlapping(setup_buf.as_ptr(), stp_bf, 8); } - let stp = unsafe { &mut *(stp_ptr as *mut TransferDescriptor) }; - // Setup TD: DATA0 (Linux: TD_CC | TD_DP_SETUP | TD_T_DATA0) - stp.hw_info = TD_CC_NO_ERROR | TD_DP_SETUP | TD_TOGGLE_0 | TD_DELAY_INT; - stp.hw_cbp = stp_bf_phys as u32; - stp.hw_be = if 8 > 0 { (stp_bf_phys + 7) as u32 } else { 0 }; - stp.hw_next_td = 0; - - let dt_ptr: *mut u8; - let dt_bf: *mut u8; - let dt_phys: usize; - if data_len > 0 { - // Data TD: DATA1 (Linux: TD_CC | TD_R | TD_T_DATA1 | DP_IN/OUT) - let (ptr, phys) = alloc_dma(core::mem::size_of::(), 16); - let (bf, bf_phys) = alloc_dma(data_len, 16); - let td = unsafe { &mut *(ptr as *mut TransferDescriptor) }; - let dir = if is_out { TD_DP_OUT } else { TD_DP_IN }; - td.hw_info = TD_CC_NO_ERROR | TD_ROUND | TD_TOGGLE_1 | TD_DELAY_INT | dir; - td.hw_cbp = bf_phys as u32; - td.hw_next_td = 0; - td.hw_be = if data_len > 0 { (bf_phys.wrapping_add(data_len).wrapping_sub(1)) as u32 } else { 0 }; - if is_out { - let src = data.unwrap().0; - unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), bf, data_len); } - } - stp.hw_next_td = phys as u32; - dt_ptr = ptr; dt_bf = bf; dt_phys = bf_phys; - } else { - dt_ptr = core::ptr::null_mut(); dt_bf = core::ptr::null_mut(); dt_phys = 0; +// ---- UsbHostController trait implementation ---- +impl UsbHostController for OhciController { + fn name(&self) -> &str { + &self.name } - // Status TD: DATA1, opposite direction (Linux: TD_DP_IN/TD_DP_OUT | TD_T_DATA1) - let (sta_ptr, sta_phys) = alloc_dma(core::mem::size_of::(), 16); - let sta = unsafe { &mut *(sta_ptr as *mut TransferDescriptor) }; - let sta_dir = if is_out || data_len == 0 { TD_DP_IN } else { TD_DP_OUT }; - sta.hw_info = TD_CC_NO_ERROR | TD_TOGGLE_1 | TD_DELAY_INT | sta_dir; - sta.hw_cbp = 0; sta.hw_next_td = dummy_phys as u32; sta.hw_be = 0; // next → dummy (Linux: td->hwNextTD = dummy->dma) - - // Chain: setup TD → data TD → status TD → dummy TD (Linux: linked list via td_fill) - if data_len > 0 { - let dt = unsafe { &mut *(dt_ptr as *mut TransferDescriptor) }; - dt.hw_next_td = sta_phys as u32; + fn port_count(&self) -> usize { + self.port_count } - // Queue: head → setup TD, tail → dummy (Linux: ed_schedule) - ed.hw_head_p = stp_phys as u32; // strip halted bit - ed.hw_tail_p = dummy_phys as u32; - - // Kickstart control list (Linux: OHCI_CLF) - mmio.write32(HC_CONTROL_HEAD_ED, ed_phys as u32); - mmio.write32(HC_CMD_STATUS, CMD_HCR | (1 << 1)); // CLF - - // Wait for DoneHead (Linux: update_done_list → read hcca->done_head, zero it, walk TDs) - let start = Instant::now(); - loop { - let done = mmio.read32(HC_DONE_HEAD); - if done != 0 { - mmio.write32(HC_DONE_HEAD, 0); // acknowledge (Linux: hcca->done_head = 0) - // Walk completed TDs via hwNextTD (simplified: just check setup TD CC) - let cc = (stp.hw_info >> 28) as u32; - if cc != 0 { return Err("setup TD error"); } - - let actual = if data_len > 0 && !is_out { - // IN transfer: copy data back from DMA buffer - let dt = unsafe { &*(dt_ptr as *const TransferDescriptor) }; - let cc_data = (dt.hw_info >> 28) as u32; - if cc_data != 0 { return Err("data TD error"); } - let len = (dt.hw_be.wrapping_sub(dt.hw_cbp) as usize).wrapping_add(1); - if let Some(ob) = out_buf.as_mut() { - let n = len.min(ob.len()); - unsafe { - let src = core::slice::from_raw_parts(dt_bf, n); - ob[..n].copy_from_slice(src); - } - } - len - } else { - 0 - }; - return Ok(actual); + fn port_status(&self, port: usize) -> Option { + if port >= self.port_count { + return None; } - if start.elapsed() > Duration::from_secs(2) { return Err("control transfer timeout"); } - thread::sleep(Duration::from_micros(100)); + let s = self.port_status(port); + Some(PortStatus { + connected: s & PORT_CCS != 0, + enabled: s & PORT_PES != 0, + suspended: s & PORT_PSS != 0, + over_current: s & PORT_POCI != 0, + reset: s & PORT_PRS != 0, + power: s & PORT_PPS != 0, + low_speed: s & PORT_LSDA != 0, + high_speed: false, // OHCI is USB 1.1, never high-speed + test_mode: false, + indicator: false, + }) + } + + fn port_reset(&mut self, port: usize) -> bool { + OhciController::port_reset(self, port) + } + + fn control_transfer( + &mut self, + device_address: u8, + setup: &SetupPacket, + data: &mut [u8], + ) -> Result { + // Build 8-byte setup packet + let mut setup_buf = [0u8; 8]; + setup_buf[0] = setup.request_type; + setup_buf[1] = setup.request; + let value = setup.value.to_le_bytes(); + setup_buf[2] = value[0]; setup_buf[3] = value[1]; + let index = setup.index.to_le_bytes(); + setup_buf[4] = index[0]; setup_buf[5] = index[1]; + let length = setup.length.to_le_bytes(); + setup_buf[6] = length[0]; setup_buf[7] = length[1]; + let is_in = setup.request_type & 0x80 != 0; + // OHCI does not track low-speed in the trait path — pass false + let low_speed = false; + let result = if data.is_empty() { + self.do_control_transfer(device_address, 0, low_speed, &setup_buf, None, None) + } else if is_in { + self.do_control_transfer(device_address, 0, low_speed, &setup_buf, None, Some(data)) + } else { + self.do_control_transfer( + device_address, + 0, + low_speed, + &setup_buf, + Some((data, false)), + None, + ) + }; + result.map_err(|e| { + error!("ohcid: control transfer error: {}", e); + UsbError::IoError + }) + } + + fn bulk_transfer( + &mut self, + _device_address: u8, + _endpoint: u8, + _data: &mut [u8], + _direction: TransferDirection, + ) -> Result { + // Bulk transfers in OHCI go through the bulk ED list. + // Not yet implemented in this driver. See P4 follow-up. + Err(UsbError::Unsupported) + } + + fn interrupt_transfer( + &mut self, + _device_address: u8, + _endpoint: u8, + _data: &mut [u8], + ) -> Result { + // Interrupt transfers in OHCI use the periodic ED list + // (HCCA.interr_table[0..31]). Not yet implemented. + // See P5 follow-up. + Err(UsbError::Unsupported) + } + + fn set_address(&mut self, _device_address: u8) -> bool { + // OHCI does not have a SET_ADDRESS controller command; + // SET_ADDRESS is a standard USB control transfer. + true } } @@ -194,6 +304,13 @@ fn main() { let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default(); info!("OHCI USB 1.1 at {}", device_path); + // Derive controller name + let ctrl_name = device_path + .rsplit('/') + .next() + .unwrap_or("ohci0") + .to_string() + "_ohci"; + let config_path = format!("{}/config", device_path); let bar0 = match fs::read(&config_path) { Ok(data) if data.len() >= 0x14 => u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]), @@ -202,9 +319,9 @@ fn main() { let mmio_addr = (bar0 & 0xFFFF_F000) as u64; let mmio = MmioRegion::map(mmio_addr, 4096, CacheType::Uncacheable, MmioProt::READ_WRITE) .expect("ohcid: MMIO map failed"); - info!("ohcid: MMIO at 0x{:08X}", mmio_addr); + info!("ohcid: {} MMIO at 0x{:08X}", ctrl_name, mmio_addr); - let ctrl = OhciController { mmio, port_count: 2 }; + let ctrl = OhciController { name: ctrl_name.clone(), mmio, port_count: 2 }; ctrl.reset(); let (_hcca, hcca_phys) = alloc_dma(core::mem::size_of::(), HCCA_ALIGN); @@ -234,9 +351,11 @@ fn main() { Ok(dev) => { info!("ohcid: port {} device {:04x}:{:04x} class {:02x}", port + 1, dev.vendor_id, dev.product_id, dev.device_class); + // P0-B1: spawn with per-controller scheme name + let scheme_name = usb_core::scheme::scheme_path(&ctrl_name); usb_core::spawn::spawn_class_driver_for_port( dev.device_class, dev.device_subclass, dev.device_protocol, - "usb", &format!("{}", port + 1), 0); + &scheme_name, &format!("{}", port + 1), 0); } Err(e) => warn!("ohcid: port {} enumeration failed: {}", port + 1, e), } @@ -253,23 +372,16 @@ fn main() { fn enumerate_device(ctrl: &OhciController, port: usize) -> Result { let low_speed = (ctrl.port_status(port) & PORT_LSDA) != 0; - - // GET_DESCRIPTOR(DEVICE, 8) — header only, address 0 let gd8: [u8; 8] = [0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00]; let mut hdr = [0u8; 8]; - control_transfer(&ctrl.mmio, 0, 0, low_speed, &gd8, None, Some(&mut hdr))?; - - // SET_ADDRESS + ctrl.do_control_transfer(0, 0, low_speed, &gd8, None, Some(&mut hdr))?; let addr = (port + 1) as u8; let sa: [u8; 8] = [0x00, 0x05, addr, 0x00, 0x00, 0x00, 0x00, 0x00]; - control_transfer(&ctrl.mmio, 0, 0, low_speed, &sa, None, None)?; + ctrl.do_control_transfer(0, 0, low_speed, &sa, None, None)?; thread::sleep(Duration::from_millis(10)); - - // GET_DESCRIPTOR(DEVICE, 18) — full descriptor at new address let gf: [u8; 8] = [0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00]; let mut dd = [0u8; 18]; - control_transfer(&ctrl.mmio, addr, 0, low_speed, &gf, None, Some(&mut dd))?; - + ctrl.do_control_transfer(addr, 0, low_speed, &gf, None, Some(&mut dd))?; Ok(PortDevice { address: addr, vendor_id: u16::from_le_bytes([dd[8], dd[9]]), @@ -277,4 +389,4 @@ fn enumerate_device(ctrl: &OhciController, port: usize) -> Result (*mut u8, usize) { // ---- Controller state ---- struct UhciController { + name: String, io_base: u16, port_count: usize, devices: Vec>, - _frame_list_dma: Vec, - _qh_dma: Vec, - _td_dma: Vec, } struct PortDevice { @@ -62,7 +62,7 @@ struct PortDevice { device_subclass: u8, device_protocol: u8, device_descriptor: [u8; 18], - config_descriptor: Vec, + _config_descriptor: Vec, low_speed: bool, } @@ -74,9 +74,6 @@ impl UhciController { fn write_reg(&self, offset: u16, value: u16) { unsafe { outw(self.io_base + offset, value) }; } - fn read32(&self, offset: u16) -> u32 { - unsafe { ind(self.io_base + offset) } - } fn write32(&self, offset: u16, value: u32) { unsafe { outd(self.io_base + offset, value) }; } @@ -89,42 +86,31 @@ impl UhciController { }; self.read_reg(offset) } - fn port_write(&self, port: usize, set: u16, clear: u16, toggles: u16) -> u16 { + fn port_write(&self, port: usize, set: u16, clear: u16) -> u16 { let offset = match port { 0 => PORTSC1, 1 => PORTSC2, _ => return 0, }; let current = self.read_reg(offset); - let mut value = (current & !clear) | set; - if toggles != 0 { - value ^= toggles; - } - // Preserve R/WC bits: write 1 to clear change bits + let value = (current & !clear) | set; let change_bits = current & PORT_CHANGE_BITS; self.write_reg(offset, value | change_bits); self.read_reg(offset) } fn reset_controller(&self) { - // Global reset self.write_reg(USBCMD, CMD_GLOBAL_RESET); thread::sleep(Duration::from_millis(50)); - // Stop the controller self.write_reg(USBCMD, 0); thread::sleep(Duration::from_millis(10)); - // Clear status self.write_reg(USBSTS, STS_HALTED); } fn init_frame_list(&self, frame_list_phys: usize, _qh_phys: usize) { - // Write frame list base address self.write32(FRBASEADD, (frame_list_phys & 0xFFFF_F000) as u32); - // Configure: 64-byte max packet, configure flag self.write_reg(USBCMD, CMD_CONFIGURE | CMD_MAX_PACKET_64); - // Start the controller self.write_reg(USBCMD, CMD_RUN_STOP | CMD_CONFIGURE | CMD_MAX_PACKET_64); - // Wait until not halted for _ in 0..100 { if self.read_reg(USBSTS) & STS_HALTED == 0 { break; @@ -139,139 +125,254 @@ impl UhciController { if portsc & PORT_CONNECT == 0 { return false; } - // Assert reset - self.port_write(port, PORT_RESET, 0, 0); + self.port_write(port, PORT_RESET, 0); thread::sleep(Duration::from_micros(PORT_RESET_HOLD_US)); - // De-assert reset - let new = self.port_write(port, 0, PORT_RESET, 0); + let new = self.port_write(port, 0, PORT_RESET); thread::sleep(Duration::from_micros(PORT_RESET_SETTLE_US)); (new & PORT_ENABLE) != 0 } + + // ---- Control transfer method (formerly free function) ---- + // Implements the UHCI-specific 3-TD chain for a USB control transfer. + // Returns Ok(bytes_transferred) on success, Err(&'static str) on failure. + // `data_buf` is Option<(&mut [u8], bool)> = (buffer, is_in). + fn do_control_transfer( + &self, + device_addr: u8, + endpoint: u8, + low_speed: bool, + setup_packet: &[u8; 8], + mut data_buf: Option<(&mut [u8], bool)>, + ) -> Result, &'static str> { + let (setup_dma_buf, setup_phys) = alloc_dma(8, 16); + let (_status_dma_buf, _) = alloc_dma(0, 16); + + let data_phys = match &data_buf { + Some((buf, _)) if !buf.is_empty() => { + let (data_dma, phys) = alloc_dma(buf.len(), 16); + if data_buf.as_ref().map(|(_, is_in)| *is_in).unwrap_or(false) { + // IN transfer: buffer filled by HC, we copy out after + } else { + // OUT transfer: copy data into DMA buffer + unsafe { + core::ptr::copy_nonoverlapping( + buf.as_ptr(), + data_dma, + buf.len(), + ); + } + } + // Leak DMA for now (cleaned up on process exit) + core::mem::forget(unsafe { Box::from_raw(data_dma) }); + phys + } + _ => 0, + }; + + // Build setup TD + unsafe { + core::ptr::copy_nonoverlapping(setup_packet.as_ptr(), setup_dma_buf, 8); + } + let setup_td_phys = unsafe { + let td = &mut *(setup_dma_buf.sub(32) as *mut TransferDescriptor); + td.link = PTR_TERM; + td.token = PID_SETUP + | ((device_addr as u32) << 8) + | ((endpoint as u32) << 15) + | (7 << 21); // maxlen = 8-1 + td.buffer = setup_phys as u32; + td.status = TD_CTRL_ACTIVE | TD_CTRL_IOC + | if low_speed { TD_CTRL_LS } else { 0 } + | (3 << 27); // 3 retries + setup_dma_buf as usize - 32 + }; + + // Poll for setup completion + let start = Instant::now(); + loop { + let td_status = unsafe { (*(setup_dma_buf.sub(32) as *const TransferDescriptor)).status }; + if td_status & TD_CTRL_ACTIVE == 0 { + if td_status & TD_STATUS_STALLED != 0 { + return Err("setup TD stalled"); + } + if td_status & (TD_STATUS_CRC | TD_STATUS_BABBLE) != 0 { + return Err("setup TD error"); + } + break; + } + if start.elapsed() > Duration::from_secs(2) { + return Err("setup TD timeout"); + } + thread::sleep(Duration::from_micros(100)); + } + + // Handle data phase + let actual_len = if let Some((ref mut buf, is_in)) = data_buf { + if buf.is_empty() { + None + } else { + unsafe { + let td = &mut *(setup_dma_buf as *mut TransferDescriptor); + td.link = PTR_TERM; + td.token = if is_in { + PID_IN | ((device_addr as u32) << 8) | ((endpoint as u32) << 15) + | ((buf.len() as u32 - 1) << 21) + } else { + PID_OUT | ((device_addr as u32) << 8) | ((endpoint as u32) << 15) + | ((buf.len() as u32 - 1) << 21) + }; + td.buffer = data_phys as u32; + td.status = TD_CTRL_ACTIVE | TD_CTRL_IOC + | TD_CTRL_SPD + | if low_speed { TD_CTRL_LS } else { 0 } + | (3 << 27); + setup_dma_buf as usize + }; + + let mut actual_len = 0usize; + loop { + let td_status = unsafe { (*(setup_dma_buf as *const TransferDescriptor)).status }; + if td_status & TD_CTRL_ACTIVE == 0 { + if td_status & TD_STATUS_STALLED != 0 { + return Err("data TD stalled"); + } + actual_len = (td_status & TD_ACTLEN_MASK) as usize + 1; + if is_in { + let n = actual_len.min(buf.len()); + let src = unsafe { core::slice::from_raw_parts(setup_dma_buf as *const u8, n) }; + (&mut buf[..n]).copy_from_slice(src); + } + break; + } + if start.elapsed() > Duration::from_secs(2) { + return Err("data TD timeout"); + } + thread::sleep(Duration::from_micros(100)); + } + Some(actual_len) + } + } else { + None + }; + + Ok(actual_len) + } } -// ---- Control transfer ---- -fn control_transfer( - io_base: u16, - device_addr: u8, - endpoint: u8, - low_speed: bool, - setup_packet: &[u8; 8], - mut data_buf: Option<(&mut [u8], bool)>, // (buffer, is_in) -) -> Result, &'static str> { - let (setup_dma_buf, setup_phys) = alloc_dma(8, 16); - let (_status_dma_buf, _) = alloc_dma(0, 16); - - let data_phys = match &data_buf { - Some((buf, _)) if !buf.is_empty() => { - let (data_dma, phys) = alloc_dma(buf.len(), 16); - if data_buf.as_ref().map(|(_, is_in)| *is_in).unwrap_or(false) { - // IN transfer: buffer filled by HC, we copy out after - } else { - // OUT transfer: copy data into DMA buffer - unsafe { - core::ptr::copy_nonoverlapping( - buf.as_ptr(), - data_dma, - buf.len(), - ); - } - } - // Leak DMA for now (cleaned up on process exit) - core::mem::forget(unsafe { Box::from_raw(data_dma) }); - phys - } - _ => 0, - }; - - // Build setup TD - unsafe { - core::ptr::copy_nonoverlapping(setup_packet.as_ptr(), setup_dma_buf, 8); - } - let setup_td_phys = unsafe { - let td = &mut *(setup_dma_buf.sub(32) as *mut TransferDescriptor); - td.link = PTR_TERM; - td.token = PID_SETUP - | ((device_addr as u32) << 8) - | ((endpoint as u32) << 15) - | (7 << 21); // maxlen = 8-1 - td.buffer = setup_phys as u32; - td.status = TD_CTRL_ACTIVE | TD_CTRL_IOC - | if low_speed { TD_CTRL_LS } else { 0 } - | (3 << 27); // 3 retries - setup_dma_buf as usize - 32 - }; - - // Poll for completion - let start = Instant::now(); - loop { - let td_status = unsafe { (*(setup_dma_buf.sub(32) as *const TransferDescriptor)).status }; - if td_status & TD_CTRL_ACTIVE == 0 { - if td_status & TD_STATUS_STALLED != 0 { - return Err("setup TD stalled"); - } - if td_status & (TD_STATUS_CRC | TD_STATUS_BABBLE) != 0 { - return Err("setup TD error"); - } - break; - } - if start.elapsed() > Duration::from_secs(2) { - return Err("setup TD timeout"); - } - thread::sleep(Duration::from_micros(100)); +// ---- UsbHostController trait implementation ---- +impl UsbHostController for UhciController { + fn name(&self) -> &str { + &self.name } - // Handle data phase - let actual_len = if let Some((ref mut buf, is_in)) = data_buf { - if buf.is_empty() { - None + fn port_count(&self) -> usize { + self.port_count + } + + fn port_status(&self, port: usize) -> Option { + if port >= self.port_count { + return None; + } + let s = self.port_status(port); + Some(PortStatus { + connected: s & PORT_CONNECT != 0, + enabled: s & PORT_ENABLE != 0, + suspended: s & PORT_SUSPEND != 0, + over_current: s & PORT_OVER_CURRENT != 0, + reset: s & PORT_RESET != 0, + power: s & PORT_POWER != 0, + low_speed: s & PORT_LOW_SPEED != 0, + high_speed: false, // UHCI is USB 1.1, never high-speed + test_mode: false, + indicator: false, + }) + } + + fn port_reset(&mut self, port: usize) -> bool { + UhciController::port_reset(self, port) + } + + fn control_transfer( + &mut self, + device_address: u8, + setup: &SetupPacket, + data: &mut [u8], + ) -> Result { + // Build 8-byte setup packet from SetupPacket + let mut setup_buf = [0u8; 8]; + setup_buf[0] = setup.request_type; + setup_buf[1] = setup.request; + let value = setup.value.to_le_bytes(); + setup_buf[2] = value[0]; + setup_buf[3] = value[1]; + let index = setup.index.to_le_bytes(); + setup_buf[4] = index[0]; + setup_buf[5] = index[1]; + let length = setup.length.to_le_bytes(); + setup_buf[6] = length[0]; + setup_buf[7] = length[1]; + // Determine direction from request_type bit 7 + let is_in = setup.request_type & 0x80 != 0; + let result = if data.is_empty() { + self.do_control_transfer( + device_address, + 0, // endpoint 0 + false, // low_speed not tracked per-transfer in trait + &setup_buf, + None, + ) } else { - // Build data TD - unsafe { - let td = &mut *(setup_dma_buf as *mut TransferDescriptor); - td.link = PTR_TERM; - td.token = if is_in { - PID_IN | ((device_addr as u32) << 8) | ((endpoint as u32) << 15) - | ((buf.len() as u32 - 1) << 21) - } else { - PID_OUT | ((device_addr as u32) << 8) | ((endpoint as u32) << 15) - | ((buf.len() as u32 - 1) << 21) - }; - td.buffer = data_phys as u32; - td.status = TD_CTRL_ACTIVE | TD_CTRL_IOC - | TD_CTRL_SPD - | if low_speed { TD_CTRL_LS } else { 0 } - | (3 << 27); - setup_dma_buf as usize + let data_opt = if is_in { + Some((data, true)) + } else { + Some((data, false)) }; + self.do_control_transfer( + device_address, + 0, + false, + &setup_buf, + data_opt, + ) + }; + result.map(|opt| opt.unwrap_or(0)).map_err(|e| { + error!("uhcid: control transfer error: {}", e); + UsbError::IoError + }) + } - // Poll data TD - let mut actual_len = 0usize; - loop { - let td_status = unsafe { (*(setup_dma_buf as *const TransferDescriptor)).status }; - if td_status & TD_CTRL_ACTIVE == 0 { - if td_status & TD_STATUS_STALLED != 0 { - return Err("data TD stalled"); - } - actual_len = (td_status & TD_ACTLEN_MASK) as usize + 1; - if is_in { - let n = actual_len.min(buf.len()); - let src = unsafe { core::slice::from_raw_parts(setup_dma_buf as *const u8, n) }; - (&mut buf[..n]).copy_from_slice(src); - } - break; - } - if start.elapsed() > Duration::from_secs(2) { - return Err("data TD timeout"); - } - thread::sleep(Duration::from_micros(100)); - } - Some(actual_len) - } - } else { - None - }; + fn bulk_transfer( + &mut self, + _device_address: u8, + _endpoint: u8, + _data: &mut [u8], + _direction: TransferDirection, + ) -> Result { + // Bulk transfers not yet implemented in UHCI driver. + // UHCI has no native bulk list — bulk transfers go through the + // control transfer path's TD chain. See P4 follow-up. + Err(UsbError::Unsupported) + } - Ok(actual_len) + fn interrupt_transfer( + &mut self, + _device_address: u8, + _endpoint: u8, + _data: &mut [u8], + ) -> Result { + // Interrupt transfers in UHCI use the periodic frame list slots. + // Not yet implemented in this driver. See P5 follow-up. + Err(UsbError::Unsupported) + } + + fn set_address(&mut self, _device_address: u8) -> bool { + // UHCI does not have a SET_ADDRESS controller command; + // SET_ADDRESS is a standard USB control transfer handled via + // control_transfer(). Returns true here to indicate "address + // accepted" (the caller is expected to send SET_ADDRESS via + // control_transfer with the standard device request). + true + } } // ---- Main entry ---- @@ -288,6 +389,13 @@ fn main() { let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default(); info!("UHCI USB 1.1 at {}", device_path); + // Derive controller name from device path + let ctrl_name = device_path + .rsplit('/') + .next() + .unwrap_or("uhci0") + .to_string() + "_uhci"; + // Read BAR4 (I/O base) from PCI config space let config_path = format!("{}/config", device_path); let bar4 = match fs::read(&config_path) { @@ -297,13 +405,11 @@ fn main() { _ => { error!("cannot read PCI config"); process::exit(1); } }; - let io_base = (bar4 & 0xFFE0) as u16; // bits 4:0 reserved for I/O BAR - info!("UHCI I/O base: 0x{:04X}", io_base); + let io_base = (bar4 & 0xFFE0) as u16; + info!("UHCI I/O base: 0x{:04X} as {}", io_base, ctrl_name); // Allocate DMA memory - // Frame list: 1024 entries × 4 bytes, 4KB aligned let (frame_list, frame_list_phys) = alloc_dma(FRAME_COUNT * 4, FRAME_LIST_ALIGN); - // Dummy QH: terminates the frame list entries let (dummy_qh, dummy_qh_phys) = alloc_dma(core::mem::size_of::(), 16); unsafe { @@ -312,7 +418,6 @@ fn main() { qh.element = PTR_TERM; } - // Initialize frame list: all entries point to the dummy QH unsafe { let frames = core::slice::from_raw_parts_mut(frame_list as *mut u32, FRAME_COUNT); for entry in frames.iter_mut() { @@ -320,34 +425,28 @@ fn main() { } } - // Reset and initialize controller let ctrl = UhciController { + name: ctrl_name, io_base, port_count: 2, devices: vec![None, None], - _frame_list_dma: Vec::new(), - _qh_dma: Vec::new(), - _td_dma: Vec::new(), }; ctrl.reset_controller(); ctrl.init_frame_list(frame_list_phys, dummy_qh_phys); - info!("uhcid: controller initialized, polling ports"); + info!("uhcid: {} controller initialized, polling ports", ctrl.name); // Main polling loop loop { for port in 0..ctrl.port_count { let portsc = ctrl.port_status(port); - // Detect newly connected device if (portsc & PORT_CONNECT) != 0 && (portsc & PORT_CSC) != 0 { - // Clear CSC - ctrl.port_write(port, 0, 0, 0); + ctrl.port_write(port, 0, 0); - info!("uhcid: port {} connect detected (status 0x{:04X})", port + 1, portsc); + info!("uhcid: port {} connect detected", port + 1); - // Reset and enumerate if ctrl.port_reset(port) { match enumerate_device(&ctrl, port) { Ok(dev) => { @@ -355,12 +454,13 @@ fn main() { "uhcid: port {} device {:04x}:{:04x} class {:02x}", port + 1, dev.vendor_id, dev.product_id, dev.device_class, ); - // P0-B1: auto-spawn class drivers + // P0-B1: auto-spawn class drivers via the trait-based path + let scheme_name = usb_core::scheme::scheme_path(&ctrl.name); usb_core::spawn::spawn_class_driver_for_port( dev.device_class, dev.device_subclass, dev.device_protocol, - "usb", + &scheme_name, &format!("{}", port + 1), 0, ); @@ -372,9 +472,8 @@ fn main() { } } - // Detect disconnect if (portsc & PORT_CONNECT) == 0 && (portsc & PORT_CSC) != 0 { - ctrl.port_write(port, 0, 0, 0); + ctrl.port_write(port, 0, 0); info!("uhcid: port {} disconnected", port + 1); } } @@ -388,33 +487,25 @@ fn enumerate_device(ctrl: &UhciController, port: usize) -> Result Result Result> 8) & 0xFF) as u8, ]; - control_transfer( - ctrl.io_base, addr, 0, low_speed, &get_cfg, - Some((&mut config, true)), - )?; + ctrl.do_control_transfer(addr, 0, low_speed, &get_cfg, Some((&mut config, true)))?; Ok(PortDevice { address: addr, @@ -453,7 +538,7 @@ fn enumerate_device(ctrl: &UhciController, port: usize) -> Result &str; + + /// Number of root-hub ports on this controller. fn port_count(&self) -> usize; - /// Get status of a specific port + /// Get status of a specific port. Returns None if the port index is invalid. fn port_status(&self, port: usize) -> Option; - /// Reset a port (USB enumeration step 1) + /// Reset a port (USB enumeration step 1). + /// Returns true if the port enabled successfully after reset. fn port_reset(&mut self, port: usize) -> bool; - /// Submit a control transfer to endpoint 0 + /// Submit a control transfer to endpoint 0. + /// `data` is the data-phase buffer (empty for setup-only transfers). + /// Returns the number of bytes transferred. fn control_transfer( &mut self, device_address: u8, @@ -20,7 +34,7 @@ pub trait UsbHostController { data: &mut [u8], ) -> Result; - /// Submit a bulk transfer + /// Submit a bulk transfer. Use for mass-storage and similar. fn bulk_transfer( &mut self, device_address: u8, @@ -29,7 +43,8 @@ pub trait UsbHostController { direction: TransferDirection, ) -> Result; - /// Submit an interrupt transfer + /// Submit an interrupt transfer. Use for HID, hubs. + /// Returns the number of bytes received. fn interrupt_transfer( &mut self, device_address: u8, @@ -37,11 +52,17 @@ pub trait UsbHostController { data: &mut [u8], ) -> Result; - /// Set device address (after reset, before config) + /// Assign a device address (called during enumeration after port reset). fn set_address(&mut self, device_address: u8) -> bool; +} - /// Get the controller name for logging - fn name(&self) -> &str; +/// Common scheme prefix for all USB host controllers. +/// Class drivers are spawned with this prefix + the controller's `name()`. +pub const SCHEME_PREFIX: &str = "usb."; + +/// Compose a scheme path for a controller. Example: `"usb.0000:00:14.0_xhci"`. +pub fn scheme_path(name: &str) -> alloc::string::String { + alloc::format!("{SCHEME_PREFIX}{name}") } #[derive(Debug)] @@ -55,3 +76,5 @@ pub enum UsbError { IoError, Unsupported, } + +extern crate alloc; \ No newline at end of file