xHCI: every report that moves a port's belief leaves it to be read - #554
Conversation
A port torn down with its device still in it (a replug collapsed inside the debounce, or a disk refused while another is held for its device) was left `Settled` with `ports_dirty` false. PORTSC's CSC stays set because the teardown step returns ahead of the acknowledge, and QEMU's `xhci_port_notify` raises no event while that bit is set, so nothing looked at the port again until an unrelated event arrived. `PortState::torn_down` now leaves the port `Unread`, which `PortState::outstanding` reports, so `XhciController::poll` steps it on the same pass. Both `AfterSlot::Teardown` and `AfterSlot::Again` reach it through that one function; the second was never staged. `xhci_flap` now paces each cycle's edges on the previous device's bind instead of a fixed 600 ms, so a lost wake is a cycle that never binds and the gate names it, rather than a collapse the next cycle rescues by parity. Its row leaves the disabled list and its issue is closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while running xhci_flap's negative control: sysroot 5dc157f7fac727be was cloned from stage2 at 22:03 and stage2's cargo link was made at 22:08, so the sysroot has no cargo and every build against its key panics in assert_toolchain_is_honest. Filed, not fixed: it is off this branch's path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s sysroot is the toolchain fix's The tracker closes an issue by deleting its file (issues/README.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #554 at 22db753, round 1Readiness. CI
Net lines. +83 −118 in total:
The production growth is one enum variant and its one transition. I accept it. The brief's questions
BLOCKERNone. NOTE
REMOVE
LAND AFTER NAMED CHANGES |
…issues xhci_flap already requires CYCLES + 1 binds, and it counts the same "xHCI: pointer on slot ... merges as source" line that the sources check reads. So the empty-sources check can no longer fire. A run with `collapsed == 0` can only be one whose every replug was seen as distinct, so the dead-port reading goes and the message keeps its first sentence. Binds now pace on `parse_pointer_sources`, not on a second copy of its literal. Filed: - issues/kernel/a-disk-refused-while-one-is-held-is-enumerated-again-and-no-test-reaches-that-arm.md - issues/kernel/a-port-given-up-on-or-enumerated-after-its-change-event-is-spent-is-never-read-again.md The second issue is the review's simulator NOTE, measured. A pure `port::due` that both loops call, plus a hub that raises an event only on a change bit's 0->1 edge, turns `repeated_replugs_stay_balanced` red on c551894's port.rs with 1 teardown for 4 replugs. It also turns two more sim tests red on this branch, one for a GaveUp and one for an enumeration's end, and three more production lines in port.rs are needed to make them green. That is beyond the brief's one function, so the patch is not landed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…enied gate wants `filter(..).last()` on a double-ended iterator is `clippy::double_ended_iterator_last`, and the host lane denies warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xHCI raises a Port Status Change Event only on a change bit's 0->1 edge, so a port whose device changed while the driver was inside an effect has spent its edge. `PortState::believe` is now the only setter of `attached` and `slot`, and it leaves the port `Work::Unread`: `torn_down`, `enumerated`, `adopt` and both `GaveUp` transitions go through it. - `port::due(signalled, ports)` is the one decision whether a pass steps the ports; `Controller::poll` and the simulator's pump both call it. - The simulated hub changes its register through one setter that raises the event on a change flag's 0->1 edge, and the pump steps the port only where `due` says so. With it, the simulator finds what per-pass stepping hid: `enumerated` and the two `GaveUp` transitions left the port Settled with its edge spent. - The GaveUp arm of `service_port` (and of the pump) reads the port again in the same pass instead of returning: nothing else wakes a pass for a port left to be read. The pump now fails `Stuck::Unwoken` when a pass leaves the port outstanding with no instant to come back at. - The boot scan's last store to PORT_WORK_AT asks for a pass when a bound port is outstanding, since the scan's acknowledge spent its edge. - New sim test: a device pulled while Enable Slot goes unanswered is torn down once the deadline ends the enumeration. Host arms, each red: port.rs as of c551894 with `due` and the edge rule kept (repeated_replugs_stay_balanced, a_replug_inside_one_debounce_is_seen); the three transitions and the GaveUp return reverted (a_device_pulled_during_the_warm_retrain_is_not_enumerated, gate_an_enumeration_that_outlives_its_port_costs_a_deadline, the new Enable Slot test); the GaveUp return alone (Unwoken, seven tests); `adopt` back to Settled (the port unit test). The issue this fixes is deleted; the acknowledge after an enumeration, which clears a replug no look has seen, is filed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #554 at b000f38, round 3Round 1 BLOCKERs. None were raised. The round-1 NOTEs on Readiness.
Net lines. +249 −171 in total:
I accept the production growth: one variant, one setter, one pure The brief's questions
BLOCKER
NOTE
REMOVE
SEND BACK |
…e pass
`device::begin` can refuse before Enable Slot: the port reads disabled after
the enumeration's acknowledge, or it came up at a speed with no packet size
or no Protocol Speed ID. `finish` then reports the port attached with no slot
and leaves it `Unread`, and `service_port` returned `outstanding.wake_at()`,
which is `None` when nothing else is outstanding. The port was left to be
read with no pass scheduled.
This is reachable on a USB3 port. A device is pulled between the step that
saw its warm completion and `begin`'s read. The retrain's CSC is still set,
so the pull raises no event. `enumeration_ack(Some(Warm))` clears CSC, and
`begin` finds PED clear.
`service_port`'s `Enumerate` arm no longer returns. The loop reads the port
again: a begun enumeration is caught at the loop's top as working and returns
its wake as before; a refused one is stepped.
The simulator's `Enumerate` arm now mirrors `begin`'s `!enabled` refusal,
which it did not model, and `Driver::pulled_as_it_enumerates` stages the pull
at the site where the reentrancy check already runs.
`a_device_pulled_as_its_enumeration_begins_is_torn_down` expects
`[Reset(Hot), Reset(Warm), Enumerated { slot: None, trained: false },
ToreDown(Disconnected)]` with no command spent. It goes red with
`Unwoken` when the refusal returns the way the kernel did, and with
`Enumerated { slot: Some(1) }` when the refusal is not modelled.
The boot scan's store to `PORT_WORK_AT` is deleted, and so is the store of 0
it replaced. It is not load-bearing. The ISR records every interrupt the
scan's own resets and commands raise, and the first `poll_if_pending` on that
CPU steps every port the scan left `Unread`. The only scan that raises none
binds no port except those given up on with no slot, because a reset that
never finished raises no event. There, the next connect's edge is read as a
replug. Whatever `PORT_WORK_AT` holds at that point is harmless: a nonzero
value costs one poll, and that poll stores the right value.
`believe`'s doc loses "the one place the driver's belief about a port is set",
since `take_slot` also sets `slot`.
Filed:
- issues/kernel/a-reset-given-up-on-with-its-connect-flag-set-is-torn-down-and-retried-every-debounce.md
- issues/kernel/a-poll-that-leaves-a-port-outstanding-with-no-wake-says-nothing.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #554 at 9f2b1f9, round 4Round 3 BLOCKERs
Readiness
Net lines: +341 −175 in total.
I accept the production growth: one variant, one setter, one pure BLOCKERNone. NOTE
REMOVE
LAND AFTER NAMED CHANGES |
A reset given up on left the port Unread, and the same pass read it again. A completion that leaves CCS and CSC set -- a warm retrain that re-detects the device and cannot enable it -- then read as a replug: the port was torn down, debounced and reset again, once per debounce for as long as its device stayed in. `PortState::give_up` is now the one rule for both give-ups. The port is attached and `Work::GivenUp`: still outstanding, so it is read once and a pull is seen, but the change flags that read finds are the given-up reset's own and are acknowledged without being judged a replug. Only an edge after that acknowledge moves the port. The simulated hub stages the case as `ResetBehaviour::RetrainsDisabled`, and `a_port_given_up_on_is_not_reset_again_until_its_device_is_pulled` holds such a device for twenty debounces, expects one hot and one warm reset and one GaveUp, then pulls it and expects the teardown. Also: `PortState::adopt` and `port_bound` go, since the boot scan only ever recorded `None`; it calls `enumerated(None)`. The publish of `XHCI` states the invariant the boot store's deletion rests on. The sim's duplicate field doc goes. The issue this fixes is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #554 at f2ba12f, round 5Round 4 BLOCKERs: there were none. The orchestrator's ruling (no give-up retry loop): CLOSED for the hot-plug machine.
Readiness
Net lines: +370 −204.
BLOCKER
NOTE
REMOVE
SEND BACK |
… up by the same rule `PortState::gave_up` is the one rule for what a give-up leaves. A warm give-up stays `Work::GivenUp`: §4.19.5.1's retrain raises a connect edge of its own, and nothing tells it apart from a replug. A hot give-up (`ResetNeverFinished(Hot)`, `ResetFailed(Hot)`) goes through `believe`, because a hot reset changes no connect state and a connect flag there is a real replug. Before this, a device replugged inside a hot reset that was then given up on stayed unenumerated until it was pulled again. `a_device_replugged_inside_a_hot_reset_given_up_on_is_enumerated` stages it: at f2ba12f it records `[Reset(Hot), GaveUp(ResetNeverFinished(Hot))]` and 0 enumerations. The boot scan's two give-ups report through `gave_up` instead of `enumerated(None)`, and `GaveUp::never_finished` is the one mapping from an unfinished reset to its `GaveUp`, which the hot-plug machine uses too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…took a pass for The `AnotherStick` boot of `usb_transport_break --nightly` at e889d03 went red: port 3 read connected and was never stepped, so the other stick was never refused by name. Nothing on the port machine's path ran. Every call of `PortState::gave_up` is preceded by a log line naming the give-up, in `service_port` and in the boot scan, and the boot has none of them. There is no `port 1 disconnected` line either, though port 1 read empty. - a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it: this boot, where cpu0 spent 4 s in logd's create under `vfs::lock()` and cpu1 took no pass until it ended. - a-shutdown-on-a-held-usb-disk-left-a-cpu-deaf-to-a-tlb-shootdown: the same boot's panic at 9.425 s, with the roles swapped. - New: a device swapped into its port inside the port rung's reset is taken for the disk it replaced. The rung re-reads no identity, and the acknowledge it shares with a warm retrain spends the swap's CSC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #554 at 430b077, round 7 (reviews what changed since round 5 at f2ba12f)Round 5 BLOCKER (a hot give-up left
Round 7's attribution of the
Readiness
Net lines: +482 −208.
BLOCKERNone. NOTE
REMOVE
LAND AFTER NAMED CHANGES |
GaveUp::ResetNeverFinished and GaveUp::ResetFailed only ever held Reset::Hot: reset_outcome and never_finished map every warm end to LinkNeverTrained. Drop the dead Reset payload from both, delete GaveUp::reset (its only caller), and inline the "hot" the two kernel log arms and boot's chose from it: a warm ResetFailed is now unrepresentable rather than merely unreached. gave_up's slot argument at the hot arm was always None, since Resetting is entered only from a port believed empty; write None rather than self.slot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
src/redlist.rs conflicted: main kept the xhci_flap row this branch's 0a3a1c8 already deleted (the fix landed and 22db753 closed its issue), and added an unrelated usb_transport_break row. Kept the branch's deletion and took main's addition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
src/redlist.rs keeps main's ftruncate_flush_race, quiesce_stops_the_machine and quiesce_wakes_on_the_last_park rows, drops quiesce_dump_holds_the_stopped and quiesce_wakes_on_the_last_exit, whose tests this branch deletes, and takes #554's removal of the xhci_flap row. The writers issue #574 renamed stays expected-red with main's exit, since quiesce_stops_the_machine's row cites it; its opening sentence named the deleted quiesce_dump_holds_the_stopped as booting quiesce_writers and goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
The rule
xHCI raises a Port Status Change Event only when a change bit goes from 0 to 1. After an effect or a give-up, the device in a port may have changed with no new edge, because the effect's own acknowledge spent it. So every transition that sets the driver's belief about a port leaves it outstanding, and a pass steps it until a look has read the register against the new belief.
PortState::believe(toyos-xhci/src/port.rs) is wheretorn_downandenumeratedsetattachedandslotand leave the portWork::Unread, and the rule is stated there.take_slotalso clearsslot, for a caller about to disable it, and leavesattachedand the work as they were.PortState::gave_upis the one rule for what a give-up leaves: the port attached and outstanding, so it is read once and a pull is seen. The hot-plug machine'sgive_upwraps it, and the boot scan's two give-ups call it.LinkNeverTrained) leavesWork::GivenUp. §4.19.5.1's retrain raises a connect edge of its own, and nothing tells it apart from a replug, so the change flags that read finds are acknowledged without being judged as a replug, and only an edge after that acknowledge moves the port. Judged as a replug, that flag would tear the port down and reset it again every debounce for as long as its device stayed in.ResetNeverFinished,ResetFailed) goes throughbelieve(true, None):slotis alwaysNonethere, becauseResettingis entered only from a port believed empty. A hot reset changes no connect state, so a connect flag there is a real replug, and what is in the port now is enumerated. It cannot loop: a second teardown needs a second real disconnect.GaveUp::never_finishedis the one mapping from a reset whose deadline passed to itsGaveUp, used by the hot-plug machine and the boot scan.The kernel paths it fixes
Settledand its edge spent, so the new device is enumerated only when some other port raises an event. This isxhci_flap's defect.beginrefuses. Take a USB3 device pulled between the step that saw its warm completion anddevice::begin's read. The retrain's CSC is still set, so the pull raises no event.enumeration_ack(Some(Warm))clears CSC, andbeginfinds PED clear and refuses before Enable Slot.service_portthen returnedoutstanding.wake_at(), which isNonewith nothing else outstanding.What changed
port::due(signalled, ports)is the one decision whether a pass steps the ports.Controller::polland the simulator's pump both call it.toyos-xhci/sim/src/hub.rs) follows the hardware's edge rule. Its register changes only through one setter, and that setter raises the event on a change flag's 0→1 edge. The pump steps the port only whereduesays so, and fails withStuck::Unwokenwhen a pass leaves the port outstanding with no instant to come back at.ResetBehaviour::RetrainsDisabledstages the warm give-up: the bus reset fails asFailsTheBusReset's does, and the warm reset completes with the device re-detected and the port disabled.a_port_given_up_on_is_not_reset_again_until_its_device_is_pulledholds such a device for2 * RESET_DEADLINE_NS + 20 * DEBOUNCE_NS. It expects exactly[Reset(Hot), Reset(Warm), GaveUp(LinkNeverTrained)], then pulls the device and expectsToreDown(Disconnected).a_device_replugged_inside_a_hot_reset_given_up_on_is_enumeratedstages the hot give-up: a reset that would complete after 50 ms, a replug during it, and a run to2 * RESET_DEADLINE_NS + 4 * DEBOUNCE_NS. It expects one enumeration.service_port'sGaveUparm, and the pump's, read the port again in the same pass instead of returning. Nothing else wakes a pass for a port that is left to be read.service_port'sEnumeratearm no longer returns afterdevice::begin. A begun enumeration is caught at the loop's top as working and returns its wake as before. A refused one is stepped. The simulator'sEnumeratearm now mirrorsbegin's!enabledrefusal, which it did not model.Driver::pulled_as_it_enumeratesstages the pull at the site where the reentrancy check already runs.PortState::adoptandXhciController::port_boundare deleted. The boot scan only ever recordedNonethrough them, for a port it gave up on. It callsgave_up.PORT_WORK_AT. The ISR records every interrupt the scan's own resets and commands raise, and no scheduler pass runs beforesmp::set_ready. So the firstpoll_if_pendingfindsXHCIpublished and steps every port the scan left outstanding. The publish inwait/boot.rsstates that invariant. Only a scan whose every bound port's reset never finished raises none. Those ports hold no slot and no device, and the next connect's edge is read there as a replug. A nonzero value left inPORT_WORK_ATcosts one poll, which stores the right value.xhci_flappaces each cycle on the previous device's bind (parse_pointer_sources), not on a flat 600 ms. A collapse that loses its wake therefore never binds, and the gate goes red witha collapsed replug was torn down and its port never looked at again. Two checks that five enforced binds made unreachable are gone. Itssrc/redlist.rsrow and its issue are deleted.issues/hardware/a-collapsed-replug-is-enumerated-only-when-another-port-event-arrives.md, because this PR fixes it.issues/kernel/a-disk-refused-while-one-is-held-is-enumerated-again-and-no-test-reaches-that-arm.mdissues/kernel/an-acknowledge-after-an-enumeration-clears-a-replug-no-look-has-seen.md:finishandrefusewrite CSC back afterenumerated, so the look this PR adds catches a pull but not a replug in that window. The simulator'senumeratedacknowledges nothing, including on the new refusal.issues/kernel/a-poll-that-leaves-a-port-outstanding-with-no-wake-says-nothing.md: the kernel has no counterpart toStuck::Unwoken.issues/kernel/a-device-swapped-into-its-port-inside-the-port-rungs-reset-is-taken-for-the-disk-it-replaced.md: the port rung re-reads no identity, and the acknowledge it shares with a warm retrain spends the swap's CSC. Derived from the code, not staged.usb_transport_break's red below, inissues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.mdandissues/kernel/a-shutdown-on-a-held-usb-disk-left-a-cpu-deaf-to-a-tlb-shootdown.md.Negative controls, host
Each arm is a checked patch applied to c1d9b4f and then restored (
tree restoredin each log). Each rancargo test -p toyos-xhci -p toyos-xhci-sim --all-features --no-fail-fast, compiled the patched crates, and exited 101 with the reason quoted.Work::GivenUpa_device_replugged_inside_a_hot_reset_given_up_on_is_enumeratedalone: 0 enumerations,[Reset(Hot), GaveUp(ResetNeverFinished)]. The same test withport.rsas it was before the hot side went throughbelievealso exits 101 with that record.believea_port_given_up_on_is_not_reset_again_until_its_device_is_pulledalone: "a port given up on was torn down and reset again: 118 reset(s) in 6000000000 ns",[Reset(Hot), Reset(Warm), GaveUp(LinkNeverTrained), ToreDown(Replugged), Reset(Hot), …]believe, and a replug judged whatever the worka_port_given_up_on_…alone, "118 reset(s)"port.rsat base, withdueadded; the edge rule and the pump keptrepeated_replugs_stay_balanced,a_replug_inside_one_debounce_is_seen("what is in the port now was not brought up"),a_device_pulled_while_enable_slot_goes_unanswered_is_torn_down("the device that left was never taken down"),gate_an_enumeration_that_outlives_its_port_costs_a_deadline("the port was never freed"), anda_port_given_up_on_…("118 reset(s)"). The same patch, with the boot scan's twogave_upcalls taken back toenumerated(None), compiles the kernel (cargo check --target x86_64-unknown-none --features boot-actuators,test-actuators, CHECK EXIT=0).enumeratedand both give-ups setSettleda_device_pulled_while_enable_slot_goes_unanswered_is_torn_down,gate_an_enumeration_that_outlives_its_port_costs_a_deadline, anda_port_given_up_on_…("118 reset(s)")GaveUparm returning again, which is base's give-upa_device_replugged_inside_a_hot_reset_…(0 enumerations) anda_device_pulled_during_the_warm_retrain_is_not_enumerated("the pull did not end in a disconnect teardown").a_port_given_up_on_…is not reset again, but fails with "the pull of a port given up on was never seen:[Reset(Hot), Reset(Warm), GaveUp(LinkNeverTrained)]".GaveUparm returning, aloneUnwokenin 9 tests, among themno_sequence_breaks_an_invariant(seeds 3, 7 and 13, "Unwoken after[Reset(Hot), GaveUp(ResetNeverFinished)]"),a_port_that_never_finishes_its_reset_is_given_up_on_and_left_aloneanda_device_replugged_inside_a_hot_reset_…beginrefusal returns, the wayservice_portdida_device_pulled_as_its_enumeration_begins_is_torn_downalone:Unwokenbeginrefusal not modelleda_device_pulled_as_its_enumeration_begins_is_torn_downalone:[Reset(Hot), Reset(Warm), Enumerated { slot: Some(1), .. }, ToreDown(Disconnected)], a slot spent on a port that read disableda_port_that_never_finishes_its_reset_is_given_up_on_and_left_aloneis green with the hot give-up throughbelieve: that port is not tried again.a_device_pulled_while_enable_slot_goes_unanswered_is_torn_downanda_device_pulled_as_its_enumeration_begins_is_torn_downassert that their case was reached:Enumerated { slot: None }is in the record, and the second also asserts that no command was issued.a_port_given_up_on_…asserts the device is still connected before it pulls it.Guest arms, run by the orchestrator
At 430b077:
--nightly, EXIT=0 each:xhci_flap,xhci_hotplug,xhci_hid_break,xhci_deaf_registers,xhci_slot_exhaustion,usb_refused_disk_first,usb_boot_stick_pulled,usb_transport_break.xhci_flapred arm (port.rstaken back to base andduekept,guest-flap-red.patch; it compiles the kernel at 430b077, CHECK EXIT=0): EXIT=1, "a collapsed replug was torn down and its port never looked at again: 1 bind(s) for 5 plugs".lan_mdns_answerfails on the macOS harness's SUN_LEN defect, which A boot's sockets are a TempDir under /tmp, reclaimed like all scratch; disable partition_claim_departure and i8042_mouse #560 fixed on main.swap_crash_rolls_backfails on the netd-swap redial flake, disabled by Disable four flaky tests behind their filed defects #565. Neither is this diff.At e889d03, before those two fixes landed on main:
usb_transport_break --nightly: EXIT=1, in theAnotherStickboot: "after the break, no line reads "usb-storage: the device on port 3 is not disk 0 come back: its serial number differs", in order".PortState::gave_upis preceded by a line naming the give-up, inservice_portand in the boot scan'sinit_device. The boot's log has none.port 1 disconnectedline. Port 3 read connected and untaken throughout, so noport 3 connectedline exists, and the arrival rule kept disk 0 held with nodid not come backline.vfs::lock(): two writes on held disk 0, each ending on its bound, with no pass between them. cpu1 logged nothing from 0.311 to 4.390 s. The two threads that waited resumed within a millisecond of that create's end:test-runner's spawn (total=7ms, so begun about 4.383) and init's line for a spawn made at 0.363. From 4.424 s cpu0 spun in the reboot's sync, and at 9.425 s cpu1 panicked in a TLB shootdown that cpu0 never answered.issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md, reached through a lock rather than a second call. The same test was green at f2ba12f and b000f38, and at 430b077 above.cargo run -- --known-red usb_transport_break: "NO, not disabled".Gates at c1d9b4f (origin/main 614698a merged)
cargo test -p toyos-xhci -p toyos-xhci-sim --all-features --no-fail-fastcargo test -p toyos-build --lib redlistcargo clippy -p toyos-build -p toyos-xhci -p toyos-xhci-sim --all-targets --keep-going -- <the six adopted lints> -D warningscargo clippyinkernel/with the same flags,x86_64-unknown-noneandaarch64-unknown-none-softfloat, each with no features,boot-actuators, andboot-actuators,test-actuatorscargo test --test toyos-build --no-runcargo run -- --build-onlyIndependent oracle
scan_portsinkernel/src/drivers/xhci/wait/boot.rsalready relies on it. That rule is what makes the fix right on real controllers. It is also why a port given up on with CSC left set can raise no event for its own pull until that CSC is acknowledged.gave_updraws, andenumeration_ackalready acknowledges connect only afterSome(Reset::Warm).hw/usb/hcd-xhci.c, sha256561518aebfcbfa1a86313d550956f682de06ca5f90d904812e07693de8565cd8, for the QEMU that.github/qemu-versiondeclares (11.1.1).xhci_port_notifyraises an event only for a bit that is not already set.xhci_port_updaterebuilds PORTSC fromPORTSC_PPbefore it notifies CSC, so in QEMU every attach and every detach raises an event. QEMU reaches path 1 because the poll that starts the teardown drains both of the replug's events. It cannot stage paths 2 to 4, where a pull lands on a CSC that is already set, and it completes every reset enabled, so it cannot stage a give-up either. Only the simulator stages those.Unsure
gave_up, not by a run.RetrainsDisabledis modelled, not measured. No machine here has produced that completion word.usb_transport_breakred is inferred, not measured. The log shows it took no pass while logd's create heldvfs::lock(). That it spun on that lock is the reading that fits both resumptions.🤖 Generated with Claude Code