diff --git a/local/recipes/drivers/ohcid/source/Cargo.toml b/local/recipes/drivers/ohcid/source/Cargo.toml index d9d31691c2..0d2ad7b618 100644 --- a/local/recipes/drivers/ohcid/source/Cargo.toml +++ b/local/recipes/drivers/ohcid/source/Cargo.toml @@ -10,9 +10,11 @@ path = "src/main.rs" [dependencies] usb-core = { path = "../../usb-core/source" } -redox_syscall = { path = "../../../../../local/sources/syscall" } +syscall = { package = "redox_syscall", path = "../../../../../local/sources/syscall", features = ["std"] } +redox-driver-sys = { path = "../../redox-driver-sys/source" } log = "0.4" [patch.crates-io] redox_syscall = { path = "../../../../../local/sources/syscall" } libredox = { path = "../../../../../local/sources/libredox" } +redox-scheme = { path = "../../../../../local/sources/redox-scheme" } diff --git a/local/recipes/drivers/ohcid/source/src/main.rs b/local/recipes/drivers/ohcid/source/src/main.rs index 113fb566a0..ccf91fb5a8 100644 --- a/local/recipes/drivers/ohcid/source/src/main.rs +++ b/local/recipes/drivers/ohcid/source/src/main.rs @@ -3,18 +3,195 @@ mod registers; use std::env; use std::process; use std::fs; +use std::time::{Duration, Instant}; +use std::thread; use log::{info, error, warn, LevelFilter}; +use redox_driver_sys::dma::DmaBuffer; +use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion}; + use registers::*; -struct StderrLogger; -impl log::Log for StderrLogger { - fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info } - fn log(&self, r: &log::Record) { eprintln!("[{}] ohcid: {}", r.level(), r.args()); } - fn flush(&self) {} +fn alloc_dma(size: usize, align: usize) -> (*mut u8, usize) { + let mapping = DmaBuffer::allocate(size, align).expect("ohcid: DMA allocation failed"); + let phys = mapping.physical_address(); + (mapping.as_ptr() as *mut u8, phys) +} + +struct OhciController { + 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, +} + +impl OhciController { + fn reg_read(&self, offset: usize) -> u32 { + self.mmio.read32(offset) + } + fn reg_write(&self, offset: usize, value: u32) { + self.mmio.write32(offset, value); + } + + fn reset(&self) { + self.reg_write(HC_CMD_STATUS, CMD_HCR); + thread::sleep(Duration::from_millis(50)); + while self.reg_read(HC_CMD_STATUS) & CMD_HCR != 0 { + thread::sleep(Duration::from_millis(1)); + } + self.reg_write(HC_INT_DISABLE, !0u32); + self.reg_write(HC_INT_STATUS, !0u32); + } + + fn start(&self, hcca_phys: usize, control_ed_phys: usize, bulk_ed_phys: usize) { + self.reg_write(HC_HCCA, hcca_phys as u32); + self.reg_write(HC_CONTROL_HEAD_ED, control_ed_phys as u32); + self.reg_write(HC_BULK_HEAD_ED, bulk_ed_phys as u32); + self.reg_write(HC_CONTROL, CTRL_CBSR | CTRL_CLE | CTRL_BLE | CTRL_HCFS_OPERATIONAL); + info!("ohcid: controller started"); + } + + fn port_status(&self, port: usize) -> u32 { + match port { + 0 => self.reg_read(HC_RH_PORT_STATUS1), + 1 => self.reg_read(HC_RH_PORT_STATUS2), + _ => 0, + } + } + + fn port_set(&self, port: usize, bits: u32) { + let offset = match port { + 0 => HC_RH_PORT_STATUS1, + 1 => HC_RH_PORT_STATUS2, + _ => return, + }; + self.reg_write(offset, bits); + } + + fn port_reset(&self, port: usize) -> bool { + if self.port_status(port) & PORT_CCS == 0 { + return false; + } + self.port_set(port, PORT_PRS); + thread::sleep(Duration::from_micros(PORT_RESET_HOLD_US)); + self.port_set(port, 0); + thread::sleep(Duration::from_micros(PORT_RESET_SETTLE_US)); + (self.port_status(port) & PORT_PES) != 0 + } + + fn port_power(&self) { + for port in 0..self.port_count { + let offset = match port { + 0 => HC_RH_PORT_STATUS1, + 1 => HC_RH_PORT_STATUS2, + _ => continue, + }; + self.reg_write(offset, PORT_PPS); + } + } +} + +fn control_transfer( + mmio: &MmioRegion, device_addr: u8, endpoint: u8, low_speed: bool, + setup_packet: &[u8; 8], data_buf: Option<(&mut [u8], bool)>, +) -> Result, &'static str> { + let has_data = data_buf.as_ref().map(|(b, _)| !b.is_empty()).unwrap_or(false); + let is_in = data_buf.as_ref().map(|(_, i)| *i).unwrap_or(false); + let (ed_ptr, _ed_phys) = alloc_dma(core::mem::size_of::(), 16); + let ed = unsafe { &mut *(ed_ptr as *mut EndpointDescriptor) }; + let info_dir = if low_speed { ED_LOW_SPEED } else { 0 }; + ed.hw_info = info_dir | ED_SKIP | ((device_addr as u32) << ED_FUNC_ADDR_SHIFT) + | ((endpoint as u32) << 7) | ED_DIR_IN | (8u32 << ED_MAX_PKT_SHIFT); + ed.hw_tail_p = 0; + ed.hw_head_p = ED_HALTED; + ed.hw_next_ed = 0; + + let (setup_td_ptr, _stp) = alloc_dma(core::mem::size_of::(), 16); + let (setup_pkt_ptr, setup_pkt_phys) = alloc_dma(8, 16); + let setup_td = unsafe { &mut *(setup_td_ptr as *mut TransferDescriptor) }; + unsafe { core::ptr::copy_nonoverlapping(setup_packet.as_ptr(), setup_pkt_ptr, 8); } + setup_td.hw_info = TD_DP_SETUP | TD_TOGGLE_0 | TD_ROUND | TD_DELAY_INT; + setup_td.hw_cbp = setup_pkt_phys as u32; + setup_td.hw_next_td = 0; + setup_td.hw_be = (setup_pkt_phys + 7) as u32; + + let (status_td_ptr, _sstp) = alloc_dma(core::mem::size_of::(), 16); + let status_td = unsafe { &mut *(status_td_ptr as *mut TransferDescriptor) }; + status_td.hw_info = TD_DP_IN | TD_TOGGLE_1 | TD_ROUND | TD_DELAY_INT; + status_td.hw_cbp = 0; + status_td.hw_next_td = 0; + status_td.hw_be = 0; + + let mut data_td_ptr: *mut u8 = core::ptr::null_mut(); + let mut data_buf_ptr: *mut u8 = core::ptr::null_mut(); + if has_data { + let buf_len = data_buf.as_ref().unwrap().0.len(); + data_td_ptr = alloc_dma(core::mem::size_of::(), 16).0; + data_buf_ptr = alloc_dma(buf_len, 16).0; + let data_td = unsafe { &mut *(data_td_ptr as *mut TransferDescriptor) }; + let dp = if is_in { TD_DP_IN } else { TD_DP_OUT }; + data_td.hw_info = dp | TD_TOGGLE_1 | TD_ROUND | TD_DELAY_INT; + data_td.hw_cbp = data_buf_ptr as u32; + data_td.hw_next_td = status_td_ptr as u32; + data_td.hw_be = (data_buf_ptr as u32 + buf_len as u32 - 1); + if !is_in { + let buf = &data_buf.as_ref().unwrap().0; + unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), data_buf_ptr, buf.len()); } + } + setup_td.hw_info |= TD_TOGGLE_1; + setup_td.hw_next_td = data_td_ptr as u32;} + + ed.hw_tail_p = status_td_ptr as u32; + ed.hw_head_p = (setup_td_ptr as u32) & !ED_HALTED; + + let ed_phys = ed_ptr as usize; + mmio.write32(HC_CONTROL_HEAD_ED, ed_ptr as u32); + mmio.write32(HC_CMD_STATUS, CMD_HCR | (1 << 1)); + + let start_time = Instant::now(); + loop { + let done = mmio.read32(HC_DONE_HEAD); + if done != 0 { + let setup_cc = setup_td.hw_info >> 28; + if setup_cc != 0 { + return if setup_cc as u32 == (TD_CC_STALL >> 28) { + Err("setup TD stalled") + } else { + Err("setup TD error") + }; + } + if is_in && has_data { + let data_td = unsafe { &*(data_td_ptr as *const TransferDescriptor) }; + let cc = data_td.hw_info >> 28; + if (cc as u32) != (TD_CC_NO_ERROR >> 28) { return Err("data TD error"); } + let actual = (data_td.hw_be.wrapping_sub(data_td.hw_cbp) as usize) + 1; + if let Some((ref mut buf, _)) = data_buf { + let n = actual.min(buf.len()); + unsafe { + let src = core::slice::from_raw_parts(data_buf_ptr, n); + buf[..n].copy_from_slice(src); + } + return Ok(Some(actual)); + } + return Ok(Some(actual)); + } + return Ok(None); + } + if start_time.elapsed() > Duration::from_secs(2) { + return Err("control transfer timeout"); + } + thread::sleep(Duration::from_micros(100)); + } } fn main() { - log::set_logger(&StderrLogger).ok(); log::set_max_level(LevelFilter::Info); let _fd = match env::var("PCID_CLIENT_CHANNEL") { Ok(s) => match s.parse::() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, @@ -22,14 +199,96 @@ fn main() { }; let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default(); info!("OHCI USB 1.1 at {}", device_path); + let config_path = format!("{}/config", device_path); - match fs::read(&config_path) { + let bar0 = match fs::read(&config_path) { Ok(data) if data.len() >= 0x14 => { - let bar0 = u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]); - info!("OHCI MMIO base: 0x{:08X} (BAR0)", bar0 & 0xFFFFFFF0); - info!("ohcid: MMIO detected, ready for port enumeration"); + u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]) } - _ => warn!("cannot read PCI config"), + _ => { error!("cannot read PCI config"); process::exit(1); } + }; + + let mmio_addr = (bar0 & !0xFFF) as usize; + info!("OHCI MMIO base: 0x{:08X}", mmio_addr); + + let mmio = MmioRegion::map( + mmio_addr, 4096, CacheType::Uncacheable, MmioProt::READ_WRITE + ).expect("ohcid: MMIO map failed"); + info!("ohcid: MMIO mapped at 0x{:08X}", mmio_addr); + + let ctrl = OhciController { mmio, port_count: 2 }; + ctrl.reset(); + + let (_hcca, hcca_phys) = alloc_dma(core::mem::size_of::(), HCCA_ALIGN); + let (_ctrl_ed, ctrl_ed_phys) = alloc_dma(core::mem::size_of::(), 16); + let (_bulk_ed, bulk_ed_phys) = alloc_dma(core::mem::size_of::(), 16); + + unsafe { + let ed = &mut *(_ctrl_ed as *const u8 as *mut EndpointDescriptor); + ed.hw_info = ED_SKIP; ed.hw_tail_p = 0; ed.hw_head_p = ED_HALTED; ed.hw_next_ed = 0; + let bed = &mut *(_bulk_ed as *const u8 as *mut EndpointDescriptor); + bed.hw_info = ED_SKIP; bed.hw_tail_p = 0; bed.hw_head_p = ED_HALTED; bed.hw_next_ed = 0; + } + + ctrl.start(hcca_phys, ctrl_ed_phys, bulk_ed_phys); + ctrl.port_power(); + thread::sleep(Duration::from_millis(100)); + info!("ohcid: controller initialized, polling ports"); + + loop { + for port in 0..ctrl.port_count { + let portsc = ctrl.port_status(port); + + if (portsc & PORT_CCS) != 0 && (portsc & PORT_CSC) != 0 { + ctrl.port_set(port, PORT_CSC); + info!("ohcid: port {} connect detected", port + 1); + + if ctrl.port_reset(port) { + match enumerate_device(&ctrl, port) { + Ok(dev) => { + info!("ohcid: port {} device {:04x}:{:04x} class {:02x}", + port + 1, dev.vendor_id, dev.product_id, dev.device_class); + usb_core::spawn::spawn_class_driver_for_port( + dev.device_class, dev.device_subclass, dev.device_protocol, + "usb", &format!("{}", port + 1), 0, + ); + } + Err(e) => warn!("ohcid: port {} enumeration failed: {}", port + 1, e), + } + } + } + if (portsc & PORT_CCS) == 0 && (portsc & PORT_CSC) != 0 { + ctrl.port_set(port, PORT_CSC); + info!("ohcid: port {} disconnected", port + 1); + } + } + thread::sleep(Duration::from_millis(100)); } - loop { std::thread::sleep(std::time::Duration::from_secs(10)); } +} + +fn enumerate_device(ctrl: &OhciController, port: usize) -> Result { + let portsc = ctrl.port_status(port); + let low_speed = (portsc & PORT_LSDA) != 0; + + let get_desc: [u8; 8] = [0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00]; + let mut header = [0u8; 8]; + control_transfer(ctrl.mmio, 0, 0, low_speed, &get_desc, Some((&mut header, true)))?; + + let addr: u8 = (port + 1) as u8; + let set_addr: [u8; 8] = [0x00, 0x05, addr, 0x00, 0x00, 0x00, 0x00, 0x00]; + control_transfer(ctrl.mmio, 0, 0, low_speed, &set_addr, None)?; + thread::sleep(Duration::from_millis(10)); + + let mut dev_desc = [0u8; 18]; + let get_full: [u8; 8] = [0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00]; + control_transfer(ctrl.mmio, addr, 0, low_speed, &get_full, 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]]); + + Ok(PortDevice { + address: addr, vendor_id, product_id, + device_class: dev_desc[4], device_subclass: dev_desc[5], device_protocol: dev_desc[6], + low_speed, + }) } diff --git a/local/recipes/drivers/ohcid/source/src/registers.rs b/local/recipes/drivers/ohcid/source/src/registers.rs index f7d8c47212..eae8ab14f8 100644 --- a/local/recipes/drivers/ohcid/source/src/registers.rs +++ b/local/recipes/drivers/ohcid/source/src/registers.rs @@ -1,44 +1,118 @@ #![allow(dead_code)] -pub const HCREVISION: usize = 0x00; -pub const HCCONTROL: usize = 0x04; -pub const HCCOMMANDSTATUS: usize = 0x08; -pub const HCINTERRUPTSTATUS: usize = 0x0C; -pub const HCINTERRUPTENABLE: usize = 0x10; -pub const HCHCCA: usize = 0x18; -pub const HCCONTROLHEADED: usize = 0x20; -pub const HCBULKHEADED: usize = 0x28; -pub const HCDONEHEAD: usize = 0x30; -pub const HCFMINTERVAL: usize = 0x34; -pub const HCFMREMAINING: usize = 0x38; -pub const HCFMNUMBER: usize = 0x3C; -pub const HCRHDESCRIPTORA: usize = 0x48; -pub const HCRHSTATUS: usize = 0x50; -pub const HCRHPORTSTATUS1: usize = 0x54; +// OHCI MMIO register offsets (32-bit), Endpoint Descriptor, Transfer Descriptor, +// and Host Controller Communication Area definitions — aligned with Linux 7.1 ohci.h. -pub const CONTROL_BULK_ENABLE: u32 = 1 << 3; -pub const PERIODIC_ENABLE: u32 = 1 << 4; -pub const CONTROL_ENABLE: u32 = 1 << 6; -pub const BULK_ENABLE: u32 = 1 << 7; -pub const HC_FUNCTIONAL_STATE_MASK: u32 = 0x3 << 6; -pub const HC_RESET: u32 = 0; -pub const HC_RESUME: u32 = 1 << 6; -pub const HC_OPERATIONAL: u32 = 2 << 6; -pub const HC_SUSPEND: u32 = 3 << 6; +pub const HC_REVISION: usize = 0x00; +pub const HC_CONTROL: usize = 0x04; +pub const HC_CMD_STATUS: usize = 0x08; +pub const HC_INT_STATUS: usize = 0x0C; +pub const HC_INT_ENABLE: usize = 0x10; +pub const HC_INT_DISABLE: usize = 0x14; +pub const HC_HCCA: usize = 0x18; +pub const HC_CONTROL_HEAD_ED: usize = 0x20; +pub const HC_CONTROL_CURRENT_ED: usize = 0x24; +pub const HC_BULK_HEAD_ED: usize = 0x28; +pub const HC_BULK_CURRENT_ED: usize = 0x2C; +pub const HC_DONE_HEAD: usize = 0x30; +pub const HC_FM_INTERVAL: usize = 0x34; +pub const HC_FM_REMAINING: usize = 0x38; +pub const HC_FM_NUMBER: usize = 0x3C; +pub const HC_PERIODIC_START: usize = 0x40; +pub const HC_RH_DESC_A: usize = 0x48; +pub const HC_RH_DESC_B: usize = 0x4C; +pub const HC_RH_STATUS: usize = 0x50; +pub const HC_RH_PORT_STATUS1: usize = 0x54; +pub const HC_RH_PORT_STATUS2: usize = 0x58; -pub const PORT_CURRENT_CONNECT: u32 = 1 << 0; -pub const PORT_ENABLE: u32 = 1 << 1; -pub const PORT_SUSPEND: u32 = 1 << 2; -pub const PORT_OVER_CURRENT: u32 = 1 << 3; -pub const PORT_RESET: u32 = 1 << 4; -pub const PORT_POWER: u32 = 1 << 8; -pub const PORT_LOW_SPEED: u32 = 1 << 9; -pub const PORT_CONNECT_CHANGE: u32 = 1 << 16; -pub const PORT_ENABLE_CHANGE: u32 = 1 << 17; +// HcControl +pub const CTRL_CBSR: u32 = 3 << 0; +pub const CTRL_CLE: u32 = 1 << 4; +pub const CTRL_BLE: u32 = 1 << 5; +pub const CTRL_HCFS_MASK: u32 = 3 << 6; +pub const CTRL_HCFS_RESET: u32 = 0 << 6; +pub const CTRL_HCFS_OPERATIONAL: u32 = 2 << 6; -pub const WRITE_BACK_DONE_HEAD: u32 = 1 << 1; -pub const START_OF_FRAME: u32 = 1 << 2; -pub const RESUME_DETECTED: u32 = 1 << 3; -pub const ROOT_HUB_STATUS_CHANGE: u32 = 1 << 6; +// HcCommandStatus +pub const CMD_HCR: u32 = 1 << 0; -pub const HCCA_SIZE: usize = 256; +// Interrupt bits +pub const INT_WDH: u32 = 1 << 1; +pub const INT_RHSC: u32 = 1 << 6; +pub const INT_MIE: u32 = 1 << 31; + +// Root Hub Status +pub const RH_LPS: u32 = 1 << 0; +pub const RH_LPSC: u32 = 1 << 16; + +// Port Status +pub const PORT_CCS: u32 = 1 << 0; +pub const PORT_PES: u32 = 1 << 1; +pub const PORT_PSS: u32 = 1 << 2; +pub const PORT_POCI: u32 = 1 << 3; +pub const PORT_PRS: u32 = 1 << 4; +pub const PORT_PPS: u32 = 1 << 8; +pub const PORT_LSDA: u32 = 1 << 9; +pub const PORT_CSC: u32 = 1 << 16; +pub const PORT_PESC: u32 = 1 << 17; +pub const PORT_PRSC: u32 = 1 << 20; + +// TD condition codes (hwINFO[31:28]) +pub const TD_CC_NO_ERROR: u32 = 0 << 28; +pub const TD_CC_STALL: u32 = 4 << 28; +pub const TD_CC_DEVICE_NOT_RESPONDING: u32 = 5 << 28; +pub const TD_CC_NOT_ACCESSED: u32 = 0xF << 28; + +pub const TD_ROUND: u32 = 1 << 18; +pub const TD_DELAY_INT: u32 = 7 << 21; +pub const TD_DP_SETUP: u32 = 0 << 19; +pub const TD_DP_OUT: u32 = 1 << 19; +pub const TD_DP_IN: u32 = 2 << 19; +pub const TD_TOGGLE_0: u32 = 2 << 24; +pub const TD_TOGGLE_1: u32 = 3 << 24; +pub const TD_TOGGLE_CARRY: u32 = 0 << 24; + +// ED info field +pub const ED_LOW_SPEED: u32 = 1 << 13; +pub const ED_SKIP: u32 = 1 << 14; +pub const ED_DIR_OUT: u32 = 1 << 11; +pub const ED_DIR_IN: u32 = 2 << 11; +pub const ED_MAX_PKT_SHIFT: u32 = 16; +pub const ED_FUNC_ADDR_SHIFT: u32 = 0; + +// ED head pointer status +pub const ED_HALTED: u32 = 1; +pub const ED_TOGGLE_CARRY: u32 = 2; + +/// Endpoint Descriptor — 16 bytes, linked list for control/bulk/interrupt transfers. +#[repr(C, align(16))] +pub struct EndpointDescriptor { + pub hw_info: u32, + pub hw_tail_p: u32, + pub hw_head_p: u32, + pub hw_next_ed: u32, +} + +/// Transfer Descriptor — 16 bytes, one per transfer buffer segment. +#[repr(C, align(16))] +pub struct TransferDescriptor { + pub hw_info: u32, + pub hw_cbp: u32, + pub hw_next_td: u32, + pub hw_be: u32, +} + +/// Host Controller Communication Area — 256 bytes. The first 32 entries +/// point to interrupt EDs; done_head tracks completed TDs. +#[repr(C, align(256))] +pub struct Hcca { + pub intr_table: [u32; 32], + pub frame_no: u16, + pub _pad: u16, + pub done_head: u32, + pub _reserved: [u8; 116], +} + +pub const PORT_RESET_HOLD_US: u64 = 50_000; +pub const PORT_RESET_SETTLE_US: u64 = 10_000; +pub const MAX_PACKET_SIZE: usize = 64; pub const HCCA_ALIGN: usize = 256;