summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 16:48:40 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 16:48:40 +0100
commit12549dabf96bd6eb08c6834134fedf9edc7452b5 (patch)
tree2237ed1a05b731f0e1a39c2374440cec3c872258
parentscripts: add test-usb; fix duplicate -bios in the test harness (diff)
downloadrs-dos-12549dabf96bd6eb08c6834134fedf9edc7452b5.tar.gz
rs-dos-12549dabf96bd6eb08c6834134fedf9edc7452b5.zip
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.
-rw-r--r--DEBUG_NOTES.md33
-rw-r--r--rsnix/src/ehci.rs39
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:
631- xHCI: no hub support; no USB3 warm-reset subtleties beyond the current 631- xHCI: no hub support; no USB3 warm-reset subtleties beyond the current
632 port-reset sequence. 632 port-reset sequence.
633- AHCI/PS2 behaviour unchanged (PS/2 remains the fallback input path). 633- AHCI/PS2 behaviour unchanged (PS/2 remains the fallback input path).
634
635### OptiPlex 7010 — first hardware QR decode
636
637The first hardware run booted to the shell with no USB input and displayed
638the EHCI failure QR:
639
640```
641EHCI fail stage=4 t=20ms sp=0 dev=0 in=true len=18
642cmd=00000020 sts=0000b013
643prtsc0=0x00001005 hp=0x00003000
644qh1=48084000 qh2=40800000 nqtd=012080c0
645setup=00080e80 data=80120d80 stat=80008c80
646```
647
648- `sts` bit4 = **HSE (Host System Error)** plus HCH: the controller aborted
649 the very first DMA pass after 20 ms, so this is a bus/DMA fault, not a
650 protocol error.
651- `cmd=0x20`: RUNSTOP self-cleared, only ASE remains.
652- `qh1/qh2/qTD tokens` are structurally correct: control QH, DTC, MPS 8,
653 split hub 0 / port 1, qTD active with TBYTES.
654- `prtsc0` PED=0: full-speed device on the root port (companion/TT path).
655
656**Root cause: 64-bit addressing capability.** The PCH EHCI advertises
657HCCPARAMS bit0, which extends every QH with `bufptr_hi[5]` (68 B) and every
658qTD with `bufptr_hi[5]` (52 B). The driver packed them 32-bit-style (48/32
659B), so the HC read the *next qTD* as the overlay's high buffer pointer and
660DMA'd to a bogus 64-bit address -> HSE. QEMU reports `caps64=false`, so only
661hardware exposed this.
662
663**Fix:** always reserve the 64-bit layout (32-bit controllers ignore the
664tail): HID qTD at QH+0x80; control qTDs at +0x100/+0x140/+0x180 and the setup
665packet at +0x1C0. The failure QR now prints `c64=` and the DMA bases
666(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
77const DMA_MIN: usize = 0x100_0000; // avoid <16 MiB (PCI-master hostile region) 77const DMA_MIN: usize = 0x100_0000; // avoid <16 MiB (PCI-master hostile region)
78const DMA_MAX: usize = 0x1_0000_0000; // 32-bit EHCI DMA pointers 78const DMA_MAX: usize = 0x1_0000_0000; // 32-bit EHCI DMA pointers
79 79
80// Descriptor layout. A controller with HCCPARAMS bit0 (64-bit addressing)
81// reads an extra bufptr_hi[5] after the 32-bit buffer list: QH = 17 dwords
82// (68 B), qTD = 13 dwords (52 B). Space every structure for that variant —
83// 32-bit controllers simply ignore the tail — otherwise the HC parses the
84// next qTD as a high buffer pointer and dies with a Host System Error.
85const QTD_SLOT: usize = 0x40; // 64-byte qTD slots (52 B + padding)
86const CTRL_QTD_OFF: usize = 0x100; // control qTDs, offset from the control QH
87const HID_QTD_OFF: usize = 0x80; // HID qTD, offset from the HID QH
88
80const BUF_DEV_DESC: usize = 0x000; // 64 B 89const BUF_DEV_DESC: usize = 0x000; // 64 B
81const BUF_CFG: usize = 0x100; // 512 B (hubs can have long configs) 90const BUF_CFG: usize = 0x100; // 512 B (hubs can have long configs)
82const BUF_HUB_DESC: usize = 0x300; // 64 B 91const BUF_HUB_DESC: usize = 0x300; // 64 B
@@ -94,6 +103,7 @@ struct Ctrl {
94 framelist: *mut u32, 103 framelist: *mut u32,
95 qh_page: *mut u32, 104 qh_page: *mut u32,
96 buf_page: *mut u8, 105 buf_page: *mut u8,
106 caps64: bool,
97} 107}
98 108
99impl Ctrl { 109impl Ctrl {
@@ -106,6 +116,7 @@ impl Ctrl {
106 framelist: core::ptr::null_mut(), 116 framelist: core::ptr::null_mut(),
107 qh_page: core::ptr::null_mut(), 117 qh_page: core::ptr::null_mut(),
108 buf_page: core::ptr::null_mut(), 118 buf_page: core::ptr::null_mut(),
119 caps64: false,
109 }; 120 };
110} 121}
111 122
@@ -282,9 +293,9 @@ unsafe fn ctl_xfer(
282 let head = cur_ctrl().async_head; 293 let head = cur_ctrl().async_head;
283 let xfer = cur_ctrl().xfer_qh; 294 let xfer = cur_ctrl().xfer_qh;
284 295
285 let st = xfer.add(0x40 / 4); // SETUP qtd (32-byte aligned) 296 let st = xfer.add(CTRL_QTD_OFF / 4); // SETUP qtd (32-byte aligned)
286 let dt = st.add(0x20 / 4); // DATA qtd 297 let dt = st.add(QTD_SLOT / 4); // DATA qtd
287 let sp = dt.add(0x20 / 4); // STATUS qtd 298 let sp = dt.add(QTD_SLOT / 4); // STATUS qtd
288 299
289 core::ptr::write_volatile(st.add(0), 0u32); 300 core::ptr::write_volatile(st.add(0), 0u32);
290 core::ptr::write_volatile(st.add(1), QT_TERMINATE); 301 core::ptr::write_volatile(st.add(1), QT_TERMINATE);
@@ -295,7 +306,7 @@ unsafe fn ctl_xfer(
295 for i in 0..5 { 306 for i in 0..5 {
296 core::ptr::write_volatile(st.add(3 + i), 0u32); 307 core::ptr::write_volatile(st.add(3 + i), 0u32);
297 } 308 }
298 let sbuf = xfer.add(0x110 / 4); 309 let sbuf = xfer.add(0x1C0 / 4);
299 let sb = core::slice::from_raw_parts_mut(sbuf.cast::<u8>(), 8); 310 let sb = core::slice::from_raw_parts_mut(sbuf.cast::<u8>(), 8);
300 sb.copy_from_slice(req); 311 sb.copy_from_slice(req);
301 core::ptr::write_volatile(st.add(3), sbuf as u32); 312 core::ptr::write_volatile(st.add(3), sbuf as u32);
@@ -434,13 +445,14 @@ unsafe fn ctl_xfer(
434 ); 445 );
435 if !QR_SHOWN.swap(true, Ordering::Relaxed) { 446 if !QR_SHOWN.swap(true, Ordering::Relaxed) {
436 let l1 = alloc::format!( 447 let l1 = alloc::format!(
437 "EHCI fail stage={} t={}ms sp={} dev={} in={} len={}", 448 "EHCI fail stage={} t={}ms sp={} dev={} in={} len={} c64={}",
438 stage, 449 stage,
439 crate::interrupts::uptime_ms().wrapping_sub(t0), 450 crate::interrupts::uptime_ms().wrapping_sub(t0),
440 speed, 451 speed,
441 dev, 452 dev,
442 dir_in, 453 dir_in,
443 len 454 len,
455 cur_ctrl().caps64 as u8
444 ); 456 );
445 let l2 = alloc::format!("cmd={:08x} sts={:08x}", rd(op(OPCMD)), rd(op(OPSTS))); 457 let l2 = alloc::format!("cmd={:08x} sts={:08x}", rd(op(OPCMD)), rd(op(OPSTS)));
446 let l3 = alloc::format!( 458 let l3 = alloc::format!(
@@ -456,7 +468,14 @@ unsafe fn ctl_xfer(
456 core::ptr::read_volatile(qh_next_qtd(xfer)) 468 core::ptr::read_volatile(qh_next_qtd(xfer))
457 ); 469 );
458 let l5 = alloc::format!("setup={:08x} data={:08x} stat={:08x}", stok, dtok, ptok); 470 let l5 = alloc::format!("setup={:08x} data={:08x} stat={:08x}", stok, dtok, ptok);
459 let all = [l1, l2, l3, l4, l5]; 471 let l6 = alloc::format!(
472 "dma a={:08x} x={:08x} s={:08x} b={:08x}",
473 cur_ctrl().async_head as usize,
474 xfer as usize,
475 sbuf as usize,
476 cur_ctrl().buf_page as usize
477 );
478 let all = [l1, l2, l3, l4, l5, l6];
460 let refs: alloc::vec::Vec<&str> = all.iter().map(|s| s.as_str()).collect(); 479 let refs: alloc::vec::Vec<&str> = all.iter().map(|s| s.as_str()).collect();
461 crate::gop::dump_qr(&refs); 480 crate::gop::dump_qr(&refs);
462 crate::gop::qr_lock(); 481 crate::gop::qr_lock();
@@ -654,7 +673,7 @@ unsafe fn register_hid(
654 } 673 }
655 let c = ctrl_mut(ci); 674 let c = ctrl_mut(ci);
656 let qh = c.qh_page.add(slot * 0x100 / 4); 675 let qh = c.qh_page.add(slot * 0x100 / 4);
657 let iqtd = qh.add(0x40 / 4); 676 let iqtd = qh.add(HID_QTD_OFF / 4);
658 let report = c.buf_page.add(BUF_REPORT + slot * 64); 677 let report = c.buf_page.add(BUF_REPORT + slot * 64);
659 core::ptr::write_bytes(qh as *mut u8, 0, 0x100); 678 core::ptr::write_bytes(qh as *mut u8, 0, 0x100);
660 core::ptr::write_bytes(report, 0, 64); 679 core::ptr::write_bytes(report, 0, 64);
@@ -981,6 +1000,7 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
981 let caplen = (rd(base) & 0xff) as usize; 1000 let caplen = (rd(base) & 0xff) as usize;
982 let opb = base + caplen; 1001 let opb = base + caplen;
983 let nports = rd(base + 4) & 0xf; 1002 let nports = rd(base + 4) & 0xf;
1003 let caps64 = (rd(base + 8) & 1) != 0;
984 crate::serial_println!( 1004 crate::serial_println!(
985 "ehci: {:02x}:{:02x}.{} mmio={:#x} caplen={:#x} ports={} 64-bit-caps={}", 1005 "ehci: {:02x}:{:02x}.{} mmio={:#x} caplen={:#x} ports={} 64-bit-caps={}",
986 bus, 1006 bus,
@@ -989,7 +1009,7 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
989 base, 1009 base,
990 caplen, 1010 caplen,
991 nports, 1011 nports,
992 (rd(base + 8) & 1) != 0 1012 caps64
993 ); 1013 );
994 1014
995 // BIOS legacy ownership: resetting the controller while SMM owns it 1015 // 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 {
1006 c.base = base; 1026 c.base = base;
1007 c.opb = opb; 1027 c.opb = opb;
1008 c.nports = nports; 1028 c.nports = nports;
1029 c.caps64 = caps64;
1009 } 1030 }
1010 1031
1011 // Halt, reset, wait for reset to clear. 1032 // Halt, reset, wait for reset to clear.