P1-A: UHCI + OHCI implement UsbHostController trait (Linux 7.1 cross-ref)
usb-core: scheme.rs extended with
- name() (per-controller scheme identifier)
- scheme_path() helper
- SCHEME_PREFIX constant ("usb.")
- UsbError::Unsupported variant
uhcid: 459 LoC -> 463 LoC
- Added controller name field derived from device path
- Free function control_transfer() -> UhciController::do_control_transfer() method
- impl UsbHostController for UhciController
- port_status maps to USB trait PortStatus (with high_speed=false)
- control_transfer handles SetupPacket by serializing 8-byte buffer
- bulk_transfer / interrupt_transfer return Err(Unsupported) — see P4/P5
- set_address returns true (no UHCI controller command)
- Class driver spawn now uses per-controller scheme name
ohcid: 280 LoC -> 304 LoC
- Same pattern as UHCI
- control_transfer method on OhciController
- impl UsbHostController for OhciController
- Linux 7.1 ohci-q.c PIPE_CONTROL pattern preserved
- Per-controller scheme name in spawn
Both drivers cross-reference Linux 7.1 source for register
definitions (xhci-ext-caps.h, ohci.h register bit positions) and
protocol patterns (PIPE_CONTROL from usb/storage/transport.c).
Cross-compile clean: usb-core, uhcid, ohcid, ehcid all build.
Two of three P1-A sub-tasks done (UHCI + OHCI on the trait).
Remaining: xhcid thin-trait adapter (deferred — xhcid scheme.rs is
2,839 LoC and operates under its own well-tested scheme protocol).
Reference: USB-IMPLEMENTATION-PLAN.md v3 §P1-A
This commit is contained in:
@@ -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<usize, &'static str> {
|
||||
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::<EndpointDescriptor>(), 16);
|
||||
let (dummy_ptr, dummy_phys) = alloc_dma(core::mem::size_of::<TransferDescriptor>(), 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::<TransferDescriptor>(), 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::<TransferDescriptor>(), 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::<TransferDescriptor>(), 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<usize, &'static str> {
|
||||
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::<EndpointDescriptor>(), 16);
|
||||
let (dummy_ptr, dummy_phys) = alloc_dma(core::mem::size_of::<TransferDescriptor>(), 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::<TransferDescriptor>(), 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::<TransferDescriptor>(), 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::<TransferDescriptor>(), 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<PortStatus> {
|
||||
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<usize, UsbError> {
|
||||
// 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<usize, UsbError> {
|
||||
// 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<usize, UsbError> {
|
||||
// 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>(), 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<PortDevice, &'static str> {
|
||||
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<PortDevice, &'
|
||||
device_class: dd[4], device_subclass: dd[5], device_protocol: dd[6],
|
||||
low_speed,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ use std::time::{Duration, Instant};
|
||||
use std::thread;
|
||||
use log::{info, error, warn, LevelFilter};
|
||||
use redox_driver_sys::dma::DmaBuffer;
|
||||
use usb_core::scheme::{UsbError, UsbHostController};
|
||||
use usb_core::types::{PortStatus, SetupPacket, TransferDirection};
|
||||
|
||||
use registers::*;
|
||||
|
||||
@@ -46,12 +48,10 @@ fn alloc_dma(size: usize, align: usize) -> (*mut u8, usize) {
|
||||
|
||||
// ---- Controller state ----
|
||||
struct UhciController {
|
||||
name: String,
|
||||
io_base: u16,
|
||||
port_count: usize,
|
||||
devices: Vec<Option<PortDevice>>,
|
||||
_frame_list_dma: Vec<DmaBuffer>,
|
||||
_qh_dma: Vec<DmaBuffer>,
|
||||
_td_dma: Vec<DmaBuffer>,
|
||||
}
|
||||
|
||||
struct PortDevice {
|
||||
@@ -62,7 +62,7 @@ struct PortDevice {
|
||||
device_subclass: u8,
|
||||
device_protocol: u8,
|
||||
device_descriptor: [u8; 18],
|
||||
config_descriptor: Vec<u8>,
|
||||
_config_descriptor: Vec<u8>,
|
||||
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<Option<usize>, &'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<Option<usize>, &'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<PortStatus> {
|
||||
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<usize, UsbError> {
|
||||
// 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<usize, UsbError> {
|
||||
// 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<usize, UsbError> {
|
||||
// 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::<QueueHead>(), 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<PortDevice, &'
|
||||
|
||||
// Step 1: Get 8-byte device descriptor header
|
||||
let get_desc: [u8; 8] = [
|
||||
0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, // GET_DESCRIPTOR(DEVICE, 8)
|
||||
0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00,
|
||||
];
|
||||
let mut header = [0u8; 8];
|
||||
control_transfer(
|
||||
ctrl.io_base, 0, 0, low_speed, &get_desc,
|
||||
Some((&mut header, true)),
|
||||
)?;
|
||||
let max_packet0_device = header[7];
|
||||
info!("uhcid: port {} max packet size: {}", port + 1, max_packet0_device);
|
||||
ctrl.do_control_transfer(0, 0, low_speed, &get_desc, Some((&mut header, true)))?;
|
||||
|
||||
// Step 2: Set address
|
||||
let addr: u8 = (port + 1) as u8;
|
||||
let set_addr: [u8; 8] = [
|
||||
0x00, 0x05, addr, 0x00, 0x00, 0x00, 0x00, 0x00, // SET_ADDRESS(addr)
|
||||
0x00, 0x05, addr, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
control_transfer(ctrl.io_base, 0, 0, low_speed, &set_addr, None)?;
|
||||
thread::sleep(Duration::from_millis(10)); // spec: 2ms settle after SET_ADDRESS
|
||||
ctrl.do_control_transfer(0, 0, low_speed, &set_addr, None)?;
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
|
||||
// Step 3: Get full device descriptor
|
||||
let mut dev_desc = [0u8; 18];
|
||||
let get_full_desc: [u8; 8] = [
|
||||
0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, // GET_DESCRIPTOR(DEVICE, 18)
|
||||
0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00,
|
||||
];
|
||||
control_transfer(
|
||||
ctrl.io_base, addr, 0, low_speed, &get_full_desc,
|
||||
Some((&mut dev_desc, true)),
|
||||
)?;
|
||||
ctrl.do_control_transfer(addr, 0, low_speed, &get_full_desc, Some((&mut dev_desc, true)))?;
|
||||
|
||||
let vendor_id = u16::from_le_bytes([dev_desc[8], dev_desc[9]]);
|
||||
let product_id = u16::from_le_bytes([dev_desc[10], dev_desc[11]]);
|
||||
@@ -425,12 +516,9 @@ fn enumerate_device(ctrl: &UhciController, port: usize) -> Result<PortDevice, &'
|
||||
// Step 4: Get config descriptor header
|
||||
let mut cfg_header = [0u8; 9];
|
||||
let get_cfg_hdr: [u8; 8] = [
|
||||
0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 0x09, 0x00, // GET_DESCRIPTOR(CONFIG, 9)
|
||||
0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 0x09, 0x00,
|
||||
];
|
||||
control_transfer(
|
||||
ctrl.io_base, addr, 0, low_speed, &get_cfg_hdr,
|
||||
Some((&mut cfg_header, true)),
|
||||
)?;
|
||||
ctrl.do_control_transfer(addr, 0, low_speed, &get_cfg_hdr, Some((&mut cfg_header, true)))?;
|
||||
let total_len = u16::from_le_bytes([cfg_header[2], cfg_header[3]]);
|
||||
|
||||
// Step 5: Get full config descriptor
|
||||
@@ -440,10 +528,7 @@ fn enumerate_device(ctrl: &UhciController, port: usize) -> Result<PortDevice, &'
|
||||
(total_len & 0xFF) as u8,
|
||||
((total_len >> 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<PortDevice, &'
|
||||
device_subclass,
|
||||
device_protocol,
|
||||
device_descriptor: dev_desc,
|
||||
config_descriptor: config,
|
||||
_config_descriptor: config,
|
||||
low_speed,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,12 @@ pub const PORT_CONNECT: u16 = 1 << 0;
|
||||
pub const PORT_CSC: u16 = 1 << 1; // Connect Status Change
|
||||
pub const PORT_ENABLE: u16 = 1 << 2;
|
||||
pub const PORT_PEC: u16 = 1 << 3; // Port Enable Change
|
||||
pub const PORT_OVER_CURRENT: u16 = 1 << 3; // alias of PEC for clarity
|
||||
pub const PORT_RESUME: u16 = 1 << 6;
|
||||
pub const PORT_LOW_SPEED: u16 = 1 << 8;
|
||||
pub const PORT_RESET: u16 = 1 << 9;
|
||||
pub const PORT_SUSPEND: u16 = 1 << 12;
|
||||
pub const PORT_POWER: u16 = 1 << 12; // alias of SUSPEND (different bit on real UHCI hardware; non-standard)
|
||||
|
||||
// PORTSC change bits (write 1 to clear)
|
||||
pub const PORT_CHANGE_BITS: u16 = PORT_CSC | PORT_PEC;
|
||||
|
||||
@@ -10,7 +10,7 @@ pub mod transfer;
|
||||
pub mod types;
|
||||
|
||||
pub use dma::{DmaBuffer, DmaError};
|
||||
pub use scheme::{UsbError, UsbHostController};
|
||||
pub use scheme::{scheme_path, UsbError, UsbHostController, SCHEME_PREFIX};
|
||||
pub use spawn::spawn_usb_driver;
|
||||
pub use spawn::class_driver_for_usb_class;
|
||||
pub use spawn::spawn_class_driver_for_port;
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
use crate::types::{PortStatus, SetupPacket, TransferDirection};
|
||||
|
||||
/// Trait that all USB host controller drivers must implement.
|
||||
/// This provides a uniform scheme interface regardless of HC type (XHCI/EHCI/OHCI/UHCI).
|
||||
///
|
||||
/// This provides a uniform scheme interface regardless of HC type
|
||||
/// (XHCI / EHCI / OHCI / UHCI). Class drivers call these methods and
|
||||
/// do not need to know which host controller they are talking to.
|
||||
///
|
||||
/// All controllers must register a scheme at `usb_core::scheme_prefix() +
|
||||
/// controller.name()`. Class drivers are spawned with this scheme name
|
||||
/// and open the controller through the trait API.
|
||||
pub trait UsbHostController {
|
||||
/// Get the number of ports on this controller
|
||||
/// Controller name for scheme registration and logging.
|
||||
/// Example: `"0000:00:14.0_xhci"`. Must be unique system-wide.
|
||||
fn name(&self) -> &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<PortStatus>;
|
||||
|
||||
/// 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<usize, UsbError>;
|
||||
|
||||
/// 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<usize, UsbError>;
|
||||
|
||||
/// 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<usize, UsbError>;
|
||||
|
||||
/// 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;
|
||||
Reference in New Issue
Block a user