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. --- rsnix/src/ehci.rs | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) (limited to 'rsnix') 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