Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 63 additions & 33 deletions kernel/src/drivers/usb/xhci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions scripts/parallels/launcher-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
# =============================================================================
Expand Down