summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 16:55:14 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 16:55:14 +0100
commite53161d6b9191533caeea0ef6148f247f4e22441 (patch)
tree4b6b251fe9fccca6520f9c0efffb3bb6c06495e3
parentehci: reserve the 64-bit QH/qTD layout (fixes hardware HSE) (diff)
downloadrs-dos-e53161d6b9191533caeea0ef6148f247f4e22441.tar.gz
rs-dos-e53161d6b9191533caeea0ef6148f247f4e22441.zip
ehci: diagnostics + hardening for the port-not-enabled case
Second OptiPlex QR: the Host System Error is gone (the 64-bit QH/qTD layout fix worked), but the SETUP qTD now halts with TBYTES=0, C_ERR=3 and no XactErr/Babble/DBE -- a split transaction with no valid response. PORTSC shows CCS=1, PP=1 but PED=0, i.e. the port never enabled, so the split has no TT to address. On Intel PCH a healthy USB2 port shows the rate-matching hub as high-speed (PED=1), so the next step is to see every port's state. - settle 100 ms after powering ports and clear stale change bits before the reset walk (RMHs come up slower than direct devices) - retry the port reset once after settling - log a clear line when a connected port remains disabled - retry a failed full-speed device as low-speed (the PORTSC speed field is only valid once the port is enabled) - persistent bottom-row summary: controller count, caps64, USB legacy handoff result, every connected port's PORTSC and the HID device count. A hardware run without serial now reports the whole picture instead of one QR.
-rw-r--r--DEBUG_NOTES.md35
-rw-r--r--rsnix/src/ehci.rs121
2 files changed, 144 insertions, 12 deletions
diff --git a/DEBUG_NOTES.md b/DEBUG_NOTES.md
index cd2b9ae..c9efdc1 100644
--- a/DEBUG_NOTES.md
+++ b/DEBUG_NOTES.md
@@ -664,3 +664,38 @@ hardware exposed this.
664tail): HID qTD at QH+0x80; control qTDs at +0x100/+0x140/+0x180 and the setup 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 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. 666(async/xfer/setup/buf) so the next run is self-describing.
667
668### OptiPlex 7010 — second hardware QR (after the 64-bit layout fix)
669
670```
671EHCI fail stage=1 t=20ms sp=0 dev=0 in=true len=18 c64=1
672cmd=00000021 sts=00008003
673prtsc0=0x00001005 hp=0x00003000
674qh1=48084000 qh2=40800000 nqtd=012081c0
675setup=00080e42 data=80120d80 stat=80008c80
676dma a=01208000 x=01208080 s=01208240 b=0120b000
677```
678
679- `sts=0x8003`: USBINT|USBERRINT|ASS — **no HSE**, the 64-bit QH/qTD layout is
680 fixed and the controller runs.
681- `stage=1`: the **SETUP qTD halted**, `setup=0x00080e42` -> TBYTES 0,
682 Halted, C_ERR still 3, no XactErr/Babble/DBE (a split that got no valid
683 response).
684- `prtsc0=0x1005`: device connected + powered, **PED=0** (port never
685 enabled), speed field reads full-speed.
686
687On Intel PCH the USB2 physical ports sit behind the integrated Rate Matching
688Hub, so a healthy root port shows the hub as a **high-speed** device
689(`PED=1`). PED=0 with a connected device means the port did not enable, and
690a full/low-speed split has no valid TT to talk to -> SETUP halts.
691
692Follow-up hardening in the driver:
693- 100 ms power-up settle (RMHs are slow) and stale port-change bits cleared
694 before the reset walk
695- one reset retry after a settle
696- explicit log line when a connected port stays disabled
697- low-speed retry for a failed full-speed device (the PORTSC speed field is
698 only trustworthy once the port is enabled)
699- persistent bottom-row summary: per controller `64b=`/`leg=` handoff result,
700 every connected port's PORTSC, and the HID device count — hardware runs
701 without serial now show the whole picture, not just the first QR
diff --git a/rsnix/src/ehci.rs b/rsnix/src/ehci.rs
index 0aa7e0d..2d16d37 100644
--- a/rsnix/src/ehci.rs
+++ b/rsnix/src/ehci.rs
@@ -104,6 +104,7 @@ struct Ctrl {
104 qh_page: *mut u32, 104 qh_page: *mut u32,
105 buf_page: *mut u8, 105 buf_page: *mut u8,
106 caps64: bool, 106 caps64: bool,
107 legacy: u32, // USB legacy handoff result (0 released, 1 handed over, 2 timeout, 3 none)
107} 108}
108 109
109impl Ctrl { 110impl Ctrl {
@@ -117,6 +118,7 @@ impl Ctrl {
117 qh_page: core::ptr::null_mut(), 118 qh_page: core::ptr::null_mut(),
118 buf_page: core::ptr::null_mut(), 119 buf_page: core::ptr::null_mut(),
119 caps64: false, 120 caps64: false,
121 legacy: 3,
120 }; 122 };
121} 123}
122 124
@@ -445,7 +447,8 @@ unsafe fn ctl_xfer(
445 ); 447 );
446 if !QR_SHOWN.swap(true, Ordering::Relaxed) { 448 if !QR_SHOWN.swap(true, Ordering::Relaxed) {
447 let l1 = alloc::format!( 449 let l1 = alloc::format!(
448 "EHCI fail stage={} t={}ms sp={} dev={} in={} len={} c64={}", 450 "EHCI c{} fail stage={} t={}ms sp={} dev={} in={} len={} c64={}",
451 CUR.load(Ordering::Relaxed),
449 stage, 452 stage,
450 crate::interrupts::uptime_ms().wrapping_sub(t0), 453 crate::interrupts::uptime_ms().wrapping_sub(t0),
451 speed, 454 speed,
@@ -1014,12 +1017,24 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
1014 1017
1015 // BIOS legacy ownership: resetting the controller while SMM owns it 1018 // BIOS legacy ownership: resetting the controller while SMM owns it
1016 // kills the firmware keyboard path and can collide with our DMA. 1019 // kills the firmware keyboard path and can collide with our DMA.
1017 match crate::pci::claim_usb_legacy(bus, dev, func, base, 0x08, false) { 1020 let legacy = match crate::pci::claim_usb_legacy(bus, dev, func, base, 0x08, false) {
1018 0 => crate::serial_println!("ehci: USB legacy: already released"), 1021 0 => {
1019 1 => crate::serial_println!("ehci: USB legacy handed over by BIOS"), 1022 crate::serial_println!("ehci: USB legacy: already released");
1020 2 => crate::serial_println!("ehci: USB legacy claim timed out"), 1023 0
1021 _ => crate::serial_println!("ehci: USB legacy: no capability"), 1024 }
1022 } 1025 1 => {
1026 crate::serial_println!("ehci: USB legacy handed over by BIOS");
1027 1
1028 }
1029 2 => {
1030 crate::serial_println!("ehci: USB legacy claim timed out");
1031 2
1032 }
1033 _ => {
1034 crate::serial_println!("ehci: USB legacy: no capability");
1035 3
1036 }
1037 };
1023 1038
1024 { 1039 {
1025 let c = ctrl_mut(ci); 1040 let c = ctrl_mut(ci);
@@ -1027,6 +1042,7 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
1027 c.opb = opb; 1042 c.opb = opb;
1028 c.nports = nports; 1043 c.nports = nports;
1029 c.caps64 = caps64; 1044 c.caps64 = caps64;
1045 c.legacy = legacy;
1030 } 1046 }
1031 1047
1032 // Halt, reset, wait for reset to clear. 1048 // Halt, reset, wait for reset to clear.
@@ -1112,7 +1128,17 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
1112 wr(psc, v | PRT_POWER); 1128 wr(psc, v | PRT_POWER);
1113 } 1129 }
1114 } 1130 }
1115 sleep_ms(20); 1131 // Rate-matching hubs need noticeably longer than a direct device to
1132 // come up after port power; give them time and clear stale change
1133 // bits before the reset walk.
1134 sleep_ms(100);
1135 for p in 0..nports as usize {
1136 let psc = prtsc(p);
1137 let v = rd(psc);
1138 if v & PRT_RWC != 0 {
1139 wr(psc, v | PRT_RWC); // change bits are write-1-to-clear
1140 }
1141 }
1116 crate::serial_println!("ehci: controller up ({} ports)", nports); 1142 crate::serial_println!("ehci: controller up ({} ports)", nports);
1117 1143
1118 N_CTRL.store((ci + 1) as u32, Ordering::Relaxed); 1144 N_CTRL.store((ci + 1) as u32, Ordering::Relaxed);
@@ -1124,7 +1150,14 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
1124 if v & PRT_CONNECT == 0 { 1150 if v & PRT_CONNECT == 0 {
1125 continue; 1151 continue;
1126 } 1152 }
1127 let hs = reset_port(p); 1153 let mut hs = reset_port(p);
1154 if !hs {
1155 // A device behind a rate-matching hub appears as high-speed;
1156 // if the first reset did not enable the port the hub may
1157 // still have been settling — retry once.
1158 sleep_ms(100);
1159 hs = reset_port(p);
1160 }
1128 if !hs && crate::uhci::controller_up() { 1161 if !hs && crate::uhci::controller_up() {
1129 crate::serial_println!( 1162 crate::serial_println!(
1130 "ehci: port {} full/low-speed — owned by companion UHCI", 1163 "ehci: port {} full/low-speed — owned by companion UHCI",
@@ -1133,6 +1166,13 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
1133 continue; 1166 continue;
1134 } 1167 }
1135 let speed = if hs { 2 } else { port_speed(p) }; 1168 let speed = if hs { 2 } else { port_speed(p) };
1169 if !hs {
1170 crate::serial_println!(
1171 "ehci: port {} still not enabled (portsc={:#x}) — full/low-speed without a root TT (RMH not up?)",
1172 p,
1173 rd(prtsc(p))
1174 );
1175 }
1136 crate::serial_println!( 1176 crate::serial_println!(
1137 "ehci: port {} connected ({}{})", 1177 "ehci: port {} connected ({}{})",
1138 p, 1178 p,
@@ -1151,7 +1191,16 @@ unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
1151 port: (p + 1) as u8, 1191 port: (p + 1) as u8,
1152 }) 1192 })
1153 }; 1193 };
1154 enumerate(0, speed, tt, p); 1194 let mut found = enumerate(0, speed, tt, p);
1195 if found == 0 && !hs && speed == 0 {
1196 // The PORTSC speed field is only trustworthy once the port is
1197 // enabled; a low-speed device serviced as full-speed halts the
1198 // SETUP split. Retry once as low-speed.
1199 crate::serial_println!("ehci: port {} retrying as low-speed", p);
1200 sleep_ms(20);
1201 found = enumerate(0, 1, tt, p);
1202 }
1203 let _ = found;
1155 } 1204 }
1156 1205
1157 arm_periodic(ci); 1206 arm_periodic(ci);
@@ -1217,14 +1266,21 @@ unsafe fn reset_port(p: usize) -> bool {
1217} 1266}
1218 1267
1219fn port_speed(p: usize) -> u32 { 1268fn port_speed(p: usize) -> u32 {
1269 // Standard PORTSC speed field first (0=FS, 1=LS, 2=HS); PED is 0 for
1270 // FS/LS devices, where the field is technically only "valid" when the
1271 // port is enabled, so fall back to Intel's HOSTPC copy.
1272 let ps = unsafe { (rd(prtsc(p)) >> 26) & 3 };
1273 if ps != 3 {
1274 return ps;
1275 }
1220 let hp = hostpc(p); 1276 let hp = hostpc(p);
1221 if hp != u32::MAX && hp != 0 { 1277 if hp != u32::MAX && hp != 0 {
1222 let f = (hp >> 25) & 3; 1278 let f = (hp >> 26) & 3;
1223 if f != 3 { 1279 if f != 3 {
1224 return f; 1280 return f;
1225 } 1281 }
1226 } 1282 }
1227 unsafe { (rd(prtsc(p)) >> 26) & 3 } 1283 0
1228} 1284}
1229 1285
1230// --------------------------------------------------------------------------- 1286// ---------------------------------------------------------------------------
@@ -1246,6 +1302,7 @@ pub fn init() -> bool {
1246 } 1302 }
1247 } 1303 }
1248 } 1304 }
1305 show_summary();
1249 if !up { 1306 if !up {
1250 crate::serial_println!("ehci: no EHCI controller"); 1307 crate::serial_println!("ehci: no EHCI controller");
1251 } else { 1308 } else {
@@ -1292,6 +1349,46 @@ pub fn rescan() {
1292 } 1349 }
1293} 1350}
1294 1351
1352/// Persistent bottom-line summary: connected ports and their PORTSC, plus
1353/// HID devices found. Hardware runs without serial still get the whole
1354/// picture (the one-shot failure QR only names the first fault).
1355fn show_summary() {
1356 let nc = N_CTRL.load(Ordering::Relaxed) as usize;
1357 let nd = N_DEV.load(Ordering::Relaxed) as usize;
1358 unsafe {
1359 for ci in 0..nc {
1360 let ct = &*core::ptr::addr_of!(CTRLS[ci]);
1361 let mut line = alloc::format!(
1362 "ehci c{} 64b={} leg={} ports:",
1363 ci,
1364 ct.caps64 as u8,
1365 ct.legacy
1366 );
1367 for p in 0..(ct.nports.min(8) as usize) {
1368 let v = rd(ct.opb + OP_PRTSC_BASE + p * OP_PRTSC_STRIDE);
1369 if v & PRT_CONNECT != 0 {
1370 line.push_str(&alloc::format!(" p{}={:04x}", p, v & 0xffff));
1371 }
1372 }
1373 crate::gop::boot_marker_at(&line, crate::gop::bottom_row(2 - ci));
1374 }
1375 let mut mice = 0u32;
1376 let mut kbs = 0u32;
1377 for i in 0..nd {
1378 let d = &*core::ptr::addr_of!(DEVS[i]);
1379 if d.kind == Kind::Mouse {
1380 mice += 1;
1381 } else {
1382 kbs += 1;
1383 }
1384 }
1385 crate::gop::boot_marker_at(
1386 &alloc::format!("usb: mouse={} kbd={}", mice, kbs),
1387 crate::gop::bottom_row(0),
1388 );
1389 }
1390}
1391
1295pub fn controller_up() -> bool { 1392pub fn controller_up() -> bool {
1296 CTRL_UP.load(Ordering::Relaxed) 1393 CTRL_UP.load(Ordering::Relaxed)
1297} 1394}