diff --git a/kernel/src/drivers/usb/xhci.rs b/kernel/src/drivers/usb/xhci.rs index 8d2a5860..b24c0d51 100644 --- a/kernel/src/drivers/usb/xhci.rs +++ b/kernel/src/drivers/usb/xhci.rs @@ -4100,48 +4100,78 @@ fn scan_ports(state: &mut XhciState) -> Result<(), &'static str> { ms_pass!(M_PORT_DET); // --- M6: SLOT_ENABLE --- + // + // Bounded retry: a slow virtual device (e.g. Parallels' emulated mouse + // on port 1) can transiently time out its EnableSlot command completion. + // A single timeout used to abandon the port for the entire session — + // mouse_slot stayed 0, start_hid_polling's guards skipped the mouse + // interrupt transfer, and the pointer was dead for the whole run. + // + // Retry the full EnableSlot -> AddressDevice sequence (not just + // EnableSlot alone) up to SLOT_ENABLE_MAX_ATTEMPTS times, since a slow + // device may need a full warm-up round rather than just a second + // EnableSlot. Only abandon the port after retries are exhausted. + const SLOT_ENABLE_MAX_ATTEMPTS: u8 = 3; + const SLOT_ENABLE_RETRY_DELAY_MS: u32 = 20; ms_begin!(M_SLOT_EN); - // Enable Slot for this device - let slot_id = match enable_slot(state) { - Ok(id) => { - crate::serial_println!("[xhci] port {} EnableSlot -> slot {}", port_id, id); - id + let mut slot_id: u8 = 0; + let mut addressed = false; + 'slot_attempt: for attempt in 1..=SLOT_ENABLE_MAX_ATTEMPTS { + if attempt > 1 { + crate::serial_println!( + "[xhci] port {} EnableSlot retry {}/{}", + port_id, + attempt, + SLOT_ENABLE_MAX_ATTEMPTS + ); + delay_ms(SLOT_ENABLE_RETRY_DELAY_MS); } - Err(e) => { - crate::serial_println!("[xhci] port {} EnableSlot failed: {}", port_id, e); - ms_fail!(M_SLOT_EN, "EnableSlot returned error"); - continue; + + let id = match enable_slot(state) { + Ok(id) if id != 0 => id, + Ok(_) => { + crate::serial_println!("[xhci] port {} EnableSlot returned 0", port_id); + continue 'slot_attempt; + } + Err(e) => { + crate::serial_println!("[xhci] port {} EnableSlot failed: {}", port_id, e); + continue 'slot_attempt; + } + }; + crate::serial_println!("[xhci] port {} EnableSlot -> slot {}", port_id, id); + + // --- M7: DEVICE_ADDRESS --- + ms_kv!( + M_ADDR_DEV, + "port={} speed={} slot={}", + port_id, + (read32(portsc_addr) >> 10) & 0xF, + id + ); + ms_begin!(M_ADDR_DEV); + // Address Device (port numbers are 1-based) + match address_device(state, id, port_id) { + Ok(()) => { + crate::serial_println!("[xhci] port {} AddressDevice OK (slot {})", port_id, id); + ms_pass!(M_ADDR_DEV); + slot_id = id; + addressed = true; + break 'slot_attempt; + } + Err(e) => { + crate::serial_println!("[xhci] port {} AddressDevice failed: {}", port_id, e); + } } - }; - if slot_id == 0 { - crate::serial_println!("[xhci] port {} EnableSlot returned 0", port_id); + } + + if !addressed { + ms_fail!(M_SLOT_EN, "EnableSlot/AddressDevice failed after retries"); continue; } ms_pass!(M_SLOT_EN); slots_used += 1; - // --- M7: DEVICE_ADDRESS --- - ms_kv!( - M_ADDR_DEV, - "port={} speed={} slot={}", - port_id, - (read32(portsc_addr) >> 10) & 0xF, - slot_id - ); - ms_begin!(M_ADDR_DEV); - // Address Device (port numbers are 1-based) - if let Err(e) = address_device(state, slot_id, port_id) { - crate::serial_println!("[xhci] port {} AddressDevice failed: {}", port_id, e); - continue; - } - crate::serial_println!( - "[xhci] port {} AddressDevice OK (slot {})", - port_id, - slot_id - ); - ms_pass!(M_ADDR_DEV); - // Linux USB 3.0 enumeration sequence (from ftrace): // 1. GET_DESCRIPTOR(Device, 8) — first 8 bytes for maxpkt0 // 2. SET_ISOCH_DELAY(40ns) — USB 3.0 isochronous delay diff --git a/scripts/parallels/launcher-smoke.sh b/scripts/parallels/launcher-smoke.sh index e2a490ea..5ce4b798 100755 --- a/scripts/parallels/launcher-smoke.sh +++ b/scripts/parallels/launcher-smoke.sh @@ -534,6 +534,37 @@ else fi export VM="$VM_NAME" +# ============================================================================= +# (c.5) USB mouse-enumeration assertion. +# +# xHCI enumeration completes during kernel boot, well before BWM emits the +# readiness marker, so start_hid_polling()'s summary line is already in the +# serial log by now. Extract mouse=slotN from it: if N==0, the mouse's +# EnableSlot/AddressDevice sequence never got a slot (even after the bounded +# in-kernel retry added for exactly this failure mode) -- the pointer is dead +# for the whole session. This is a real device-bring-up failure and must FAIL +# honestly rather than let the run limp forward into the launcher/bterm +# phases pretending input is healthy. Always copy the line into the evidence +# dir regardless of outcome (pass or fail) for offline inspection. +# ============================================================================= +HID_POLL_MARKER='[xhci] start_hid_polling:' +HID_POLL_LINE="$(grep -aF -- "$HID_POLL_MARKER" "$SERIAL_LOG" 2>/dev/null | tail -1 || true)" +echo "$HID_POLL_LINE" > "$EVIDENCE_DIR/hid-poll-line.txt" + +if [[ -z "$HID_POLL_LINE" ]]; then + finish_fail "USB_MOUSE_ENUM: no '$HID_POLL_MARKER' line found in serial log (start_hid_polling never ran or was not reached)" +fi + +MOUSE_SLOT="$(printf '%s\n' "$HID_POLL_LINE" | grep -oE 'mouse=slot[0-9]+' | grep -oE '[0-9]+' || true)" +if [[ -z "$MOUSE_SLOT" || "$MOUSE_SLOT" -eq 0 ]]; then + # Pull any retry-exhaustion evidence from the kernel's own EnableSlot + # retry logging so the FAIL message is directly actionable. + ENUM_RETRY_EVIDENCE="$(grep -aE 'EnableSlot retry|EnableSlot failed|EnableSlot/AddressDevice failed after retries' "$SERIAL_LOG" 2>/dev/null | tail -5 || true)" + [[ -n "$ENUM_RETRY_EVIDENCE" ]] && echo "$ENUM_RETRY_EVIDENCE" >> "$EVIDENCE_DIR/hid-poll-line.txt" + finish_fail "USB_MOUSE_ENUM: mouse never enumerated a slot (mouse=slot${MOUSE_SLOT:-?}): $HID_POLL_LINE" +fi +log "USB mouse enumerated: slot $MOUSE_SLOT ($HID_POLL_LINE)" + # ============================================================================= # (d) VirGL warmup. # =============================================================================