Skip to content

xHCI: every report that moves a port's belief leaves it to be read - #554

Merged
Japabu merged 17 commits into
mainfrom
wt/toyos-xhciwake
Sep 28, 2026
Merged

Japabu merged 17 commits into
mainfrom
wt/toyos-xhciwake

Conversation

@Japabu

@Japabu Japabu commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

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 where torn_down and enumerated set attached and slot and leave the port Work::Unread, and the rule is stated there. take_slot also clears slot, for a caller about to disable it, and leaves attached and the work as they were.
  • PortState::gave_up is 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's give_up wraps it, and the boot scan's two give-ups call it.
    • A warm give-up (LinkNeverTrained) leaves Work::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.
    • A hot give-up (ResetNeverFinished, ResetFailed) goes through believe(true, None): slot is always None there, because Resetting is 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_finished is the one mapping from a reset whose deadline passed to its GaveUp, used by the hot-plug machine and the boot scan.

The kernel paths it fixes

  1. A collapsed replug. A teardown for a device that is still in its port ends with the port Settled and its edge spent, so the new device is enumerated only when some other port raises an event. This is xhci_flap's defect.
  2. A port given up on. Take a USB3 device pulled during the warm retrain after a failed reset. The port is given up on as attached, and its absence is never torn down.
  3. The end of an enumeration. Take a device pulled while Enable Slot goes unanswered. Enable Slot is never cancelled, so when its deadline ends the enumeration the port is reported attached, and nothing reads it again.
  4. An enumeration begin refuses. Take a USB3 device pulled between the step that saw its warm completion and device::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 and refuses before Enable Slot. service_port then returned outstanding.wake_at(), which is None with nothing else outstanding.

What changed

  • 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 (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 where due says so, and fails with Stuck::Unwoken when a pass leaves the port outstanding with no instant to come back at.
  • ResetBehaviour::RetrainsDisabled stages the warm give-up: the bus reset fails as FailsTheBusReset'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_pulled holds such a device for 2 * RESET_DEADLINE_NS + 20 * DEBOUNCE_NS. It expects exactly [Reset(Hot), Reset(Warm), GaveUp(LinkNeverTrained)], then pulls the device and expects ToreDown(Disconnected).
  • a_device_replugged_inside_a_hot_reset_given_up_on_is_enumerated stages the hot give-up: a reset that would complete after 50 ms, a replug during it, and a run to 2 * RESET_DEADLINE_NS + 4 * DEBOUNCE_NS. It expects one enumeration.
  • service_port's GaveUp arm, 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's Enumerate arm no longer returns after device::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's Enumerate arm now mirrors begin's !enabled refusal, which it did not model. Driver::pulled_as_it_enumerates stages the pull at the site where the reentrancy check already runs.
  • PortState::adopt and XhciController::port_bound are deleted. The boot scan only ever recorded None through them, for a port it gave up on. It calls gave_up.
  • The boot scan no longer stores into PORT_WORK_AT. The ISR records every interrupt the scan's own resets and commands raise, and no scheduler pass runs before smp::set_ready. So the first poll_if_pending finds XHCI published and steps every port the scan left outstanding. The publish in wait/boot.rs states 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 in PORT_WORK_AT costs one poll, which stores the right value.
  • xhci_flap paces 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 with a collapsed replug was torn down and its port never looked at again. Two checks that five enforced binds made unreachable are gone. Its src/redlist.rs row and its issue are deleted.
  • Deleted: issues/hardware/a-collapsed-replug-is-enumerated-only-when-another-port-event-arrives.md, because this PR fixes it.
  • Filed:
    • issues/kernel/a-disk-refused-while-one-is-held-is-enumerated-again-and-no-test-reaches-that-arm.md
    • issues/kernel/an-acknowledge-after-an-enumeration-clears-a-replug-no-look-has-seen.md: finish and refuse write CSC back after enumerated, so the look this PR adds catches a pull but not a replug in that window. The simulator's enumerated acknowledges 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 to Stuck::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.
  • Recorded as sightings: usb_transport_break's red below, in issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md and issues/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 restored in each log). Each ran cargo test -p toyos-xhci -p toyos-xhci-sim --all-features --no-fail-fast, compiled the patched crates, and exited 101 with the reason quoted.

arm exit red, with the reason
a hot give-up left Work::GivenUp 101 a_device_replugged_inside_a_hot_reset_given_up_on_is_enumerated alone: 0 enumerations, [Reset(Hot), GaveUp(ResetNeverFinished)]. The same test with port.rs as it was before the hot side went through believe also exits 101 with that record.
a warm give-up through believe 101 a_port_given_up_on_is_not_reset_again_until_its_device_is_pulled alone: "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), …]
every give-up through believe, and a replug judged whatever the work 101 a_port_given_up_on_… alone, "118 reset(s)"
port.rs at base, with due added; the edge rule and the pump kept 101 repeated_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"), and a_port_given_up_on_… ("118 reset(s)"). The same patch, with the boot scan's two gave_up calls taken back to enumerated(None), compiles the kernel (cargo check --target x86_64-unknown-none --features boot-actuators,test-actuators, CHECK EXIT=0).
enumerated and both give-ups set Settled 101 The port unit test: "enumerated: nothing would read…". a_device_pulled_while_enable_slot_goes_unanswered_is_torn_down, gate_an_enumeration_that_outlives_its_port_costs_a_deadline, and a_port_given_up_on_… ("118 reset(s)")
that, plus the GaveUp arm returning again, which is base's give-up 101 All of the row above's enumeration reds, plus a_device_replugged_inside_a_hot_reset_… (0 enumerations) and a_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)]".
the GaveUp arm returning, alone 101 Unwoken in 9 tests, among them no_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_alone and a_device_replugged_inside_a_hot_reset_…
the sim's begin refusal returns, the way service_port did 101 a_device_pulled_as_its_enumeration_begins_is_torn_down alone: Unwoken
the sim's begin refusal not modelled 101 a_device_pulled_as_its_enumeration_begins_is_torn_down alone: [Reset(Hot), Reset(Warm), Enumerated { slot: Some(1), .. }, ToreDown(Disconnected)], a slot spent on a port that read disabled

