// --------------------------------------------------------------------------- // rsdos EHCI (USB 2.0 host controller) driver — HID mouse + keyboard. // // Real-hardware topology (Intel PCH, e.g. Q77/OptiPlex 7010): the USB2 // controllers (00:1a.0, 00:1d.0) each present an *integrated Rate Matching // Hub* on their root port (8087:0024, class 9). Full/low-speed devices // (mice, keyboards) sit behind that hub, so the driver must // // 1. enumerate every EHCI controller (not just the first), // 2. enumerate a root-port hub, power/reset its ports and read the child // speed via hub class requests, // 3. drive the child through split transactions using the hub's USB // address and the child port (TT), // 4. arm a periodic interrupt-IN QH per HID device and poll reports. // // A device connected directly to a root port (QEMU's topology) uses the // root-hub TT (hub address 0, port = root port + 1). // // Polled: no IRQ/MSI — one report outstanding per device, drained from the // cursor task (mouse) and the WM input pump (keyboard). DMA structures are // allocated from the page allocator (physical == CPU address, identity map) // below 4 GiB because EHCI programs 32-bit DMA pointers. // --------------------------------------------------------------------------- use crate::ps2::{KeyEvent, MouseEvent}; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; // ---- Registers (opbase = base + CAPLENGTH) ---- const OPCMD: usize = 0x00; const OPSTS: usize = 0x04; const OPINTR: usize = 0x08; const OPFRIDX: usize = 0x0C; const OPCFGFLAG: usize = 0x40; const OP_PRTSC_BASE: usize = 0x44; const OP_PRTSC_STRIDE: usize = 0x04; const CMD_RUN: u32 = 1 << 0; const CMD_HCRESET: u32 = 1 << 1; const CMD_PSE: u32 = 1 << 4; const CMD_ASE: u32 = 1 << 5; const STS_HCH: u32 = 1 << 12; const STS_SE: u32 = 1 << 4; const STS_RECL: u32 = 1 << 14; const PRT_CONNECT: u32 = 1 << 0; const PRT_PED: u32 = 1 << 1; const PRT_RESET: u32 = 1 << 8; const PRT_POWER: u32 = 1 << 12; const PRT_RWC: u32 = (1 << 17) | (1 << 18) | (1 << 16) | (1 << 20); // ---- QH / QTD fields ---- const Q_TYPE_QH: u32 = 1 << 1; const QT_TERMINATE: u32 = 1; const QH_HEAD: u32 = 1 << 15; const QH_CONTROL_EP: u32 = 1 << 27; const QH_DTC: u32 = 1 << 14; const QH_RL: u32 = 4 << 28; const QH_EPS_HIGH: u32 = 2 << 12; const QH_EPS_FULL: u32 = 0 << 12; const QH_EPS_LOW: u32 = 1 << 12; const QH_MULT: u32 = 1 << 30; const TOK_IOC: u32 = 1 << 15; const TOK_ACTIVE: u32 = 1 << 7; const TOK_HALT: u32 = 1 << 6; const TOK_XACT: u32 = 1 << 3; const TOK_MMF: u32 = 1 << 2; const PID_IN: u32 = 1; const PID_SETUP: u32 = 2; const CCERR_3: u32 = 3 << 10; // ---- Limits / DMA layout ---- const MAX_CTRL: usize = 2; // OptiPlex 7010: 00:1a.0 + 00:1d.0 const MAX_DEV: usize = 4; // mouse + keyboard on each controller const DMA_MIN: usize = 0x100_0000; // avoid <16 MiB (PCI-master hostile region) const DMA_MAX: usize = 0x1_0000_0000; // 32-bit EHCI DMA pointers // Descriptor layout. A controller with HCCPARAMS bit0 (64-bit addressing) // reads an extra bufptr_hi[5] after the 32-bit buffer list: QH = 17 dwords // (68 B), qTD = 13 dwords (52 B). Space every structure for that variant — // 32-bit controllers simply ignore the tail — otherwise the HC parses the // next qTD as a high buffer pointer and dies with a Host System Error. const QTD_SLOT: usize = 0x40; // 64-byte qTD slots (52 B + padding) const CTRL_QTD_OFF: usize = 0x100; // control qTDs, offset from the control QH const HID_QTD_OFF: usize = 0x80; // HID qTD, offset from the HID QH const BUF_DEV_DESC: usize = 0x000; // 64 B const BUF_CFG: usize = 0x100; // 512 B (hubs can have long configs) const BUF_HUB_DESC: usize = 0x300; // 64 B const BUF_PORT_STATUS: usize = 0x340; // 16 B const BUF_REPORT: usize = 0x400; // 64 B per device /// One EHCI controller instance (addresses are per controller). #[derive(Clone, Copy)] struct Ctrl { base: usize, opb: usize, nports: u32, async_head: *mut u32, xfer_qh: *mut u32, framelist: *mut u32, qh_page: *mut u32, buf_page: *mut u8, caps64: bool, } impl Ctrl { const EMPTY: Ctrl = Ctrl { base: 0, opb: 0, nports: 0, async_head: core::ptr::null_mut(), xfer_qh: core::ptr::null_mut(), framelist: core::ptr::null_mut(), qh_page: core::ptr::null_mut(), buf_page: core::ptr::null_mut(), caps64: false, }; } #[derive(Clone, Copy, PartialEq)] enum Kind { Mouse, Keyboard, } /// Transaction translator for a device behind a hub (`None` = high-speed /// device or direct root-port high-speed). #[derive(Clone, Copy)] struct Tt { hub: u8, port: u8, } /// One armed HID device. #[derive(Clone, Copy)] struct Dev { kind: Kind, ctrl: u32, qh: *mut u32, iqtd: *mut u32, report: *mut u8, len: u32, kbd: crate::hid::Keyboard, armed: bool, failures: u32, reports: u32, } impl Dev { const EMPTY: Dev = Dev { kind: Kind::Mouse, ctrl: 0, qh: core::ptr::null_mut(), iqtd: core::ptr::null_mut(), report: core::ptr::null_mut(), len: 0, kbd: crate::hid::Keyboard::new(), armed: false, failures: 0, reports: 0, }; } static mut CTRLS: [Ctrl; MAX_CTRL] = [Ctrl::EMPTY; MAX_CTRL]; static mut DEVS: [Dev; MAX_DEV] = [Dev::EMPTY; MAX_DEV]; static N_CTRL: AtomicU32 = AtomicU32::new(0); static N_DEV: AtomicU32 = AtomicU32::new(0); /// Controller currently being initialized (registers/pointers resolve here). static CUR: AtomicU32 = AtomicU32::new(0); static CTRL_UP: AtomicBool = AtomicBool::new(false); static LAST_SCAN: AtomicU32 = AtomicU32::new(0); static QR_SHOWN: AtomicBool = AtomicBool::new(false); unsafe fn ctrl_mut(i: usize) -> &'static mut Ctrl { unsafe { &mut *core::ptr::addr_of_mut!(CTRLS[i]) } } unsafe fn dev_mut(i: usize) -> &'static mut Dev { unsafe { &mut *core::ptr::addr_of_mut!(DEVS[i]) } } fn cur_ctrl() -> &'static mut Ctrl { unsafe { ctrl_mut(CUR.load(Ordering::Relaxed) as usize) } } unsafe fn rd(a: usize) -> u32 { unsafe { (a as *const u32).read_volatile() } } unsafe fn wr(a: usize, v: u32) { unsafe { (a as *mut u32).write_volatile(v) } } fn op(off: usize) -> usize { cur_ctrl().opb + off } fn prtsc(p: usize) -> usize { op(OP_PRTSC_BASE + p * OP_PRTSC_STRIDE) } fn hostpc(p: usize) -> u32 { unsafe { rd(cur_ctrl().base + 0x84 + p * 4) } } // ---- QH/QTD dword helpers ---- fn qh_next(qh: *mut u32) -> *mut u32 { qh.wrapping_add(0) } fn qh_info1(qh: *mut u32) -> *mut u32 { qh.wrapping_add(1) } fn qh_info2(qh: *mut u32) -> *mut u32 { qh.wrapping_add(2) } fn qh_current(qh: *mut u32) -> *mut u32 { qh.wrapping_add(3) } fn qh_next_qtd(qh: *mut u32) -> *mut u32 { qh.wrapping_add(4) } fn qh_alt_qtd(qh: *mut u32) -> *mut u32 { qh.wrapping_add(5) } fn qh_token(qh: *mut u32) -> *mut u32 { qh.wrapping_add(6) } fn qh_buf(qh: *mut u32) -> *mut u32 { qh.wrapping_add(7) } fn link_of(ptr: usize) -> u32 { (ptr as u32) | Q_TYPE_QH } unsafe fn wait_recl(timeout: usize) { unsafe { let t0 = crate::interrupts::uptime_ms(); while t0 + timeout as u64 > crate::interrupts::uptime_ms() && (rd(op(OPSTS)) & STS_RECL) == 0 { core::hint::spin_loop(); } } } fn sleep_ms(ms: u64) { let t0 = crate::interrupts::uptime_ms(); while crate::interrupts::uptime_ms().saturating_sub(t0) < ms { core::hint::spin_loop(); } } /// 4 KiB-aligned DMA page below 4 GiB (EHCI uses 32-bit pointers). unsafe fn dma_alloc(bytes: usize, max_addr: usize) -> *mut u8 { unsafe { let pages = bytes.div_ceil(4096).max(1); let Some(phys) = crate::memory::alloc_pages_range(pages, DMA_MIN, max_addr) else { return core::ptr::null_mut(); }; let p = phys as *mut u8; core::ptr::write_bytes(p, 0, pages * 4096); p } } fn ctrl_req(bmreq: u8, breq: u8, wval: u16, widx: u16, wlen: u16) -> [u8; 8] { [ bmreq, breq, (wval & 0xff) as u8, (wval >> 8) as u8, (widx & 0xff) as u8, (widx >> 8) as u8, (wlen & 0xff) as u8, (wlen >> 8) as u8, ] } /// One control transfer on the async schedule. `tt` selects split /// transactions (device behind a hub, or a full/low-speed root port). /// Returns bytes transferred or a negative error (-1 timeout, -2 halt, /// -3 setup halted, -4 data halted, -5 XactErr, -6 missed microframe). unsafe fn ctl_xfer( dev: u32, mps: u32, speed: u32, tt: Option, req: &[u8; 8], dir_in: bool, buf: *mut u8, len: u32, diag_port: usize, ) -> i32 { unsafe { let head = cur_ctrl().async_head; let xfer = cur_ctrl().xfer_qh; let st = xfer.add(CTRL_QTD_OFF / 4); // SETUP qtd (32-byte aligned) let dt = st.add(QTD_SLOT / 4); // DATA qtd let sp = dt.add(QTD_SLOT / 4); // STATUS qtd core::ptr::write_volatile(st.add(0), 0u32); core::ptr::write_volatile(st.add(1), QT_TERMINATE); core::ptr::write_volatile( st.add(2), (8 << 16) | CCERR_3 | (PID_SETUP << 8) | TOK_ACTIVE, ); for i in 0..5 { core::ptr::write_volatile(st.add(3 + i), 0u32); } let sbuf = xfer.add(0x1C0 / 4); let sb = core::slice::from_raw_parts_mut(sbuf.cast::(), 8); sb.copy_from_slice(req); core::ptr::write_volatile(st.add(3), sbuf as u32); let data_pid_w = (if dir_in { 1u32 } else { 0u32 }) << 8; if len == 0 { core::ptr::write_volatile(dt.add(2), 0u32); } else { core::ptr::write_volatile(dt.add(0), sp as u32); core::ptr::write_volatile(dt.add(1), QT_TERMINATE); core::ptr::write_volatile( dt.add(2), ((len & 0x7fff) << 16) | (1 << 31) | CCERR_3 | data_pid_w | TOK_ACTIVE, ); for i in 0..5 { core::ptr::write_volatile(dt.add(3 + i), 0u32); } core::ptr::write_volatile(dt.add(3), buf as u32); } let setup_in = (req[0] & 0x80) != 0; let status_pid_w = if len > 0 { if dir_in { 0u32 } else { 1u32 } } else if setup_in { 0u32 } else { 1u32 } << 8; core::ptr::write_volatile(sp.add(0), 1u32); core::ptr::write_volatile(sp.add(1), QT_TERMINATE); core::ptr::write_volatile( sp.add(2), (1 << 31) | CCERR_3 | status_pid_w | TOK_ACTIVE | TOK_IOC, ); for i in 0..5 { core::ptr::write_volatile(sp.add(3 + i), 0u32); } if len == 0 { core::ptr::write_volatile(st.add(0), sp as u32); } else { core::ptr::write_volatile(st.add(0), dt as u32); core::ptr::write_volatile(dt.add(0), sp as u32); } let eps = match speed { 2 => QH_EPS_HIGH, 1 => QH_EPS_LOW, _ => QH_EPS_FULL, }; let control_flag = if speed == 2 { 0 } else { QH_CONTROL_EP }; let info1 = (mps & 0x7ff) << 16 | QH_RL | QH_DTC | control_flag | eps | dev; *qh_info1(xfer) = info1; // Split fields: hub address bits 22:16, port bits 29:23, MULT bit 30. *qh_info2(xfer) = match tt { Some(t) if speed != 2 => QH_MULT | ((t.hub as u32) << 16) | ((t.port as u32) << 23), _ => 0, }; *qh_current(xfer) = 0; *qh_next_qtd(xfer) = st as u32; *qh_alt_qtd(xfer) = QT_TERMINATE; *qh_token(xfer) = 0; for i in 0..5 { *qh_buf(xfer).add(i) = 0; } *qh_next(xfer) = link_of(head as usize); *qh_next(head) = link_of(xfer as usize); wr(op(OPSTS), 0x1F); core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_ASE); let t0 = crate::interrupts::uptime_ms(); let mut stage: i32 = 0; loop { let stok = core::ptr::read_volatile(st.add(2).cast::()); let ptok = core::ptr::read_volatile(sp.add(2).cast::()); if ptok & TOK_ACTIVE == 0 { break; } if stok & TOK_HALT != 0 { stage = 1; break; } if len > 0 { let dtok = core::ptr::read_volatile(dt.add(2).cast::()); if dtok & TOK_HALT != 0 { stage = 2; break; } } if rd(op(OPSTS)) & (STS_HCH | STS_SE) != 0 { stage = 4; break; } if t0 + 5000 < crate::interrupts::uptime_ms() { stage = 3; break; } core::hint::spin_loop(); } if stage != 0 { let stok = core::ptr::read_volatile(st.add(2).cast::()); let dtok = if len > 0 { core::ptr::read_volatile(dt.add(2).cast::()) } else { 0 }; let ptok = core::ptr::read_volatile(sp.add(2).cast::()); crate::serial_println!( "ehci: xfer fail stage={} speed={} dev={} in={} len={} tt={:?}", stage, speed, dev, dir_in, len, tt.map(|t| (t.hub, t.port)) ); crate::serial_println!( "ehci: cmd={:#x} sts={:#x} prtsc{}={:#x} hostpc={:#x}", rd(op(OPCMD)), rd(op(OPSTS)), diag_port, rd(prtsc(diag_port)), hostpc(diag_port) ); crate::serial_println!( "ehci: qh i1={:#x} i2={:#x} nqtd={:#x} setup={:#x} data={:#x} stat={:#x}", core::ptr::read_volatile(qh_info1(xfer)), core::ptr::read_volatile(qh_info2(xfer)), core::ptr::read_volatile(qh_next_qtd(xfer)), stok, dtok, ptok ); if !QR_SHOWN.swap(true, Ordering::Relaxed) { let l1 = alloc::format!( "EHCI fail stage={} t={}ms sp={} dev={} in={} len={} c64={}", stage, crate::interrupts::uptime_ms().wrapping_sub(t0), speed, dev, dir_in, len, cur_ctrl().caps64 as u8 ); let l2 = alloc::format!("cmd={:08x} sts={:08x}", rd(op(OPCMD)), rd(op(OPSTS))); let l3 = alloc::format!( "prtsc{}=0x{:08x} hp=0x{:08x}", diag_port, rd(prtsc(diag_port)), hostpc(diag_port) ); let l4 = alloc::format!( "qh1={:08x} qh2={:08x} nqtd={:08x}", core::ptr::read_volatile(qh_info1(xfer)), core::ptr::read_volatile(qh_info2(xfer)), core::ptr::read_volatile(qh_next_qtd(xfer)) ); let l5 = alloc::format!("setup={:08x} data={:08x} stat={:08x}", stok, dtok, ptok); let l6 = alloc::format!( "dma a={:08x} x={:08x} s={:08x} b={:08x}", cur_ctrl().async_head as usize, xfer as usize, sbuf as usize, cur_ctrl().buf_page as usize ); let all = [l1, l2, l3, l4, l5, l6]; let refs: alloc::vec::Vec<&str> = all.iter().map(|s| s.as_str()).collect(); crate::gop::dump_qr(&refs); crate::gop::qr_lock(); } *qh_next(head) = link_of(head as usize); wait_recl(100); return match stage { 1 => -3, 2 => -4, _ => -1, }; } let tok = core::ptr::read_volatile(sp.add(2).cast::()); if tok & TOK_HALT != 0 { *qh_next(head) = link_of(head as usize); wait_recl(100); return -2; } if tok & TOK_XACT != 0 { *qh_next(head) = link_of(head as usize); wait_recl(100); return -5; } if tok & TOK_MMF != 0 { *qh_next(head) = link_of(head as usize); wait_recl(100); return -6; } let mut got = len; if len > 0 { let dtok = core::ptr::read_volatile(dt.add(2).cast::()); let rem = ((dtok >> 16) & 0x7fff) as u32; got = len - rem.min(len); } *qh_next(head) = link_of(head as usize); wait_recl(100); got as i32 } } // --------------------------------------------------------------------------- // Descriptor parsing // --------------------------------------------------------------------------- const HID_CLASS: u8 = 0x03; const HID_SUBCLASS_BOOT: u8 = 0x01; const HID_PROTO_KEYBOARD: u8 = 1; const HID_PROTO_MOUSE: u8 = 2; /// Walk a config descriptor: find the first boot HID mouse/keyboard /// interface and its interrupt-IN endpoint. /// Returns (kind, interface number, ep address, mps, interval). fn find_hid(cfg: &[u8]) -> Option<(Kind, u8, u8, u32, u32)> { let mut i = 0usize; let mut cand: Option<(Kind, u8)> = None; while i + 2 <= cfg.len() { let blen = cfg[i] as usize; if blen < 2 { break; } let bt = cfg[i + 1]; if bt == 4 && blen >= 9 && i + 9 <= cfg.len() { // interface: class/subclass/protocol at +5/+6/+7 let iface = cfg[i + 2]; let class = cfg[i + 5]; let sub = cfg[i + 6]; let proto = cfg[i + 7]; cand = if class == HID_CLASS && sub == HID_SUBCLASS_BOOT { match proto { HID_PROTO_KEYBOARD => Some((Kind::Keyboard, iface)), HID_PROTO_MOUSE => Some((Kind::Mouse, iface)), _ => None, } } else { None }; } else if bt == 5 && blen >= 7 && i + 7 <= cfg.len() { if let Some((kind, iface)) = cand { let addr = cfg[i + 2]; let attrs = cfg[i + 3]; let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8); let interval = cfg[i + 6] as u32; if (addr & 0x80) != 0 && (attrs & 0x03) == 0x03 { return Some((kind, iface, addr, mps, interval)); } } } i += blen; } None } /// Hub class descriptor: port count lives at offset 2. fn hub_ports(desc: &[u8]) -> u32 { if desc.len() >= 3 && desc[1] == 0x29 { desc[2] as u32 } else { 0 } } // --------------------------------------------------------------------------- // Hub port control (USB 2.0 hub class requests) // --------------------------------------------------------------------------- const HUB_GET_STATUS: u8 = 0x00; const HUB_CLEAR_FEATURE: u8 = 0x01; const HUB_SET_FEATURE: u8 = 0x03; const PORT_RESET_FEAT: u16 = 4; const PORT_POWER_FEAT: u16 = 8; const C_PORT_RESET_FEAT: u16 = 20; const PS_CONNECTION: u32 = 1 << 0; const PS_ENABLE: u32 = 1 << 1; const PS_RESET: u32 = 1 << 4; const PS_LOW_SPEED: u32 = 1 << 9; const PS_HIGH_SPEED: u32 = 1 << 10; /// Read (status, change) for one hub port. unsafe fn hub_port_status(hub_addr: u8, hub_mps: u32, port: u16) -> Option<(u32, u32)> { unsafe { let buf = cur_ctrl().buf_page.add(BUF_PORT_STATUS); core::ptr::write_bytes(buf, 0, 8); let req = ctrl_req(0xA3, HUB_GET_STATUS, 0, port, 4); let r = ctl_xfer( hub_addr as u32, hub_mps, 2, None, &req, true, buf, 4, port as usize, ); if r < 4 { return None; } let b = core::slice::from_raw_parts(buf, 4); let st = u16::from_le_bytes([b[0], b[1]]) as u32; let ch = u16::from_le_bytes([b[2], b[3]]) as u32; Some((st, ch)) } } unsafe fn hub_feature(hub_addr: u8, hub_mps: u32, set: bool, feat: u16, port: u16) { unsafe { let req = ctrl_req( if set { 0x23 } else { 0x23 }, if set { HUB_SET_FEATURE } else { HUB_CLEAR_FEATURE }, feat, port, 0, ); let _ = ctl_xfer( hub_addr as u32, hub_mps, 2, None, &req, false, core::ptr::null_mut(), 0, port as usize, ); } } // --------------------------------------------------------------------------- // Enumeration // --------------------------------------------------------------------------- static NEXT_DEV_ADDR: AtomicU32 = AtomicU32::new(1); fn take_addr() -> u8 { NEXT_DEV_ADDR.fetch_add(1, Ordering::Relaxed) as u8 } /// Register + arm one HID device. unsafe fn register_hid( ci: usize, addr: u8, kind: Kind, ep: u8, mps: u32, interval: u32, speed: u32, tt: Option, ) -> bool { unsafe { let slot = N_DEV.load(Ordering::Relaxed) as usize; if slot >= MAX_DEV { return false; } let c = ctrl_mut(ci); let qh = c.qh_page.add(slot * 0x100 / 4); let iqtd = qh.add(HID_QTD_OFF / 4); let report = c.buf_page.add(BUF_REPORT + slot * 64); core::ptr::write_bytes(qh as *mut u8, 0, 0x100); core::ptr::write_bytes(report, 0, 64); let len = mps.min(8).max(1); let info1 = QH_RL | (speed << 12) | ((mps.max(4) & 0x7ff) << 16) | (addr as u32 & 0x7f) | (((ep as u32) & 0x0f) << 8); let info2 = match tt { Some(t) if speed != 2 => { let (smask, cmask) = (0x01u32, 0x1Cu32 << 8); smask | cmask | QH_MULT | ((t.hub as u32) << 16) | ((t.port as u32) << 23) } _ => 0xFF, // high-speed: schedule every microframe }; *qh_next(qh) = QT_TERMINATE; *qh_info1(qh) = info1; *qh_info2(qh) = info2; *qh_current(qh) = 0; *qh_alt_qtd(qh) = QT_TERMINATE; *qh_token(qh) = 0; for i in 0..5 { *qh_buf(qh).add(i) = 0; } core::ptr::write_volatile(iqtd.add(0), QT_TERMINATE); core::ptr::write_volatile(iqtd.add(1), QT_TERMINATE); core::ptr::write_volatile( iqtd.add(2), ((len & 0x7fff) << 16) | (PID_IN << 8) | TOK_ACTIVE | TOK_IOC, ); for i in 0..5 { core::ptr::write_volatile(iqtd.add(3 + i), 0u32); } core::ptr::write_volatile(iqtd.add(3), report as u32); *qh_next_qtd(qh) = iqtd as u32; let d = dev_mut(slot); d.kind = kind; d.ctrl = ci as u32; d.qh = qh; d.iqtd = iqtd; d.report = report; d.len = len; d.kbd = crate::hid::Keyboard::new(); d.armed = true; d.failures = 0; d.reports = 0; N_DEV.fetch_add(1, Ordering::Relaxed); crate::serial_println!( "ehci: HID {} armed (addr {} ep{:#x} mps {} interval {})", if kind == Kind::Mouse { "mouse" } else { "keyboard" }, addr, ep, mps, interval ); true } } /// Enumerate the device at `addr` (0 = unaddressed) on the current /// controller. Handles HID devices and one level of USB hub. /// Returns the number of HID devices registered. unsafe fn enumerate(addr: u8, speed: u32, tt: Option, diag_port: usize) -> u32 { unsafe { let mut addr = addr; let mut mps = if speed == 2 { 64u32 } else { 8u32 }; let cbuf = cur_ctrl().buf_page; // 1. Device descriptor (learn bMaxPacketSize0 + class). let dd = cbuf.add(BUF_DEV_DESC); core::ptr::write_bytes(dd, 0, 64); let req = ctrl_req(0x80, 6, 0x0100, 0, 18); let mut got = ctl_xfer( addr as u32, mps, speed, tt, &req, true, dd, 18, diag_port, ); if got < 8 { crate::serial_println!( "ehci: get-dev-desc failed (addr {} sp {} tt {:?}) code={}", addr, speed, tt.map(|t| (t.hub, t.port)), got ); return 0; } let dd = core::slice::from_raw_parts(dd, 18); let class = dd[4]; let mps0 = dd[7] as u32; if mps0 != 0 { mps = mps0; } // 2. Assign an address (root/unaddressed devices only). if addr == 0 { let new = take_addr(); let req = ctrl_req(0x00, 5, new as u16, 0, 0); got = ctl_xfer( 0, mps, speed, tt, &req, false, core::ptr::null_mut(), 0, diag_port, ); if got < 0 { crate::serial_println!("ehci: set-address failed code={}", got); return 0; } addr = new; sleep_ms(2); // USB 2.0 §9.2.6.3: 2 ms recovery } crate::serial_println!( "ehci: dev {:04x}:{:04x} class={:#x} mps0={} addr={} sp={}{}", u16::from_le_bytes([dd[8], dd[9]]), u16::from_le_bytes([dd[10], dd[11]]), class, mps, addr, speed, if tt.is_some() { " (split)" } else { "" } ); // 3. Hub: configure it, then scan its ports for HID devices. if class == 9 { return scan_hub(addr, mps, diag_port); } // 4. HID: read the config descriptor. let cfg = cbuf.add(BUF_CFG); core::ptr::write_bytes(cfg, 0, 512); let req = ctrl_req(0x80, 6, 0x0200, 0, 512); got = ctl_xfer(addr as u32, mps, speed, tt, &req, true, cfg, 512, diag_port); if got < 9 { crate::serial_println!("ehci: get-config failed code={}", got); return 0; } let cfgs = core::slice::from_raw_parts(cfg, got as usize); let Some((kind, iface, ep, epmps, interval)) = find_hid(cfgs) else { crate::serial_println!("ehci: no HID mouse/keyboard endpoint in config"); return 0; }; // 5. SET_CONFIGURATION(config 1) then boot protocol for the interface. let cfgval = cfgs[5]; if cfgval == 0 { return 0; } let req = ctrl_req(0x00, 9, cfgval as u16, 0, 0); if ctl_xfer(addr as u32, mps, speed, tt, &req, false, core::ptr::null_mut(), 0, diag_port) < 0 { crate::serial_println!("ehci: set-config failed"); return 0; } // HID class SET_PROTOCOL(boot) — report layout becomes fixed. let req = ctrl_req(0x21, 0x0B, 0, iface as u16, 0); if ctl_xfer(addr as u32, mps, speed, tt, &req, false, core::ptr::null_mut(), 0, diag_port) < 0 { crate::serial_println!("ehci: set-protocol(boot) stalled (continuing)"); } // HID SET_IDLE(0) — only report on change; some devices stall it. let req = ctrl_req(0x21, 0x0A, 0, iface as u16, 0); let _ = ctl_xfer(addr as u32, mps, speed, tt, &req, false, core::ptr::null_mut(), 0, diag_port); let ci = CUR.load(Ordering::Relaxed) as usize; if register_hid(ci, addr, kind, ep, epmps, interval, speed, tt) { 1 } else { 0 } } } /// Configure a USB hub, then enumerate every connected port's device. unsafe fn scan_hub(hub_addr: u8, hub_mps: u32, diag_port: usize) -> u32 { unsafe { let cbuf = cur_ctrl().buf_page; // Config + SET_CONFIGURATION. let cfg = cbuf.add(BUF_CFG); core::ptr::write_bytes(cfg, 0, 512); let req = ctrl_req(0x80, 6, 0x0200, 0, 512); let got = ctl_xfer(hub_addr as u32, hub_mps, 2, None, &req, true, cfg, 512, diag_port); if got < 9 { crate::serial_println!("ehci: hub get-config failed code={}", got); return 0; } let cfgval = core::slice::from_raw_parts(cfg, got as usize)[5]; let req = ctrl_req(0x00, 9, cfgval as u16, 0, 0); if ctl_xfer(hub_addr as u32, hub_mps, 2, None, &req, false, core::ptr::null_mut(), 0, diag_port) < 0 { crate::serial_println!("ehci: hub set-config failed"); return 0; } // Hub class descriptor -> port count. let hd = cbuf.add(BUF_HUB_DESC); core::ptr::write_bytes(hd, 0, 64); let req = ctrl_req(0xA0, 6, 0x2900, 0, 9); let got = ctl_xfer(hub_addr as u32, hub_mps, 2, None, &req, true, hd, 9, diag_port); if got < 3 { crate::serial_println!("ehci: hub descriptor failed code={}", got); return 0; } let hd = core::slice::from_raw_parts(hd, got as usize); let nports = hub_ports(hd); crate::serial_println!("ehci: hub addr={} ports={}", hub_addr, nports); // Power every port, then give the hub time to settle. for p in 1..=nports as u16 { hub_feature(hub_addr, hub_mps, true, PORT_POWER_FEAT, p); } sleep_ms(100); let mut found = 0u32; for p in 1..=nports as u16 { let Some((st, _ch)) = hub_port_status(hub_addr, hub_mps, p) else { continue; }; if st & PS_CONNECTION == 0 { continue; } // Reset the port and wait for the hub to finish. hub_feature(hub_addr, hub_mps, true, PORT_RESET_FEAT, p); let t0 = crate::interrupts::uptime_ms(); let mut reset_done = false; while crate::interrupts::uptime_ms().saturating_sub(t0) < 500 { if let Some((s, _)) = hub_port_status(hub_addr, hub_mps, p) && s & PS_RESET == 0 { reset_done = true; break; } sleep_ms(10); } if !reset_done { crate::serial_println!("ehci: hub port {} reset timeout", p); continue; } hub_feature(hub_addr, hub_mps, false, C_PORT_RESET_FEAT, p); let Some((st, _)) = hub_port_status(hub_addr, hub_mps, p) else { continue; }; if st & PS_ENABLE == 0 { crate::serial_println!("ehci: hub port {} not enabled (st={:#x})", p, st); continue; } let speed = if st & PS_HIGH_SPEED != 0 { 2 } else if st & PS_LOW_SPEED != 0 { 1 } else { 0 }; let child_tt = if speed == 2 { None } else { Some(Tt { hub: hub_addr, port: p as u8, }) }; crate::serial_println!( "ehci: hub port {}: {} device{}", p, match speed { 2 => "high-speed", 1 => "low-speed", _ => "full-speed", }, if child_tt.is_some() { " (split)" } else { "" } ); if st & PS_CONNECTION == 0 { continue; } found += enumerate(0, speed, child_tt, diag_port); } found } } /// Bring one controller up and scan it. unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool { unsafe { let ci = N_CTRL.load(Ordering::Relaxed) as usize; if ci >= MAX_CTRL { return false; } CUR.store(ci as u32, Ordering::Relaxed); NEXT_DEV_ADDR.store(1, Ordering::Relaxed); // Memory space + bus master, INTx disabled (polled). let cmd = crate::pci::config_read(bus, dev, func, 0x04); crate::pci::config_write( bus, dev, func, 0x04, (cmd & 0xFFFF_0000) | (cmd & 0xFFFF) | 0x6 | 0x400, ); let bar = crate::pci::bar64(bus, dev, func, 0); let base = bar.unwrap_or(0) as usize; if base == 0 { crate::serial_println!("ehci: bad BAR"); return false; } let caplen = (rd(base) & 0xff) as usize; let opb = base + caplen; let nports = rd(base + 4) & 0xf; let caps64 = (rd(base + 8) & 1) != 0; crate::serial_println!( "ehci: {:02x}:{:02x}.{} mmio={:#x} caplen={:#x} ports={} 64-bit-caps={}", bus, dev, func, base, caplen, nports, caps64 ); // BIOS legacy ownership: resetting the controller while SMM owns it // kills the firmware keyboard path and can collide with our DMA. match crate::pci::claim_usb_legacy(bus, dev, func, base, 0x08, false) { 0 => crate::serial_println!("ehci: USB legacy: already released"), 1 => crate::serial_println!("ehci: USB legacy handed over by BIOS"), 2 => crate::serial_println!("ehci: USB legacy claim timed out"), _ => crate::serial_println!("ehci: USB legacy: no capability"), } { let c = ctrl_mut(ci); c.base = base; c.opb = opb; c.nports = nports; c.caps64 = caps64; } // Halt, reset, wait for reset to clear. wr(op(OPCMD), 0); let mut t = 0; while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 { t += 1; core::hint::spin_loop(); } wr(op(OPCMD), CMD_HCRESET); t = 0; while t < 10_000_000 && (rd(op(OPCMD)) & CMD_HCRESET) != 0 { t += 1; core::hint::spin_loop(); } t = 0; while t < 10_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 { t += 1; core::hint::spin_loop(); } wr(op(OPINTR), 0); // DMA structures (<4 GiB, >=16 MiB). let async_head = dma_alloc(0x400, DMA_MAX) as *mut u32; let framelist = dma_alloc(0x1000, DMA_MAX) as *mut u32; let qh_page = dma_alloc(0x1000, DMA_MAX) as *mut u32; let buf_page = dma_alloc(0x1000, DMA_MAX); if async_head.is_null() || framelist.is_null() || qh_page.is_null() || buf_page.is_null() { crate::serial_println!("ehci: DMA allocation failed"); return false; } { let c = ctrl_mut(ci); c.async_head = async_head; c.xfer_qh = async_head.add(0x80 / 4); c.framelist = framelist; c.qh_page = qh_page; c.buf_page = buf_page; } crate::serial_println!( "ehci: dma async={:#x} framelist={:#x} qh={:#x} buf={:#x}", async_head as usize, framelist as usize, qh_page as usize, buf_page as usize ); let xfer_qh = cur_ctrl().xfer_qh; *qh_next(async_head) = link_of(cur_ctrl().xfer_qh as usize); *qh_info1(async_head) = QH_HEAD; *qh_info2(async_head) = 0; *qh_current(async_head) = 0; *qh_next_qtd(async_head) = 0; *qh_next(xfer_qh) = link_of(async_head as usize); *qh_info1(xfer_qh) = QH_EPS_FULL | (64 << 16); *qh_info2(xfer_qh) = 0; *qh_current(xfer_qh) = 0; *qh_next_qtd(xfer_qh) = 0; *qh_alt_qtd(xfer_qh) = QT_TERMINATE; *qh_token(xfer_qh) = 0; for i in 0..5 { *qh_buf(xfer_qh).add(i) = 0; } wr(op(OPFRIDX), 0); wr(op(0x10), 0); // CTRLDSSEGMENT: all our DMA is below 4 GiB wr(op(0x18), (async_head as usize as u32) & !0x1F); for i in 0..1024 { core::ptr::write_volatile(framelist.add(i), 0u32); } wr(op(0x14), (framelist as usize as u32) & !0x3FF); wr(op(OPCMD), CMD_RUN); t = 0; while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) != 0 { t += 1; core::hint::spin_loop(); } wr(op(OPCFGFLAG), 1); // route all ports to the host for p in 0..nports as usize { let psc = prtsc(p); let v = rd(psc); if v & PRT_POWER == 0 { wr(psc, v | PRT_POWER); } } sleep_ms(20); crate::serial_println!("ehci: controller up ({} ports)", nports); N_CTRL.store((ci + 1) as u32, Ordering::Relaxed); CTRL_UP.store(true, Ordering::Relaxed); // ---- scan ports ---- for p in 0..nports as usize { let v = rd(prtsc(p)); if v & PRT_CONNECT == 0 { continue; } let hs = reset_port(p); if !hs && crate::uhci::controller_up() { crate::serial_println!( "ehci: port {} full/low-speed — owned by companion UHCI", p ); continue; } let speed = if hs { 2 } else { port_speed(p) }; crate::serial_println!( "ehci: port {} connected ({}{})", p, match speed { 2 => "high-speed", 1 => "low-speed", _ => "full-speed", }, if hs { "" } else { ", root-port split" } ); let tt = if hs { None } else { Some(Tt { hub: 0, port: (p + 1) as u8, }) }; enumerate(0, speed, tt, p); } arm_periodic(ci); true } } /// Chain every armed device QH of this controller into its frame list and /// enable the periodic schedule. unsafe fn arm_periodic(ci: usize) { unsafe { CUR.store(ci as u32, Ordering::Relaxed); let c = ctrl_mut(ci); let mut first: *mut u32 = core::ptr::null_mut(); let mut prev: *mut u32 = core::ptr::null_mut(); let n = N_DEV.load(Ordering::Relaxed) as usize; for i in 0..n { let d = dev_mut(i); if d.ctrl != ci as u32 || !d.armed { continue; } if first.is_null() { first = d.qh; } else { *qh_next(prev) = link_of(d.qh as usize); } prev = d.qh; } if prev.is_null() { return; } *qh_next(prev) = QT_TERMINATE; for f in 0..1024 { core::ptr::write_volatile(c.framelist.add(f), link_of(first as usize)); } core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_PSE); crate::serial_println!("ehci: periodic schedule enabled ({} device(s) on ctrl {})", n, ci); } } /// Reset a root port. Returns true when the port enabled (high-speed). unsafe fn reset_port(p: usize) -> bool { unsafe { let psc = prtsc(p); let v = rd(psc); wr(psc, (v | PRT_RESET) & !PRT_RWC); let t0 = crate::interrupts::uptime_ms(); while crate::interrupts::uptime_ms().saturating_sub(t0) < 50 { core::hint::spin_loop(); if rd(psc) & PRT_RESET == 0 { break; } } wr(psc, (rd(psc) & !PRT_RESET) & !PRT_RWC); let mut t = 0; while t < 2_000_000 && (rd(psc) & PRT_PED) == 0 { t += 1; core::hint::spin_loop(); } rd(psc) & PRT_PED != 0 } } fn port_speed(p: usize) -> u32 { let hp = hostpc(p); if hp != u32::MAX && hp != 0 { let f = (hp >> 25) & 3; if f != 3 { return f; } } unsafe { (rd(prtsc(p)) >> 26) & 3 } } // --------------------------------------------------------------------------- // Boot + polling // --------------------------------------------------------------------------- pub fn init() -> bool { let devs = crate::pci::probe(); let mut up = false; for d in devs { if d.class == 0x0C && d.subclass == 0x03 && d.prog_if == 0x20 { if N_CTRL.load(Ordering::Relaxed) as usize >= MAX_CTRL { break; } unsafe { if init_one(d.bus, d.device, d.func) { up = true; } } } } if !up { crate::serial_println!("ehci: no EHCI controller"); } else { crate::serial_println!( "ehci: {} controller(s), {} HID device(s)", N_CTRL.load(Ordering::Relaxed), N_DEV.load(Ordering::Relaxed) ); } up } pub fn rescan() { // Hotplug is only handled while nothing is armed: re-run the boot scan. if N_DEV.load(Ordering::Relaxed) == 0 { let n = N_CTRL.load(Ordering::Relaxed) as usize; for ci in 0..n { unsafe { CUR.store(ci as u32, Ordering::Relaxed); let np = ctrl_mut(ci).nports as usize; NEXT_DEV_ADDR.store((N_DEV.load(Ordering::Relaxed) + 1) as u32, Ordering::Relaxed); for p in 0..np { if rd(prtsc(p)) & PRT_CONNECT == 0 { continue; } let hs = reset_port(p); if !hs && crate::uhci::controller_up() { continue; } let speed = if hs { 2 } else { port_speed(p) }; let tt = if hs { None } else { Some(Tt { hub: 0, port: (p + 1) as u8, }) }; enumerate(0, speed, tt, p); } arm_periodic(ci); } } } } pub fn controller_up() -> bool { CTRL_UP.load(Ordering::Relaxed) } pub fn mouse_present() -> bool { let n = N_DEV.load(Ordering::Relaxed) as usize; for i in 0..n { if unsafe { dev_mut(i) }.kind == Kind::Mouse { return true; } } false } pub fn poll_mouse() -> Option { unsafe { let n = N_DEV.load(Ordering::Relaxed) as usize; for i in 0..n { let d = dev_mut(i); if d.kind != Kind::Mouse || !d.armed { continue; } let tok = core::ptr::read_volatile(d.iqtd.add(2).cast::()); if tok & TOK_ACTIVE != 0 { continue; } if tok & (TOK_HALT | TOK_XACT) != 0 { d.armed = false; d.failures += 1; continue; } let hb = d.report; let b0 = core::ptr::read_volatile(hb); let b1 = core::ptr::read_volatile(hb.add(1)); let b2 = core::ptr::read_volatile(hb.add(2)); let ev = MouseEvent { dx: (b1 as i8) as i16, dy: (b2 as i8) as i16, left: b0 & 0x01 != 0, right: b0 & 0x02 != 0, middle: b0 & 0x04 != 0, }; d.reports += 1; if d.reports == 1 { crate::serial_println!( "ehci: mouse report dx={} dy={} b={:#x}", ev.dx, ev.dy, b0 ); } rearm(d); return Some(ev); } // Nothing armed: retry discovery at most once per second. if N_DEV.load(Ordering::Relaxed) == 0 && CTRL_UP.load(Ordering::Relaxed) { let now = crate::interrupts::uptime_ms() as u32; if now.wrapping_sub(LAST_SCAN.load(Ordering::Relaxed)) >= 1000 { LAST_SCAN.store(now, Ordering::Relaxed); rescan(); } } None } } pub fn try_poll_key() -> Option { unsafe { if let Some(b) = crate::hid::pop_byte() { return crate::ps2::feed_scancode(b); } let n = N_DEV.load(Ordering::Relaxed) as usize; for i in 0..n { let d = dev_mut(i); if d.kind != Kind::Keyboard || !d.armed { continue; } let tok = core::ptr::read_volatile(d.iqtd.add(2).cast::()); if tok & TOK_ACTIVE != 0 { continue; } if tok & (TOK_HALT | TOK_XACT) != 0 { d.armed = false; d.failures += 1; continue; } let mut rep = [0u8; 8]; let len = (d.len as usize).min(8); for (k, b) in rep.iter_mut().take(len).enumerate() { *b = core::ptr::read_volatile(d.report.add(k)); } d.kbd.diff(&rep); d.reports += 1; if d.reports == 1 { crate::serial_println!( "ehci: kbd report mods={:#x} keys={:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", rep[0], rep[2], rep[3], rep[4], rep[5], rep[6], rep[7] ); } rearm(d); return crate::hid::pop_byte().and_then(crate::ps2::feed_scancode); } None } } /// Restore one device's periodic qtd to ACTIVE and re-link it. /// /// Standard EHCI re-arm (same shape as Linux's `qh_append_tds`): the HC owns /// the QH status/current-qTD/toggle overlay, so firmware must not rewrite it /// — only re-activate the qTD and point Next-qTD at it. unsafe fn rearm(d: &mut Dev) { unsafe { core::ptr::write_volatile( d.iqtd.add(2), ((d.len & 0x7fff) << 16) | (PID_IN << 8) | TOK_ACTIVE | TOK_IOC, ); *qh_next_qtd(d.qh) = d.iqtd as u32; } } // --------------------------------------------------------------------------- // Diagnostics (shell `usb`) // --------------------------------------------------------------------------- pub fn diagnose() -> alloc::string::String { let mut s = alloc::string::String::new(); let nc = N_CTRL.load(Ordering::Relaxed); let nd = N_DEV.load(Ordering::Relaxed); s.push_str(&alloc::format!( "ehci: {} controller(s), {} device(s) up={}\n", nc, nd, CTRL_UP.load(Ordering::Relaxed) )); for c in 0..nc as usize { let ct = unsafe { &*core::ptr::addr_of!(CTRLS[c]) }; s.push_str(&alloc::format!( " ctrl{} mmio={:#x} ports={}\n", c, ct.base, ct.nports )); for p in 0..ct.nports.min(8) as usize { let v = unsafe { rd(ct.opb + OP_PRTSC_BASE + p * OP_PRTSC_STRIDE) }; s.push_str(&alloc::format!( " port{}: {}{:#x}\n", p, if v & PRT_CONNECT != 0 { "*" } else { "" }, v )); } } for i in 0..nd as usize { let d = unsafe { &*core::ptr::addr_of!(DEVS[i]) }; s.push_str(&alloc::format!( " {} dev{} ctrl={} armed={} fails={} reports={} qh={:#x}\n", if d.kind == Kind::Mouse { "mouse" } else { "kbd" }, i, d.ctrl, d.armed, d.failures, d.reports, d.qh as usize )); } s }