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/pci.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) (limited to 'rsnix/src/pci.rs') diff --git a/rsnix/src/pci.rs b/rsnix/src/pci.rs index b9c2265..738c5e9 100644 --- a/rsnix/src/pci.rs +++ b/rsnix/src/pci.rs @@ -61,13 +61,32 @@ pub fn config_read(bus: u8, device: u8, func: u8, reg: u8) -> u32 { outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); inl(CONFIG_DATA) } - /// Write a dword to a device's config space (`reg` must be dword-aligned). pub fn config_write(bus: u8, device: u8, func: u8, reg: u8, val: u32) { outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); outl(CONFIG_DATA, val) } +fn outb(port: u16, val: u8) { + unsafe { + core::arch::asm!( + "out dx, al", + in("dx") port, + in("al") val, + options(nomem, nostack, preserves_flags) + ); + } +} + +/// Write a single byte to config space (any byte offset). The PCI +/// byte-enable cycle goes out on port 0xCFC + lane, exactly like `pci_ +/// write_config_byte` everywhere else; QEMU and real chipsets both decode +/// the low two address bits of the config-data port. +pub fn config_write_byte(bus: u8, device: u8, func: u8, reg: u8, val: u8) { + outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); + outb(CONFIG_DATA + ((reg & 3) as u16), val) +} + /// Read a memory BAR (index 0-5) as a 64-bit address, handling 32/64-bit /// BARs. Returns None for I/O BARs or invalid values. pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option { @@ -90,6 +109,110 @@ pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option { } } +/// Claim USB Legacy ownership from the BIOS on a controller whose MMIO base +/// exposes the USB Legacy Support extended capability (USBLEGSUP, cap id 1). +/// +/// EHCI/xHCI “USB Legacy Support” ownership handoff (EHCI spec §5.1). +/// +/// The 2012-era PCH BIOS drives USB keyboards through SMM USB-legacy +/// emulation while it owns the controller: its SMI handler polls the EHCI +/// over USB-error/port-change events. If the OS reprograms the controller +/// and runs async DMA underneath the still-active BIOS module, the SMM poll +/// collides with the OS transfer at an unpredictable moment — a classic +/// real-silicon Host System Error (SE) that QEMU cannot reproduce (no SMM). +/// +/// CRITICAL: the extended-capability pointer is a PCI *config-space* +/// offset, not an MMIO offset. Read it from an MMIO register (`eccp_src` +/// = MMIO dword offset; EHCI: HCCPARAMS @+0x08 bits 15:8; xHCI: HCSPARAMS1 +/// @+0x04 bits 31:16), then walk config space — exactly Linux's +/// `quirk_usb_handoff_ehci` (pci-quirks.c) and FreeBSD's `ehci_pci_attach`. +/// Our previous implementation treated EECP as an MMIO offset, read a +/// reserved register (0), and silently never claimed ownership — the +/// observed “claim skipped” on the 7010. +/// +/// Handshake (Linux/FreeBSD-identical): if the BIOS-owned semaphore +/// (bit 16 of the cap dword) is set, write the OS-owned semaphore byte +/// (cap+3) = 1 and spin (bounded ~1 s) for bit 16 to clear. USBLEGCTLSTS +/// (cap+4) is deliberately left alone — Linux notes some systems “get +/// upset” if it's touched. +/// +/// Returns: 0 = already released/owned (no claim needed), 1 = handed over, +/// 2 = BIOS never released, 3 = no legacy capability / chain out of range. +pub fn claim_usb_legacy( + bus: u8, + dev: u8, + func: u8, + mmio_base: usize, + eccp_src: usize, + eccp_bits_high: bool, +) -> u8 { + let src = unsafe { (mmio_base as *const u32).add(eccp_src).read_volatile() }; + crate::serial_println!( + "usb-legacy: probe mmio=+{:#x} hcc={:#010x} high={}", + eccp_src, + src, + eccp_bits_high + ); + let mut offset = if eccp_bits_high { + ((src >> 16) & 0xFFFF) as u8 // xHCI: XECP (u8 caps the walk at 0xFF config) + } else { + ((src >> 8) & 0xFF) as u8 // EHCI: EECP + }; + if offset == 0 { + return 3; + } + let mut guard = 32; // bounded walk, no panics + while offset != 0 && guard > 0 { + guard -= 1; + let cap = config_read(bus, dev, func, offset); + crate::serial_println!( + "usb-legacy: cap@{:#x} id={:#04x} next={:#04x} d={:#010x}", + offset, + cap & 0xFF, + (cap >> 8) & 0xFF, + cap + ); + let id = cap & 0xFF; + let next = ((cap >> 8) & 0xFF) as u8; + match id { + 0 => return 3, // illegal reserved cap terminates the chain + 1 => { + // USB Legacy Support capability. + let ctlsts = config_read(bus, dev, func, offset + 4); + crate::serial_println!( + "usb-legacy: cap@{:#x} bios_sem={} os_sem={} ctlsts={:#x}", + offset, + (cap >> 16) & 1, + (cap >> 24) & 1, + ctlsts + ); + if cap & (1 << 24) != 0 { + return 0; // OS already owns + } + if cap & (1 << 16) == 0 { + return 0; // BIOS not claiming it + } + crate::serial_println!("usb-legacy: BIOS owns controller - claiming (OS sem)"); + config_write_byte(bus, dev, func, offset + 3, 1); + let t0 = crate::interrupts::uptime_ms(); + let mut handed = false; + while crate::interrupts::uptime_ms().wrapping_sub(t0) < 1000 { + if config_read(bus, dev, func, offset) & (1 << 16) == 0 { + handed = true; + break; + } + core::hint::spin_loop(); + } + return if handed { 1 } else { 2 }; + } + _ => { + offset = next; + } + } + } + 3 +} + /// Enumerate every device/function present on all buses. pub fn probe() -> alloc::vec::Vec { let mut out = alloc::vec::Vec::new(); -- cgit v1.2.3