a_port_that_never_finishes_its_reset_is_given_up_on_and_left_alone is green with the hot give-up through believe: that port is not tried again. a_device_pulled_while_enable_slot_goes_unanswered_is_torn_down and a_device_pulled_as_its_enumeration_begins_is_torn_down assert 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:

At e889d03, before those two fixes landed on main:

  • usb_transport_break --nightly: EXIT=1, in the AnotherStick boot: "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".
    • Every call of PortState::gave_up is preceded by a line naming the give-up, in service_port and in the boot scan's init_device. The boot's log has none.
    • No port was stepped after the break at all. Port 1 read empty and has no port 1 disconnected line. Port 3 read connected and untaken throughout, so no port 3 connected line exists, and the arrival rule kept disk 0 held with no did not come back line.
    • cpu0 spent 0.383 to 4.385 s in logd's create under 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.
    • That is 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)

gate exit
cargo test -p toyos-xhci -p toyos-xhci-sim --all-features --no-fail-fast 0
cargo test -p toyos-build --lib redlist 0
cargo clippy -p toyos-build -p toyos-xhci -p toyos-xhci-sim --all-targets --keep-going -- <the six adopted lints> -D warnings 0
cargo clippy in kernel/ with the same flags, x86_64-unknown-none and aarch64-unknown-none-softfloat, each with no features, boot-actuators, and boot-actuators,test-actuators 0 (all six)
cargo test --test toyos-build --no-run 0
cargo run -- --build-only 0

Independent oracle

  • The xHCI rule. A Port Status Change Event is raised only when a change bit goes from 0 to 1. scan_ports in kernel/src/drivers/xhci/wait/boot.rs already 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.
  • xHCI 1.2 §4.19.5.1: a warm reset retrains the link and re-detects the device, raising a connect edge of its own; a hot reset does not change CCS. That is the line gave_up draws, and enumeration_ack already acknowledges connect only after Some(Reset::Warm).
  • QEMU's hw/usb/hcd-xhci.c, sha256 561518aebfcbfa1a86313d550956f682de06ca5f90d904812e07693de8565cd8, for the QEMU that .github/qemu-version declares (11.1.1). xhci_port_notify raises an event only for a bit that is not already set. xhci_port_update rebuilds PORTSC from PORTSC_PP before 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.
static void xhci_port_notify(XHCIPort *port, uint32_t bits)
{
    ...
    if ((port->portsc & bits) == bits) {
        return;
    }
    ...
    port->portsc |= bits;
    ...
    xhci_event(port->xhci, &ev, 0);
}

Unsure

  • No test can fail on the boot store's deletion. It rests on the scan's own interrupts being recorded, and QEMU raises one on every reset completion.
  • No test reaches the boot scan's give-ups. QEMU completes every reset enabled, and the simulator has no boot scan; the calls are checked by the kernel compiling against gave_up, not by a run.
  • RetrainsDisabled is modelled, not measured. No machine here has produced that completion word.
  • What held cpu1 in the usb_transport_break red is inferred, not measured. The log shows it took no pass while logd's create held vfs::lock(). That it spun on that lock is the reading that fits both resumptions.

🤖 Generated with Claude Code

Japabu and others added 3 commits September 27, 2026 21:56
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>
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #554 at 22db753, round 1

