From be868a43185826b02bf6d091019bb779678525bb Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 17 Aug 2026 14:53:52 +0200 Subject: USB2/1.1 (EHCI/UHCI) drivers + real-hardware bring-up hardening - Add polled EHCI (USB2) and UHCI (USB1.1) host-controller drivers with HID boot mouse support, wired into cursor polling and the 'usb' shell cmd - Add USB Legacy ownership handoff (claim_usb_legacy) plus config_write_byte - Harden boot: verify the PIT/timer ticks before shell entry, EOI stray interrupts in the LAPIC to stop interrupt storms, bound PS/2 controller waits, and INTx-disable AHCI (polled driver) - Add boot stage markers, klog screen echo, and QR lock; scheduler mark_shell_running; NO_USB stopgap to keep BIOS USB-Legacy input alive - alloc_pages_above for DMA buffers that must avoid low conventional memory - Expand DEBUG_NOTES with EHCI/QEMU bring-up findings --- rsnix/src/usb.rs | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 144 insertions(+), 3 deletions(-) (limited to 'rsnix/src/usb.rs') diff --git a/rsnix/src/usb.rs b/rsnix/src/usb.rs index 71df781..3ed3a0c 100644 --- a/rsnix/src/usb.rs +++ b/rsnix/src/usb.rs @@ -229,6 +229,15 @@ unsafe fn wr64(a: usize, v: u64) { fn op(off: usize) -> usize { unsafe { OPB + off } } + +/// 16-bit I/O read (UHCI port status/control registers live in I/O space). +unsafe fn io_iow16(port: u16) -> u16 { + let v: u16; + unsafe { + core::arch::asm!("in ax, dx", out("ax") v, in("dx") port, options(nomem, nostack, preserves_flags)); + } + v +} fn db(slot: u32) -> usize { unsafe { DBB }.wrapping_add(slot as usize * 4) } @@ -727,7 +736,13 @@ pub fn init() -> bool { dev, func, 0x04, - (cmd & 0xFFFF0000) | ((cmd & 0xFFFF) | 0x6), + // Memory space + bus master + INTx disable (bit 10). The + // driver is polled (event ring drained from the cursor task), + // so the controller must never assert INTx: an unhandled + // xHCI interrupt would hit the kernel's catch-all handler + // and (being level-triggered) re-fire forever on real + // hardware. + (cmd & 0xFFFF_0000) | ((cmd & 0xFFFF) | 0x6 | 0x400), ); let bar = crate::pci::bar64(b, dev, func, 0); let base = bar.unwrap() as usize; @@ -745,6 +760,15 @@ pub fn init() -> bool { MAXPORTS = (hcs1 >> 24) & 0xff; // MaxPorts is bits 31:24 (bits 23:16 are MaxIntrs) let _ = hcc; CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 }; + + // Claim USB Legacy ownership from the BIOS before resetting the + // controller (same SMM-legacy rationale as the EHCI driver). + match crate::pci::claim_usb_legacy(b, dev, func, base, 0x04, true) { + 0 => crate::serial_println!("usb: xHCI USB legacy: already released (no claim needed)"), + 1 => crate::serial_println!("usb: xHCI USB legacy handed over by BIOS"), + 2 => crate::serial_println!("usb: xHCI USB legacy claim timed out (BIOS sem stuck)"), + _ => crate::serial_println!("usb: xHCI USB legacy: no capability found"), + } } // Reset the controller (stop, then reset). Wait for reset completion @@ -802,8 +826,10 @@ pub fn init() -> bool { wr(rt(RT_ERSTSZ), 1); wr64(rt(RT_ERSTBA), ERST as u64); wr64(rt(RT_ERDP), EV.raw as u64); - // Interrupter 0: clear pending, enable. - wr(rt(RT_IMAN), rd(rt(RT_IMAN)) | 0x2); + // Interrupter 0: keep the interrupt DISABLED (IE=0, clear any + // pending IP) — the driver is polled and the controller must not + // assert INTx (see the PCI command-register comment above). + wr(rt(RT_IMAN), (rd(rt(RT_IMAN)) & !0x2) | 0x1); wr(rt(RT_IMOD), 0); // Run. wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN); @@ -973,5 +999,120 @@ pub fn diagnose() -> alloc::string::String { unsafe { QUEUED } )); } + + // Cross-controller attach scan: report which PCI USB controller(s) + // currently have a device with bit0 (connect) set, across xHCI / EHCI / + // UHCI / OHCI. Read-only diagnostic to locate the mouse on real hw. + s.push_str("usbctl: scanning all USB controllers:\n"); + unsafe { + for d in crate::pci::probe() { + if d.class != 0x0C || d.subclass != 0x03 { + continue; + } + let lbl = match d.prog_if { + 0x00 => "UHCI", + 0x10 => "OHCI", + 0x20 => "EHCI", + 0x30 => "xHCI", + _ => "USB?", + }; + let bar = crate::pci::bar64(d.bus, d.device, d.func, 0); + let base = bar.unwrap_or(0); + s.push_str(&alloc::format!( + " {} {:02x}:{:02x}.{} bar={:#x}\n", + lbl, d.bus, d.device, d.func, base + )); + if base == 0 || base == u64::MAX { + // BAR not decoded / not enabled — nothing to probe. + continue; + } + match d.prog_if { + 0x30 => { + if base == 0 { + continue; + } + let caplen = rd(base as usize); + let hcs1 = rd(base as usize + 0x04); + let np = ((hcs1 >> 24) & 0xff) as usize; + let opb = base as usize + (caplen & 0xff) as usize; + s.push_str(&alloc::format!( + " xhci ports={} sc=", np + )); + for p in 0..np.min(16) { + let v = rd(opb + 0x400 + p * 0x10); + s.push_str(&alloc::format!( + "{}{:#x}", + if v & 1 != 0 { "*" } else { "" }, + v & 0x3ff + )); + if p + 1 < (np.min(16)) { + s.push(' '); + } + } + s.push('\n'); + } + 0x20 => { + // EHCI: MMIO BAR. CAPLENGTH byte0, HcSParams (nports) @0x04. + let cl = rd(base as usize) & 0xff; + let sp = rd(base as usize + 0x04); + let hc = rd(base as usize + 0x08); + let np = (sp & 0xf) as usize; + let opb = base as usize + cl as usize; + let cmd = crate::pci::config_read(d.bus, d.device, d.func, 0x04); + let eccp = crate::pci::config_read(d.bus, d.device, d.func, 0x34); + s.push_str(&alloc::format!( + " ehci nports={} hrs={} hcc={:#x} cmd={:#x} eccp={:#x}\n", + np, sp, hc, cmd, (eccp >> 8) & 0xff + )); + let cfg = rd(opb + 0x40); + s.push_str(&alloc::format!( + " ehci cfgflag={:#x} ", cfg + )); + for p in 0..np.min(16) { + let v = rd(opb + 0x44 + p * 0x04); + s.push_str(&alloc::format!( + "{}{:#x},", + if v & 1 != 0 { "*" } else { "" }, + v & 0xffff + )); + } + s.push('\n'); + // Legacy BIOS handoff status (USBLEGSUP at EECP). + let eo = ((eccp >> 8) & 0xff) as usize; + if eo != 0 && (eccp & 0xff) != 0 { + let hs = crate::pci::config_read( + d.bus, + d.device, + d.func, + (eo & 0xfc) as u8, + ); + s.push_str(&alloc::format!( + " ehci legacy=usblegsup={:#x} os-owner={} bios-owned={}\n", + hs, + (hs & (1 << 24)) != 0, + (hs & (1 << 16)) != 0 || (hs & 0x100) != 0 + )); + } + } + 0x00 => { + // UHCI: I/O BAR (bar64 refuses I/O bars — read raw). + let lo = crate::pci::config_read(d.bus, d.device, d.func, 0x10); + let io = (lo & 0xFFF0) as u16; + if io == 0 { + continue; + } + for p in 0..2 { + let pr = io_iow16(io + 0x10 + (p as u16) * 2); + s.push_str(&alloc::format!( + "{}{:x}:", if pr & 1 != 0 { "*" } else { "" }, + (pr & 0xff) + )); + } + s.push('\n'); + } + _ => {} + } + } + } s } -- cgit v1.2.3