Implement the Linux 7.1 xhci.c:4650 xhci_set_usb2_hardware_lpm() enable
path, closing the P7-A gap on top of the P2-B substrate:
- usb/bos.rs: BosUsb2ExtDesc bmAttributes accessors (LPM support, BESL
support/validity, baseline/deep BESL fields) + 3 unit tests
- xhci/mod.rs:
- change_max_exit_latency(): MEL update via Evaluate Context (slot
output context copied, SLOT add flag, DWORD1 low 16 bits = MEL) —
Linux xhci.c:4520
- calculate_hird_besl(): HCS_PARAMS3 U2 latency + device BESL
attributes — Linux xhci.c:4594
- enable_usb2_hw_lpm(): BESL vs HIRD parameter selection, MEL
Evaluate Context, PORTHLPMC/PORTPMSC programming via the existing
Port::enable_lpm — Linux xhci.c:4686-4722
- BESL_ENCODING_US table, XHCI_L1_TIMEOUT_US=512, XHCI_DEFAULT_BESL=4
- xhci/scheme.rs (get_desc): attach-time gate chain — hw_lpm_support &&
per-port HLC (USB 2.0 protocol only) && device USB_LPM_SUPPORT in BOS
&& non-hub class && root-hub-direct (get_desc only runs for root-port
devices). LPM enable failure logs a warning and continues — LPM is
opportunistic; the device works in U0 regardless.
Verified: cargo check -Z build-std --target x86_64-unknown-redox -p
xhcid clean (no new warnings). Unit tests compile-verified; execution
requires redoxer like the crate's existing test suite. Runtime L1
entry validation requires LPM-capable hardware (QEMU's xHCI does not
advertise HLC) — recorded as hardware-validation debt in the USB plan.
Same bug class as the ahcid fix (ad40fffd): the kernel masks the IRQ
line when it delivers an interrupt and only re-arms it on the userspace
write-back (irq scheme write -> acknowledge() -> pic/ioapic_unmask).
When a driver acks only if the interrupt was its own, one foreign
interrupt on a shared INTx line leaves the line masked forever and all
later interrupts for the device are lost.
- e1000d: ack unconditionally; ICR is read-to-clear, so the ownership
check doubles as the device-level status clear
- ihdad: ack before the not-ours continue
- vboxd: ack unconditionally; device-side ack and event handling stay
conditional on host_events
- xhcid: ack unconditionally in the IRQ reactor; for INTx the ownership
check already clears IMAN.IP (RW1C) and device interrupts are masked
before re-arming, so no storm. MSI/MSI-X vectors are never shared, so
behavior there is unchanged (every delivery is ours by construction)
Verified: cargo check -Z build-std --target x86_64-unknown-redox for
e1000d, ihdad, vboxd, xhcid — clean, no new warnings.
'Running IRQ reactor with IRQ file and event queue' and 'in polling
mode' were debug! since the file was created, so test-xhci-irq-qemu.sh
(which greps for the interrupt-driven line) could never see them at the
default info log level — the IRQ-driven path had zero runtime evidence.
They are one-time startup lines; promote to info so the interrupt
delivery mode is observable and the proof can detect it.
The diagnostic milestones used to root-cause the intermittent boot
'freeze' (attach_device milestones, usbhubd HUBFLOW decision points,
reactor/enumerator startup) moved from info to debug! so production
logs stay clean. One-line-per-boot startup confirmations (scheme
registered, daemon ready, reactor started, enumerator started, reactor
dispatch mode, enumerator running) remain at info as genuinely useful
low-volume startup evidence.
Traces at scheme registration, daemon.ready, reactor/enumerator start,
reactor dispatch + IRQ subscribe, and enumerator run entry, to pinpoint
where xhcid stops in boots that lose the intermittent IRQ-reactor
startup race. Diagnostic; level to be revisited after root cause.
Temporary info-level ATTACH milestones in attach_device (begin, slot,
addressed, desc8, desc-full, drivers spawned) to trace hub-child
enumeration in QEMU — the kbd behind a hub reaches port-enable but
produces no visible outcome at the default log level. Level to be
revisited after the hub-child attach path is diagnosed.
open_handle_port required a PortState for every port<N> directory open,
but hub child ports have no PortState until the hub daemon triggers
attach_device via port<N>/attach — an endpoint that itself is
state-free (open_handle_attach_device). This made XhciClientHandle::new
fail with ENOENT for unenumerated hub children, deadlocking the attach
flow: usbhubd could never start enumeration for a device behind a
non-root hub (panic at startup, caught by test-usb-hub-qemu.sh).
Open the directory with the static listing when no PortState exists
yet; state-dependent subpaths (descriptors, state, configure) still
gate on the PortState being present.
Sync the base fork with 13 upstream commits (xhci event-processing
dedup + IRQ race fix, randd permission simplification, nvmed TimeSpec
fix, /dev/ptmx, fpath legacy-path cleanup, dynamically-linked init
fix, and more). Brings process_one_event into the tree, satisfying the
verify-fork-functions gate for base.
Conflicts resolved (3 of 8 overlapping files; 5 auto-merged):
- drivers/usb/xhcid/src/xhci/irq_reactor.rs: took upstream's
process_one_event/EventProcessResult refactor (b2ed85ea) including
the a01d3ce6 race fix (process events between NoEvent and
unmasking), and re-applied the Red Bear SPURIOUS_REBOOT quirk on the
NoEvent warning (downgrade to debug when quirked).
- randd/src/main.rs: took upstream's permission-handling
simplification (e26db606); re-applied the RB fcntl improvement
(F_GETFL/F_GETFD/F_SETFL/F_SETFD handling, Linux random_fops xref)
and the is_cpu_feature_detected early-return cleanup. Dropped
test_scheme_perms (deleted by upstream's simplification).
- Makefile: kept upstream's new /dev symlink block (dev/null, ptmx,
random, urandom, zero, tty, stdin/stdout/stderr).
Verified: cargo check clean; 57/57 tests pass (xhcid 35, usbhubd 14,
xhci trb 8); verify-fork-functions.sh --no-fetch base reports all
upstream functions present.
Port the Linux 7.1 hub.c port state machine into usbhubd, replacing the
minimal connect/reset handling:
New module port_ops.rs (pure logic, side effects injected, 14 unit
tests):
- debounce_until_connected: hub_port_debounce_be_connected() port
(hub.c:4696-4737) — 25ms polls, connection stable for 100ms, 2s
budget, connection-change bit cleared in-loop.
- wait_for_reset: hub_port_wait_reset() port (hub.c:2953-3047) — 10ms
polls until RESET clears with CONNECTION set, escalate to 200ms after
two short waits, 800ms budget; then 50ms TRSTRCY recovery (hub.c:3159)
and C_PORT_RESET clear. Replaces the previous bare sleep(10ms).
- wait_for_u0: USB 3.0 polling→U0 wait after port power-on — 36ms steps,
400ms ceiling (tPollingLFPSTimeout = 360ms; Linux hub.c:1226 debounce
path).
- accumulate_hub_delay_ns: wHubDelay chain rule (hub.c:1507-1519:
wHubDelay + parent->hub_delay + 40ns, cap 65535ns).
main.rs wiring:
- Port status normalized to PortStatusSnapshot (decouples the state
machine from the V2/V3 wire formats; V3 link state extracted from
bits 8:5).
- Debounce on connection-change before attach; C_PORT_ENABLE cleared
once handled (Linux port_event semantics).
- Reset path uses wait_for_reset instead of sleep(10ms).
- USB 3 power-on path waits for U0 before proceeding.
- wHubDelay: ancestor-chain walk fetching USB 3 ancestor hub
descriptors, accumulated per Linux; delivered to newly attached
SuperSpeed children via SET_ISOCH_DELAY (USB 3.0 9.4.11; Linux
message.c:1142 — hubs and non-SS skipped, children inherit the hub's
accumulated delay verbatim per hub.c:5128-5129).
- attach/detach failure logs now identify the port.
hub.rs (xhcid usb module):
- HubDescriptorV3 extended with device_removable: u16 — the SS hub
descriptor is 12 bytes (spec Table 10-15); the old struct under-read
by 2 bytes. Stale TODO corrected: SS descriptors have no
PortPwrCtrlMask (that is USB 2.0-only, still unparsed).
Verified: cargo check clean (0 usbhubd warnings), 14/14 usbhubd tests,
xhcid unaffected (43/43 tests).
Three bugs in the xHCI endpoint-restart path used by all error recovery
(stall hard reset, transaction-error soft retry, resource retry,
split/babble hard reset):
1. Latent deadlock: restart_endpoint held the port_states write guard
across set_tr_deque_ptr(), which internally re-acquires a read guard
on the same key (std RwLock read-while-write on one thread).
Unobserved because error injection is not yet exercised at runtime
(P8-C). Fixed by scoping phase-1 ring priming so the guard drops
before the async command.
2. Doorbell ordering violated xHCI spec 4.6.8/4.6.10: after Reset
Endpoint the TR Dequeue Pointer is undefined, so Set TR Dequeue
Pointer must complete BEFORE the doorbell transitions the endpoint
Stopped->Running. The old order (doorbell first) ran the endpoint
with an undefined dequeue pointer — undefined xHC behavior on real
hardware. Linux rings the doorbell from the Set TR Dequeue command
completion path (xhci_handle_cmd_set_deq, ring.c:1416-1554); xhcid
now issues Set TR Dequeue, awaits completion, then rings.
3. Priming NoOp never executed: ring.register() was captured after
ring.next() advanced the enqueue index, so the dequeue pointed past
the NoOp (dead TRB). Now captured before next(), priming the
hardware dequeue AT the NoOp so the xHC executes it on restart —
same semantics as Linux xhci_move_dequeue_past_td pointing at the
first valid TRB.
Verified: cargo check clean (138 warnings, unchanged), 43/43 tests pass.
Gate xHCI 1.1+ features on their capability bits, cross-referenced with
Linux 7.1 xhci driver behavior:
LEC (HCC2_LEC, scheme.rs):
- lec now uses Linux xhci-mem.c:1350 exact condition:
hci_version > 0x100 && hcc_params2 & HCC2_LEC (HCCPARAMS2 register
space is reserved on 1.0 controllers).
- Max ESIT Payload Hi zeroed when LEC=0 (spec Table 6-8 RsvdZ).
U3C (HCC2_U3C, mod.rs suspend_port):
- Refuse SuperSpeed U3 entry with ENOSYS when hci_ver >= 0x110 and
HCC2_U3C=0 (spec 4.15.1). USB2 suspend unaffected. Linux 7.1 defines
but never gates this bit; xhcid follows the spec.
CIC (HCC2_CIC): CIE gate pre-existing (set_cie from cic()); added the
hci_ver > 0x100 version guard. HCC2 capability log block similarly
guarded.
HW LPM (extended.rs, mod.rs, port.rs):
- New SupportedProtoCap::{l1_capable,hw_lpm_capable,besl_lpm_capable}
reading protocol-defined bits (Linux xhci-ext-caps.h:62-66 L1C/HLC/BLC).
- Xhci::hw_lpm_support computed per Linux xhci-mem.c:2137:
hci_ver >= 0x100 && !HW_LPM_DISABLE && any USB2 protocol cap has HLC.
- attach_device(): defensive LPM clear on USB 2.0 protocol ports
(rev_major() != 3) when hw_lpm_support is false — Linux xhci.c:4725
disable path; USB3 excluded because PORTPMSC L1DS aliases U2 timeout.
- Port::enable_lpm/disable_lpm register targets fixed: HLE/HIRD/RWE/
L1DS belong in PORTPMSC (offset 0x04), not PORTHLPMC (0x0C, bit 16
RsvdZ) — spec Tables 5-21/5-23, Linux xhci-port.h:135-158. Helpers
rewritten to Linux xhci.c:4686-4737 two-register sequence.
- Per-device L1 enablement (BESL, MEL Evaluate Context) defers to P3.
Bug fix: removed bogus CapabilityRegs::hlc() + HCC_PARAMS1_HLC_BIT —
they read xECP pointer bits 16-31 of HCCPARAMS1 (spec Table 5-13), not
HLC. HLC lives in the Supported Protocol capability port_info DWORD.
Verification: cargo check clean (138 warnings, -2 vs baseline: the new
disable_lpm call site also revived PORT_HLE/PORT_HIRD_MASK), 43/43
tests pass.
Replace xhcid's self-contained 294-line xhci/quirks.rs (7-flag bitflags
type + ~30-entry quirk table) with a thin re-export shim that delegates
to redox-driver-sys::quirks, which now carries all 51 Linux 7.1 quirk
flags and the full ~85-entry canonical controller table.
Changes:
- Cargo.toml: add redox-driver-sys path dependency
- xhci/quirks.rs: replace table with pub use XhciControllerQuirkFlags
as XhciQuirks + delegating lookup_quirks(vendor, device, revision,
hci_version)
- main.rs: read real HCIVERSION from MMIO offset 0x04 via
cap.hci_ver.read() instead of hardcoded 0x100 — matches Linux 7.1
xhci_gen_setup() at xhci.c:5455. Two table entries depend on the
value: AMD_0x96_HOST (xhci-pci.c:308) and >= 0x120 spec rule
(xhci-pci.c:511).
- main.rs: resolve quirks BEFORE get_int_method() so BROKEN_MSI skips
MSI/MSI-X probing entirely (matching Linux xhci_try_enable_msi at
xhci-pci.c:143-209). Previously the quirk only overrode the method
label while irq_file still held the MSI handle — a mismatch causing
silent interrupt-delivery failures on BROKEN_MSI controllers.
- get_int_method (both cfg variants): accept quirks parameter
maybe_recover_transfer_error previously handled only the first-tier
codes (UsbTransaction, Resource, Stall, BabbleDetected, DataBuffer,
Trb, SplitTransaction) and silently returned Ok(false) for every
other completion code via a catch-all arm.
Cross-referenced Linux 7.1 drivers/usb/host/xhci-ring.c
handle_tx_event() (line 2608+) and handle_transferless_tx_event()
(line 2561+) to add explicit recovery for the remaining ~29 codes:
- Stopped/StoppedLengthInvalid/StoppedShortPacket: restart endpoint
+ retry (up to MAX_SOFT_RETRY), then hard-reset
- InvalidStreamType/InvalidStreamId: soft reset + retry, then
hard-reset
- IncompatibleDevice: disable slot (device must re-enumerate)
- MissedService/NoPingResponse: log informational, surface to caller
- ContextState/Parameter: hard-reset to resync driver/xHC state
- Bandwidth/BandwidthOverrun/SecondaryBandwidth: log, no transfer
recovery (config must change)
- IsochBuffer: hard-reset endpoint
- MaxExitLatencyTooLarge: log, surface
- EventLost/Undefined: hard-reset (event ring may be corrupted)
- SlotNotEnabled/EndpointNotEnabled/NoSlotsAvailable: log driver
state mismatch
- CommandRingStopped/CommandAborted: log xHC state confusion
- Reserved/vendor: default arm logs explicitly
Added completion_code_to_errno() mapping transfer completion codes
to POSIX errnos matching Linux 7.1 semantics:
Stall -> EPIPE
BabbleDetected -> EOVERFLOW
UsbTransaction/SplitTransaction/IncompatibleDevice -> EPROTO
Trb -> EILSEQ
DataBuffer -> ENOSR
SlotNotEnabled/EndpointNotEnabled/NoSlotsAvailable -> ENODEV
default -> EIO
Rewrote handle_transfer_event_trb() to use the new errno mapping
instead of always returning EIO.
Added 14 unit tests covering all errno mappings and transfer
event handling paths. Full suite (41 tests) passes.