Readiness. CI host at 22db753 concluded SKIPPED, because the PR is a draft and ci.yml runs only on non-draft PRs. It is not green, and under reviewer.md that alone means NOT READY FOR REVIEW. I reviewed anyway because the brief supplies both guest arms at this code: toyos-xhci/src/port.rs has not changed since e7ad935, and 22db753 only deletes two issue files.

  • Host: cargo test -p toyos-xhci -p toyos-xhci-sim --all-features passed, with 115 unit tests and every sim suite ok, including a_torn_down_port_is_outstanding_until_it_is_read. cargo test -p toyos-build --lib redlist also passed.
  • Red: port.rs at c551894 with the gate kept gave xhci_flap EXIT=1: "a collapsed replug was torn down and its port never looked at again: 1 bind(s) for 5 plugs". The port's last line was the collapse at 0.973 s, and 30 s passed with nothing after it.
  • Green: e7ad935 gave EXIT=0: "4 replugs collapsed inside the debounce (4 seen as such): 5 slot(s) enabled".

Net lines. +83 −118 in total:

  • production: +9 −4 in toyos-xhci/src/port.rs
  • tests: +17 in the port.rs unit test, and +57 −36 in tests/common/usb.rs
  • −4 in the redlist and −74 in the issue file

The production growth is one enum variant and its one transition. I accept it.

The brief's questions

  • One rule in one function? Yes. PortState::torn_down is the only report of a finished teardown, and it always leaves the port outstanding(). Outside bring-up's EMPTY, no public entry point gives "not attached, Settled" without a look. In the kernel, the look depends on two things:

    • poll (kernel/src/drivers/xhci/mod.rs:1525) tests outstanding after advance_outstanding.
    • No teardown completes inside the boot scan's settle_outstanding before boot.rs:144 zeroes PORT_WORK_AT. awaited is filled only by a runtime teardown, so refuse_for_now cannot choose Again during the scan.

    Both hold at this head. Nothing enforces either one.

  • Is AfterSlot::Again covered? Yes, for what this branch changes. The change reaches Again only through torn_down, and the unit test pins that function both ways: outstanding after a teardown, and at rest after one empty read. The arm's own wiring is untested, and that gap predates this branch (NOTE).

  • Waits: the gate waits on the bind line and on mev lines. The 30 s guest input window and the 60 s run ceiling only bound a hang, and the verdict is a count. The back-to-back del/add is the staging itself, and tests/toyos.rs:1464 already names it.

  • Guest runs before landing: the Fast tier at this head (the PR body says it has not run), plus xhci_hotplug, xhci_hid_break and usb_transport_break. Every teardown with a slot now ends in Unread. usb_transport_break's held-disk arms are the only runs that can reach AfterSlot::Again.

  • Left to delete: two checks in xhci_flap that this branch made unreachable (NOTEs).

  • PR body: not fit for main's record. It says the negative control was not measured and that the issue was set status: closed, and both are false at this head. The oracle is named.

BLOCKER

None.

NOTE

  • PR body: add the orchestrator's measurements to the negative-control and green rows, with the command, EXIT=1 / EXIT=0 and the red sentence. A high-risk change names its control with its result, and this body becomes the merge commit.
  • PR: run gh pr ready, then get host green at the landing head. No CI has run on this branch, including clippy with warnings denied.
  • Guest: the Fast tier, xhci_hotplug, xhci_hid_break and usb_transport_break must be green at this head, run by the orchestrator (see above).
  • tests/common/usb.rs:4052: sources.is_empty() can no longer fire once binds == CYCLES + 1 has passed, because both count the same "xHCI: pointer on slot … merges as source" line. Delete it.
  • tests/common/usb.rs:4003-4019: with five binds enforced, collapsed == 0 can only mean every replug was seen as distinct. The dead-port reading and both counts in the message are dead. Delete the comment, and cut the message to its first sentence.
  • tests/common/usb.rs:3917: BOUND is a second copy of the literal parse_pointer_sources parses (tests/toyos.rs:18499). Pace on !crate::parse_pointer_sources(line).is_empty(), or share one constant.
  • toyos-xhci/sim/src/driver.rs:312: pump steps the port on every pass. The kernel steps ports only when ports_dirty || outstanding (kernel/src/drivers/xhci/mod.rs:1525). So the simulator that calls itself "the loop the kernel runs" could not show this defect, and no host test can fail on the ordering inside poll: moving the outstanding test above advance_outstanding keeps every host suite green, and only the nightly xhci_flap catches it. File an issue. The gating decision belongs in toyos-xhci, where the kernel and the sim both read it. A sim hub that wakes the driver only on a change bit's 0→1 would turn repeated_replugs_stay_balanced red on c551894's port.rs.
  • kernel/src/drivers/xhci/mod.rs:1481-1487: no test reads "is enumerated again while a disk is held", and no run has measured whether any guest test reaches the Again arm. File an issue; it is outside this branch's fence.
  • PR body, oracle: cite xHCI's rule beside QEMU's xhci_port_notify. That rule is a Port Status Change Event only on a change bit's 0→1 transition, which scan_ports's own comment in wait/boot.rs already relies on. QEMU is not the hardware, and the specification is what makes the fix right on real controllers too.

