summaryrefslogtreecommitdiffstats
path: root/rsnix/src/usb.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 16:33:05 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 16:33:05 +0100
commit50b4d2f0ab05901a847d8e1318152308265e579b (patch)
treecd9fdf79e23e501e32646f9fb0af06523d48e169 /rsnix/src/usb.rs
parentusb: re-enable EHCI, share HID keyboard decoding, fix boot protocol (diff)
downloadrs-dos-50b4d2f0ab05901a847d8e1318152308265e579b.tar.gz
rs-dos-50b4d2f0ab05901a847d8e1318152308265e579b.zip
usb: real-hardware rework — hubs, both EHCI controllers, keyboard, xHCI hardening
EHCI (OptiPlex 7010 / Q77 and similar Intel PCH): - enumerate *both* EHCI controllers (00:1a.0 + 00:1d.0), not just the first - hub support: read the hub class descriptor, power/reset each port via hub class requests, read the child speed, then drive the child with split transactions carrying the hub's address and port (TT). On 7-series PCH the root port presents the integrated Rate Matching Hub (8087:0024, class 9) and the mouse/keyboard sit behind it; without this the driver stopped at "no HID mouse/keyboard endpoint in config". - HID mouse *and* keyboard, boot protocol + idle, per-device periodic QH chained into the frame list, per-device report buffers and counters - periodic re-arm follows the standard shape (qTD re-activated, Next-qTD re-linked); the HC owns the status/toggle overlay - DMA from the page allocator inside [16 MiB, 4 GiB): EHCI programs 32-bit addresses and the page bitmap spans all RAM - 2 ms recovery after SET_ADDRESS xHCI: - scratchpad buffers (HCSPARAMS2) and MaxSlotsEn > 1, with per-slot EP0 and interrupt rings plus per-slot device contexts - DMA from the page allocator instead of the heap allocator - boot protocol + idle, mouse + keyboard, first-report diagnostics - transfer events are routed to every device before matching a waiter, so a second device's enumeration cannot swallow an armed device's report shared: - rsnix/src/hid.rs: HID usage -> scancode-set-1 tables, one scancode FIFO feeding ps2::feed_scancode, per-device boot-keyboard report differ - WM input pump polls PS/2 -> UHCI -> EHCI -> xHCI - memory::alloc_pages_range(count, min, max) for bounded DMA allocations QEMU coverage: make test-usb (EHCI + xHCI, mouse + keyboard). QEMU's EHCI split path delivers only the first report for FS devices, so repeated-report delivery and the RMH path remain hardware-test items.
Diffstat (limited to '')
-rw-r--r--rsnix/src/usb.rs1102
1 files changed, 620 insertions, 482 deletions
diff --git a/rsnix/src/usb.rs b/rsnix/src/usb.rs
index 3ed3a0c..5a4136f 100644
--- a/rsnix/src/usb.rs
+++ b/rsnix/src/usb.rs
@@ -1,33 +1,22 @@
1// --------------------------------------------------------------------------- 1// ---------------------------------------------------------------------------
2// USB host controller driver — xHCI (USB 2.0/3.x) + HID mouse support. 2// USB host controller driver — xHCI (USB 2.0/3.x), HID mouse + keyboard.
3// 3//
4// **Polled** driver: it runs the xHCI event ring from the cursor task loop 4// **Polled** driver: the event ring is drained from the cursor task loop (and
5// instead of wiring MSI/INTx into the PIC, matching how the kernel already 5// the WM input pump for keyboards) instead of wiring MSI/INTx into the PIC.
6// polls the PS/2 mouse (see `cursor`).
7// 6//
8// Minimal, mouse-only scope: 7// Scope:
9// • take the controller out of reset, set up the command + event rings 8// • controller reset, command + event rings, scratchpad buffers (§5.4.5)
10// • reset ports, run the slot/address/configure firmware sequence 9// • one slot per HID device (up to MAX_XDEV), address/configure sequence
11// • find the HID mouse boot interface, open its interrupt-IN endpoint, 10// • boot-protocol HID mouse and keyboard interfaces
12// and translate the standard report into `ps2::MouseEvent` (reusing 11// • reports decoded into ps2::MouseEvent / ps2::feed_scancode
13// the cursor task's consumption path).
14// 12//
15// DMA / MMIO: the kernel is identity-mapped (physical == virtual), so bus 13// DMA: rings/contexts come from the page allocator (physical == kernel
16// addresses are plain kernel pointers and every heap allocation is DMA-able. 14// address via the identity map), never the heap, and are 64-byte aligned as
17// Rings/contexts are allocated once at boot and handed to the controller 15// the xHCI requires. The controller is 64-bit capable, so no 4 GiB bound.
18// permanently.
19// 16//
20// Register/TRB/context layouts follow the Linux xHCI driver definitions 17// Register/TRB/context layouts follow the Linux xHCI driver definitions and
21// (drivers/usb/host/xhci*.h) and the xHCI 1.2 spec. All exact field 18// the xHCI 1.2 spec (TRB type bits 10-15, completion code bits 24-31 of event
22// positions were cross-checked against the Linux sources: 19// dword2, slot id bits 24-31 / ep id bits 16-20 of event dword3).
23// * TRB type field is bits 10-15 (TRB_TYPE(p) = p << 10)
24// * transfer/command completion code is bits 24-31 of event dword2
25// * slot id = bits 24-31, ep id = bits 16-20 of event dword3
26// * endpoint context: type at bits 3-5, interval at 16-23,
27// max packet size at bits 16-31 of dword1, TR dequeue is a 64-bit
28// field at bytes 8-15 with DCS in bit 0
29// * slot context: route/LAST_CTX in dword0, root-hub-port at 16-23 of
30// dword1, speed at bits 20-23
31// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
32#![allow(non_snake_case)] // macro-style helpers mirror the Linux xHCI headers 21#![allow(non_snake_case)] // macro-style helpers mirror the Linux xHCI headers
33use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; 22use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
@@ -36,7 +25,8 @@ use crate::ps2::MouseEvent;
36 25
37// ---- capability registers (offset from mmio base) ---- 26// ---- capability registers (offset from mmio base) ----
38const CAP_CAPLENGTH: usize = 0x00; 27const CAP_CAPLENGTH: usize = 0x00;
39const HCSPARAMS1: usize = 0x04; // MaxSlots[7:0] MaxIntrs[15:8] MaxPorts[23:16] 28const HCSPARAMS1: usize = 0x04; // MaxSlots[7:0] MaxIntrs[15:8] MaxPorts[31:24]
29const HCSPARAMS2: usize = 0x08; // scratchpad buffers, ERST max
40const HCCPARAMS1: usize = 0x10; // bit0=64-bit addressing; bit2=64-byte contexts 30const HCCPARAMS1: usize = 0x10; // bit0=64-bit addressing; bit2=64-byte contexts
41const DBOFF: usize = 0x14; 31const DBOFF: usize = 0x14;
42const RTSOFF: usize = 0x18; 32const RTSOFF: usize = 0x18;
@@ -47,7 +37,8 @@ const OP_USBSTS: usize = 0x04;
47const OP_CRCR: usize = 0x18; // 64-bit 37const OP_CRCR: usize = 0x18; // 64-bit
48const OP_DCBAAP: usize = 0x30; // 64-bit 38const OP_DCBAAP: usize = 0x30; // 64-bit
49const OP_CONFIG: usize = 0x38; 39const OP_CONFIG: usize = 0x38;
50const OP_PORTSC: usize = 0x400; // PORTSC port 1 (port set stride 0x10) 40const OP_PORTSC: usize = 0x400;
41const PORT_STRIDE: usize = 0x10;
51 42
52// ---- runtime registers (relative to mmio + RTSOFF), interrupter 0 ---- 43// ---- runtime registers (relative to mmio + RTSOFF), interrupter 0 ----
53const RT_IMAN: usize = 0x20; 44const RT_IMAN: usize = 0x20;
@@ -67,7 +58,7 @@ const CMD_RING_CYCLE: u32 = 1 << 0;
67const TRB_CYCLE: u32 = 1 << 0; 58const TRB_CYCLE: u32 = 1 << 0;
68const TRB_IOC: u32 = 1 << 5; 59const TRB_IOC: u32 = 1 << 5;
69const TRB_IDT: u32 = 1 << 6; 60const TRB_IDT: u32 = 1 << 6;
70const TRB_TYPE_BITMASK: u32 = 0xfc00; // bits 10..15 61const TRB_TYPE_BITMASK: u32 = 0xfc00;
71fn TYPE(t: u32) -> u32 { 62fn TYPE(t: u32) -> u32 {
72 t << 10 63 t << 10
73} 64}
@@ -148,7 +139,6 @@ fn DEV_SPEED(p: u32) -> u32 {
148} 139}
149 140
150// ---- USB meta protocol ---- 141// ---- USB meta protocol ----
151const USB_DIR_IN: u8 = 0x80;
152const USB_REQ_GET_DESCRIPTOR: u8 = 0x06; 142const USB_REQ_GET_DESCRIPTOR: u8 = 0x06;
153const USB_REQ_SET_CONFIGURATION: u8 = 0x09; 143const USB_REQ_SET_CONFIGURATION: u8 = 0x09;
154const DT_DEVICE: u8 = 0x01; 144const DT_DEVICE: u8 = 0x01;
@@ -156,6 +146,19 @@ const DT_CONFIG: u8 = 0x02;
156const DT_INTERFACE: u8 = 0x04; 146const DT_INTERFACE: u8 = 0x04;
157const DT_ENDPOINT: u8 = 0x05; 147const DT_ENDPOINT: u8 = 0x05;
158const HID_CLASS: u8 = 0x03; 148const HID_CLASS: u8 = 0x03;
149const HID_SUBCLASS_BOOT: u8 = 0x01;
150const HID_PROTO_KEYBOARD: u8 = 1;
151const HID_PROTO_MOUSE: u8 = 2;
152const REQ_SET_PROTOCOL: u8 = 0x0B;
153const REQ_SET_IDLE: u8 = 0x0A;
154
155const MAX_XDEV: usize = 4;
156
157#[derive(Clone, Copy, PartialEq)]
158enum Kind {
159 Mouse,
160 Keyboard,
161}
159 162
160// --------------------------------------------------------------------------- 163// ---------------------------------------------------------------------------
161// State (cooperative single-task kernel → unsafe statics) 164// State (cooperative single-task kernel → unsafe statics)
@@ -166,12 +169,14 @@ static mut DBB: usize = 0; // doorbell base
166static mut RTB: usize = 0; // runtime base 169static mut RTB: usize = 0; // runtime base
167static mut CTRL_CS: usize = 32; // context size 170static mut CTRL_CS: usize = 32; // context size
168static mut MAXPORTS: u32 = 0; 171static mut MAXPORTS: u32 = 0;
172static mut N_SLOTS: u32 = 1; // MaxSlotsEn we program
169 173
174#[derive(Clone, Copy)]
170struct Ring { 175struct Ring {
171 raw: *mut u32, 176 raw: *mut u32,
172 count: usize, // TRBs 177 count: usize,
173 idx: usize, // producer (command/transfer) or consumer (event) index 178 idx: usize,
174 cyc: u32, // software cycle 179 cyc: u32,
175} 180}
176const fn ri_empty(count: usize) -> Ring { 181const fn ri_empty(count: usize) -> Ring {
177 Ring { 182 Ring {
@@ -182,29 +187,60 @@ const fn ri_empty(count: usize) -> Ring {
182 } 187 }
183} 188}
184static mut CMD: Ring = ri_empty(64); 189static mut CMD: Ring = ri_empty(64);
185static mut EP0: Ring = ri_empty(8);
186static mut EPIN: Ring = ri_empty(8);
187static mut EV: Ring = ri_empty(32); 190static mut EV: Ring = ri_empty(32);
188 191
189static mut DCBAA: *mut u64 = core::ptr::null_mut(); 192static mut DCBAA: *mut u64 = core::ptr::null_mut();
190static mut DEVCTX: *mut u8 = core::ptr::null_mut();
191static mut INCTX: *mut u8 = core::ptr::null_mut(); 193static mut INCTX: *mut u8 = core::ptr::null_mut();
192static mut ERST: *mut u8 = core::ptr::null_mut(); 194static mut ERST: *mut u8 = core::ptr::null_mut();
195static mut SPB_ARRAY: *mut u64 = core::ptr::null_mut();
196static mut NUM_SPB: u32 = 0;
197/// Report buffers (16 bytes per device) in one DMA page.
198static mut REPORTS: *mut u8 = core::ptr::null_mut();
193 199
194static SLOT: AtomicU32 = AtomicU32::new(0); 200/// One enumerated HID device (slot + interrupt-IN endpoint).
195static HID_EPID: AtomicU32 = AtomicU32::new(0); // device-context index of the IN endpoint 201#[derive(Clone, Copy)]
196static mut HID_BUF: [u8; 8] = [0; 8]; 202struct XDev {
197static mut HID_LEN: usize = 4; 203 slot: u32,
198static mut MPS0: u32 = 8; 204 dci: u32, // endpoint context index of the IN endpoint
199 205 ep0: Ring,
200static mut GOT_REPORT: bool = false; 206 epin: Ring,
201static mut QUEUED: bool = false; 207 devctx: *mut u8,
202static mut RBTN: u8 = 0; 208 kind: Kind,
203static mut RDX: i8 = 0; 209 len: u32,
204static mut RDY: i8 = 0; 210 kbd: crate::hid::Keyboard,
211 armed: bool,
212 ready: bool,
213 code: u32,
214 reports: u32,
215 failures: u32,
216}
217impl XDev {
218 const EMPTY: XDev = XDev {
219 slot: 0,
220 dci: 0,
221 ep0: ri_empty(8),
222 epin: ri_empty(8),
223 devctx: core::ptr::null_mut(),
224 kind: Kind::Mouse,
225 len: 0,
226 kbd: crate::hid::Keyboard::new(),
227 armed: false,
228 ready: false,
229 code: 0,
230 reports: 0,
231 failures: 0,
232 };
233}
234static mut XDEVS: [XDev; MAX_XDEV] = [XDev::EMPTY; MAX_XDEV];
235static NXDEV: AtomicU32 = AtomicU32::new(0);
205 236
206static INITOK: AtomicBool = AtomicBool::new(false); 237static INITOK: AtomicBool = AtomicBool::new(false);
207static MOUSEPRESENT: AtomicBool = AtomicBool::new(false); 238static DISCOVERED_TRIED: AtomicU32 = AtomicU32::new(0);
239static LAST_SCAN: AtomicU32 = AtomicU32::new(0);
240
241unsafe fn dev_mut(i: usize) -> &'static mut XDev {
242 unsafe { &mut *core::ptr::addr_of_mut!(XDEVS[i]) }
243}
208 244
209fn cs() -> usize { 245fn cs() -> usize {
210 unsafe { CTRL_CS } 246 unsafe { CTRL_CS }
@@ -245,14 +281,17 @@ fn rt(off: usize) -> usize {
245 unsafe { RTB + off } 281 unsafe { RTB + off }
246} 282}
247 283
248fn dma_alloc(bytes: usize, align: usize) -> *mut u8 { 284/// DMA page(s) from the page allocator (physical == CPU address). Always
249 unsafe { 285/// 4 KiB aligned, which satisfies every xHCI ring/context alignment rule.
250 let layout = alloc::alloc::Layout::from_size_align(bytes.max(1), align).unwrap(); 286fn dma_alloc(bytes: usize, _align: usize) -> *mut u8 {
251 let p = alloc::alloc::alloc(layout); 287 let pages = bytes.div_ceil(4096).max(1);
252 if !p.is_null() { 288 match crate::memory::alloc_pages_above(pages, 0x100_0000) {
253 core::ptr::write_bytes(p, 0, bytes.max(1)); 289 Some(p) => {
290 let ptr = p as *mut u8;
291 unsafe { core::ptr::write_bytes(ptr, 0, pages * 4096) };
292 ptr
254 } 293 }
255 p 294 None => core::ptr::null_mut(),
256 } 295 }
257} 296}
258 297
@@ -273,10 +312,8 @@ unsafe fn trb_push(r: &mut Ring, d0: u32, d1: u32, d2: u32, d3: u32) {
273 } 312 }
274} 313}
275 314
276// --------------------------------------------------------------------------- 315/// Drain produced events, calling `handle(d0,d1,d2,d3)` per event, then
277// Event ring: drain produced events, calling handle(d0,d1,d2,d3) per event, 316/// advance ERDP so the controller can reuse the segment entries.
278// then advance ERDP to the consumption point so the controller reuses it.
279// ---------------------------------------------------------------------------
280unsafe fn drain_events(handle: &mut dyn FnMut(u32, u32, u32, u32)) { 317unsafe fn drain_events(handle: &mut dyn FnMut(u32, u32, u32, u32)) {
281 unsafe { 318 unsafe {
282 let ev = &mut *core::ptr::addr_of_mut!(EV); 319 let ev = &mut *core::ptr::addr_of_mut!(EV);
@@ -309,18 +346,16 @@ fn wait_cmd() -> (u32, u32) {
309 let mut slot = 0u32; 346 let mut slot = 0u32;
310 let mut done = false; 347 let mut done = false;
311 let mut seen = 0u32; 348 let mut seen = 0u32;
312 let mut raw: [u32; 4] = [0; 4];
313 for _ in 0..4_000_000 { 349 for _ in 0..4_000_000 {
314 if done { 350 if done {
315 break; 351 break;
316 } 352 }
317 unsafe { 353 unsafe {
318 drain_events(&mut |d0, d1, d2, d3| { 354 drain_events(&mut |_d0, _d1, d2, d3| {
319 seen += 1; 355 seen += 1;
320 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_CMD_COMPLETE { 356 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_CMD_COMPLETE {
321 code = GET_COMP_CODE(d2); 357 code = GET_COMP_CODE(d2);
322 slot = TRB_TO_SLOT_ID(d3); 358 slot = TRB_TO_SLOT_ID(d3);
323 raw = [d0, d1, d2, d3];
324 done = true; 359 done = true;
325 } 360 }
326 }); 361 });
@@ -328,31 +363,20 @@ fn wait_cmd() -> (u32, u32) {
328 core::hint::spin_loop(); 363 core::hint::spin_loop();
329 } 364 }
330 if code != COMP_SUCCESS { 365 if code != COMP_SUCCESS {
331 unsafe { 366 crate::serial_println!(
332 let evidx = core::ptr::addr_of!(EV).cast::<Ring>().read().idx; 367 "usb: cmd-complete code={} slot={} seen={}",
333 crate::serial_println!( 368 code,
334 "usb: cmd-complete code={} slot={} seen={} evidx={}", 369 slot,
335 code, slot, seen, evidx 370 seen
336 ); 371 );
337 for i in 0..4 {
338 let s = EV.raw.add(i * 4);
339 crate::serial_println!(
340 " ev[{i}] {:08x} {:08x} {:08x} {:08x}",
341 s.add(0).read(), s.add(1).read(), s.add(2).read(), s.add(3).read()
342 );
343 }
344 }
345 } 372 }
346 (code, slot) 373 (code, slot)
347} 374}
348 375
349/// Enqueue a command TRB and ring the host command doorbell.
350fn issue_cmd(d3: u32) -> (u32, u32) { 376fn issue_cmd(d3: u32) -> (u32, u32) {
351 issue_cmd_ptr(d3, 0) 377 issue_cmd_ptr(d3, 0)
352} 378}
353 379
354/// Enqueue a command TRB that carries a 64-bit parameter (used by
355/// Address-Device / Configure-Endpoint for the input-context pointer).
356fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) { 380fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) {
357 unsafe { 381 unsafe {
358 trb_push( 382 trb_push(
@@ -368,19 +392,39 @@ fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) {
368 wait_cmd() 392 wait_cmd()
369} 393}
370 394
371/// Wait for a transfer-complete event on the given device-context endpoint id. 395/// Record a transfer event against the device it belongs to. Called for
372fn wait_xfer(epid: u32) -> u32 { 396/// *every* transfer event, including ones drained while waiting for a
373 let mut code = 0u32; 397/// control transfer: dropping them would lose an armed device's report and
398/// stall that device forever.
399fn route_event(d2: u32, d3: u32) {
400 if TRB_FIELD_TO_TYPE(d3) != TRB_EV_TRANSFER {
401 return;
402 }
403 let slot = TRB_TO_SLOT_ID(d3);
404 let epid = TRB_TO_EP_ID(d3);
405 let n = NXDEV.load(Ordering::Relaxed) as usize;
406 for i in 0..n {
407 let d = unsafe { dev_mut(i) };
408 if d.slot == slot && d.dci == epid {
409 d.ready = true;
410 d.code = GET_COMP_CODE(d2);
411 }
412 }
413}
414
415/// Wait for a transfer-complete event on (slot, endpoint id).
416fn wait_xfer(slot: u32, epid: u32) -> u32 {
417 let mut code = u32::MAX;
374 let mut done = false; 418 let mut done = false;
375 let sl = SLOT.load(Ordering::Relaxed);
376 for _ in 0..4_000_000 { 419 for _ in 0..4_000_000 {
377 if done { 420 if done {
378 break; 421 break;
379 } 422 }
380 unsafe { 423 unsafe {
381 drain_events(&mut |_, _, d2, d3| { 424 drain_events(&mut |_d0, _d1, d2, d3| {
425 route_event(d2, d3);
382 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER 426 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER
383 && TRB_TO_SLOT_ID(d3) == sl 427 && TRB_TO_SLOT_ID(d3) == slot
384 && TRB_TO_EP_ID(d3) == epid 428 && TRB_TO_EP_ID(d3) == epid
385 { 429 {
386 code = GET_COMP_CODE(d2); 430 code = GET_COMP_CODE(d2);
@@ -390,14 +434,14 @@ fn wait_xfer(epid: u32) -> u32 {
390 } 434 }
391 core::hint::spin_loop(); 435 core::hint::spin_loop();
392 } 436 }
393 code 437 if !done { 0 } else { code }
394} 438}
395 439
396// --------------------------------------------------------------------------- 440// ---------------------------------------------------------------------------
397// Control transfer on EP0 (setup + optional data + status), per Linux 441// Control transfer on a device's EP0 (setup + optional data + status).
398// xhci_queue_ctrl_tx. Returns OK(len) or the failure completion code.
399// --------------------------------------------------------------------------- 442// ---------------------------------------------------------------------------
400fn ctrl_xfer( 443fn ctrl_xfer(
444 di: usize,
401 bmreq: u8, 445 bmreq: u8,
402 breq: u8, 446 breq: u8,
403 wval: u16, 447 wval: u16,
@@ -406,10 +450,10 @@ fn ctrl_xfer(
406 buflen: u32, 450 buflen: u32,
407 dir_in: bool, 451 dir_in: bool,
408) -> Result<usize, u32> { 452) -> Result<usize, u32> {
409 let slot = SLOT.load(Ordering::Relaxed);
410 let e0 = unsafe { &mut *core::ptr::addr_of_mut!(EP0) };
411 unsafe { 453 unsafe {
412 // Setup stage (immediate data, 8-byte setup packet in d0/d1). 454 let d = dev_mut(di);
455 let slot = d.slot;
456 let e0 = &mut d.ep0;
413 let tx = if buflen > 0 { 457 let tx = if buflen > 0 {
414 TRB_TX_TYPE(if dir_in { TRB_DATA_IN } else { TRB_DATA_OUT }) 458 TRB_TX_TYPE(if dir_in { TRB_DATA_IN } else { TRB_DATA_OUT })
415 } else { 459 } else {
@@ -431,13 +475,12 @@ fn ctrl_xfer(
431 TYPE(TRB_DATA) | if dir_in { TRB_DIR_IN } else { 0 }, 475 TYPE(TRB_DATA) | if dir_in { TRB_DIR_IN } else { 0 },
432 ); 476 );
433 } 477 }
434 // Status stage.
435 let stat_dir = if buflen > 0 && dir_in { 0 } else { TRB_DIR_IN }; 478 let stat_dir = if buflen > 0 && dir_in { 0 } else { TRB_DIR_IN };
436 trb_push(e0, 0, 0, 0, TYPE(TRB_STATUS) | TRB_IOC | stat_dir); 479 trb_push(e0, 0, 0, 0, TYPE(TRB_STATUS) | TRB_IOC | stat_dir);
437 wr(db(slot), 1); // ring EP0 (doorbell target value 1) 480 wr(db(slot), 1);
438 let _ = rd(db(slot)); 481 let _ = rd(db(slot));
439 } 482 }
440 let code = wait_xfer(1); 483 let code = wait_xfer(unsafe { XDEVS[di].slot }, 1);
441 if code == COMP_SUCCESS || code == COMP_SHORT { 484 if code == COMP_SUCCESS || code == COMP_SHORT {
442 Ok(buflen as usize) 485 Ok(buflen as usize)
443 } else { 486 } else {
@@ -446,9 +489,9 @@ fn ctrl_xfer(
446} 489}
447 490
448// --------------------------------------------------------------------------- 491// ---------------------------------------------------------------------------
449// Context construction helpers 492// Context construction
450// --------------------------------------------------------------------------- 493// ---------------------------------------------------------------------------
451fn build_addr_ictx(port_speed: u32, port_num: u32) { 494fn build_addr_ictx(port_speed: u32, port_num: u32, ep0: *mut u32, mps0: u32) {
452 unsafe { 495 unsafe {
453 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs()); 496 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs());
454 let cc = INCTX as *mut u32; 497 let cc = INCTX as *mut u32;
@@ -459,8 +502,8 @@ fn build_addr_ictx(port_speed: u32, port_num: u32) {
459 *sc.add(1) = ROOTPORT(port_num + 1); 502 *sc.add(1) = ROOTPORT(port_num + 1);
460 let e0 = INCTX.add(32 + cs()) as *mut u32; 503 let e0 = INCTX.add(32 + cs()) as *mut u32;
461 *e0.add(0) = 0; 504 *e0.add(0) = 0;
462 *e0.add(1) = EP_TYPE(CTRL_EP) | MAX_PACKET(MPS0) | ERROR_COUNT(3); 505 *e0.add(1) = EP_TYPE(CTRL_EP) | MAX_PACKET(mps0) | ERROR_COUNT(3);
463 (INCTX.add(32 + cs() + 8) as *mut u64).write_volatile((EP0.raw as u64) | 1); 506 (INCTX.add(32 + cs() + 8) as *mut u64).write_volatile((ep0 as u64) | 1);
464 (INCTX.add(32 + cs() + 16) as *mut u32).write_volatile(EP_AVG(8)); 507 (INCTX.add(32 + cs() + 16) as *mut u32).write_volatile(EP_AVG(8));
465 } 508 }
466} 509}
@@ -470,13 +513,11 @@ fn build_addr_ictx(port_speed: u32, port_num: u32) {
470// --------------------------------------------------------------------------- 513// ---------------------------------------------------------------------------
471unsafe fn reset_port(port: u32) -> Result<u32, ()> { 514unsafe fn reset_port(port: u32) -> Result<u32, ()> {
472 unsafe { 515 unsafe {
473 let psc = op(OP_PORTSC) + (port as usize) * 0x10; 516 let psc = op(OP_PORTSC) + (port as usize) * PORT_STRIDE;
474 // Ensure powered.
475 wr(psc, rd(psc) | PORT_POWER); 517 wr(psc, rd(psc) | PORT_POWER);
476 for _ in 0..1000 { 518 for _ in 0..1000 {
477 core::hint::spin_loop(); 519 core::hint::spin_loop();
478 } 520 }
479 // Assert reset, wait for the hardware to clear it (reset done).
480 wr(psc, rd(psc) | PORT_RESET); 521 wr(psc, rd(psc) | PORT_RESET);
481 for _ in 0..4_000_000 { 522 for _ in 0..4_000_000 {
482 if rd(psc) & PORT_RESET == 0 { 523 if rd(psc) & PORT_RESET == 0 {
@@ -484,7 +525,6 @@ unsafe fn reset_port(port: u32) -> Result<u32, ()> {
484 } 525 }
485 core::hint::spin_loop(); 526 core::hint::spin_loop();
486 } 527 }
487 // Wait for the port to be enabled.
488 for _ in 0..4_000_000 { 528 for _ in 0..4_000_000 {
489 if rd(psc) & PORT_PE != 0 { 529 if rd(psc) & PORT_PE != 0 {
490 break; 530 break;
@@ -499,213 +539,253 @@ unsafe fn reset_port(port: u32) -> Result<u32, ()> {
499} 539}
500 540
501// --------------------------------------------------------------------------- 541// ---------------------------------------------------------------------------
502// Enumeration + HID setup 542// Descriptor parsing: first boot HID mouse/keyboard interface
503// --------------------------------------------------------------------------- 543// ---------------------------------------------------------------------------
504fn read_device_descriptor() -> Result<[u8; 18], u32> { 544/// Returns (kind, interface number, ep address, mps, interval).
505 let mut d = [0u8; 18]; 545fn find_hid(cfg: &[u8]) -> Option<(Kind, u8, u8, u32, u32)> {
506 ctrl_xfer(
507 USB_DIR_IN,
508 USB_REQ_GET_DESCRIPTOR,
509 (DT_DEVICE as u16) << 8,
510 0,
511 d.as_mut_ptr(),
512 18,
513 true,
514 )?;
515 Ok(d)
516}
517
518fn read_config_descriptor(buf: &mut [u8]) -> Result<usize, u32> {
519 let total = ctrl_xfer(
520 USB_DIR_IN,
521 USB_REQ_GET_DESCRIPTOR,
522 (DT_CONFIG as u16) << 8,
523 0,
524 buf.as_mut_ptr(),
525 buf.len() as u32,
526 true,
527 )?;
528 Ok(total)
529}
530
531/// Walk a config descriptor tree looking for a HID interrupt IN endpoint
532/// with an interrupt (transfer type 3). Returns (mps, interval, eepid).
533fn find_hid_mouse(cfg: &[u8]) -> Option<(u32, u32, u32)> {
534 let mut i = 0usize; 546 let mut i = 0usize;
535 let mut in_iface = false; 547 let mut cand: Option<(Kind, u8)> = None;
536 while i + 2 <= cfg.len() { 548 while i + 2 <= cfg.len() {
537 let bLen = cfg[i] as usize; 549 let blen = cfg[i] as usize;
538 if bLen == 0 { 550 if blen < 2 {
539 break; 551 break;
540 } 552 }
541 let btype = cfg[i + 1]; 553 let bt = cfg[i + 1];
542 if btype == DT_INTERFACE && i + 9 <= cfg.len() { 554 if bt == DT_INTERFACE && blen >= 9 && i + 9 <= cfg.len() {
543 // interface descriptor: +5 = bInterfaceClass 555 let iface = cfg[i + 2];
544 in_iface = cfg[i + 5] == HID_CLASS; 556 let class = cfg[i + 5];
545 } else if btype == DT_ENDPOINT && in_iface && i + 7 <= cfg.len() { 557 let sub = cfg[i + 6];
546 let addr = cfg[i + 2]; 558 let proto = cfg[i + 7];
547 let attrs = cfg[i + 3]; 559 cand = if class == HID_CLASS && sub == HID_SUBCLASS_BOOT {
548 let dir_in = addr & 0x80 != 0; 560 match proto {
549 let xfer = attrs & 0x03; 561 HID_PROTO_KEYBOARD => Some((Kind::Keyboard, iface)),
550 if dir_in && xfer == 3 { 562 HID_PROTO_MOUSE => Some((Kind::Mouse, iface)),
563 _ => None,
564 }
565 } else {
566 None
567 };
568 } else if bt == DT_ENDPOINT && blen >= 7 && i + 7 <= cfg.len() {
569 if let Some((kind, iface)) = cand {
570 let addr = cfg[i + 2];
571 let attrs = cfg[i + 3];
551 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8); 572 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8);
552 let interval = cfg[i + 6] as u32; 573 let interval = cfg[i + 6] as u32;
553 let epnum = (addr & 0x0f) as u32; 574 if (addr & 0x80) != 0 && (attrs & 0x03) == 0x03 {
554 let euid = 2 * epnum + 1; // device-context index (IN) 575 return Some((kind, iface, addr, mps, interval));
555 return Some((mps, interval, euid)); 576 }
556 } 577 }
557 } 578 }
558 i += bLen; 579 i += blen;
559 } 580 }
560 None 581 None
561} 582}
562 583
563// --------------------------------------------------------------------------- 584// ---------------------------------------------------------------------------
564// Bring one port's device through address + configure to an HID mouse 585// Enumeration: slot + address + descriptors + configure IN endpoint
565// --------------------------------------------------------------------------- 586// ---------------------------------------------------------------------------
566fn enumerate(port: u32, speed: u32) -> bool { 587fn enumerate(port: u32, speed: u32) -> bool {
567 let _ = DEV_SPEED; 588 let idx = NXDEV.load(Ordering::Relaxed) as usize;
568 // 1. Enable slot. 589 if idx >= MAX_XDEV {
569 let (code, slotid) = issue_cmd(TYPE(TRB_ENABLE_SLOT)); 590 return false;
570 if code != COMP_SUCCESS || slotid == 0 { 591 }
571 crate::serial_println!("usb: enable-slot failed code={} slot={}", code, slotid); 592 // Per-device rings + output device context.
593 let ep0_raw = dma_alloc(8 * 16, 64) as *mut u32;
594 let epin_raw = dma_alloc(8 * 16, 64) as *mut u32;
595 let devctx = dma_alloc((1 + 8) * cs(), 64);
596 let report = unsafe { REPORTS.add(idx * 16) };
597 if ep0_raw.is_null() || epin_raw.is_null() || devctx.is_null() || report.is_null() {
598 crate::serial_println!("usb: DMA allocation failed");
599 return false;
600 }
601
602 // Enable slot.
603 let (code, slot) = issue_cmd(TYPE(TRB_ENABLE_SLOT));
604 if code != COMP_SUCCESS || slot == 0 {
605 crate::serial_println!("usb: enable-slot failed code={}", code);
572 return false; 606 return false;
573 } 607 }
574 SLOT.store(slotid, Ordering::Relaxed);
575 unsafe { 608 unsafe {
576 *DCBAA.add(slotid as usize) = DEVCTX as u64; 609 *DCBAA.add(slot as usize) = devctx as u64;
610 let d = dev_mut(idx);
611 d.slot = slot;
612 d.ep0 = Ring {
613 raw: ep0_raw,
614 count: 8,
615 idx: 0,
616 cyc: 1,
617 };
618 d.epin = Ring {
619 raw: epin_raw,
620 count: 8,
621 idx: 0,
622 cyc: 1,
623 };
624 d.devctx = devctx;
625 d.armed = false;
626 d.ready = false;
627 d.code = 0;
628 d.reports = 0;
629 d.failures = 0;
630 d.kbd = crate::hid::Keyboard::new();
577 } 631 }
578 // 2. Address device. 632
579 build_addr_ictx(speed, port); 633 // Address device with EP0 max packet 8 (first transactions are tiny).
580 let (code, _) = issue_cmd_ptr(TYPE(TRB_ADDR_DEV) | SLOT_ID(slotid), unsafe { INCTX } as usize); 634 build_addr_ictx(speed, port, ep0_raw, 8);
635 let (code, _) = issue_cmd_ptr(TYPE(TRB_ADDR_DEV) | SLOT_ID(slot), unsafe {
636 INCTX as usize
637 });
581 if code != COMP_SUCCESS { 638 if code != COMP_SUCCESS {
582 crate::serial_println!("usb: address-device failed code={}", code); 639 crate::serial_println!("usb: address-device failed code={}", code);
583 return false; 640 return false;
584 } 641 }
585 // 3. Read the device descriptor (picks up bMaxPacketSize0). 642
586 match read_device_descriptor() { 643 // Device descriptor.
644 match read_device_descriptor(idx) {
587 Ok(d) => { 645 Ok(d) => {
588 let mps = d[7];
589 if mps != 0 {
590 unsafe {
591 MPS0 = mps as u32;
592 }
593 }
594 crate::serial_println!( 646 crate::serial_println!(
595 "usb: dev {:04x}:{:04x} class={:#x} mps0={}", 647 "usb: dev {:04x}:{:04x} class={:#x} mps0={} slot={}",
596 u16::from_le_bytes([d[8], d[9]]), 648 u16::from_le_bytes([d[8], d[9]]),
597 u16::from_le_bytes([d[10], d[11]]), 649 u16::from_le_bytes([d[10], d[11]]),
598 d[4], 650 d[4],
599 d[7] 651 d[7],
652 slot
600 ); 653 );
601 } 654 }
602 Err(c) => crate::serial_println!("usb: get-device-desc failed code={}", c), 655 Err(c) => {
656 crate::serial_println!("usb: get-device-desc failed code={}", c);
657 return false;
658 }
603 } 659 }
604 // 4. Read the whole config descriptor and find the HID mouse IN ep. 660
605 let mut cfg = [0u8; 256]; 661 // Config descriptor -> HID interface.
606 let got = match read_config_descriptor(&mut cfg) { 662 let mut cfg = [0u8; 512];
663 let got = match read_config_descriptor(idx, &mut cfg) {
607 Ok(n) => n, 664 Ok(n) => n,
608 Err(c) => { 665 Err(c) => {
609 crate::serial_println!("usb: get-config failed code={}", c); 666 crate::serial_println!("usb: get-config failed code={}", c);
610 return false; 667 return false;
611 } 668 }
612 }; 669 };
613 let _ = got; 670 let Some((kind, iface, _epaddr, mps, interval)) = find_hid(&cfg[..got]) else {
614 match find_hid_mouse(&cfg) { 671 crate::serial_println!("usb: no HID mouse/keyboard endpoint in config");
615 Some((mps, interval, euid)) => { 672 return false;
616 crate::serial_println!( 673 };
617 "usb: HID mouse IN ep mps={} interval={} euid={}", 674 let cfgval = cfg[5];
618 mps, 675 if cfgval == 0 {
619 interval, 676 return false;
620 euid
621 );
622 // 5. Set configuration = 1 (first config value present).
623 let cfgval = cfg[5];
624 if cfgval == 0 {
625 return false;
626 }
627 if read_configuration(cfgval).is_err() {
628 return false;
629 }
630 // 6. Configure the IN endpoint.
631 if !configure_hid_endpoint(euid, mps, interval) {
632 return false;
633 }
634 HID_EPID.store(euid, Ordering::Relaxed);
635 unsafe {
636 HID_LEN = mps as usize;
637 QUEUED = false;
638 }
639 // 7. Arm the first IN read.
640 queue_report();
641 MOUSEPRESENT.store(true, Ordering::Relaxed);
642 crate::serial_println!(
643 "usb: HID boot mouse armed (slot {})",
644 SLOT.load(Ordering::Relaxed)
645 );
646 true
647 }
648 None => {
649 crate::serial_println!("usb: no HID mouse endpoint in config");
650 false
651 }
652 } 677 }
678 if ctrl_xfer(idx, 0x00, USB_REQ_SET_CONFIGURATION, cfgval as u16, 0, core::ptr::null_mut(), 0, false)
679 .is_err()
680 {
681 return false;
682 }
683 // Boot protocol: fixed 3-byte mouse / 8-byte keyboard reports.
684 if ctrl_xfer(idx, 0x21, REQ_SET_PROTOCOL, 0, iface as u16, core::ptr::null_mut(), 0, false)
685 .is_err()
686 {
687 crate::serial_println!("usb: set-protocol(boot) stalled (continuing)");
688 }
689 let _ = ctrl_xfer(idx, 0x21, REQ_SET_IDLE, 0, iface as u16, core::ptr::null_mut(), 0, false);
690
691 if !configure_hid_endpoint(idx, _epaddr, mps, interval) {
692 return false;
693 }
694 unsafe {
695 let d = dev_mut(idx);
696 d.kind = kind;
697 d.len = mps.min(8).max(1);
698 d.armed = true;
699 }
700 NXDEV.fetch_add(1, Ordering::Relaxed);
701 queue_report(idx);
702 crate::serial_println!(
703 "usb: HID {} armed (slot {} mps {} interval {})",
704 if kind == Kind::Mouse { "mouse" } else { "keyboard" },
705 slot,
706 mps,
707 interval
708 );
709 true
653} 710}
654 711
655fn read_configuration(val: u8) -> Result<(), u32> { 712fn read_device_descriptor(di: usize) -> Result<[u8; 18], u32> {
713 let mut d = [0u8; 18];
714 let b = d.as_mut_ptr();
656 ctrl_xfer( 715 ctrl_xfer(
716 di,
717 0x80,
718 USB_REQ_GET_DESCRIPTOR,
719 (DT_DEVICE as u16) << 8,
657 0, 720 0,
658 USB_REQ_SET_CONFIGURATION, 721 b,
659 val.into(), 722 18,
660 0, 723 true,
661 core::ptr::null_mut(), 724 )
725 .map(|_| d)
726}
727
728fn read_config_descriptor(di: usize, buf: &mut [u8; 512]) -> Result<usize, u32> {
729 ctrl_xfer(
730 di,
731 0x80,
732 USB_REQ_GET_DESCRIPTOR,
733 (DT_CONFIG as u16) << 8,
662 0, 734 0,
663 false, 735 buf.as_mut_ptr(),
736 buf.len() as u32,
737 true,
664 ) 738 )
665 .map(|_| ())
666} 739}
667 740
668fn configure_hid_endpoint(euid: u32, mps: u32, interval: u32) -> bool { 741/// Commit the interrupt-IN endpoint context and ring its doorbell.
742fn configure_hid_endpoint(di: usize, epaddr: u8, mps: u32, interval: u32) -> bool {
669 unsafe { 743 unsafe {
670 let slot = SLOT.load(Ordering::Relaxed); 744 let d = dev_mut(di);
671 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs()); 745 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs());
672 let cc = INCTX as *mut u32; 746 let cc = INCTX as *mut u32;
673 *cc.add(0) = 0; // Drop flags 747 // Add Context flags are DCI bits: bit0 = slot, bit(2*ep+1) = IN ep.
674 *cc.add(1) = (1 << 0) | (1 << euid); // Add slot + this EP 748 // Endpoint DCI for an IN endpoint is 2 * ep_number + 1 (EP1 IN -> 3).
675 // Input slot context: copy the output (post-address) slot context so 749 let euid = ((epaddr & 0x0f) as u32) * 2 + 1;
676 // route/speed/rootport are preserved, then bump LAST_CTX to the ep. 750 *cc.add(0) = 0;
677 let osc = DEVCTX.offset(0).cast::<u32>(); 751 *cc.add(1) = (1 << 0) | (1 << euid);
752 d.dci = euid;
753 let osc = d.devctx.cast::<u32>();
678 let sc = INCTX.offset(32).cast::<u32>(); 754 let sc = INCTX.offset(32).cast::<u32>();
679 *sc.add(0) = (*osc.add(0) & !LAST_CTX(31)) | LAST_CTX(euid); 755 *sc.add(0) = (*osc.add(0) & !LAST_CTX(31)) | LAST_CTX(euid);
680 *sc.add(1) = *osc.add(1); 756 *sc.add(1) = *osc.add(1);
681 // Endpoint IN context.
682 let ep = INCTX.add(32 + (euid as usize) * cs()).cast::<u32>(); 757 let ep = INCTX.add(32 + (euid as usize) * cs()).cast::<u32>();
683 *ep.add(0) = EP_INTERVAL(interval); 758 *ep.add(0) = EP_INTERVAL(interval);
684 *ep.add(1) = EP_TYPE(INT_IN_EP) | MAX_PACKET(mps) | ERROR_COUNT(3); 759 *ep.add(1) = EP_TYPE(INT_IN_EP) | MAX_PACKET(mps) | ERROR_COUNT(3);
685 *ep.add(4) = EP_AVG(mps); 760 *ep.add(4) = EP_AVG(mps);
686 let deq = EPIN.raw as u64 | 1; 761 let deq = d.epin.raw as u64 | 1;
687 *ep.offset(2).cast::<u64>() = deq; 762 *ep.offset(2).cast::<u64>() = deq;
763 let slot = d.slot;
688 let (code, _) = issue_cmd_ptr(TYPE(TRB_CONFIG_EP) | SLOT_ID(slot), INCTX as usize); 764 let (code, _) = issue_cmd_ptr(TYPE(TRB_CONFIG_EP) | SLOT_ID(slot), INCTX as usize);
689 code == COMP_SUCCESS 765 if code != COMP_SUCCESS {
766 crate::serial_println!("usb: configure-endpoint failed code={}", code);
767 return false;
768 }
769 true
690 } 770 }
691} 771}
692 772
693fn queue_report() { 773/// Arm one interrupt-IN transfer on the device's endpoint ring.
694 let slot = SLOT.load(Ordering::Relaxed); 774fn queue_report(di: usize) {
695 let epid = HID_EPID.load(Ordering::Relaxed);
696 let epin = unsafe { &mut *core::ptr::addr_of_mut!(EPIN) };
697 unsafe { 775 unsafe {
698 let b = core::ptr::addr_of_mut!(HID_BUF) as *mut u8; 776 let d = dev_mut(di);
777 let slot = d.slot;
778 let epid = d.dci;
699 trb_push( 779 trb_push(
700 epin, 780 &mut d.epin,
701 b as u32, 781 REPORTS.add(di * 16) as u32,
702 (b as usize >> 32) as u32, 782 (REPORTS.add(di * 16) as usize >> 32) as u32,
703 TRB_LEN(HID_LEN as u32), 783 TRB_LEN(d.len),
704 TYPE(TRB_NORMAL) | TRB_IOC, 784 TYPE(TRB_NORMAL) | TRB_IOC,
705 ); 785 );
786 d.ready = false;
706 wr(db(slot), epid); 787 wr(db(slot), epid);
707 let _ = rd(db(slot)); 788 let _ = rd(db(slot));
708 QUEUED = true;
709 } 789 }
710} 790}
711 791
@@ -728,7 +808,6 @@ pub fn init() -> bool {
728 }; 808 };
729 crate::serial_println!("usb: xHCI at {:02x}:{:02x}.{}", b, dev, func); 809 crate::serial_println!("usb: xHCI at {:02x}:{:02x}.{}", b, dev, func);
730 810
731 // Enable MMIO + bus-mastering on the PCI command register.
732 unsafe { 811 unsafe {
733 let cmd = crate::pci::config_read(b, dev, func, 0x04); 812 let cmd = crate::pci::config_read(b, dev, func, 0x04);
734 crate::pci::config_write( 813 crate::pci::config_write(
@@ -736,48 +815,55 @@ pub fn init() -> bool {
736 dev, 815 dev,
737 func, 816 func,
738 0x04, 817 0x04,
739 // Memory space + bus master + INTx disable (bit 10). The
740 // driver is polled (event ring drained from the cursor task),
741 // so the controller must never assert INTx: an unhandled
742 // xHCI interrupt would hit the kernel's catch-all handler
743 // and (being level-triggered) re-fire forever on real
744 // hardware.
745 (cmd & 0xFFFF_0000) | ((cmd & 0xFFFF) | 0x6 | 0x400), 818 (cmd & 0xFFFF_0000) | ((cmd & 0xFFFF) | 0x6 | 0x400),
746 ); 819 );
747 let bar = crate::pci::bar64(b, dev, func, 0); 820 let bar = crate::pci::bar64(b, dev, func, 0);
748 let base = bar.unwrap() as usize; 821 let base = bar.unwrap_or(0) as usize;
822 if base == 0 {
823 crate::serial_println!("usb: bad BAR");
824 return false;
825 }
749 M = base; 826 M = base;
750 let caplen = rd(base + CAP_CAPLENGTH); 827 let caplen = rd(base + CAP_CAPLENGTH);
751 let hcs1 = rd(base + HCSPARAMS1); 828 let hcs1 = rd(base + HCSPARAMS1);
829 let hcs2 = rd(base + HCSPARAMS2);
752 let hcc = rd(base + HCCPARAMS1); 830 let hcc = rd(base + HCCPARAMS1);
753 crate::serial_println!(
754 "usb: mmio={:#x} caplen={:#x} hcs1={:#x} hcc1={:#x}",
755 base, caplen, hcs1, hcc
756 );
757 OPB = base + (caplen & 0xff) as usize; 831 OPB = base + (caplen & 0xff) as usize;
758 DBB = base + rd(base + DBOFF) as usize; 832 DBB = base + rd(base + DBOFF) as usize;
759 RTB = base + rd(base + RTSOFF) as usize; 833 RTB = base + rd(base + RTSOFF) as usize;
760 MAXPORTS = (hcs1 >> 24) & 0xff; // MaxPorts is bits 31:24 (bits 23:16 are MaxIntrs) 834 MAXPORTS = (hcs1 >> 24) & 0xff;
761 let _ = hcc; 835 N_SLOTS = (hcs1 & 0xff).clamp(1, MAX_XDEV as u32);
762 CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 }; 836 CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 };
837 // HCSPARAMS2: Max Scratchpad Buffers = Hi[31:27] << 5 | Lo[25:21].
838 NUM_SPB = (((hcs2 >> 27) & 0x1f) << 5) | ((hcs2 >> 21) & 0x1f);
839 crate::serial_println!(
840 "usb: mmio={:#x} caplen={:#x} hcs1={:#x} hcs2={:#x} hcc1={:#x}",
841 base,
842 caplen,
843 hcs1,
844 hcs2,
845 hcc
846 );
847 let (maxports, nslots, csz, nspb) = (MAXPORTS, N_SLOTS, CTRL_CS, NUM_SPB);
848 crate::serial_println!(
849 "usb: ports={} slots={} context={}B scratchpad={}",
850 maxports,
851 nslots,
852 csz,
853 nspb
854 );
763 855
764 // Claim USB Legacy ownership from the BIOS before resetting the
765 // controller (same SMM-legacy rationale as the EHCI driver).
766 match crate::pci::claim_usb_legacy(b, dev, func, base, 0x04, true) { 856 match crate::pci::claim_usb_legacy(b, dev, func, base, 0x04, true) {
767 0 => crate::serial_println!("usb: xHCI USB legacy: already released (no claim needed)"), 857 0 => crate::serial_println!("usb: xHCI USB legacy: already released"),
768 1 => crate::serial_println!("usb: xHCI USB legacy handed over by BIOS"), 858 1 => crate::serial_println!("usb: xHCI USB legacy handed over by BIOS"),
769 2 => crate::serial_println!("usb: xHCI USB legacy claim timed out (BIOS sem stuck)"), 859 2 => crate::serial_println!("usb: xHCI USB legacy claim timed out"),
770 _ => crate::serial_println!("usb: xHCI USB legacy: no capability found"), 860 _ => crate::serial_println!("usb: xHCI USB legacy: no capability"),
771 } 861 }
772 } 862 }
773 863
774 // Reset the controller (stop, then reset). Wait for reset completion 864 // Reset: stop, then RESET, then wait for CNR to clear.
775 // on the CMD_RESET bit clearing (per Linux xhci_reset), then for the
776 // Controller-Not-Ready flag to clear before any doorbell/op-register
777 // activity.
778 unsafe { 865 unsafe {
779 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) & !CMD_RUN); 866 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) & !CMD_RUN);
780 // Wait for halt.
781 let mut t = 0; 867 let mut t = 0;
782 while t < 1_000_000 && rd(op(OP_USBSTS)) & STS_HALT == 0 { 868 while t < 1_000_000 && rd(op(OP_USBSTS)) & STS_HALT == 0 {
783 t += 1; 869 t += 1;
@@ -796,42 +882,80 @@ pub fn init() -> bool {
796 } 882 }
797 } 883 }
798 884
799 // Allocate rings / contexts. 885 // Rings, contexts, scratchpads.
800 unsafe { 886 unsafe {
801 CMD.raw = dma_alloc(64 * 16, 64) as *mut u32; 887 let cmd_ring = dma_alloc(64 * 16, 64) as *mut u32;
802 EV.raw = dma_alloc(32 * 16, 64) as *mut u32; 888 let ev_ring = dma_alloc(32 * 16, 64) as *mut u32;
803 EP0.raw = dma_alloc(8 * 16, 64) as *mut u32; 889 let erst = dma_alloc(16, 64);
804 EPIN.raw = dma_alloc(8 * 16, 64) as *mut u32; 890 let dcb = dma_alloc((N_SLOTS as usize + 1) * 8, 64) as *mut u64;
805 ERST = dma_alloc(16, 64); 891 let ictx = dma_alloc(32 + 8 * cs(), 64);
806 DCBAA = dma_alloc(64 * 8, 64) as *mut u64; 892 let reports = dma_alloc(4096, 64);
807 DEVCTX = dma_alloc((1 + 8) * cs(), 64); 893 if cmd_ring.is_null()
808 INCTX = dma_alloc(32 + 8 * cs(), 64); 894 || ev_ring.is_null()
809 // ERST segment 0 -> event ring. 895 || erst.is_null()
896 || dcb.is_null()
897 || ictx.is_null()
898 || reports.is_null()
899 {
900 crate::serial_println!("usb: DMA allocation failed");
901 return false;
902 }
903 CMD = Ring {
904 raw: cmd_ring,
905 count: 64,
906 idx: 0,
907 cyc: 1,
908 };
909 EV = Ring {
910 raw: ev_ring,
911 count: 32,
912 idx: 0,
913 cyc: 1,
914 };
915 ERST = erst;
916 DCBAA = dcb;
917 INCTX = ictx;
918 REPORTS = reports;
919
920 // Scratchpad buffers (§4.20): a 64-byte array of buffer addresses
921 // whose entries the controller requires to exist when HCSPARAMS2
922 // reports them. DCBAAP[0] must point at that array.
923 if NUM_SPB > 0 {
924 let arr = dma_alloc(NUM_SPB as usize * 8 + 64, 64) as *mut u64;
925 if !arr.is_null() {
926 for i in 0..NUM_SPB as usize {
927 let p = dma_alloc(4096, 64);
928 if p.is_null() {
929 crate::serial_println!("usb: scratchpad buffer {} failed", i);
930 return false;
931 }
932 arr.add(i).write_volatile(p as u64);
933 }
934 SPB_ARRAY = arr;
935 *DCBAA.add(0) = arr as u64;
936 let nspb = NUM_SPB;
937 crate::serial_println!("usb: allocated {} scratchpad buffer(s)", nspb);
938 }
939 }
940
810 let e = ERST as *mut u32; 941 let e = ERST as *mut u32;
811 *e.add(0) = EV.raw as u32; 942 *e.add(0) = ev_ring as u32;
812 *e.add(1) = (EV.raw as usize >> 32) as u32; 943 *e.add(1) = (ev_ring as usize >> 32) as u32;
813 *e.add(2) = 32; // segment size (TRBs) 944 *e.add(2) = 32;
814 } 945 }
815 946
816 // Program registers.
817 unsafe { 947 unsafe {
818 wr64(op(OP_DCBAAP), DCBAA as u64); 948 wr64(op(OP_DCBAAP), DCBAA as u64);
819 wr64( 949 wr64(
820 op(OP_CRCR), 950 op(OP_CRCR),
821 ((CMD.raw as u64) & !0x3f) | CMD_RING_CYCLE as u64, 951 ((CMD.raw as u64) & !0x3f) | CMD_RING_CYCLE as u64,
822 ); 952 );
823 // MaxSlotsEn = 1 (we use a single slot). 953 wr(op(OP_CONFIG), (rd(op(OP_CONFIG)) & !0xff) | N_SLOTS);
824 wr(op(OP_CONFIG), (rd(op(OP_CONFIG)) & !0xff) | 1);
825 // Event ring.
826 wr(rt(RT_ERSTSZ), 1); 954 wr(rt(RT_ERSTSZ), 1);
827 wr64(rt(RT_ERSTBA), ERST as u64); 955 wr64(rt(RT_ERSTBA), ERST as u64);
828 wr64(rt(RT_ERDP), EV.raw as u64); 956 wr64(rt(RT_ERDP), EV.raw as u64);
829 // Interrupter 0: keep the interrupt DISABLED (IE=0, clear any
830 // pending IP) — the driver is polled and the controller must not
831 // assert INTx (see the PCI command-register comment above).
832 wr(rt(RT_IMAN), (rd(rt(RT_IMAN)) & !0x2) | 0x1); 957 wr(rt(RT_IMAN), (rd(rt(RT_IMAN)) & !0x2) | 0x1);
833 wr(rt(RT_IMOD), 0); 958 wr(rt(RT_IMOD), 0);
834 // Run.
835 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN); 959 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN);
836 let mut t = 0; 960 let mut t = 0;
837 while t < 4_000_000 && rd(op(OP_USBSTS)) & STS_HALT != 0 { 961 while t < 4_000_000 && rd(op(OP_USBSTS)) & STS_HALT != 0 {
@@ -845,36 +969,38 @@ pub fn init() -> bool {
845 } 969 }
846 INITOK.store(true, Ordering::Relaxed); 970 INITOK.store(true, Ordering::Relaxed);
847 crate::serial_println!( 971 crate::serial_println!(
848 "usb: controller up ({} ports) — mouse discovery deferred to cursor task", 972 "usb: controller up ({} ports) — HID discovery deferred to polling",
849 unsafe { MAXPORTS } 973 unsafe { MAXPORTS }
850 ); 974 );
851 true 975 true
852} 976}
853 977
854/// Bitmask of root ports whose attempt already failed; skip them so we don't 978/// Scan ports until all slots are used or every port has been tried.
855/// re-reset the port on every poll.
856static DISCOVERED_TRIED: AtomicU32 = AtomicU32::new(0);
857
858/// Periodic, cheap scan called from the cursor task until a HID mouse is
859/// found. A USB device can finish connecting long after the controller
860/// starts, so discovery is driven asynchronously rather than blocking boot.
861fn maybe_discover() { 979fn maybe_discover() {
862 if MOUSEPRESENT.load(Ordering::Relaxed) { 980 if !INITOK.load(Ordering::Relaxed) {
863 return; 981 return;
864 } 982 }
983 if NXDEV.load(Ordering::Relaxed) as usize >= MAX_XDEV {
984 return;
985 }
986 let now = crate::interrupts::uptime_ms() as u32;
987 if now.wrapping_sub(LAST_SCAN.load(Ordering::Relaxed)) < 200 {
988 return;
989 }
990 LAST_SCAN.store(now, Ordering::Relaxed);
865 let maxports = unsafe { MAXPORTS }; 991 let maxports = unsafe { MAXPORTS };
866 for p in 0..maxports { 992 for p in 0..maxports {
867 if DISCOVERED_TRIED.load(Ordering::Relaxed) & (1 << p) != 0 { 993 if DISCOVERED_TRIED.load(Ordering::Relaxed) & (1 << p) != 0 {
868 continue; 994 continue;
869 } 995 }
870 unsafe { 996 unsafe {
871 let psc = op(OP_PORTSC) + (p as usize) * 0x10; 997 let psc = op(OP_PORTSC) + (p as usize) * PORT_STRIDE;
872 // Only touch ports that are both connected and not yet enabled.
873 if rd(psc) & (PORT_CONNECT | PORT_PE) == PORT_CONNECT { 998 if rd(psc) & (PORT_CONNECT | PORT_PE) == PORT_CONNECT {
874 crate::serial_println!("usb: port {} connect (speed {})", p, DEV_SPEED(rd(psc))); 999 crate::serial_println!("usb: port {} connect (speed {})", p, DEV_SPEED(rd(psc)));
875 if let Ok(speed) = reset_port(p) 1000 if let Ok(speed) = reset_port(p) {
876 && enumerate(p, speed) { 1001 if enumerate(p, speed) {
877 return; 1002 continue; // keep scanning for the other HID device
1003 }
878 } 1004 }
879 DISCOVERED_TRIED.fetch_or(1 << p, Ordering::Relaxed); 1005 DISCOVERED_TRIED.fetch_or(1 << p, Ordering::Relaxed);
880 } 1006 }
@@ -882,237 +1008,249 @@ fn maybe_discover() {
882 } 1008 }
883} 1009}
884 1010
1011fn has_kind(kind: Kind) -> bool {
1012 let n = NXDEV.load(Ordering::Relaxed) as usize;
1013 for i in 0..n {
1014 if unsafe { XDEVS[i].kind } == kind {
1015 return true;
1016 }
1017 }
1018 false
1019}
1020
885pub fn mouse_present() -> bool { 1021pub fn mouse_present() -> bool {
886 MOUSEPRESENT.load(Ordering::Relaxed) 1022 has_kind(Kind::Mouse)
887} 1023}
888 1024
889/// True once the xHCI controller is brought up (regardless of whether a
890/// device has attached yet). The cursor task is spawned as long as this is
891/// set so async mouse discovery can run.
892pub fn controller_up() -> bool { 1025pub fn controller_up() -> bool {
893 INITOK.load(Ordering::Relaxed) 1026 INITOK.load(Ordering::Relaxed)
894} 1027}
895 1028
896/// Reset discovery state (used by the `usb` shell command to rescan).
897pub fn rescan() { 1029pub fn rescan() {
898 DISCOVERED_TRIED.store(0, Ordering::Relaxed); 1030 DISCOVERED_TRIED.store(0, Ordering::Relaxed);
1031 LAST_SCAN.store(0, Ordering::Relaxed);
1032}
1033
1034/// Drain completion events into the device table.
1035fn pump_reports() {
1036 unsafe {
1037 drain_events(&mut |_d0, _d1, d2, d3| route_event(d2, d3));
1038 }
899} 1039}
900 1040
901/// Non-blocking poll: drain the event ring, then decode one mouse report if
902/// one arrived (re-arming the IN transfer so reports keep flowing).
903/// Returns `None` when no report is pending (or no mouse).
904pub fn poll_mouse() -> Option<MouseEvent> { 1041pub fn poll_mouse() -> Option<MouseEvent> {
905 // Drive discovery while no device is attached, then drain reports. 1042 if !INITOK.load(Ordering::Relaxed) {
906 if !MOUSEPRESENT.load(Ordering::Relaxed) { 1043 return None;
907 if INITOK.load(Ordering::Relaxed) { 1044 }
908 maybe_discover(); 1045 if !has_kind(Kind::Mouse) {
1046 maybe_discover();
1047 }
1048 pump_reports();
1049 let n = NXDEV.load(Ordering::Relaxed) as usize;
1050 for i in 0..n {
1051 let d = unsafe { dev_mut(i) };
1052 if d.kind != Kind::Mouse || !d.armed || !d.ready {
1053 continue;
1054 }
1055 if d.code != COMP_SUCCESS && d.code != COMP_SHORT {
1056 d.armed = false;
1057 d.failures += 1;
1058 d.ready = false;
1059 continue;
1060 }
1061 let hb = unsafe { REPORTS.add(i * 16) };
1062 let (b0, b1, b2) = unsafe {
1063 (
1064 core::ptr::read_volatile(hb),
1065 core::ptr::read_volatile(hb.add(1)),
1066 core::ptr::read_volatile(hb.add(2)),
1067 )
1068 };
1069 let ev = MouseEvent {
1070 dx: (b1 as i8) as i16,
1071 dy: (b2 as i8) as i16,
1072 left: b0 & 0x01 != 0,
1073 right: b0 & 0x02 != 0,
1074 middle: b0 & 0x04 != 0,
1075 };
1076 d.reports += 1;
1077 if d.reports == 1 {
1078 crate::serial_println!("usb: mouse report dx={} dy={} b={:#x}", ev.dx, ev.dy, b0);
909 } 1079 }
1080 queue_report(i);
1081 return Some(ev);
1082 }
1083 None
1084}
1085
1086pub fn try_poll_key() -> Option<crate::ps2::KeyEvent> {
1087 if !INITOK.load(Ordering::Relaxed) {
910 return None; 1088 return None;
911 } 1089 }
912 let epid = HID_EPID.load(Ordering::Relaxed); 1090 if !has_kind(Kind::Keyboard) {
913 let sl = SLOT.load(Ordering::Relaxed); 1091 maybe_discover();
914 unsafe { 1092 }
915 drain_events(&mut |_, _, d2, d3| { 1093 if let Some(b) = crate::hid::pop_byte() {
916 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER 1094 return crate::ps2::feed_scancode(b);
917 && TRB_TO_SLOT_ID(d3) == sl 1095 }
918 && TRB_TO_EP_ID(d3) == epid 1096 pump_reports();
919 { 1097 let n = NXDEV.load(Ordering::Relaxed) as usize;
920 let c = GET_COMP_CODE(d2); 1098 for i in 0..n {
921 if c == COMP_SUCCESS || c == COMP_SHORT { 1099 let d = unsafe { dev_mut(i) };
922 // A fresh report sits in HID_BUF. 1100 if d.kind != Kind::Keyboard || !d.armed || !d.ready {
923 let p = core::ptr::addr_of!(HID_BUF) as *const u8; 1101 continue;
924 RBTN = core::ptr::read_volatile(p); 1102 }
925 RDX = core::ptr::read_volatile(p.add(1)) as i8; 1103 if d.code != COMP_SUCCESS && d.code != COMP_SHORT {
926 RDY = core::ptr::read_volatile(p.add(2)) as i8; 1104 d.armed = false;
927 GOT_REPORT = true; 1105 d.failures += 1;
928 QUEUED = false; 1106 d.ready = false;
929 } 1107 continue;
930 }
931 });
932 if GOT_REPORT {
933 GOT_REPORT = false;
934 let ev = Some(MouseEvent {
935 dx: RDX as i16,
936 dy: RDY as i16,
937 left: RBTN & 0x01 != 0,
938 right: RBTN & 0x02 != 0,
939 middle: RBTN & 0x04 != 0,
940 });
941 queue_report(); // re-arm for the next report
942 return ev;
943 } 1108 }
944 if !QUEUED { 1109 let hb = unsafe { REPORTS.add(i * 16) };
945 queue_report(); // keep exactly one IN read outstanding 1110 let mut rep = [0u8; 8];
1111 let len = (d.len as usize).min(8);
1112 for (k, b) in rep.iter_mut().take(len).enumerate() {
1113 *b = unsafe { core::ptr::read_volatile(hb.add(k)) };
946 } 1114 }
1115 d.kbd.diff(&rep);
1116 d.reports += 1;
1117 if d.reports == 1 {
1118 crate::serial_println!(
1119 "usb: kbd report mods={:#x} keys={:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
1120 rep[0], rep[2], rep[3], rep[4], rep[5], rep[6], rep[7]
1121 );
1122 }
1123 queue_report(i);
1124 return crate::hid::pop_byte().and_then(crate::ps2::feed_scancode);
947 } 1125 }
948 None 1126 None
949} 1127}
950 1128
951/// One-line status for the shell's `usb` command. 1129// ---------------------------------------------------------------------------
1130// Diagnostics (shell `usb`)
1131// ---------------------------------------------------------------------------
952pub fn diagnose() -> alloc::string::String { 1132pub fn diagnose() -> alloc::string::String {
953 let mut s = alloc::string::String::new(); 1133 let mut s = alloc::string::String::new();
954 s.push_str(&alloc::format!( 1134 s.push_str(&alloc::format!(
955 "xhci mmio={:#x} mapped={} ports={} mouse={} slot={}\n", 1135 "xhci mmio={:#x} mapped={} ports={} slots={} scratchpad={}\n",
956 unsafe { M }, 1136 unsafe { M },
957 if crate::paging::is_mapped(unsafe { M }) { "yes" } else { "no" }, 1137 if crate::paging::is_mapped(unsafe { M }) {
958 unsafe { MAXPORTS },
959 if MOUSEPRESENT.load(Ordering::Relaxed) {
960 "yes" 1138 "yes"
961 } else { 1139 } else {
962 "no" 1140 "no"
963 }, 1141 },
964 SLOT.load(Ordering::Relaxed) 1142 unsafe { MAXPORTS },
1143 unsafe { N_SLOTS },
1144 unsafe { NUM_SPB }
965 )); 1145 ));
966 unsafe { 1146 let n = NXDEV.load(Ordering::Relaxed) as usize;
967 // Page-table walk for the MMIO base (physical == virtual here). 1147 s.push_str(&alloc::format!(" {} device(s):\n", n));
968 let phys = M; 1148 for i in 0..n {
969 let pml4 = crate::paging::cr3() as *const u64; 1149 let d = unsafe { &*core::ptr::addr_of!(XDEVS[i]) };
970 let l4 = (phys >> 39) & 0x1FF;
971 let l3 = (phys >> 30) & 0x1FF;
972 let l2 = (phys >> 21) & 0x1FF;
973 let l1 = (phys >> 12) & 0x1FF;
974 let pml4e = pml4.add(l4).read_volatile();
975 let pdpte = if pml4e & 1 != 0 {
976 ((pml4e & 0x000f_ffff_ffff_f000) as *const u64).add(l3).read_volatile()
977 } else { 0 };
978 let pde = if pdpte & 1 != 0 && pdpte & (1 << 7) == 0 {
979 ((pdpte & 0x000f_ffff_ffff_f000) as *const u64).add(l2).read_volatile()
980 } else { 0 };
981 let pte = if pde & 1 != 0 && pde & (1 << 7) == 0 {
982 ((pde & 0x000f_ffff_ffff_f000) as *const u64).add(l1).read_volatile()
983 } else { 0 };
984 s.push_str(&alloc::format!( 1150 s.push_str(&alloc::format!(
985 " pt: pml4e={:#018x} pdpte={:#018x} pde={:#018x} pte={:#018x}\n", 1151 " {} slot={} dci={} armed={} fails={} reports={} ring={:#x}\n",
986 pml4e, pdpte, pde, pte 1152 if d.kind == Kind::Mouse { "mouse" } else { "kbd" },
1153 d.slot,
1154 d.dci,
1155 d.armed,
1156 d.failures,
1157 d.reports,
1158 d.epin.raw as usize
987 )); 1159 ));
1160 }
1161 unsafe {
988 for p in 0..8 { 1162 for p in 0..8 {
989 let psc = op(OP_PORTSC) + (p as usize) * 0x10; 1163 let psc = op(OP_PORTSC) + (p as usize) * PORT_STRIDE;
990 let v = rd(psc); 1164 let v = rd(psc);
991 s.push_str(&alloc::format!(" port{}: {:#010x}\n", p, v)); 1165 s.push_str(&alloc::format!(" port{}: {:#010x}\n", p, v));
992 } 1166 }
993 } 1167 }
994 if MOUSEPRESENT.load(Ordering::Relaxed) {
995 s.push_str(&alloc::format!(
996 " hid ep-index={} mps={} armed={}\n",
997 HID_EPID.load(Ordering::Relaxed),
998 unsafe { HID_LEN },
999 unsafe { QUEUED }
1000 ));
1001 }
1002 1168
1003 // Cross-controller attach scan: report which PCI USB controller(s) 1169 // Cross-controller attach scan (read-only): locate devices on real hw.
1004 // currently have a device with bit0 (connect) set, across xHCI / EHCI /
1005 // UHCI / OHCI. Read-only diagnostic to locate the mouse on real hw.
1006 s.push_str("usbctl: scanning all USB controllers:\n"); 1170 s.push_str("usbctl: scanning all USB controllers:\n");
1007 unsafe { 1171 unsafe {
1008 for d in crate::pci::probe() { 1172 for d in crate::pci::probe() {
1009 if d.class != 0x0C || d.subclass != 0x03 { 1173 if d.class != 0x0C || d.subclass != 0x03 {
1010 continue; 1174 continue;
1011 }
1012 let lbl = match d.prog_if {
1013 0x00 => "UHCI",
1014 0x10 => "OHCI",
1015 0x20 => "EHCI",
1016 0x30 => "xHCI",
1017 _ => "USB?",
1018 };
1019 let bar = crate::pci::bar64(d.bus, d.device, d.func, 0);
1020 let base = bar.unwrap_or(0);
1021 s.push_str(&alloc::format!(
1022 " {} {:02x}:{:02x}.{} bar={:#x}\n",
1023 lbl, d.bus, d.device, d.func, base
1024 ));
1025 if base == 0 || base == u64::MAX {
1026 // BAR not decoded / not enabled — nothing to probe.
1027 continue;
1028 }
1029 match d.prog_if {
1030 0x30 => {
1031 if base == 0 {
1032 continue;
1033 }
1034 let caplen = rd(base as usize);
1035 let hcs1 = rd(base as usize + 0x04);
1036 let np = ((hcs1 >> 24) & 0xff) as usize;
1037 let opb = base as usize + (caplen & 0xff) as usize;
1038 s.push_str(&alloc::format!(
1039 " xhci ports={} sc=", np
1040 ));
1041 for p in 0..np.min(16) {
1042 let v = rd(opb + 0x400 + p * 0x10);
1043 s.push_str(&alloc::format!(
1044 "{}{:#x}",
1045 if v & 1 != 0 { "*" } else { "" },
1046 v & 0x3ff
1047 ));
1048 if p + 1 < (np.min(16)) {
1049 s.push(' ');
1050 }
1051 }
1052 s.push('\n');
1053 } 1175 }
1054 0x20 => { 1176 let lbl = match d.prog_if {
1055 // EHCI: MMIO BAR. CAPLENGTH byte0, HcSParams (nports) @0x04. 1177 0x00 => "UHCI",
1056 let cl = rd(base as usize) & 0xff; 1178 0x10 => "OHCI",
1057 let sp = rd(base as usize + 0x04); 1179 0x20 => "EHCI",
1058 let hc = rd(base as usize + 0x08); 1180 0x30 => "xHCI",
1059 let np = (sp & 0xf) as usize; 1181 _ => "USB?",
1060 let opb = base as usize + cl as usize; 1182 };
1061 let cmd = crate::pci::config_read(d.bus, d.device, d.func, 0x04); 1183 let bar = crate::pci::bar64(d.bus, d.device, d.func, 0);
1062 let eccp = crate::pci::config_read(d.bus, d.device, d.func, 0x34); 1184 let base = bar.unwrap_or(0);
1063 s.push_str(&alloc::format!( 1185 s.push_str(&alloc::format!(
1064 " ehci nports={} hrs={} hcc={:#x} cmd={:#x} eccp={:#x}\n", 1186 " {} {:02x}:{:02x}.{} bar={:#x}\n",
1065 np, sp, hc, cmd, (eccp >> 8) & 0xff 1187 lbl,
1066 )); 1188 d.bus,
1067 let cfg = rd(opb + 0x40); 1189 d.device,
1068 s.push_str(&alloc::format!( 1190 d.func,
1069 " ehci cfgflag={:#x} ", cfg 1191 base
1070 )); 1192 ));
1071 for p in 0..np.min(16) { 1193 if base == 0 || base == u64::MAX {
1072 let v = rd(opb + 0x44 + p * 0x04); 1194 continue;
1073 s.push_str(&alloc::format!(
1074 "{}{:#x},",
1075 if v & 1 != 0 { "*" } else { "" },
1076 v & 0xffff
1077 ));
1078 }
1079 s.push('\n');
1080 // Legacy BIOS handoff status (USBLEGSUP at EECP).
1081 let eo = ((eccp >> 8) & 0xff) as usize;
1082 if eo != 0 && (eccp & 0xff) != 0 {
1083 let hs = crate::pci::config_read(
1084 d.bus,
1085 d.device,
1086 d.func,
1087 (eo & 0xfc) as u8,
1088 );
1089 s.push_str(&alloc::format!(
1090 " ehci legacy=usblegsup={:#x} os-owner={} bios-owned={}\n",
1091 hs,
1092 (hs & (1 << 24)) != 0,
1093 (hs & (1 << 16)) != 0 || (hs & 0x100) != 0
1094 ));
1095 }
1096 } 1195 }
1097 0x00 => { 1196 match d.prog_if {
1098 // UHCI: I/O BAR (bar64 refuses I/O bars — read raw). 1197 0x30 => {
1099 let lo = crate::pci::config_read(d.bus, d.device, d.func, 0x10); 1198 let caplen = rd(base as usize);
1100 let io = (lo & 0xFFF0) as u16; 1199 let hcs1 = rd(base as usize + 0x04);
1101 if io == 0 { 1200 let np = ((hcs1 >> 24) & 0xff) as usize;
1102 continue; 1201 let opb = base as usize + (caplen & 0xff) as usize;
1202 s.push_str(" xhci ports=");
1203 for p in 0..np.min(16) {
1204 let v = rd(opb + 0x400 + p * 0x10);
1205 s.push_str(&alloc::format!(
1206 "{}{:#x} ",
1207 if v & 1 != 0 { "*" } else { "" },
1208 v & 0x3ff
1209 ));
1210 }
1211 s.push('\n');
1103 } 1212 }
1104 for p in 0..2 { 1213 0x20 => {
1105 let pr = io_iow16(io + 0x10 + (p as u16) * 2); 1214 let cl = rd(base as usize) & 0xff;
1215 let sp = rd(base as usize + 0x04);
1216 let hc = rd(base as usize + 0x08);
1217 let np = (sp & 0xf) as usize;
1218 let opb = base as usize + cl as usize;
1219 let cfg = rd(opb + 0x40);
1106 s.push_str(&alloc::format!( 1220 s.push_str(&alloc::format!(
1107 "{}{:x}:", if pr & 1 != 0 { "*" } else { "" }, 1221 " ehci nports={} hcc={:#x} cfgflag={:#x} ",
1108 (pr & 0xff) 1222 np, hc, cfg
1109 )); 1223 ));
1224 for p in 0..np.min(16) {
1225 let v = rd(opb + 0x44 + p * 0x04);
1226 s.push_str(&alloc::format!(
1227 "{}{:#x},",
1228 if v & 1 != 0 { "*" } else { "" },
1229 v & 0xffff
1230 ));
1231 }
1232 s.push('\n');
1233 }
1234 0x00 => {
1235 let lo = crate::pci::config_read(d.bus, d.device, d.func, 0x10);
1236 let io = (lo & 0xFFF0) as u16;
1237 if io == 0 {
1238 continue;
1239 }
1240 s.push_str(" uhci ports=");
1241 for p in 0..2 {
1242 let pr = io_iow16(io + 0x10 + (p as u16) * 2);
1243 s.push_str(&alloc::format!(
1244 "{}{:x},",
1245 if pr & 1 != 0 { "*" } else { "" },
1246 pr & 0xff
1247 ));
1248 }
1249 s.push('\n');
1110 } 1250 }
1111 s.push('\n'); 1251 _ => {}
1112 } 1252 }
1113 _ => {}
1114 } 1253 }
1115 } 1254 }
1116 }
1117 s 1255 s
1118} 1256}