diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-17 14:53:52 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-17 14:53:52 +0200 |
| commit | be868a43185826b02bf6d091019bb779678525bb (patch) | |
| tree | 586988c15f1d700b1621c7f0208584b30878a56c /rsnix/src/pci.rs | |
| parent | v0.5.10: change em-dashes to dashes (diff) | |
| download | rs-dos-be868a43185826b02bf6d091019bb779678525bb.tar.gz rs-dos-be868a43185826b02bf6d091019bb779678525bb.zip | |
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
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/pci.rs | 125 |
1 files changed, 124 insertions, 1 deletions
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 { | |||
| 61 | outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); | 61 | outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); |
| 62 | inl(CONFIG_DATA) | 62 | inl(CONFIG_DATA) |
| 63 | } | 63 | } |
| 64 | |||
| 65 | /// Write a dword to a device's config space (`reg` must be dword-aligned). | 64 | /// Write a dword to a device's config space (`reg` must be dword-aligned). |
| 66 | pub fn config_write(bus: u8, device: u8, func: u8, reg: u8, val: u32) { | 65 | pub fn config_write(bus: u8, device: u8, func: u8, reg: u8, val: u32) { |
| 67 | outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); | 66 | outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); |
| 68 | outl(CONFIG_DATA, val) | 67 | outl(CONFIG_DATA, val) |
| 69 | } | 68 | } |
| 70 | 69 | ||
| 70 | fn outb(port: u16, val: u8) { | ||
| 71 | unsafe { | ||
| 72 | core::arch::asm!( | ||
| 73 | "out dx, al", | ||
| 74 | in("dx") port, | ||
| 75 | in("al") val, | ||
| 76 | options(nomem, nostack, preserves_flags) | ||
| 77 | ); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | /// Write a single byte to config space (any byte offset). The PCI | ||
| 82 | /// byte-enable cycle goes out on port 0xCFC + lane, exactly like `pci_ | ||
| 83 | /// write_config_byte` everywhere else; QEMU and real chipsets both decode | ||
| 84 | /// the low two address bits of the config-data port. | ||
| 85 | pub fn config_write_byte(bus: u8, device: u8, func: u8, reg: u8, val: u8) { | ||
| 86 | outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); | ||
| 87 | outb(CONFIG_DATA + ((reg & 3) as u16), val) | ||
| 88 | } | ||
| 89 | |||
| 71 | /// Read a memory BAR (index 0-5) as a 64-bit address, handling 32/64-bit | 90 | /// Read a memory BAR (index 0-5) as a 64-bit address, handling 32/64-bit |
| 72 | /// BARs. Returns None for I/O BARs or invalid values. | 91 | /// BARs. Returns None for I/O BARs or invalid values. |
| 73 | pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option<u64> { | 92 | pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option<u64> { |
| @@ -90,6 +109,110 @@ pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option<u64> { | |||
| 90 | } | 109 | } |
| 91 | } | 110 | } |
| 92 | 111 | ||
| 112 | /// Claim USB Legacy ownership from the BIOS on a controller whose MMIO base | ||
| 113 | /// exposes the USB Legacy Support extended capability (USBLEGSUP, cap id 1). | ||
| 114 | /// | ||
| 115 | /// EHCI/xHCI “USB Legacy Support” ownership handoff (EHCI spec §5.1). | ||
| 116 | /// | ||
| 117 | /// The 2012-era PCH BIOS drives USB keyboards through SMM USB-legacy | ||
| 118 | /// emulation while it owns the controller: its SMI handler polls the EHCI | ||
| 119 | /// over USB-error/port-change events. If the OS reprograms the controller | ||
| 120 | /// and runs async DMA underneath the still-active BIOS module, the SMM poll | ||
| 121 | /// collides with the OS transfer at an unpredictable moment — a classic | ||
| 122 | /// real-silicon Host System Error (SE) that QEMU cannot reproduce (no SMM). | ||
| 123 | /// | ||
| 124 | /// CRITICAL: the extended-capability pointer is a PCI *config-space* | ||
| 125 | /// offset, not an MMIO offset. Read it from an MMIO register (`eccp_src` | ||
| 126 | /// = MMIO dword offset; EHCI: HCCPARAMS @+0x08 bits 15:8; xHCI: HCSPARAMS1 | ||
| 127 | /// @+0x04 bits 31:16), then walk config space — exactly Linux's | ||
| 128 | /// `quirk_usb_handoff_ehci` (pci-quirks.c) and FreeBSD's `ehci_pci_attach`. | ||
| 129 | /// Our previous implementation treated EECP as an MMIO offset, read a | ||
| 130 | /// reserved register (0), and silently never claimed ownership — the | ||
| 131 | /// observed “claim skipped” on the 7010. | ||
| 132 | /// | ||
| 133 | /// Handshake (Linux/FreeBSD-identical): if the BIOS-owned semaphore | ||
| 134 | /// (bit 16 of the cap dword) is set, write the OS-owned semaphore byte | ||
| 135 | /// (cap+3) = 1 and spin (bounded ~1 s) for bit 16 to clear. USBLEGCTLSTS | ||
| 136 | /// (cap+4) is deliberately left alone — Linux notes some systems “get | ||
| 137 | /// upset” if it's touched. | ||
| 138 | /// | ||
| 139 | /// Returns: 0 = already released/owned (no claim needed), 1 = handed over, | ||
| 140 | /// 2 = BIOS never released, 3 = no legacy capability / chain out of range. | ||
| 141 | pub fn claim_usb_legacy( | ||
| 142 | bus: u8, | ||
| 143 | dev: u8, | ||
| 144 | func: u8, | ||
| 145 | mmio_base: usize, | ||
| 146 | eccp_src: usize, | ||
| 147 | eccp_bits_high: bool, | ||
| 148 | ) -> u8 { | ||
| 149 | let src = unsafe { (mmio_base as *const u32).add(eccp_src).read_volatile() }; | ||
| 150 | crate::serial_println!( | ||
| 151 | "usb-legacy: probe mmio=+{:#x} hcc={:#010x} high={}", | ||
| 152 | eccp_src, | ||
| 153 | src, | ||
| 154 | eccp_bits_high | ||
| 155 | ); | ||
| 156 | let mut offset = if eccp_bits_high { | ||
| 157 | ((src >> 16) & 0xFFFF) as u8 // xHCI: XECP (u8 caps the walk at 0xFF config) | ||
| 158 | } else { | ||
| 159 | ((src >> 8) & 0xFF) as u8 // EHCI: EECP | ||
| 160 | }; | ||
| 161 | if offset == 0 { | ||
| 162 | return 3; | ||
| 163 | } | ||
| 164 | let mut guard = 32; // bounded walk, no panics | ||
| 165 | while offset != 0 && guard > 0 { | ||
| 166 | guard -= 1; | ||
| 167 | let cap = config_read(bus, dev, func, offset); | ||
| 168 | crate::serial_println!( | ||
| 169 | "usb-legacy: cap@{:#x} id={:#04x} next={:#04x} d={:#010x}", | ||
| 170 | offset, | ||
| 171 | cap & 0xFF, | ||
| 172 | (cap >> 8) & 0xFF, | ||
| 173 | cap | ||
| 174 | ); | ||
| 175 | let id = cap & 0xFF; | ||
| 176 | let next = ((cap >> 8) & 0xFF) as u8; | ||
| 177 | match id { | ||
| 178 | 0 => return 3, // illegal reserved cap terminates the chain | ||
| 179 | 1 => { | ||
| 180 | // USB Legacy Support capability. | ||
| 181 | let ctlsts = config_read(bus, dev, func, offset + 4); | ||
| 182 | crate::serial_println!( | ||
| 183 | "usb-legacy: cap@{:#x} bios_sem={} os_sem={} ctlsts={:#x}", | ||
| 184 | offset, | ||
| 185 | (cap >> 16) & 1, | ||
| 186 | (cap >> 24) & 1, | ||
| 187 | ctlsts | ||
| 188 | ); | ||
| 189 | if cap & (1 << 24) != 0 { | ||
| 190 | return 0; // OS already owns | ||
| 191 | } | ||
| 192 | if cap & (1 << 16) == 0 { | ||
| 193 | return 0; // BIOS not claiming it | ||
| 194 | } | ||
| 195 | crate::serial_println!("usb-legacy: BIOS owns controller - claiming (OS sem)"); | ||
| 196 | config_write_byte(bus, dev, func, offset + 3, 1); | ||
| 197 | let t0 = crate::interrupts::uptime_ms(); | ||
| 198 | let mut handed = false; | ||
| 199 | while crate::interrupts::uptime_ms().wrapping_sub(t0) < 1000 { | ||
| 200 | if config_read(bus, dev, func, offset) & (1 << 16) == 0 { | ||
| 201 | handed = true; | ||
| 202 | break; | ||
| 203 | } | ||
| 204 | core::hint::spin_loop(); | ||
| 205 | } | ||
| 206 | return if handed { 1 } else { 2 }; | ||
| 207 | } | ||
| 208 | _ => { | ||
| 209 | offset = next; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | 3 | ||
| 214 | } | ||
| 215 | |||
| 93 | /// Enumerate every device/function present on all buses. | 216 | /// Enumerate every device/function present on all buses. |
| 94 | pub fn probe() -> alloc::vec::Vec<DeviceInfo> { | 217 | pub fn probe() -> alloc::vec::Vec<DeviceInfo> { |
| 95 | let mut out = alloc::vec::Vec::new(); | 218 | let mut out = alloc::vec::Vec::new(); |