REMOVE

  • PR body "Draft, blocked: … every guest run in this worktree now exits 101 before a guest boots.": false at this head.
  • PR body "(filed as issues/build/a-sysroot-cloned-while-stage2-had-no-cargo-stays-broken.md)": 22db753 deleted that file.
  • PR body gate rows "negative control … not measured …" and "Fast tier | not run": replaced by the measurements (NOTE).
  • PR body "The issue is set status: closed with one closing sentence, as the brief asked.": false, because the file is deleted.
  • PR body "## Unsure", second bullet (status: closed): no longer applies.
  • PR body "The logs are in the job scratchpad under xhciwake-r1/.": scratchpad paths rot.
  • PR body "The file was not re-fetched here because this task had no web access.": narration.
  • PR body ", plus the synchronous teardown_port path": that path already stepped the port again inside the same service_port loop, so listing it as fixed misleads.

LAND AFTER NAMED CHANGES

Japabu and others added 2 commits September 27, 2026 23:02
…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>
@Japabu
Japabu marked this pull request as ready for review September 27, 2026 21:05
Japabu and others added 2 commits September 27, 2026 23:22
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>
@Japabu Japabu changed the title xHCI: a torn-down port is read again without waiting for an event xHCI: every report that moves a port's belief leaves it to be read Sep 27, 2026
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #554 at b000f38, round 3

Round 1 BLOCKERs. None were raised. The round-1 NOTEs on xhci_flap's dead checks, BOUND, the sim's per-pass stepping, the Again issue and the xHCI edge citation are all done at this head.

Readiness.

  • CI host concluded success at b000f38 (run 36351696122).
  • Host gates at this head: cargo test -p toyos-xhci -p toyos-xhci-sim --all-features EXIT=0, and the redlist, clippy (host and six kernel shapes) and harness builds all EXIT=0 (xhciwake-r3/gate-*.log).
  • The orchestrator's guest runs at b000f38 all gave EXIT=0: xhci_flap, xhci_hotplug, xhci_hid_break, usb_transport_break, xhci_deaf_registers, xhci_slot_exhaustion, usb_refused_disk_first and usb_boot_stick_pulled.
  • The red arm (port.rs taken back to base, with due kept) gave xhci_flap EXIT=1: "a collapsed replug was torn down and its port never looked at again: 1 bind(s) for 5 plugs".
  • The Fast tier gave EXIT=1, 395 passed and 1 failed. The failure is lan_mdns_answer: QEMU refused the lane-10 tap socket path as past 104 bytes before any guest booted. issues/build/a-lane-s-tap-socket-path-outgrows-sun-len-on-the-dev-host.md tracks it. It is not this diff, but the redlist does not carry it.

Net lines. +249 −171 in total:

  • production: +39 −17 (the kernel +6 −4, port.rs +33 −13), plus −4 in the redlist
  • tests: +152 −76
  • issues: +58 −74

I accept the production growth: one variant, one setter, one pure due, and the boot store.

The brief's questions

  • Is believe one rule, complete across every transition? For attached, yes: every writer goes through it. take_slot (port.rs:350) still writes slot outside it. It needs no look, but it makes the "one place" wording false (REMOVE).
  • Is the same-pass re-read safe? Within a pass, yes. GaveUp leaves Resetting only, and nothing gets back to Resetting without a debounce, so a pass holds at most one GaveUp. MAX_EFFECTS and STEP_BUDGET bound the rest. Across passes, the PR's own Unsure case (a completed reset left with CCS and CSC set) turns GaveUp into a teardown and a retry at debounce cadence, forever. That case is unrecorded (NOTE).
  • Is the (b) arm honest? Yes. It also shows that believe inside the GaveUp transitions cannot be seen apart from Settled once the re-read follows. That is acceptable as uniformity, and no test can pin it.
  • Is the boot wake right? Its effect is right. It ignores ports_dirty and no test can fail on it (BLOCKER 2).
  • Is Stuck::Unwoken the kernel's loop? Only for the arms the sim models. device::begin's refusals before Enable Slot are not modelled, and there the kernel leaves exactly the unwoken state this PR defines as a failure (BLOCKER 1).

