summaryrefslogtreecommitdiffstats
path: root/rsnix/src/pci.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix/src/pci.rs')
-rw-r--r--rsnix/src/pci.rs125
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).
66pub fn config_write(bus: u8, device: u8, func: u8, reg: u8, val: u32) { 65pub 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
70fn 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.
85pub 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.
73pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option<u64> { 92pub 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.
141pub 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.
94pub fn probe() -> alloc::vec::Vec<DeviceInfo> { 217pub fn probe() -> alloc::vec::Vec<DeviceInfo> {
95 let mut out = alloc::vec::Vec::new(); 218 let mut out = alloc::vec::Vec::new();