summaryrefslogtreecommitdiffstats
path: root/rsnix/src/ehci.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/ehci.rs1274
1 files changed, 1274 insertions, 0 deletions
diff --git a/rsnix/src/ehci.rs b/rsnix/src/ehci.rs
new file mode 100644
index 0000000..6209155
--- /dev/null
+++ b/rsnix/src/ehci.rs
@@ -0,0 +1,1274 @@
1// ---------------------------------------------------------------------------
2// rsdos EHCI (USB2.0 host controller) driver — mouse only.
3//
4// Enumerates devices connected to an Intel-class EHCI (direct root-port
5// connect), discovers a HID boot-mouse interrupt-IN endpoint, and drains its
6// reports into ps2::MouseEvent. Uses the EHCI async schedule for control
7// transfers and the periodic (frame-list) schedule for the interrupt IN
8// endpoint. Polled: no IRQ/MSI — keep exactly one report outstanding and
9// drain it from the cursor task.
10//
11// Why EHCI: on real hardware (e.g. a Q67/Q77-era OptiPlex) a USB mouse is a
12// USB2 (full/low-speed) device attached to the EHCI USB2 controllers, not to
13// the xHCI's USB3/SS ports. QEMU only exercises the xHCI path, so this class
14// of real hardware requires its own driver.
15//
16// Identity map (phys==virt) means any kernel-heap pointer is a valid DMA
17// address; data structures must be 32-byte aligned for the EHCI.
18// ---------------------------------------------------------------------------
19
20use crate::ps2::MouseEvent;
21use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
22
23// ---- Registers (host controller registers; opbase = base + CAPLENGTH) ----
24const OPCMD: usize = 0x00;
25const OPSTS: usize = 0x04;
26const OPINTR: usize = 0x08;
27const OPFRIDX: usize = 0x0C;
28const OPCFGFLAG: usize = 0x40;
29const OP_PRTSC_BASE: usize = 0x44; // PORTSC #1
30const OP_PRTSC_STRIDE: usize = 0x04;
31
32const CMD_RUN: u32 = 1 << 0;
33const CMD_HCRESET: u32 = 1 << 1;
34const CMD_PSE: u32 = 1 << 4;
35const CMD_ASE: u32 = 1 << 5;
36
37const STS_HCH: u32 = 1 << 12;
38const STS_SE: u32 = 1 << 4;
39const STS_RECL: u32 = 1 << 14;
40
41const PRT_CONNECT: u32 = 1 << 0;
42const PRT_PED: u32 = 1 << 1;
43const PRT_RESET: u32 = 1 << 8;
44const PRT_POWER: u32 = 1 << 12;
45const PRT_RWC: u32 = (1 << 17) | (1 << 18) | (1 << 16) | (1 << 20); // CSC|PEC|CRC|OCC
46
47// ---- QH / QTD fields ----
48const Q_TYPE_QH: u32 = 1 << 1;
49const QT_TERMINATE: u32 = 1; // terminate = 1 in a link pointer
50const QH_HEAD: u32 = 1 << 15;
51const QH_CONTROL_EP: u32 = 1 << 27;
52const QH_DTC: u32 = 1 << 14; // data toggle comes from the qTDs (control endpoints)
53const QH_RL: u32 = 4 << 28; // NAK reload count (Linux EHCI_TUNE_RL_{HS,TT})
54const QH_EPS_HIGH: u32 = 2 << 12;
55const QH_EPS_FULL: u32 = 0 << 12;
56const QH_EPS_LOW: u32 = 1 << 12;
57const QH_MULT: u32 = 1 << 30; // MULT=1: one split transaction per uframe
58
59const TOK_IOC: u32 = 1 << 15;
60const TOK_ACTIVE: u32 = 1 << 7;
61const TOK_HALT: u32 = 1 << 6;
62const TOK_XACT: u32 = 1 << 3;
63const TOK_MMF: u32 = 1 << 2; // missed microframe: split did not complete in its window
64const PID_IN: u32 = 1;
65const PID_SETUP: u32 = 2;
66
67const CCERR_3: u32 = 3 << 10;
68
69// ---- Static control state ----
70static EHCI_BASE: AtomicU32 = AtomicU32::new(0);
71static N_PORTS: AtomicU32 = AtomicU32::new(0);
72static CTRL_UP: AtomicBool = AtomicBool::new(false);
73static MOUSE_PRES: AtomicBool = AtomicBool::new(false);
74static DEV_ADDR: AtomicU32 = AtomicU32::new(0);
75static HID_EP: AtomicU32 = AtomicU32::new(0); // EP number (1..15), IN
76static HID_MPS: AtomicU32 = AtomicU32::new(0);
77static ARMED: AtomicBool = AtomicBool::new(false);
78// When the last enumerate attempt ran (ms): a failed/glitched mouse must
79// never trigger a re-enumeration hot loop.
80static LAST_ENUM: AtomicU32 = AtomicU32::new(0);
81// Root ports observed in full/low-speed mode with a UHCI companion present.
82// Those ports are owned by the companion controller — the EHCI can never
83// talk to them, so they are skipped instead of failing a 5 s descriptor
84// timeout every second (the "stuck in a loop" the OptiPlex showed).
85static FS_LS_PORTS: AtomicU32 = AtomicU32::new(0);
86// One-shot: the first transfer failure renders a diagnostic QR on screen
87// (photograph it); retries keep dumping serial but must not re-render.
88static QR_SHOWN: AtomicBool = AtomicBool::new(false);
89// DEBUG: run the one-boot transfer variant matrix (real-hardware bisection
90// of the EHCI System Error trigger). MATRIX_DONE gates it to one pass;
91// XFER_TWEAK flips construction variables inside ctl_xfer.
92static DEBUG_MATRIX: bool = true;
93static MATRIX_DONE: AtomicBool = AtomicBool::new(false);
94static XFER_TWEAK: AtomicU32 = AtomicU32::new(0);
95// Low-region (<1 MB) control QH for the matrix's low-region variant.
96static mut LOW_XFER: *mut u32 = core::ptr::null_mut();
97
98// DMA buffers. Allocated once at init; base pointers recorded here.
99static mut ASYNC_HEAD: *mut u32 = core::ptr::null_mut(); // QH (32 bytes min, align 32)
100static mut XFER_QH: *mut u32 = core::ptr::null_mut(); // control QH
101static mut FRAMELIST: *mut u32 = core::ptr::null_mut(); // 1024 entries, 4K aligned
102static mut HID_QH: *mut u32 = core::ptr::null_mut();
103
104// One dedicated 4 KiB DMA page for every transfer buffer (device descriptor,
105// config descriptor, HID report). All DMA targets live in controller-visible
106// pages — never in .bss adjacent to kernel statics — so an overrun (report
107// longer than MPS, descriptor longer than requested, short-packet arithmetic
108// slip) can only clobber spare DMA space, on QEMU or on real silicon.
109static mut BUF_PAGE: *mut u8 = core::ptr::null_mut();
110const BUF_DEV_DESC: usize = 0x000; // 64 B incl. red zone
111const BUF_CFG: usize = 0x100; // 320 B incl. red zone
112const BUF_HID_REPORT: usize = 0x400; // 64 B incl. red zone
113
114unsafe fn rd(a: usize) -> u32 {
115 unsafe { (a as *const u32).read_volatile() }
116}
117unsafe fn wr(a: usize, v: u32) {
118 unsafe { (a as *mut u32).write_volatile(v) }
119}
120
121fn opbase() -> usize {
122 unsafe {
123 EHCI_BASE.load(Ordering::Relaxed) as usize
124 + (rd(EHCI_BASE.load(Ordering::Relaxed) as usize) & 0xff) as usize
125 }
126}
127fn op(off: usize) -> usize {
128 opbase() + off
129}
130fn prtsc(p: usize) -> usize {
131 opbase() + OP_PRTSC_BASE + p * OP_PRTSC_STRIDE
132}
133
134// ---- QH/QTD helpers: all writes go through raw dword pointers ----
135fn qh_next(qh: *mut u32) -> *mut u32 {
136 qh.wrapping_add(0)
137}
138fn qh_info1(qh: *mut u32) -> *mut u32 {
139 qh.wrapping_add(1)
140}
141fn qh_info2(qh: *mut u32) -> *mut u32 {
142 qh.wrapping_add(2)
143}
144fn qh_current(qh: *mut u32) -> *mut u32 {
145 qh.wrapping_add(3)
146}
147fn qh_next_qtd(qh: *mut u32) -> *mut u32 {
148 qh.wrapping_add(4)
149}
150fn qh_alt_qtd(qh: *mut u32) -> *mut u32 {
151 qh.wrapping_add(5)
152}
153fn qh_token(qh: *mut u32) -> *mut u32 {
154 qh.wrapping_add(6)
155}
156fn qh_buf(qh: *mut u32) -> *mut u32 {
157 qh.wrapping_add(7)
158}
159
160fn link_of(ptr: usize) -> u32 {
161 (ptr as u32) | Q_TYPE_QH
162}
163
164unsafe fn wait_recl(timeout: usize) {
165 unsafe {
166 let t0 = crate::interrupts::uptime_ms();
167 while t0 + timeout as u64 > crate::interrupts::uptime_ms()
168 && (rd(op(OPSTS)) & STS_RECL) == 0
169 {
170 core::hint::spin_loop();
171 }
172 }
173}
174
175/// Issue a single control transfer on the async schedule. `dev` is the USB
176/// device address (0 for the first GET_DESCRIPTOR), `mps` the EP0 max packet,
177/// `speed` the device speed (0=full, 1=low, 2=high), `port` the root port
178/// index (0-based; used only for full/low-speed split QHs), `req` the 8-byte
179/// setup (bmRequestType,bRequest,..), `dir_in` true for a device->host data
180/// stage, `buf/len` the data buffer. Returns bytes transferred, or a
181/// negative error code (-1 timeout, -2 HALT, -3 XactErr, -4 missed uframe).
182unsafe fn ctl_xfer(
183 dev: u32,
184 mps: u32,
185 speed: u32,
186 port: usize,
187 req: &[u8; 8],
188 dir_in: bool,
189 buf: *mut u8,
190 len: u32,
191) -> i32 {
192 // DEBUG matrix hook: XFER_TWEAK bits flip construction variables.
193 // bit0: no-ASE arm; bit1: no-split fields; bit2: no qtd chain;
194 // bit3: zero-length (no DATA stage); bit6: short timeout (1.2 s).
195 let len = if XFER_TWEAK.load(Ordering::Relaxed) & 8 != 0 {
196 0
197 } else {
198 len
199 };
200 unsafe {
201 let head = ASYNC_HEAD;
202 let tw0 = XFER_TWEAK.load(Ordering::Relaxed);
203 // DEBUG matrix: execute the same transfer from the low (<1 MB) page.
204 let xfer = if tw0 & 0x80 != 0 && !LOW_XFER.is_null() {
205 LOW_XFER
206 } else {
207 XFER_QH
208 };
209
210 // QH occupies dwords 0..11 (48 bytes) incl. the 5-dword buffer list.
211 // QEMU's NLPTR_GET masks qtd addresses to 32-byte alignment
212 // (& 0xffffffe0), so every qtd must sit on a 32-byte boundary.
213 // Place them at +0x40 / +0x60 / +0x80 (each 8 dwords = 32 bytes).
214 let st = xfer.add(0x40 / 4); // SETUP qtd at QH+0x40 (32-aligned)
215 let dt = st.add(0x20 / 4); // DATA qtd at QH+0x60 (32-aligned)
216 let sp = dt.add(0x20 / 4); // STATUS qtd at QH+0x80 (32-aligned)
217
218 // SETUP token: toggle 0, len 8, PID_SETUP, active, IOC off.
219 core::ptr::write_volatile(st.add(0), 0u32); // next (chained below)
220 core::ptr::write_volatile(st.add(1), QT_TERMINATE); // alt: terminate
221 core::ptr::write_volatile(
222 st.add(2),
223 (8 << 16) | CCERR_3 | (PID_SETUP << 8) | TOK_ACTIVE,
224 );
225 for i in 0..5 {
226 core::ptr::write_volatile(st.add(3 + i), 0u32);
227 }
228 // Setup packet buffer: 8 bytes at +0x110 (clear of the QH + qtds).
229 let sbuf = xfer.add(0x110 / 4);
230 let sb = core::slice::from_raw_parts_mut(sbuf.cast::<u8>(), 8);
231 sb.copy_from_slice(req);
232 core::ptr::write_volatile(st.add(3), sbuf as u32);
233
234 // DATA qtd. EHCI token PID field (bits 9:8): OUT=0, IN=1, SETUP=2.
235 let data_pid_w = (if dir_in { 1u32 } else { 0u32 }) << 8;
236 if len == 0 {
237 // no data stage
238 core::ptr::write_volatile(dt.add(2), 0u32);
239 } else {
240 core::ptr::write_volatile(dt.add(0), sp as u32);
241 // altnext MUST be terminate (1): QEMU's ADVANCEQUEUE follows altnext
242 // whenever TBYTES != 0 after an IN (e.g. a short packet). altnext=0
243 // is treated as *valid* (bit0==0) so the controller would jump to
244 // qtd address 0 and hang. Terminate keeps short transfers chained
245 // to the STATUS qtd, as required for control transfers.
246 core::ptr::write_volatile(dt.add(1), QT_TERMINATE);
247 core::ptr::write_volatile(
248 dt.add(2),
249 ((len & 0x7fff) << 16) | (1 << 31) | CCERR_3 | (data_pid_w) | TOK_ACTIVE,
250 );
251 for i in 0..5 {
252 core::ptr::write_volatile(dt.add(3 + i), 0u32);
253 }
254 // data buffer
255 core::ptr::write_volatile(dt.add(3), buf as u32);
256 }
257
258 // STATUS qtd: toggle 1, len 0, PID opposite of the data stage; for a
259 // zero-length control the status is opposite of the setup direction.
260 let setup_in = (req[0] & 0x80) != 0;
261 let status_pid_w = if len > 0 {
262 if dir_in {
263 0u32
264 } else {
265 1u32
266 } // opposite of data
267 } else if setup_in {
268 0u32
269 } else {
270 1u32
271 } << 8;
272 core::ptr::write_volatile(sp.add(0), 1u32); // terminate
273 core::ptr::write_volatile(sp.add(1), QT_TERMINATE);
274 core::ptr::write_volatile(
275 sp.add(2),
276 (1 << 31) | CCERR_3 | (status_pid_w) | TOK_ACTIVE | TOK_IOC,
277 );
278 for i in 0..5 {
279 core::ptr::write_volatile(sp.add(3 + i), 0u32);
280 }
281
282 // Chain the qtd list.
283 if len == 0 {
284 core::ptr::write_volatile(st.add(0), sp as u32);
285 } else {
286 core::ptr::write_volatile(st.add(0), dt as u32);
287 core::ptr::write_volatile(dt.add(0), sp as u32);
288 }
289
290 // Configure the control QH (EP0).
291 let eps = match speed {
292 2 => QH_EPS_HIGH,
293 1 => QH_EPS_LOW,
294 _ => QH_EPS_FULL,
295 };
296 let control_flag = if speed == 2 { 0 } else { QH_CONTROL_EP };
297 // FS/LS split QH: DTC (toggles come from the qTDs), NAK reload, MULT=1,
298 // hub addr 0 (integrated root-hub TT) and the 1-based root port number
299 // in bits 29:23. The controller runs SSPLIT/CSPLIT pairs for every qTD
300 // on this QH; no S/C masks are needed on the async list (Linux and
301 // FreeBSD both leave them 0 for control/bulk).
302 let info1 = (mps & 0x7ff) << 16 | QH_RL | QH_DTC | control_flag | eps | dev; // devaddr in low 7 bits
303 *qh_info1(xfer) = info1;
304 *qh_info2(xfer) = if speed == 2 || XFER_TWEAK.load(Ordering::Relaxed) & 2 != 0 {
305 0
306 } else {
307 QH_MULT | (((port as u32) + 1) << 23)
308 };
309 *qh_current(xfer) = 0;
310 *qh_next_qtd(xfer) = st as u32;
311 *qh_alt_qtd(xfer) = QT_TERMINATE;
312 *qh_token(xfer) = 0;
313 for i in 0..5 {
314 *qh_buf(xfer).add(i) = 0;
315 }
316
317 // Link: head -> xfer -> head (reclamation cycle).
318 *qh_next(xfer) = link_of(head as usize);
319 *qh_next(head) = link_of(xfer as usize);
320 if XFER_TWEAK.load(Ordering::Relaxed) & 4 != 0 {
321 *qh_next_qtd(xfer) = 1; // no qtd chain: HC treats the QH as idle
322 }
323
324 // Ensure the controller is running and both schedules enabled.
325 // Re-assert every transfer: QEMU may drop RUNSTOP, and re-setting the
326 // bits is what makes it (re)schedule the async walk.
327 //
328 // Clear the W1C status bits first so a failure dump reflects only
329 // this transfer's window (USBINT|USBERR|PCD|FR|SE).
330 wr(op(OPSTS), 0x1F);
331 // Fence: make all DMA writes globally visible before the controller reads.
332 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
333 let tw = XFER_TWEAK.load(Ordering::Relaxed);
334 if tw & 0x20 != 0 {
335 // wire-off: disable the port so no transaction can reach the wire.
336 wr(prtsc(port), rd(prtsc(port)) & !(1 << 1)); // PED=0
337 }
338 wr(
339 op(OPCMD),
340 rd(op(OPCMD)) | CMD_RUN | (if tw & 1 == 0 { CMD_ASE } else { 0 }),
341 ); // no PSE — periodic not needed for control
342 let ctl_timeout = if tw & 0x40 != 0 { 1200u64 } else { 5000u64 };
343 // Poll for completion. Watch the whole chain: a HALT on the SETUP
344 // or DATA qtd stops the queue and the STATUS qtd would never clear
345 // (which previously read as a plain timeout). Exit early naming the
346 // failing stage, and on any failure dump the controller + queue state
347 // for real-hardware iteration.
348 let t0 = crate::interrupts::uptime_ms();
349 let mut stage: i32 = 0; // 0=ok, 1=setup halted, 2=data halted, 3=timeout, 4=hc halted/SE
350 loop {
351 let stok = core::ptr::read_volatile(st.add(2).cast::<u32>());
352 let ptok = core::ptr::read_volatile(sp.add(2).cast::<u32>());
353 if ptok & TOK_ACTIVE == 0 {
354 break; // status completed
355 }
356 if stok & TOK_HALT != 0 {
357 stage = 1;
358 break;
359 }
360 if len > 0 {
361 let dtok = core::ptr::read_volatile(dt.add(2).cast::<u32>());
362 if dtok & TOK_HALT != 0 {
363 stage = 2;
364 break;
365 }
366 }
367 // Controller-level fault: the HC halted itself (HCH) or threw a
368 // system error (SE). Polling further is pointless — the qtds
369 // would sit ACTIVE forever and read back as a plain timeout.
370 // Catching it here names the window precisely.
371 if rd(op(OPSTS)) & (STS_HCH | STS_SE) != 0 {
372 stage = 4;
373 break;
374 }
375 if t0 + ctl_timeout < crate::interrupts::uptime_ms() {
376 stage = 3;
377 break;
378 }
379 core::hint::spin_loop();
380 }
381 if stage != 0 {
382 let stok = core::ptr::read_volatile(st.add(2).cast::<u32>());
383 let dtok = if len > 0 {
384 core::ptr::read_volatile(dt.add(2).cast::<u32>())
385 } else {
386 0
387 };
388 let ptok = core::ptr::read_volatile(sp.add(2).cast::<u32>());
389 crate::serial_println!(
390 "ehci: xfer fail stage={} (1=setup 2=data 3=timeout) speed={} dev={} dir_in={} len={}",
391 stage,
392 speed,
393 dev,
394 dir_in,
395 len
396 );
397 let base = EHCI_BASE.load(Ordering::Relaxed) as usize;
398 crate::serial_println!(
399 "ehci: cmd={:#x} sts={:#x} prtsc{}={:#x} hostpc={:#x}",
400 rd(op(OPCMD)),
401 rd(op(OPSTS)),
402 port,
403 rd(prtsc(port)),
404 rd(base + 0x84 + port * 4)
405 );
406 crate::serial_println!(
407 "ehci: qh i1={:#x} i2={:#x} cur={:#x} nqtd={:#x}",
408 core::ptr::read_volatile(qh_info1(xfer)),
409 core::ptr::read_volatile(qh_info2(xfer)),
410 core::ptr::read_volatile(qh_current(xfer)),
411 core::ptr::read_volatile(qh_next_qtd(xfer))
412 );
413 crate::serial_println!(
414 "ehci: tok setup={:#x} data={:#x} stat={:#x}",
415 stok,
416 dtok,
417 ptok
418 );
419 if stage == 4 {
420 crate::serial_println!(
421 "ehci: HC halted/SE after {} ms since arm (frindex={:#x} asyncbase={:#x})",
422 crate::interrupts::uptime_ms().wrapping_sub(t0),
423 rd(op(OPFRIDX)),
424 rd(op(0x18))
425 );
426 }
427 // One-shot on-screen QR of the whole diagnostic: band text plus a
428 // scannable matrix below. Used on hardware with no serial pipe —
429 // point a phone at the screen. Lock the klog screen echo so the
430 // retry loop cannot overwrite it (serial keeps logging).
431 if !QR_SHOWN.swap(true, Ordering::Relaxed) {
432 let l1 = alloc::format!(
433 "EHCI ctl fail stage={} t={}ms speed={} dev={} in={} len={}",
434 stage,
435 crate::interrupts::uptime_ms().wrapping_sub(t0),
436 speed,
437 dev,
438 dir_in,
439 len
440 );
441 let l2 = alloc::format!("cmd={:08x} sts={:08x}", rd(op(OPCMD)), rd(op(OPSTS)));
442 let l3 = alloc::format!(
443 "prtsc{}=0x{:08x} hp=0x{:08x}",
444 port,
445 rd(prtsc(port)),
446 rd(base + 0x84 + port * 4)
447 );
448 let l4 = alloc::format!(
449 "qh1={:08x} qh2={:08x} cur={:08x} nqtd={:08x}",
450 core::ptr::read_volatile(qh_info1(xfer)),
451 core::ptr::read_volatile(qh_info2(xfer)),
452 core::ptr::read_volatile(qh_current(xfer)),
453 core::ptr::read_volatile(qh_next_qtd(xfer))
454 );
455 let l5 = alloc::format!("setup={:08x} data={:08x} stat={:08x}", stok, dtok, ptok);
456 let l6 = alloc::format!(
457 "dma a={:08x} x={:08x} f={:08x} h={:08x} b={:08x}",
458 ASYNC_HEAD as usize,
459 XFER_QH as usize,
460 FRAMELIST as usize,
461 HID_QH as usize,
462 BUF_PAGE as usize
463 );
464 let all = [l1, l2, l3, l4, l5, l6];
465 let refs: alloc::vec::Vec<&str> = all.iter().map(|s| s.as_str()).collect();
466 crate::gop::dump_qr(&refs);
467 crate::gop::qr_lock();
468 }
469 *qh_next(head) = link_of(head as usize);
470 wait_recl(100);
471 return match stage {
472 1 => -3, // setup halted
473 2 => -4, // data halted
474 _ => -1, // timeout
475 };
476 }
477 // Status completed — check its error bits.
478 let tok = core::ptr::read_volatile(sp.add(2).cast::<u32>());
479 if tok & TOK_HALT != 0 {
480 *qh_next(head) = link_of(head as usize);
481 wait_recl(100);
482 return -2; // HALT — qtd halted on error (babble/DBE/...)
483 }
484 if tok & TOK_XACT != 0 {
485 *qh_next(head) = link_of(head as usize);
486 wait_recl(100);
487 return -5; // XactErr — CRC/timeout on the transaction
488 }
489 if tok & TOK_MMF != 0 {
490 *qh_next(head) = link_of(head as usize);
491 wait_recl(100);
492 return -6; // missed microframe — split window missed (retryable)
493 }
494 // Bytes transferred = requested - remaining on the DATA qtd.
495 let mut got = len;
496 if len > 0 {
497 let dtok = core::ptr::read_volatile(dt.add(2).cast::<u32>());
498 let rem = ((dtok >> 16) & 0x7fff) as u32;
499 got = len - rem.min(len);
500 }
501 // Unlink: idle self-loop must keep the QH type bit, otherwise QEMU
502 // parses it as an iTD and resets the HC ("non queue head request").
503 *qh_next(head) = link_of(head as usize);
504 wait_recl(100);
505 let _ = qh_next_qtd;
506 got as i32
507 }
508}
509
510// Control-transfer convenience using the standard control request layout.
511fn ctrl_req(bmreq: u8, breq: u8, wval: u16, widx: u16, wlen: u16) -> [u8; 8] {
512 [
513 bmreq,
514 breq,
515 (wval & 0xff) as u8,
516 (wval >> 8) as u8,
517 (widx & 0xff) as u8,
518 (widx >> 8) as u8,
519 (wlen & 0xff) as u8,
520 (wlen >> 8) as u8,
521 ]
522}
523
524// ---------------------------------------------------------------------------
525// Controller bring-up
526// ---------------------------------------------------------------------------
527
528/// Find an EHCI controller (class 0x0C class 0x03, prog_if 0x20).
529fn find_ehci() -> Option<(u8, u8, u8)> {
530 for d in crate::pci::probe() {
531 if d.class == 0x0C && d.subclass == 0x03 && d.prog_if == 0x20 {
532 return Some((d.bus, d.device, d.func));
533 }
534 }
535 None
536}
537
538/// Return the number of the first root port with a device attached, or usize::MAX.
539fn find_connected_port() -> usize {
540 let np = N_PORTS.load(Ordering::Relaxed) as usize;
541 let skip = FS_LS_PORTS.load(Ordering::Relaxed);
542 for p in 0..np {
543 if skip & (1 << p) != 0 {
544 continue; // full/low-speed — owned by the companion UHCI
545 }
546 let v = unsafe { rd(prtsc(p)) };
547 if v & PRT_CONNECT != 0 {
548 return p;
549 }
550 }
551 usize::MAX
552}
553
554/// Clear the FS/LS port mask so discovery retries (shell `usb rescan`).
555pub fn rescan() {
556 FS_LS_PORTS.store(0, Ordering::Relaxed);
557}
558
559pub fn init() -> bool {
560 let Some((b, d, f)) = find_ehci() else {
561 crate::serial_println!("ehci: no EHCI controller");
562 return false;
563 };
564 crate::serial_println!("ehci: USB2 EHCI at {:02x}:{:02x}.{}", b, d, f);
565
566 unsafe {
567 // Enable Memory Space + Bus Master, and disable INTx assertion
568 // (bit 10) — the driver is polled (OPINTR=0), and an unhandled
569 // EHCI interrupt on real hardware would fire a stray IRQ that the
570 // kernel's catch-all handler EOI's but cannot service.
571 let cmd = crate::pci::config_read(b, d, f, 0x04);
572 crate::pci::config_write(
573 b,
574 d,
575 f,
576 0x04,
577 (cmd & 0xFFFF_0000) | (cmd & 0xFFFF) | 0x6 | 0x400,
578 );
579 let bar = crate::pci::bar64(b, d, f, 0);
580 let base = bar.unwrap_or(0) as usize;
581 EHCI_BASE.store(base as u32, Ordering::Relaxed);
582 let cap = rd(base);
583 let sp = rd(base + 4);
584 let hc = rd(base + 8);
585 let np = sp & 0xf;
586 N_PORTS.store(np, Ordering::Relaxed);
587 crate::serial_println!(
588 "ehci: mmio={:#x} caplen={:#x} ports={} 64-bit-caps={}",
589 base,
590 cap & 0xff,
591 np,
592 (hc & 1) != 0
593 );
594 if base == 0 {
595 return false;
596 }
597
598 // Claim USB Legacy ownership from the BIOS before resetting the
599 // controller. The PCH BIOS drives USB keyboards through SMM
600 // legacy emulation while it owns the EHCI; resetting/reprogramming
601 // the controller underneath it silently kills the legacy keyboard
602 // (observed on the 7010) and the still-active SMI poll can collide
603 // with our async DMA — a Host System Error. The capability lives
604 // in PCI config space (EHCI spec §5.1); the claim walks it exactly
605 // like Linux/FreeBSD. Bounded, no-panic; reports what happened.
606 match crate::pci::claim_usb_legacy(b, d, f, base, 0x08, false) {
607 0 => crate::serial_println!("ehci: USB legacy: already released (no claim needed)"),
608 1 => crate::serial_println!("ehci: USB legacy handed over by BIOS"),
609 2 => crate::serial_println!("ehci: USB legacy claim timed out (BIOS sem stuck)"),
610 _ => crate::serial_println!("ehci: USB legacy: no capability found"),
611 }
612
613 // Halt the controller.
614 wr(op(OPCMD), 0);
615 let mut t = 0;
616 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
617 t += 1;
618 core::hint::spin_loop();
619 }
620 crate::serial_println!(
621 "ehci: halted {}",
622 if rd(op(OPSTS)) & STS_HCH != 0 {
623 "ok"
624 } else {
625 "TIMEOUT"
626 }
627 );
628 // Ensure not in reset already; then issue HC reset.
629 wr(op(OPCMD), CMD_HCRESET);
630 t = 0;
631 while t < 10_000_000 && (rd(op(OPCMD)) & CMD_HCRESET) != 0 {
632 t += 1;
633 core::hint::spin_loop();
634 }
635 crate::serial_println!(
636 "ehci: hcreset {}",
637 if rd(op(OPCMD)) & CMD_HCRESET == 0 {
638 "ok"
639 } else {
640 "TIMEOUT"
641 }
642 );
643 t = 0;
644 while t < 10_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
645 t += 1;
646 core::hint::spin_loop();
647 }
648 crate::serial_println!(
649 "ehci: post-reset halt {}",
650 if rd(op(OPSTS)) & STS_HCH != 0 {
651 "ok"
652 } else {
653 "TIMEOUT"
654 }
655 );
656 // Disable interrupts (polled).
657 wr(op(OPINTR), 0);
658 // Clear HCHalted? After reset the HC is halted; running is set later.
659
660 // Allocate the async head QH + control QH + frame list + HID structures.
661 ASYNC_HEAD = dma_alloc(0x400, 0x20) as *mut u32; // 1KB region
662 XFER_QH = ASYNC_HEAD.add(0x80 / 4); // control QH at +0x80
663 // qtds live after XFER_QH's 11 dwords (44 bytes); space reserved.
664 FRAMELIST = dma_alloc(0x1000, 0x1000) as *mut u32;
665 HID_QH = dma_alloc(0x400, 0x20) as *mut u32;
666 // DEBUG matrix: low-region control QH (sub-1MB page) to test whether
667 // the SE is tied to the 16 MB DMA region (write protection) or not.
668 if let Some(lp) = crate::memory::alloc_pages_above(2, 0) {
669 LOW_XFER = lp as *mut u32;
670 crate::serial_println!("ehci: low-xfer debug page at {:#x}", lp);
671 }
672 BUF_PAGE = dma_alloc(0x1000, 0x20);
673 if BUF_PAGE.is_null() {
674 crate::serial_println!("ehci: buffer page allocation failed");
675 return false;
676 }
677 crate::serial_println!(
678 "ehci: dma async={:#x} xfer={:#x} framelist={:#x} hid={:#x} buf={:#x}",
679 ASYNC_HEAD as usize,
680 XFER_QH as usize,
681 FRAMELIST as usize,
682 HID_QH as usize,
683 BUF_PAGE as usize
684 );
685
686 // Init async head QH: self-linked, marked as head of reclamation.
687 *qh_next(ASYNC_HEAD) = link_of(XFER_QH as usize); // circular: head → control QH → head
688 *qh_info1(ASYNC_HEAD) = QH_HEAD;
689 *qh_info2(ASYNC_HEAD) = 0;
690 *qh_current(ASYNC_HEAD) = 0;
691 *qh_next_qtd(ASYNC_HEAD) = 0;
692
693 // Configure CONTROL QH as an idle EP0 (filled per transfer).
694 *qh_next(XFER_QH) = link_of(ASYNC_HEAD as usize);
695 *qh_info1(XFER_QH) = QH_EPS_FULL | (64 << 16); // HS mps 64
696 *qh_info2(XFER_QH) = 0;
697 *qh_current(XFER_QH) = 0;
698 *qh_next_qtd(XFER_QH) = 0; // idle
699 *qh_alt_qtd(XFER_QH) = QT_TERMINATE;
700 *qh_token(XFER_QH) = 0;
701
702 // Async schedule address.
703 wr(op(OPFRIDX), 0);
704 // Set ASYNCLISTADDR (32-bit). Controller supports 64-bit (hcc&1) but we
705 // keep everything below 4 GB; CTRLDSSEGMENT=0 suffices.
706 // ASYNCLISTADDR low 32 bits: address of HEAD QH (lower 5 bits zero).
707 // With hcc 64-bit set, the controller reads the 64-bit address
708 // CTRLDSSEGMENT:ASYNCLISTADDR. Zero DSEG + low 32-bit pointer is fine
709 // for addresses < 4 GB.
710 let head_addr = ASYNC_HEAD as usize;
711 wr(op(0x10), 0); // CTRLDSSEGMENT
712 // ASYNCLISTADDR is at op+0x18
713 wr(op(0x18), (head_addr as u32) & !0x1F);
714 // Periodic frame list address.
715 // Zero the frame list to avoid stale QH/ITD pointers causing
716 // QEMU to reset the controller when PSE is later enabled.
717 let fl = FRAMELIST as usize;
718 for i in 0..1024 {
719 core::ptr::write_volatile(FRAMELIST.add(i), 0u32);
720 }
721 wr(op(0x14), (fl as u32) & !0x3FF);
722 // Frame list size: FLS field (bits 2-3) stays 0 → 1024 entries.
723 // Intel/QEMU layout: PSE=bit4, ASE=bit5.
724 // NOTE: do NOT enable PSE here — the frame list is empty (all zeros).
725 // Enabling PSE on an empty frame list is fine (QEMU skips zero entries),
726 // but we want to avoid any race with stale OVMF entries. PSE will be
727 // enabled later in poll_mouse when the HID QH is linked into the list.
728 wr(op(OPCMD), 0x01u32); // CMD_RUN only, no PSE
729 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN);
730 // Wait to leave halt.
731 let mut t = 0;
732 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) != 0 {
733 t += 1;
734 core::hint::spin_loop();
735 }
736 crate::serial_println!(
737 "ehci: running {}",
738 if rd(op(OPSTS)) & STS_HCH == 0 {
739 "ok"
740 } else {
741 "STUCK HALTED"
742 }
743 );
744 // Configure flag: route all ports to the host (CF=1) and power them.
745 wr(op(OPCFGFLAG), 1);
746 for p in 0..np as usize {
747 let psc = prtsc(p);
748 let v = rd(psc);
749 if v & PRT_POWER == 0 {
750 wr(psc, v | PRT_POWER);
751 }
752 }
753 CTRL_UP.store(true, Ordering::Relaxed);
754 crate::serial_println!("ehci: controller up ({} ports)", np);
755
756 // DIAGNOSTIC: run the async schedule with an idle head (self-loop,
757 // no qtds) for 150 ms. If this faults (HCH/SE), the SE happens on
758 // schedule/DMA mechanics alone — nothing to do with the split
759 // transfer. If clean, the fault is specific to transfer execution.
760 *qh_next(ASYNC_HEAD) = link_of(ASYNC_HEAD as usize);
761 wr(op(OPSTS), 0x1F);
762 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
763 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_ASE);
764 let pt0 = crate::interrupts::uptime_ms();
765 loop {
766 let s = rd(op(OPSTS));
767 if s & (STS_HCH | STS_SE) != 0 {
768 crate::serial_println!(
769 "ehci: idle-probe FAULTED {}ms: cmd={:#x} sts={:#x} frindex={:#x} asyncbase={:#x} prtsc0={:#x}",
770 crate::interrupts::uptime_ms().wrapping_sub(pt0),
771 rd(op(OPCMD)),
772 s,
773 rd(op(OPFRIDX)),
774 rd(op(0x18)),
775 rd(prtsc(0))
776 );
777 break;
778 }
779 if crate::interrupts::uptime_ms().wrapping_sub(pt0) >= 150 {
780 crate::serial_println!("ehci: idle-probe ok (150ms, no fault)");
781 break;
782 }
783 core::hint::spin_loop();
784 }
785 }
786 true
787}
788
789/// Port reset on USB2 EHCI. Assert PR, hold it briefly (real EHCI clears
790/// PR itself once reset signaling finishes; QEMU needs the driver to
791/// deassert), then wait for the port to enable. Returns true if PED set
792/// (high-speed device). Full/low-speed stay PED=0 and need a TT path.
793unsafe fn reset_port(p: usize) -> bool {
794 unsafe {
795 let psc = prtsc(p);
796 let v = rd(psc);
797 // Put port in reset.
798 wr(psc, (v | PRT_RESET) & !PRT_RWC);
799 // Hold ~50 ms, bailing early if the HC cleared PR on its own.
800 let t0 = crate::interrupts::uptime_ms();
801 while crate::interrupts::uptime_ms().saturating_sub(t0) < 50 {
802 core::hint::spin_loop();
803 if rd(psc) & PRT_RESET == 0 {
804 break;
805 }
806 }
807 // Deassert explicitly (QEMU only completes the reset on deassert).
808 wr(psc, (rd(psc) & !PRT_RESET) & !PRT_RWC);
809 // Wait briefly for the port to enable (PED). High-speed devices set
810 // PED on reset; full/low-speed stay PED=0 (they need split/TT). Do
811 // not spin forever — decide on the line state instead.
812 let mut t = 0;
813 while t < 2_000_000 && (rd(psc) & PRT_PED) == 0 {
814 t += 1;
815 core::hint::spin_loop();
816 }
817 // A high-speed device gains PED. Full/low-speed won't (needs companion/TT).
818 rd(psc) & PRT_PED != 0
819 }
820}
821
822/// Distinguish full- vs low-speed on a connected root port (for the split QH
823/// EPS field). Intel PCH EHCIs expose the speed in HOSTPC (base+0x84+4p,
824/// bits 27:26: 0=full, 1=low, 2=high); other controllers fall back to the
825/// PORTSC bits 27:26. Only meaningful after the port has completed reset.
826unsafe fn port_speed(p: usize) -> u32 {
827 unsafe {
828 let base = EHCI_BASE.load(Ordering::Relaxed) as usize;
829 let hp = rd(base + 0x84 + p * 4);
830 if hp != u32::MAX && hp != 0 {
831 let f = (hp >> 25) & 3;
832 if f != 3 {
833 return f;
834 }
835 }
836 (rd(prtsc(p)) >> 26) & 3
837 }
838}
839
840// ---------------------------------------------------------------------------
841// Enumeration + HID discovery
842// ---------------------------------------------------------------------------
843
844const HID_CLASS: u8 = 0x03;
845
846fn find_hid_ep(cfg: &[u8]) -> Option<(u8, u32, u32, u8)> {
847 // returns (epaddr, mps, interval_bint, epnum)
848 let mut i = 0usize;
849 let mut in_iface = false;
850 while i + 2 <= cfg.len() {
851 let blen = cfg[i] as usize;
852 if blen == 0 {
853 break;
854 }
855 let bt = cfg[i + 1];
856 if bt == 4 && i + 9 <= cfg.len() {
857 in_iface = cfg[i + 5] == HID_CLASS;
858 } else if bt == 5 && in_iface && i + 7 <= cfg.len() {
859 let addr = cfg[i + 2];
860 let attrs = cfg[i + 3];
861 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8);
862 let bint = cfg[i + 6] as u32;
863 if (addr & 0x80) != 0 && (attrs & 0x03) == 0x03 {
864 return Some((addr, mps, bint, addr & 0x0f));
865 }
866 }
867 i += blen;
868 }
869 None
870}
871
872fn enumerate() -> bool {
873 let port = find_connected_port();
874 if port == usize::MAX {
875 return false;
876 }
877 // DEBUG: one-boot transfer variant matrix replaces normal enumeration.
878 if DEBUG_MATRIX && !MATRIX_DONE.swap(true, Ordering::Relaxed) {
879 unsafe {
880 debug_xfer_matrix(port);
881 }
882 return false; // diagnostic build
883 }
884 unsafe {
885 // If a periodic schedule is live (a previous arm), stop it cleanly
886 // before touching the controller: point every frame at terminate,
887 // clear PSE and wait for the reclaim handshake. Re-enumerating while
888 // the old HID QH is still being walked interleaves the periodic walk
889 // with control transfers — fragile on any HC.
890 if rd(op(OPCMD)) & CMD_PSE != 0 {
891 let fl = FRAMELIST;
892 for f in 0..1024 {
893 core::ptr::write_volatile(fl.add(f), QT_TERMINATE);
894 }
895 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
896 wr(op(OPCMD), rd(op(OPCMD)) & !CMD_PSE);
897 wait_recl(1000);
898 }
899 // Discovery state: nothing armed/present until the arm at the end.
900 ARMED.store(false, Ordering::Relaxed);
901 MOUSE_PRES.store(false, Ordering::Relaxed);
902 let hs = reset_port(port);
903 if !hs && crate::uhci::controller_up() {
904 // A real (non-QEMU) EHCI can never transfer to a full/low-speed
905 // device: the port is owned by the companion UHCI. Mark the
906 // port and stop — a descriptor timeout here costs 5 s and
907 // repeats every second, which is exactly the "stuck in a loop"
908 // the OptiPlex showed. (QEMU's usb-ehci accepts PED=0 with a
909 // high-speed device behind it, so this skip only triggers when
910 // a real UHCI companion is actually present.)
911 FS_LS_PORTS.store(
912 FS_LS_PORTS.load(Ordering::Relaxed) | (1 << port),
913 Ordering::Relaxed,
914 );
915 crate::serial_println!(
916 "ehci: port {} full/low-speed — owned by companion UHCI (uhci driver handles it)",
917 port
918 );
919 return false;
920 }
921 crate::serial_println!(
922 "ehci: port {} resolved{}",
923 port,
924 if hs {
925 " (high-speed)"
926 } else {
927 " (full/low — TT path)"
928 }
929 );
930 // Speed for the QH EPS field: 2=HS, 1=LS, 0=FS. FS/LS on an EHCI
931 // root port must be serviced with split transactions (integrated TT).
932 let speed = if hs { 2 } else { port_speed(port) };
933 let speed_name = match speed {
934 2 => "high-speed",
935 1 => "low-speed (split)",
936 _ => "full-speed (split)",
937 };
938 let hostpc = unsafe { rd(EHCI_BASE.load(Ordering::Relaxed) as usize + 0x84 + port * 4) };
939 crate::serial_println!(
940 "ehci: port {} speed: {} (hostpc={:#x})",
941 port,
942 speed_name,
943 hostpc
944 );
945 let mps = if hs { 64u32 } else { 8u32 };
946
947 // 1. GET_DESCRIPTOR (device) — 18 bytes to learn idVendor etc.
948 let req = ctrl_req(0x80, 6, 0x0100, 0, 18);
949 let dd = BUF_PAGE.add(BUF_DEV_DESC);
950 let mut got = ctl_xfer(0, mps, speed, port, &req, true, dd, 18);
951 if got < 0 {
952 crate::serial_println!("ehci: get-dev-desc failed code={}", got);
953 return false;
954 }
955 if got < 8 {
956 crate::serial_println!("ehci: short dev-desc ({})", got);
957 return false;
958 }
959 let dd = dd as *const u8;
960 let mps0 = core::ptr::read_volatile(dd.add(7)) as u32; // bMaxPacketSize0
961 let _ = mps0;
962 crate::serial_println!(
963 "ehci: dev {:04x}:{:04x} class={:#x} mps0={}",
964 (core::ptr::read_volatile(dd.add(9)) as u16)
965 | ((core::ptr::read_volatile(dd.add(8)) as u16) << 8),
966 (core::ptr::read_volatile(dd.add(11)) as u16)
967 | ((core::ptr::read_volatile(dd.add(10)) as u16) << 8),
968 core::ptr::read_volatile(dd.add(4)),
969 core::ptr::read_volatile(dd.add(7))
970 );
971
972 // 2. SET_ADDRESS to 1.
973 let req = ctrl_req(0x00, 5, 1, 0, 0);
974 if ctl_xfer(0, mps, speed, port, &req, false, core::ptr::null_mut(), 0) < 0 {
975 crate::serial_println!("ehci: set-address failed");
976 return false;
977 }
978 DEV_ADDR.store(1, Ordering::Relaxed);
979
980 // 3. GET_CONFIG(0) — 256 bytes, enough for a HID descriptor.
981 let cfg = BUF_PAGE.add(BUF_CFG);
982 core::ptr::write_bytes(cfg, 0, 256);
983 let req = ctrl_req(0x80, 6, 0x0200, 0, 256);
984 got = ctl_xfer(1, mps, speed, port, &req, true, cfg, 256);
985 if got < 0 {
986 crate::serial_println!("ehci: get-config failed code={}", got);
987 return false;
988 }
989 let Some((epaddr, epmps, bint, epnum)) =
990 find_hid_ep(core::slice::from_raw_parts(cfg as *const u8, 256))
991 else {
992 crate::serial_println!("ehci: no HID mouse endpoint in config");
993 return false;
994 };
995 crate::serial_println!(
996 "ehci: HID mouse IN ep{}({:#x}) mps={} interval={}",
997 epnum,
998 epaddr,
999 epmps,
1000 bint
1001 );
1002
1003 // 4. SET_CONFIGURATION(1).
1004 let req = ctrl_req(0x00, 9, 1, 0, 0);
1005 if ctl_xfer(1, mps, speed, port, &req, false, core::ptr::null_mut(), 0) < 0 {
1006 crate::serial_println!("ehci: set-config failed");
1007 return false;
1008 }
1009
1010 // 5. Arm the HID interrupt-IN endpoint on the periodic schedule.
1011 HID_EP.store(epnum as u32, Ordering::Relaxed);
1012 HID_MPS.store(epmps, Ordering::Relaxed);
1013 // Build HID QH for EP<epnum> IN on device 1.
1014 let hq = HID_QH;
1015 // eps: speed (0/1/2) maps directly onto the QH EPS field (bits 13:12).
1016 let info1 = QH_RL
1017 | (speed << 12)
1018 | ((epmps.max(4) & 0x7ff) << 16)
1019 | (DEV_ADDR.load(Ordering::Relaxed) & 0x7f)
1020 | (((epnum as u32) & 0x0f) << 8);
1021 // Poll every frame. HS: SMASK covers every uframe. FS/LS: split —
1022 // SSPLIT at uframe 0, CSPLIT at uframes 2..4 (Linux NEWSCHED), hub
1023 // addr 0 (integrated root TT), 1-based root port in bits 29:23.
1024 let (smask, cmask, split) = if speed == 2 {
1025 (0xFFu32, 0u32, 0u32)
1026 } else {
1027 (0x01u32, 0x1Cu32 << 8, QH_MULT | (((port as u32) + 1) << 23))
1028 };
1029 *qh_next(hq) = QT_TERMINATE; // end of this frame's periodic list
1030 *qh_info1(hq) = info1;
1031 *qh_info2(hq) = smask | cmask | split;
1032 *qh_current(hq) = 0;
1033 let iqtd = hq.add(0x40 / 4); // qtd at +0x40 (must be 32-byte aligned)
1034 // HID report qtd: len = epmps, PID IN.
1035 core::ptr::write_volatile(iqtd.wrapping_add(0), QT_TERMINATE);
1036 core::ptr::write_volatile(iqtd.wrapping_add(1), QT_TERMINATE);
1037 core::ptr::write_volatile(
1038 iqtd.wrapping_add(2),
1039 ((epmps.max(4) & 0x7fff) << 16) | (PID_IN << 8) | TOK_ACTIVE | TOK_IOC,
1040 );
1041 for i in 0..5 {
1042 core::ptr::write_volatile(iqtd.add(3 + i), 0u32);
1043 }
1044 core::ptr::write_volatile(iqtd.wrapping_add(3), BUF_PAGE.add(BUF_HID_REPORT) as u32);
1045 *qh_next_qtd(hq) = iqtd as u32;
1046 *qh_alt_qtd(hq) = QT_TERMINATE;
1047 *qh_token(hq) = 0;
1048 // QEMU reads the report buffer from the qtd's bufptr (ehci_init_transfer),
1049 // so the QH overlay's buffer list need not be pre-populated.
1050 // Point every period frame entry at the HID QH.
1051 let fl = FRAMELIST;
1052 for f in 0..1024 {
1053 core::ptr::write_volatile(fl.add(f), link_of(hq as usize));
1054 }
1055 // Enable the periodic schedule so the frame list is actually walked.
1056 // PSE was deliberately off until now (empty frame list during init).
1057 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
1058 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_PSE);
1059 ARMED.store(true, Ordering::Relaxed);
1060 MOUSE_PRES.store(true, Ordering::Relaxed);
1061 crate::serial_println!("ehci: HID boot mouse armed (device 1)");
1062 true
1063 }
1064}
1065
1066// ---------------------------------------------------------------------------
1067// DEBUG: one-boot transfer variant matrix (real-hardware SE bisection).
1068// Each variant does a full HC reset + port reset, then one ctl_xfer with a
1069// single construction variable flipped (see XFER_TWEAK in ctl_xfer), and
1070// logs the outcome. A wedged port from an earlier SE cannot confound the
1071// comparisons because every variant restarts the controller.
1072// ---------------------------------------------------------------------------
1073unsafe fn hc_restart() {
1074 unsafe {
1075 wr(op(OPCMD), 0);
1076 let mut t = 0;
1077 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
1078 t += 1;
1079 core::hint::spin_loop();
1080 }
1081 wr(op(OPCMD), CMD_HCRESET);
1082 t = 0;
1083 while t < 10_000_000 && (rd(op(OPCMD)) & CMD_HCRESET) != 0 {
1084 t += 1;
1085 core::hint::spin_loop();
1086 }
1087 t = 0;
1088 while t < 10_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
1089 t += 1;
1090 core::hint::spin_loop();
1091 }
1092 wr(op(0x18), (ASYNC_HEAD as u32) & !0x1F);
1093 wr(op(0x14), (FRAMELIST as u32) & !0x3FF);
1094 wr(op(OPCFGFLAG), 1);
1095 let np = N_PORTS.load(Ordering::Relaxed);
1096 for p in 0..np as usize {
1097 let psc = prtsc(p);
1098 let v = rd(psc);
1099 if v & PRT_POWER == 0 {
1100 wr(psc, v | PRT_POWER);
1101 }
1102 }
1103 wr(op(OPSTS), 0x1F);
1104 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN);
1105 t = 0;
1106 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) != 0 {
1107 t += 1;
1108 core::hint::spin_loop();
1109 }
1110 }
1111}
1112
1113unsafe fn debug_xfer_matrix(port: usize) {
1114 unsafe {
1115 QR_SHOWN.store(true, Ordering::Relaxed); // serial-only diagnostics
1116 crate::serial_println!(
1117 "ehci: ===== DEBUG xfer variant matrix (port {}) =====",
1118 port
1119 );
1120 let tweaks: [u32; 7] = [0, 1, 2, 4, 8, 0x20, 0x80];
1121 let names = [
1122 "base",
1123 "no-ASE",
1124 "no-split",
1125 "no-qtd",
1126 "len0",
1127 "wire-off",
1128 "low-region",
1129 ];
1130 let ebase = EHCI_BASE.load(Ordering::Relaxed) as usize;
1131 for (i, &tw) in tweaks.iter().enumerate() {
1132 hc_restart();
1133 let _hs = reset_port(port);
1134 let req: [u8; 8] = [0x80, 0x06, 0x00, 0x01, 0, 0, 0x12, 0]; // get_desc dev, 18B
1135 let mut buf = [0u8; 18];
1136 XFER_TWEAK.store(tw | 0x40, Ordering::Relaxed); // + short timeout
1137 let t0 = crate::interrupts::uptime_ms();
1138 let r = ctl_xfer(0, 8, 0, port, &req, true, buf.as_mut_ptr(), 18);
1139 let dt = crate::interrupts::uptime_ms().wrapping_sub(t0);
1140 crate::serial_println!(
1141 "ehci: v{} {:<10}: ret={} t={}ms sts={:#x} cmd={:#x} prtsc0={:#x} hp={:#x}",
1142 i,
1143 names[i],
1144 r,
1145 dt,
1146 rd(op(OPSTS)),
1147 rd(op(OPCMD)),
1148 rd(prtsc(0)),
1149 rd(ebase + 0x84)
1150 );
1151 XFER_TWEAK.store(0, Ordering::Relaxed);
1152 let w0 = crate::interrupts::uptime_ms();
1153 while crate::interrupts::uptime_ms().wrapping_sub(w0) < 600 {
1154 core::hint::spin_loop();
1155 }
1156 }
1157 crate::serial_println!("ehci: ===== matrix done =====");
1158 }
1159}
1160
1161// ---------------------------------------------------------------------------
1162// Active interface (called by cursor task / shell)
1163// ---------------------------------------------------------------------------
1164
1165pub fn controller_up() -> bool {
1166 CTRL_UP.load(Ordering::Relaxed)
1167}
1168pub fn mouse_present() -> bool {
1169 MOUSE_PRES.load(Ordering::Relaxed)
1170}
1171
1172pub fn poll_mouse() -> Option<MouseEvent> {
1173 unsafe {
1174 // Hot path first. The qtd's ACTIVE bit (in controller-visible DMA
1175 // memory) is the ground truth for "report outstanding"; a kernel
1176 // static must never gate report delivery.
1177 if ARMED.load(Ordering::Relaxed) {
1178 let hq = HID_QH;
1179 let iqtd = hq.add(0x40 / 4); // same 32-byte-aligned location as arming
1180 let tok = core::ptr::read_volatile(iqtd.wrapping_add(2).cast::<u32>());
1181 if tok & TOK_ACTIVE == 0 {
1182 if tok & (TOK_HALT | TOK_XACT) != 0 {
1183 // Transfer error: drop the arm; a bounded re-enum follows.
1184 ARMED.store(false, Ordering::Relaxed);
1185 return None;
1186 }
1187 // Completed: decode the report from the dedicated DMA page.
1188 let hb = BUF_PAGE.add(BUF_HID_REPORT) as *const u8;
1189 let b0 = core::ptr::read_volatile(hb);
1190 let b1 = core::ptr::read_volatile(hb.add(1));
1191 let b2 = core::ptr::read_volatile(hb.add(2));
1192 let ev = Some(MouseEvent {
1193 dx: (b1 as i8) as i16,
1194 dy: (b2 as i8) as i16,
1195 left: b0 & 0x01 != 0,
1196 right: b0 & 0x02 != 0,
1197 middle: b0 & 0x04 != 0,
1198 });
1199 // Re-arm the periodic interrupt-IN qtd: restore its token to
1200 // ACTIVE and re-link it as the QH's next qtd so the HC fetches
1201 // it again next frame (canonical one-qtd periodic re-arm).
1202 core::ptr::write_volatile(
1203 iqtd.wrapping_add(2),
1204 ((HID_MPS.load(Ordering::Relaxed).max(4) & 0x7fff) << 16)
1205 | (PID_IN << 8)
1206 | TOK_ACTIVE
1207 | TOK_IOC,
1208 );
1209 *qh_next_qtd(hq) = iqtd as u32;
1210 return ev;
1211 }
1212 return None;
1213 }
1214 // Not armed: bring the mouse up lazily, but never hot-loop — at most
1215 // one enumerate attempt per second, and only while the controller is
1216 // up. A transient glitch can delay a report; it must not wedge the
1217 // system in a re-enumeration loop.
1218 if CTRL_UP.load(Ordering::Relaxed) {
1219 let now = crate::interrupts::uptime_ms() as u32;
1220 if now.wrapping_sub(LAST_ENUM.load(Ordering::Relaxed)) >= 1000 {
1221 LAST_ENUM.store(now, Ordering::Relaxed);
1222 enumerate();
1223 }
1224 }
1225 }
1226 None
1227}
1228
1229/// Allocate DMA-safe memory above 16 MB. Uses the page allocator
1230/// (physical, identity-mapped addresses) instead of the heap allocator
1231/// (virtual addresses) because the EHCI controller's DMA engine needs
1232/// physical addresses that match what the CPU sees. The 16 MB floor
1233/// keeps the schedule structures out of the first 640 K, which some real
1234/// chipsets reject from PCI masters (controller System Error halt).
1235unsafe fn dma_alloc(bytes: usize, _align: usize) -> *mut u8 {
1236 unsafe {
1237 let pages = bytes.div_ceil(4096);
1238 let Some(phys) = crate::memory::alloc_pages_above(pages, 0x100_0000) else {
1239 return core::ptr::null_mut();
1240 };
1241 let p = phys as *mut u8;
1242 core::ptr::write_bytes(p, 0, pages * 4096);
1243 p
1244 }
1245}
1246
1247/// One-line status for the shell `usb` command.
1248pub fn diagnose() -> alloc::string::String {
1249 let mut s = alloc::string::String::new();
1250 s.push_str(&alloc::format!(
1251 "ehci mmio={:#x} ports={} mouse={} dev={} hid_ep={} armed={}\n",
1252 EHCI_BASE.load(Ordering::Relaxed),
1253 N_PORTS.load(Ordering::Relaxed),
1254 if MOUSE_PRES.load(Ordering::Relaxed) {
1255 "yes"
1256 } else {
1257 "no"
1258 },
1259 DEV_ADDR.load(Ordering::Relaxed),
1260 HID_EP.load(Ordering::Relaxed),
1261 ARMED.load(Ordering::Relaxed)
1262 ));
1263 let np = N_PORTS.load(Ordering::Relaxed) as usize;
1264 for p in 0..np.min(8) {
1265 let v = unsafe { rd(prtsc(p)) };
1266 s.push_str(&alloc::format!(
1267 " eport{}: {}{:#x}\n",
1268 p,
1269 if v & PRT_CONNECT != 0 { "*" } else { "" },
1270 v & 0xffff
1271 ));
1272 }
1273 s
1274}