BLOCKER

  1. kernel/src/drivers/xhci/mod.rs:1318-1320: when device::begin refuses synchronously (device.rs:289, 296, 305 → finish → enumerated → Unread), service_port returns self.outstanding.wake_at(). That is None when idle, so the port is left Unread with no pass scheduled. This is the kernel's own Stuck::Unwoken, and it is reachable:

    • A USB3 device is pulled between the step that saw its warm completion and begin's read.
    • CSC is already 1 from the retrain, so the pull raises no edge.
    • enumeration_ack(Some(Warm)) then clears CSC. The re-read finds PED clear, and the port is believed attached with nothing to wake it.

    Fix: in the Enumerate arm, continue when self.ports[port_idx].working().is_none() after begin. Mirror begin's !enabled refusal in the sim's Enumerate arm (toyos-xhci/sim/src/driver.rs:391-426), which today issues Enable Slot regardless. Add a sim test that detaches the port between the Enumerate step and the ack (a hook at the reenter site) and expects ToreDown(Disconnected). That test must go red with Unwoken when the sim arm returns the way mod.rs:1320 does.

  2. kernel/src/drivers/xhci/wait/boot.rs:144-146, high-risk code, and the PR itself says no test can fail on this claim. The mutation PORT_WORK_AT.store(0, Ordering::Relaxed); turns nothing red. Name the test it turns red: a device the scan bound, pulled after its enumeration and before acknowledge_port_changes spends its edge, torn down within a debounce with no other port event. If no such staging can be found, the store is not load-bearing and goes.

NOTE

  • kernel/src/drivers/xhci/wait/boot.rs:144: port::due(false, &c.ports) passes a literal where c.ports_dirty is the input due names. An inline drain during the scan sets that field, and this store discards it.
  • The kernel has no counterpart to Stuck::Unwoken. poll silently stores 0 when a port is still outstanding and no wake is named, so this class stays invisible on hardware. A loud line in poll for due(false, ports) && wake_at.is_none() would name it.
  • PR "Unsure", second bullet: a GaveUp that leaves CCS and CSC set becomes a reset loop at debounce cadence, which is what GaveUp exists to stop (port.rs:478-480). File it in issues/ with an owner and an exit, or stage the word in FakePort.
  • PR body: replace the "Guest arms" section with the orchestrator's b000f38 measurements listed above: the eight EXIT=0 runs, the red arm's EXIT=1 with its sentence, and the Fast tier's EXIT=1 with lan_mdns_answer and its issue. Also cite round 1's whole-revert arm (port.rs at c551894 on the base kernel, EXIT=1) as the negative control that reverts the whole change.

REMOVE

  • PR body "is the only setter of attached and slot": false, because take_slot (port.rs:350) sets slot.
  • toyos-xhci/src/port.rs:400 "The one place the driver's belief about a port is set": false for the same reason.
  • kernel/src/drivers/xhci/mod.rs:1319 "Either enumeration is under way and the port waits, or it refused before spending a command.": it presents the refusal's return as correct, and it is not (BLOCKER 1).
  • PR body "These were measured at e7ad935, before due, the edge rule and the three transitions. They are owed again at this head.": stale.
  • PR body gate row "Fast tier, xhci_flap, … | not run here; the orchestrator runs them": stale.
  • PR body "Its version was not measured again for this PR.": not load-bearing.
  • PR body Unsure, first bullet, "…and they should stay green.": the runs were made.
  • PR body Unsure, third bullet (AfterSlot::Again staged by no test): the filed issue carries it.

SEND BACK

Japabu and others added 2 commits September 28, 2026 00:11
…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>
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #554 at 9f2b1f9, round 4

