From 12549dabf96bd6eb08c6834134fedf9edc7452b5 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 16:48:40 +0100 Subject: ehci: reserve the 64-bit QH/qTD layout (fixes hardware HSE) First OptiPlex run: the controller halted with HSE (USBSTS bit4) 20 ms into the first control transfer, with otherwise valid QH/qTD fields (control QH, DTC, MPS 8, split hub 0/port 1, active qTD). The PCH EHCI advertises HCCPARAMS bit0 (64-bit addressing capability), which extends every QH with bufptr_hi[5] (17 dwords / 68 B) and every qTD the same way (13 dwords / 52 B). Packing them 32-bit-style (48/32 B) made the controller read the next qTD as the overlay's high buffer pointer and DMA to a bogus 64-bit address -> Host System Error. Space every structure for the 64-bit variant (32-bit controllers ignore the tail): HID qTD at QH+0x80; control qTDs at QH+0x100/0x140/0x180 with the setup packet at +0x1C0. The failure QR now reports the capability and the DMA bases so the next hardware iteration is self-describing. --- DEBUG_NOTES.md | 33 +++++++++++++++++++++++++++++++++ rsnix/src/ehci.rs | 39 ++++++++++++++++++++++++++++++--------- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/DEBUG_NOTES.md b/DEBUG_NOTES.md index b7c0e08..cd2b9ae 100644 --- a/DEBUG_NOTES.md +++ b/DEBUG_NOTES.md @@ -631,3 +631,36 @@ Failure signatures to report: - xHCI: no hub support; no USB3 warm-reset subtleties beyond the current port-reset sequence. - AHCI/PS2 behaviour unchanged (PS/2 remains the fallback input path). + +### OptiPlex 7010 — first hardware QR decode + +The first hardware run booted to the shell with no USB input and displayed +the EHCI failure QR: + +``` +EHCI fail stage=4 t=20ms sp=0 dev=0 in=true len=18 +cmd=00000020 sts=0000b013 +prtsc0=0x00001005 hp=0x00003000 +qh1=48084000 qh2=40800000 nqtd=012080c0 +setup=00080e80 data=80120d80 stat=80008c80 +``` + +- `sts` bit4 = **HSE (Host System Error)** plus HCH: the controller aborted + the very first DMA pass after 20 ms, so this is a bus/DMA fault, not a + protocol error. +- `cmd=0x20`: RUNSTOP self-cleared, only ASE remains. +- `qh1/qh2/qTD tokens` are structurally correct: control QH, DTC, MPS 8, + split hub 0 / port 1, qTD active with TBYTES. +- `prtsc0` PED=0: full-speed device on the root port (companion/TT path). + +**Root cause: 64-bit addressing capability.** The PCH EHCI advertises +HCCPARAMS bit0, which extends every QH with `bufptr_hi[5]` (68 B) and every +qTD with `bufptr_hi[5]` (52 B). The driver packed them 32-bit-style (48/32 +B), so the HC read the *next qTD* as the overlay's high buffer pointer and +DMA'd to a bogus 64-bit address -> HSE. QEMU reports `caps64=false`, so only +hardware exposed this. + +**Fix:** always reserve the 64-bit layout (32-bit controllers ignore the +tail): HID qTD at QH+0x80; control qTDs at +0x100/+0x140/+0x180 and the setup +packet at +0x1C0. The failure QR now prints `c64=` and the DMA bases +(async/xfer/setup/buf) so the next run is self-describing. diff --git a/rsnix/src/ehci.rs b/rsnix/src/ehci.rs index 5ee11cf..0aa7e0d 100644 --- a/rsnix/src/ehci.rs +++ b/rsnix/src/ehci.rs @@ -77,6 +77,15 @@ const MAX_DEV: usize = 4; // mouse + keyboard on each controller const DMA_MIN: usize = 0x100_0000; // avoid <16 MiB (PCI-master hostile region) const DMA_MAX: usize = 0x1_0000_0000; // 32-bit EHCI DMA pointers +// Descriptor layout. A controller with HCCPARAMS bit0 (64-bit addressing) +// reads an extra bufptr_hi[5] after the 32-bit buffer list: QH = 17 dwords +// (68 B), qTD = 13 dwords (52 B). Space every structure for that variant — +// 32-bit controllers simply ignore the tail — otherwise the HC parses the +// next qTD as a high buffer pointer and dies with a Host System Error. +const QTD_SLOT: usize = 0x40; // 64-byte qTD slots (52 B + padding) +const CTRL_QTD_OFF: usize = 0x100; // control qTDs, offset from the control QH +const HID_QTD_OFF: usize = 0x80; // HID qTD, offset from the HID QH + const BUF_DEV_DESC: usize = 0x000; // 64 B const BUF_CFG: usize = 0x100; // 512 B (hubs can have long configs) const BUF_HUB_DESC: usize = 0x300; // 64 B @@ -94,6 +103,7 @@ struct Ctrl { framelist: *mut u32, qh_page: *mut u32, buf_page: *mut u8, + caps64: bool, } impl Ctrl { @@ -106,6 +116,7 @@ impl Ctrl { framelist: core::ptr::null_mut(), qh_page: core::ptr::null_mut(), buf_page: core::ptr::null_mut(), + caps64: false, }; } @@ -282,9 +293,9 @@ unsafe fn ctl_xfer( let head = cur_ctrl().async_head; let xfer = cur_ctrl().xfer_qh; - let st = xfer.add(0x40 / 4); // SETUP qtd (32-byte aligned) - let dt = st.add(0x20 / 4); // DATA qtd - let sp = dt.add(0x20 / 4); // STATUS qtd + let st = xfer.add(CTRL_QTD_OFF / 4); // SETUP qtd (32-byte aligned) + let dt = st.add(QTD_SLOT / 4); // DATA qtd + let sp = dt.add(QTD_SLOT / 4); // STATUS qtd core::ptr::write_volatile(st.add(0), 0u32); core::ptr::write_volatile(st.add(1), QT_TERMINATE); @@ -295,7 +306,7 @@ unsafe fn ctl_xfer( for i in 0..5 { core::ptr::write_volatile(st.add(3 + i), 0u32); } - let sbuf = xfer.add(0x110 / 4); + let sbuf = xfer.add(0x1C0 / 4); let sb = core::slice::from_raw_parts_mut(sbuf.cast::(), 8); sb.copy_from_slice(req); core::ptr::write_volatile(st.add(3), sbuf as u32); @@ -434,13 +445,14 @@ unsafe fn ctl_xfer( ); if !QR_SHOWN.swap(true, Ordering::Relaxed) { let l1 = alloc::format!( - "EHCI fail stage={} t={}ms sp={} dev={} in={} len={}", + "EHCI fail stage={} t={}ms sp={} dev={} in={} len={} c64={}", stage, crate::interrupts::uptime_ms().wrapping_sub(t0), speed, dev, dir_in, - len + len, + cur_ctrl().caps64 as u8 ); let l2 = alloc::format!("cmd={:08x} sts={:08x}", rd(op(OPCMD)), rd(op(OPSTS))); let l3 = alloc::format!( @@ -456,7 +468,14 @@ unsafe fn ctl_xfer( core::ptr::read_volatile(qh_next_qtd(xfer)) ); let l5 = alloc::format!("setup={:08x} data={:08x} stat={:08x}", stok, dtok, ptok); - let all = [l1, l2, l3, l4, l5]; + let l6 = alloc::format!( + "dma a={:08x} x={:08x} s={:08x} b={:08x}", + cur_ctrl().async_head as usize, + xfer as usize, + sbuf as usize, + cur_ctrl().buf_page as usize + ); + let all = [l1, l2, l3, l4, l5, l6]; let refs: alloc::vec::Vec<&str> = all.iter().map(|s| s.as_str()).collect(); crate::gop::dump_qr(&refs); crate::gop::qr_lock(); @@ -654,7 +673,7 @@ unsafe fn register_hid( } let c = ctrl_mut(ci); let qh = c.qh_page.add(slot * 0x100 / 4); - let iqtd = qh.add(0x40 / 4); + let iqtd = qh.add(HID_QTD_OFF / 4); let report = c.buf_page.add(BUF_REPORT + slot * 64); core::ptr::write_bytes(qh as *mut u8, 0, 0x100); core::ptr::write_bytes(report, 0, 64); @@ -981,6 +1000,7 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool { let caplen = (rd(base) & 0xff) as usize; let opb = base + caplen; let nports = rd(base + 4) & 0xf; + let caps64 = (rd(base + 8) & 1) != 0; crate::serial_println!( "ehci: {:02x}:{:02x}.{} mmio={:#x} caplen={:#x} ports={} 64-bit-caps={}", bus, @@ -989,7 +1009,7 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool { base, caplen, nports, - (rd(base + 8) & 1) != 0 + caps64 ); // BIOS legacy ownership: resetting the controller while SMM owns it @@ -1006,6 +1026,7 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool { c.base = base; c.opb = opb; c.nports = nports; + c.caps64 = caps64; } // Halt, reset, wait for reset to clear. -- cgit v1.2.3