Round 3 BLOCKERs

  1. CLOSED. service_port's Enumerate arm returned after a begin that refused.
    • The fix: kernel/src/drivers/xhci/mod.rs:1318-1319 no longer returns there.
    • The measurements, at 9f2b1f9:
      • With the sim's refusal returning the way the kernel did, a_device_pulled_as_its_enumeration_begins_is_torn_down fails with Unwoken (xhciwake-r4/arm-sim-returns.log, ARM EXIT=101, tree restored).
      • With the refusal not modelled, the same test fails on its did assertion (arm-sim-no-refusal.log, EXIT=101).
      • The head gates give EXIT=0.
    • The loop terminates on every path. A refusal leaves the port Unread through finish, and the next read returns one of four ways:
      • Idle: the port reads as believed, whether connected and refused on speed or disabled with no change flag.
      • Wait: a fresh Debouncing { at: now }.
      • A Write, followed by one of the other outcomes.
      • Teardown, then torn_down, then Wait.
    • Enumerate can only come from a debounce held for DEBOUNCE_NS or from a completed Resetting, and neither can be entered and finished in the same pass. So a refused port gets no second begin in that pass, and MAX_EFFECTS bounds the loop in any case. A port that stays refused is attached and reads Idle. It does not spin.
  2. CLOSED, by deleting the store. The code supports the author's argument:
    • xhci_handler records every MSI with isr_publish. The only thing that consumes the record is poll_if_pending, and it runs only inside a pass.
    • Before smp::set_ready, no pass runs on any CPU. On the BSP, do_preempt returns when there is no current_tid (kernel/src/scheduler.rs:356). The APs spin until ROSTER.released() (kernel/src/arch/x86_64/smp.rs:357). So the record survives until a pass that finds XHCI published and due true for the Unread ports. This holds even under iommu-dest-apic1.
    • Every reset that completes raises an edge, because reset_write acknowledges PRC/WRC before it resets (toyos-xhci/src/port.rs:103-104). Every begin that gets past its checks submits Enable Slot.
    • Only a reset that never finishes raises nothing. Its port went through adopt(None), so it holds no slot and no device. A device pulled from it leaves a stale attached, and the next connect's CSC edge reads that as Replugged (port.rs:498) and tears it down at no cost.
    • No test can reach this, and the case it would have covered costs nothing. The deletion stands.

Readiness

  • CI host concluded success at 9f2b1f9 (run 36354508419; the run's headSha matches).
  • The author's host gates at 9f2b1f9 are all EXIT=0 (xhciwake-r4/gate-*.log). Those are cargo test -p toyos-xhci -p toyos-xhci-sim --all-features, the redlist, clippy on the host and on six kernel shapes, the harness build, and --build-only.
  • The guest runs at this head are queued (orch-runs/queue11.sh) and none has reported. The last guest measurements are from b000f38, and since then the kernel has changed in the Enumerate arm and the boot store.

Net lines: +341 −175 in total.

  • Production: +36 −21. That is the kernel +4 −8 and port.rs outside its tests +32 −13. The redlist adds −4 on top.
  • Sim and tests: +183 −76.
  • Issues: +106 −74.

I accept the production growth: one variant, one setter, one pure due.

BLOCKER

None.

NOTE

  • toyos-xhci/src/port.rs:396: adopt is now enumerated under a second name. Its only caller, port_bound (kernel/src/drivers/xhci/mod.rs:1192, from wait/boot.rs:494 and 509), always passes None.
    • Delete adopt and port_bound's slot parameter, and have boot.rs call enumerated(None).
    • Drop the adopt row from a_port_whose_belief_moved_is_outstanding_until_it_is_read and the "adopt sets Settled" arm.
  • kernel/src/drivers/xhci/wait/boot.rs:161: the deletion depends on an invariant that is enforced in two other files: no pass runs before smp::set_ready, so the scan's interrupt record reaches the first poll with XHCI published. Put that one clause at the publish. A future boot-time pass would otherwise drop the post-boot read, and no test would notice.
  • issues/kernel/a-reset-given-up-on-...: this branch introduces that loop. On main, ports_dirty is cleared before service_ports, so a GaveUp left with CSC set waited for another event. The file is the right record. Its owner should know that the regression dates from this merge.
  • Guest runs owed at 9f2b1f9 (queue 11):
    • Each of the eight USB tests must give EXIT=0.
    • 554r4-red xhci_flap must give EXIT=1 with "a collapsed replug was torn down and its port never looked at again".
    • The Fast tier may fail on lan_mdns_answer alone.

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>
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #554 at f2ba12f, round 5

Round 4 BLOCKERs: there were none.

The orchestrator's ruling (no give-up retry loop): CLOSED for the hot-plug machine.

  • give_up (toyos-xhci/src/port.rs:413) is the one place both GaveUp transitions go through (port.rs:468 and :482). The replug judgement skips Work::GivenUp (port.rs:499).
  • Arm c reverts give_up to believe and removes the skip. It goes red with "118 reset(s) in 6000000000 ns" (xhciwake-r5/arm-c-giveup-reverted.log, ARM EXIT=101, tree restored).
  • Arm b-whole goes red with "the pull of a port given up on was never seen" (arm-b-whole.log, EXIT=101).
  • The boot scan does not go through the rule. See the first NOTE.

Readiness

  • CI ci concluded success at f2ba12f (run 36355876723, headSha matches).
  • The author's gates at f2ba12f are all EXIT=0 (xhciwake-r5/gate-*.log, gates.head = f2ba12f).
  • The orchestrator's runs at f2ba12f (orch-runs/summary.txt:131-140):
    • All eight USB tests: EXIT=0.
    • 554r5-red xhci_flap: EXIT=1, with "a collapsed replug was torn down and its port never looked at again: 1 bind(s) for 5 plugs".
    • Fast tier: 395/1. The one failure is lan_mdns_answer, which fails on SUN_LEN (554r5-fast.log:1074).
  • The branch merges cleanly into origin/main 41ad548 (git merge-tree EXIT=0).

Net lines: +370 −204.

  • Production: +49 −37. That is port.rs outside its tests +42 −22, and the kernel +7 −15.
  • Sim and tests: +243 −89.
  • Issues: +78 −74. The redlist adds −4 on top.
  • I accept the production growth: one variant, one setter and due.

BLOCKER

  • toyos-xhci/src/port.rs:413-416 and :499: Work::GivenUp is applied to hot give-ups too, where a connect flag is a real replug. This loses a device that main enumerates.
    • Why a hot give-up's flag is real. A hot reset does not change CCS. Only §4.19.5.1's warm retrain re-detects a device, which is exactly why enumeration_ack acknowledges connect only after Some(Reset::Warm) (port.rs:116-121). A hot give-up is ResetNeverFinished(Hot) or ResetFailed(Hot), which only happen on a non-USB3 port or one that reads disconnected. If CSC is set there with CCS=1, the device really was pulled and pushed back.
    • What the branch does. The GivenUp read acknowledges that flag and settles. The device stays unenumerated until it is pulled again.
    • What main does. On main and at the merge base e5ffe95, the port was left Settled and attached with CSC set. service_ports steps every port on every pass (kernel/src/drivers/xhci/mod.rs:1201-1213), so the next pass reads the port as Gone::Replugged and enumerates the device. That pass can come from another port's event, look_for or recheck_ports.
    • The PR body's Unsure line says as much: "On base, the same port raised no event for anything until another port did."
    • Fix. give_up stays the one rule. It leaves a warm give-up Work::GivenUp, because §4.19.5.1's connect edge is the reset's own and cannot be told apart from a replug. It leaves a hot one believe(true, self.slot), so the flag is judged.
    • Why the fix cannot loop. A hot reset raises no connect edge of its own, so a second teardown needs a second real disconnect. a_port_that_never_finishes_its_reset_is_given_up_on_and_left_alone already guards this. It was green at 9f2b1f9, where every give-up was believe.
    • The test that must go red at f2ba12f and green after the fix, in toyos-xhci/sim/tests/scenarios.rs:
      #[test]
      fn a_device_replugged_inside_a_hot_reset_given_up_on_is_enumerated() {
          let mut port = FakePort::occupied(ResetBehaviour::Completes { after: 50_000_000 });
          let mut driver = Driver::new();
          driver.run_to(&mut port, 0, DEBOUNCE_NS + 2 * PASS, PASS).unwrap();
          assert_eq!(driver.did, [Did::Reset(Reset::Hot)]);
          port.replug(); // the detach ends the reset (hub.rs:137-143); the attach finds CSC already set
          driver
              .run_to(&mut port, DEBOUNCE_NS + 2 * PASS, 2 * RESET_DEADLINE_NS + 4 * DEBOUNCE_NS, PASS)
              .unwrap();
          assert_eq!(driver.enumerations(), 1, "{:?}", driver.did);
      }
      • At f2ba12f I expect [Reset(Hot), GaveUp(ResetNeverFinished(Hot))] and 0 enumerations.
      • With the fix, a_port_given_up_on_is_not_reset_again_until_its_device_is_pulled must stay green. Mutating the warm side to believe must still turn it red, as arm c did.

NOTE

  • kernel/src/drivers/xhci/wait/boot.rs:495 and :510: the boot scan's give-ups report enumerated(None) rather than the give-up rule.
    • A boot port whose warm retrain completed disabled with CSC set is therefore torn down and reset once more by the first pass after boot. Only after that does give_up stop it.
    • It happens once, so it is not the loop. But it is a second answer to "what a give-up leaves".
    • Fix it on the way: have the boot scan report through the same rule, for example a pub setter that give_up wraps.
  • PR body: the guest section records runs at b000f38. Main's record should carry the runs at the landing head: orch-runs/summary.txt:131-140 now, or their re-run after the fix.

REMOVE

  • PR body, "(origin/main is merged; git merge origin/main reports already up to date)": main is at 41ad548, and the merge base is e5ffe95.
  • PR body, "At b000f38:" and its bullets under "Guest arms, run by the orchestrator".
  • PR body, Unsure, "A device replugged inside a reset that is then given up on…": after the fix this is false for hot give-ups. For warm give-ups the device is one the retrain failed, which is exactly what GaveUp declares.

SEND BACK

Japabu and others added 4 commits September 28, 2026 01:31
… 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>
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #554 at 430b077, round 7 (reviews what changed since round 5 at f2ba12f)

Round 5 BLOCKER (a hot give-up left Work::GivenUp, which loses a device replugged inside the reset): CLOSED.

  • PortState::gave_up (toyos-xhci/src/port.rs:428-442) is now the one rule. A warm give-up leaves Work::GivenUp, and a hot give-up goes through believe(true, …). give_up (:444) wraps it, and the boot scan's two give-ups call it (wait/boot.rs:495, :510).
  • The test I specified, a_device_replugged_inside_a_hot_reset_given_up_on_is_enumerated (sim/tests/scenarios.rs:113), was added verbatim.
  • Arm hot-givenup (the hot side reverted to GivenUp) is red on that test alone: xhciwake-r7/arm-hot-givenup.log, 14 passed and 1 failed, ARM EXIT=101, tree restored. The PR body records the same red with f2ba12f's port.rs.
  • Arm warm-believe is red on a_port_given_up_on_is_not_reset_again_until_its_device_is_pulled with "118 reset(s) in 6000000000 ns": arm-warm-believe.log, ARM EXIT=101, tree restored.
  • Round 5 NOTE (the boot scan's give-ups): closed. adopt and port_bound are deleted, and both boot give-ups go through gave_up.

Round 7's attribution of the usb_transport_break red at e889d03: sound. But its evidence is that no pass ran, not that the log has no give-up line.

  • In orch-runs/554r6-usb_transport_break.log, no port was stepped after the break at 0.386. There is no port 1 disconnected line, although port 1 read empty, and nothing stepped port 3.
  • Whether a pass runs is not in this diff. port::due is the expression it replaces (ports_dirty || any(outstanding)). Removing the boot's PORT_WORK_AT.store(0) can only add polls.
  • believe did run in that boot, through port 1's boot enumeration at 0.267. It leaves the port Unread, and that only makes a pass step the port sooner.
  • The issue is on main with an earlier sighting at 99a81a8, before this branch existed.
  • At 430b077 the same test is EXIT=0 (orch-runs/summary.txt:185).
  • Round 7's inference that cpu1 spun on vfs::lock() is unmeasured, and the issue says so. A ticket-lock wait logs LOCK CONTENTION at 50M spins (kernel/src/sync.rs:144), and the red log has no such line. So either each spin took more than 80 ns under TCG, or cpu1 was held somewhere else. Neither is this diff.

Readiness

Net lines: +482 −208.

  • Production: +79 −40. That is port.rs outside its unit test +72 −25, and the kernel +7 −15.
  • Sim and tests: +236 −89 in sim and tests/, plus the 23-line port unit test.
  • Issues: +144 −75. The redlist adds −4.
  • Production grew by 27 lines since round 5, for gave_up, never_finished and GaveUp::reset. The first two I accept. The last is the NOTE below.

BLOCKER

None.

NOTE

  • toyos-xhci/src/port.rs:139-146 and :158-164 — the Reset payloads of GaveUp::ResetNeverFinished and ResetFailed only ever hold Hot. Both never_finished and reset_outcome map every warm end to LinkNeverTrained. So GaveUp::reset's warm arm for those two variants is dead, and so are the kernel's "warm" log arms (mod.rs:1250 and :1268, boot.rs:489). Drop the payload: GaveUp::reset then goes, gave_up matches LinkNeverTrained against the rest, and a warm ResetFailed becomes unrepresentable.
  • toyos-xhci/src/port.rs:440 — believe(true, self.slot): self.slot is always None at a give-up, because Resetting is entered only from a port believed empty. It is a parameter with one value, so write None.
  • PR body — main's record carries no guest run at the landing head. Add the 430b077 runs from orch-runs/summary.txt:185-194:

REMOVE

  • PR body, "Nothing this PR changes ran in that boot.": false. Port 1's boot enumeration ran enumerated → believe, and the publish without the PORT_WORK_AT store ran too.
  • PR body, the "At e889d03:" bullets other than the usb_transport_break red and its sub-bullets: the runs at 430b077 supersede them.
  • PR body, "Owed at 430b077, run by the orchestrator: …": those runs have been made.
  • PR body, Unsure, "One red boot in three runs of the test is the whole sample, so its rate at this head is unknown.": a count that is already stale.

LAND AFTER NAMED CHANGES

Japabu and others added 2 commits September 28, 2026 09:02
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
@Japabu
Japabu enabled auto-merge September 28, 2026 07:16
@Japabu
Japabu added this pull request to the merge queue Sep 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 28, 2026
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
@Japabu
Japabu added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 69d1b53 Sep 28, 2026
1 check passed
@Japabu
Japabu deleted the wt/toyos-xhciwake branch September 28, 2026 11:28
Japabu added a commit that referenced this pull request Sep 28, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant