Skip to content

USB mass storage: the BOT round trip and SCSI bring-up are toyos-xhci machines, and a stick that leaves its port is no break - #588

Merged
Japabu merged 14 commits into
mainfrom
wt/toyos-botscsi
Sep 30, 2026
Merged

Japabu merged 14 commits into
mainfrom
wt/toyos-botscsi

Conversation

@Japabu

@Japabu Japabu commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

The USB mass-storage BOT round trip and the SCSI bring-up above it become pure toyos-xhci code with host tests. kernel/src/drivers/xhci/wait/msc.rs keeps the shim: it queues the transfers the machines ask for, waits for them in place, publishes the phase to the reset path, runs the staged actuators, and writes the log lines. It still runs the ladder's climb loop and decides that a disconnect mid-command goes to the port's teardown, offline: dev.failed, and data_out. A userland usbd can drive the same machines unchanged. The crate is no_std, has no unsafe code and makes no x86 assumptions.

The T14 reading of this branch showed one defect in the ladder that the branch carried over from main: a stick that left its port under the port rung's reset was counted as a break and narrated as taken offline. That is fixed here too; see the ladder section.

What changed and why

toyos_xhci::bot

  • cbw() builds the 31-byte CBW (USB MSC BOT 1.0 §5.1): LUN 0, direction taken from the CDB.
  • RoundTrip is the round trip as a stepped machine. Its acts are Command, Data(pipe), Status and Restart { pipe, then }, and it is answered with Moved { code, residue }, Silent, Gone or Restarted.
    • CBW and CSW are fixed-length legs: a short one is a break, not a short transfer.
    • A stalled data phase is restarted and its status is read (§6.7.2). What it delivers is the smaller of what the controller's residue and the CSW's leave.
    • A stalled CSW is retried once, as §5.3.3 / Figure 2 allows, and that includes a CSW after a data-stall restart.
    • CswDue::judge checks signature and tag (§6.3.1), then applies §6.3.2's meaningful test. Status 2 is a phase error whatever the residue. Statuses 0 and 1 are refused when the residue is larger than the transfer. Statuses 3..=255 are Broke::Reserved. Delivered bytes are min(controller's count, device's count), and a controller residue larger than the transfer delivers nothing.
  • Broke<W> carries left() and event() and is generic over the driver's silence reason.
    • A disconnect is its own Gone variant; it used to be Silence { why: Quiet::Gone }. The crate has to tell a disconnect apart, and the kernel's Quiet is not a type the crate has.
    • Broke<Quiet>::Silence { why: Quiet::Gone } can still be built, and only completed() in msc.rs keeps it apart from Broke::Gone. It is not made unrepresentable because the price is a second kernel enum that mirrors three of Quiet's four variants (one of them cfg'd), with conversions both ways, all to guard one match in one function. Deleting that match's Gone arm is the negative control below, run in the guest.
    • left() matches on a Phase. It used to match on a phase's name string.
  • CC_STALL joins CC_SUCCESS and CC_SHORT_PACKET in toyos_xhci::job. The kernel imports all three from there, and its own copies are deleted.

toyos_xhci::scsi

  • Cdb can only be built inside this module, and it carries its own direction. The kernel's cdb_len/data_in parameters and its CDB-shape assert are gone, because a mismatch can no longer be written.
  • Sense::of believes sense data only if at least 14 bytes arrived (ASCQ is byte 13; SPC-4 §4.5.3).
  • Reply is the answer to one SCSI command. It is named apart from the crate root's toyos_xhci::Outcome.
  • flushed(): INVALID COMMAND OPERATION CODE means the device has no cache, which is not a failure.
  • Transfer does the READ/WRITE(10) batching and sector addressing. Only the first batch may answer "ask again". Geometry's fields are private and read through accessors, so only the bring-up can size a disk, and the expect in Transfer::next depends on that. Geometry::NONE fits no transfer, and a test checks it.
  • BringUp is a stepped machine:
    • TEST UNIT READY on a budget, then sense or a recovery climb;
    • INQUIRY with the peripheral-type check;
    • READ CAPACITY(10), then (16) when the 10-byte answer is all ones;
    • every refusal by name (Refusal).
  • The sector sizes served are 512, 1024, 2048 and 4096. This set is the driver's own and is carried over unchanged. It is not SBC-3's, which allows any logical block length. 256 divides the host block and is still refused, and the test says so.
  • The kernel drives BringUp blocking, the same way the boot scan drives enumeration. A stepped kernel driver for the hot-plug bind is not built here; the renamed issue below owns that.
  • Printable moved here unchanged.

toyos_xhci::ladder::Run holds a device's break count and highest rung. It used to be two loose fields. identity::first_language reads the LANGID of string descriptor zero.

The ladder: a rung whose device left its port ends as the device leaving.

  • What the T14 showed at 95ebbaf (usb_transport_break on metal, SanDisk 0781:5581 on the xHC at 00:14.0):
    • The stick sat on port 1, one of the controller's USB 2.0 ports (its Supported Protocol capability: USB 2.0 on 1..=12, USB 3.1 on 13..=16), at high speed.
    • The staged break was in the data phase of a WRITE(10), so the stick was owed Data-Out and the ladder entered at the port rung with no class reset (ladder::enters_at, unchanged; the log says so by name). This is the ladder main has: enters_at, next, the port rung's steps and take_offline are the same there.
    • The port rung's hot reset completed with the port Enabled at high speed (PORTSC 0x00000e03 then 0x00200e03). 50 ms later Address Device answered USB Transaction Error.
    • The rung ended Failed, the climb counted break 2 and entered the Offline rung. That rung read port 1 Disconnected (0x000202a0: CCS clear, CSC set, RxDetect), wrote no reset and sent no command, and said is offline.
    • The disk was held. At 2.210 s, 0.93 s after port 1 read empty, the stick connected on port 13 at SuperSpeed, bound as the same device, took disk 0 back, and the WRITE went out again on it and completed.
  • Why the rung could not see the stick go: a USB2 port detects no disconnect while it drives a reset (xHCI 1.2 §4.19.1.1.2, note 57), and it advances to Enabled when the reset ends (§4.19.1.1.4). A peripheral uses one connection at a time (§4.19.7). So a SuperSpeed stick that answers the reset by training on the USB3 half of its receptacle can only be seen to leave the USB2 port once the reset's completion has been read. The rung's next step then meets an empty port.
  • Portsc::holds: connected, with no unconsumed connect change. The port machine, the HID recovery, the climb and reset_port all read it; the first two spelled it inline before.
  • The decision is toyos_xhci::ladder's. Run::unverified(&Unverified<W>, holds) answers AfterRung::Left for a rung below Offline, whichever rung and however it ended, on a port that no longer holds its device: no break is counted and no rung above runs. Otherwise it answers the next climb and the transfer event the next rung quiesces against. The kernel reads PORTSC once the rung has ended, applies the answer, and writes the line: the port reset was not answered, and port 1 no longer holds the device (PORTSC 0x000202a0): its port's teardown takes it from here. The disk is held if a reset of this driver's earns it one, and the slot is the port's teardown's (§4.4). The kernel's Climbed enum is replaced by Result<(), ladder::Unverified<Quiet>>.
  • AfterReset::goes_on is the port rung's decision to go on past its reset: only Enumerate. A reset that reads the port empty (AfterReset::Left) ends the rung Failed and reaches the same read. Every AfterReset::Left reads !holds there: an empty port reads connected again only on a new connect, which raises CSC, and the only acknowledge between the two reads is enumeration_ack, which clears CSC only after a warm reset whose read carried it.
  • The arm reads every rung, so a class rung whose device left also ends there, without the port rung's quiesce. No guest test stages that direction; c1 below is its host control, and its outcome is main's: the device is left to the teardown.
  • A bind whose TEST UNIT READY broke as Gone still climbs, and meets the empty port only at the first rung's end. main has that too: filed as issues/kernel/a-bind-whose-device-left-aims-the-ladder-at-its-empty-port.md.

usb_stick_left, the fix's test, with no redlist row (tests/common/usb.rs):

  • The gate's data stick (Profile::UsbDisk) takes the staged break on its first WRITE(10), is owed its data, and the ladder enters at the port rung. reset_moves holds the rung once; the host unplugs the stick on the hold's cue (device_del of usbdev0) and never plugs it back, so no call waits on a device that returns.
  • Three boots, one per hold:
    • usb-reset-moves, before the reset's completion is read: the reset reads the port empty.
    • usb-reset-moves-after, once the completion has been read with the stick on the port. QEMU 11.1.1 answers Address Device for a port with no device with TRB Error (hw/usb/hcd-xhci.c, xhci_address_slot through xhci_lookup_uport), so the rung fails at the step the T14's did.
    • usb-reset-moves-configured, once every step of the rung was answered and before its TEST UNIT READY. QEMU detaches the slot's port before it updates PORTSC (xhci_detach calls xhci_detach_slot first), and a doorbell on a slot whose port is detached kicks nothing (xhci_kick_epctx, xhci_slot_ok), so the CBW's wait reads the port disconnected and the rung ends OutOfStep(Broke::Gone).
  • Each boot judges, in order after the staged break: the ladder entering at the port rung, the hold, the leave line naming the held port, then xHCI: slot N disabled for the stick's slot. It refuses usb-storage: <slot> is offline and any break 2 of line of that slot.
  • The usb-reset-moves and -configured boots refuse Reset Device failed and Address Device (after the port reset) between the hold and the leave, which B2's fold would send to the empty port.
  • The T14 row judged by transport_break_on_metal moves from usb_transport_break onto usb_stick_left; its arm (usbbreak, tests/jobcase, usb-transport-break) is unchanged.

usb_transport_break and its redlist row are as on main. This branch's AfterItsReset and BeforeItsTestUnitReady boots and its is offline refusal leave it, and every actuator only it arms is untouched for #639. It shares usb-reset-moves and the metal verdict's transport_break_on_metal with usb_stick_left.

The metal verdict (tests/common/usb.rs). Its kernel-log half is transport_break_recovered:

  • The ladder entered at the port rung.
  • The stick is never is offline.
  • Either the stick was held, came back as the same device and the WRITE completed on it; or the port rung took and the WRITE completed. Each shape's lines are asserted in order.
  • The old Reset Recovery took alternative is gone, since this break never enters at the class reset. The old moved branch did not refuse is offline, and it passed the reading above.
  • metal_usb_judge (tests/checks/usb.rs) holds it to the T14's records at 95ebbaf (refused) and to the shape this branch writes (passed), beside five more.

Kernel shim: msc.rs goes from 2642 to 2372 lines, and kernel/src from 62668 to 62400, on main at 3c24b6e and on this head. The staged PortGone fault is now the Command act's completion, completed(Err(Quiet::Gone)), so the actuator goes through the arm that sends a disconnect to the teardown. Nothing is queued for it, as before.

Issues

  • The slug the-bot-scsi-machine-is-still-hand-written-in-the-kernel is no longer true. What the issue still owes is the bind, which is the one call site inside a scheduling pass. So it is renamed to issues/hardware/a-disk-plugged-in-after-boot-is-bound-inside-a-scheduling-pass.md, and its citation moves with it (issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md).
  • issues/hardware/the-t14s-superspeed-stick-enumerates-at-high-speed-under-toyos.md gains the reading above, and the open question of why the port rung's reset moved the stick where the scan's reset of the same port did not.
  • Filed: issues/kernel/a-usb3-device-attached-but-untrained-reads-as-gone.md. A USB3 port that detected a device it could not enable reads CAS with CCS clear, so it reads !holds, and nothing here warm-resets it. That holds on main too.
  • Filed: issues/kernel/a-bind-whose-device-left-aims-the-ladder-at-its-empty-port.md, above.

Behaviour

The refactor changes nothing on the wire: the commands, bytes, order, waits and recovery are the same. The two source spellings that toyos-blackbox checks against msc.rs are kept. The ladder fix above is the one behaviour change, and it sends nothing new.

These things changed off the wire:

  • The CBW is written as one 31-byte copy instead of field writes through Unaligned: the same bytes at the same DMA offset. The CSW is read as one 13-byte copy.
  • REQUEST SENSE's response is copied out whenever the round trip completed, and then thrown away if fewer than 14 bytes arrived.
  • Bring-up copies the whole allocation (36/8/32 bytes) rather than the 36/8/12 it reads.
  • A status-stall retry publishes StatusOwed twice, with the same value and ring positions.
  • Transfer::next now panics (expect) where the old code silently truncated a sector number to 32 bits. Bring-up's refusal of disks past 2^32 sectors and the range check already make that case unreachable.
  • msc_flush's inner if !dev.no_write_cache is removed. It was always true, because the latch is checked above it.
  • Two log lines change. A CSW with status 2 and a residue larger than the transfer now logs as a phase error instead of CSW claims N B unmoved, and a reserved status (3..=255) logs the CSW carries status 0x.., which the class reserves instead of a phase error. Both break in the status phase and climb the same ladder as before.
  • With boot-actuators, the staged PortGone now takes a tag and writes its CBW into the pool before its Command act completes as the disconnect. Nothing reaches the controller.

Gates

At 989aec4 (logs under the job scratchpad, orch/588r6/logs/):

gate exit log
cargo run -- --ci host ([ci] Host: 55 step(s), all green) 0 ci-host.txt
cargo run -- --build-only 0 build-only.txt
cargo test -p toyos-xhci (150 tests) 0 green-xhci.txt
kernel cargo clippy -- -D warnings with src/clippy.rs's adopted lints, x86_64 and AArch64, each with no features, boot-actuators, and boot-actuators,test-actuators 0 in all six green-clippy-clippy-{1..6}.txt
cargo test --test toyos-checks metal_usb_judge 0 green-checks.txt
cargo test --test toyos-build --no-run 0 green-harness.txt

At d1471df, which adds the T14's three boot.usbbreak.* records (1160 / 3905 / 21851) from the 989aec4 run below to tests/metal/lenovo-20w0003amz.toml: cargo run -- --ci host ([ci] Host: 55 step(s), all green) exit 0, log orch/588r7/ci-host.txt.

The orchestrator's runs at 989aec4 (orch/logs/588r6-588r6-*.log, issuecomment-5919300334):

run command exit log
usb_stick_left, run 1 cargo test --test toyos-build -- usb_stick_left 0 588r6-588r6-stick-left-1.log
usb_stick_left, run 2 cargo test --test toyos-build -- usb_stick_left 0 588r6-588r6-stick-left-2.log
usb_stick_left, run 3 cargo test --test toyos-build -- usb_stick_left 0 588r6-588r6-stick-left-3.log
b2k applied cargo test --test toyos-build -- usb_stick_left 1: BeforeItsCompletion: "[kernel 0.324 cpu0] xHCI: Address Device (after the port reset) failed: code 5 (TRB Error)" between the hold and the leave 588r6-588r6-b2k-kernel-fold.log
whole cargo test --test toyos-build 0: 499 passed, 499 total 588r6-588r6-whole.log
T14 cargo test --test toyos-build -- --metal usb_stick_left 0: PASS usb_stick_left 588r6-588r6-metal.log

The orchestrator's runs at 63a2a04 (orch/logs/588r5-*.log, issuecomment-5914619271), before the leave decision moved into toyos_xhci::ladder:

run command exit log
T14, redlist row lifted cargo test --test toyos-build -- --metal usb_transport_break 0: the port reset was not answered, and port 1 no longer holds the device (PORTSC 0x000202a0): its port's teardown takes it from here, then PASS usb_transport_break 588r5-metal-forced.log

High-risk: negative controls and the independent oracle

Negative controls. Each is a checked patch: git apply --check, applied, shown to build, its test run, git apply -R, and the tree clean after. The patches are in the comment "Mutation patches at 989aec4".

The green arm is the gates above at 989aec4.

mutation built result
n1 Run::unverified leaves only a Failed rung: if !holds && matches!(ended, Unverified::Failed) cargo test -p toyos-xhci --no-run 0 cargo test -p toyos-xhci EXIT=101, a_rung_that_did_not_verify_left_exactly_where_its_port_no_longer_holds_the_device: ClassReset OutOfStep(Code { .. }) answered Climbs, not Left
c1 only a port rung leaves: if !holds && self.climbed == Some(Rung::PortReset) 0 EXIT=101, the same test: ClassReset Failed answered Climbs
k1 no rung ends as its device leaving: the !holds arm deleted 0 EXIT=101, the same test: ClassReset Failed answered Climbs
b2c AfterReset::goes_on folds Left into Enumerate, B2's fold in the crate 0 EXIT=101, a_device_is_addressed_again_only_as_what_it_was_enumerated_as: Left
b2k B2's fold at the kernel site: PortStep::Reset => matches!(self.reset_port(dev, RECOVERING), AfterReset::Enumerate | AfterReset::Left) the six kernel clippy shapes 0; cargo test --test toyos-build --no-run 0 usb_stick_left EXIT=1, the Gates row "b2k applied"

Earlier rounds' controls, re-run at 989aec4, each built (EXIT=0) and red: m01 to m23 (cargo test -p toyos-xhci EXIT=101; m16 re-expressed at this head, since Run::unverified now follows Run::over), h1 and h2 (EXIT=101, a_port_holds_its_device_only_while_it_reads_connected_with_no_change_unconsumed), j0, j1 and j2 (cargo test --test toyos-checks metal_usb_judge EXIT=101). Log older-controls.txt.

mutation red test(s)
m01 CBW direction flag inverted a_cbw_is_laid_out_as_the_class_defines_it
m02 CSW tag not checked a_csw_is_believed_only_when_it_is_valid_and_meaningful
m03 delivered trusts the device alone what_is_delivered_is_what_both_the_controller_and_the_device_say_arrived, a_stalled_data_phase_delivers_what_both_the_controller_and_the_device_say_arrived
m04 status stall retried without bound a_stalled_status_is_asked_for_again_once
m05 CSW residue unbounded a_csw_is_believed_only_when_it_is_valid_and_meaningful, by the subtraction underflow the check exists to prevent
m06 short CBW/CSW accepted a_command_or_status_block_that_moved_short_breaks
m07 data stall not restarted five bot tests
m08 sense believed at 13 bytes sense_is_believed_only_when_its_ascq_arrived
m09 "ask again" after blocks moved only_the_first_batch_may_answer_ask_again
m10 READ CAPACITY(16) never asked a_disk_too_big_for_read_capacity_10_is_asked_the_16_byte_form
m11 disk past READ(10) served same
m12 no-cache flush is a failure a_flush_the_device_does_not_implement_is_no_failure_and_every_other_refusal_is
m13 ready budget ignored a_disk_that_never_becomes_ready_is_given_up_on_at_its_budget
m14 sector LBA unscaled a_transfer_is_its_batches_in_order_and_only_a_whole_one_advances
m15 peripheral qualifier read as the type a_device_that_is_not_a_disk_this_driver_serves_is_refused_by_name
m16 Run::over keeps its rung a_run_climbs_one_rung_per_break_until_a_round_trip_ends_it
m17 skip-the-class-reset said on every rung two ladder tests
m18 LANGID read from an empty descriptor a_language_is_read_only_from_a_string_descriptor_that_carries_one
m19 after a data stall, moved: self.data_len (the device's residue alone) a_stalled_data_phase_delivers_what_both_the_controller_and_the_device_say_arrived
m20 after a data stall, .status(true) (no §5.3.3 retry) a_status_stalled_after_a_data_stall_is_still_asked_for_again
m21 data residue wrapping_sub what_is_delivered_is_what_both_the_controller_and_the_device_say_arrived
m22 CSW residue checked before status 2 a_csw_is_believed_only_when_it_is_valid_and_meaningful
m23 data-stall residue wrapping_sub a_stalled_data_phase_delivers_what_both_the_controller_and_the_device_say_arrived

The disconnect seam's control is gone-arm-deleted (the comment "Mutation patches at 95ebbaf"). It deletes Err(Quiet::Gone) => bot::Answer::Gone, from completed(), and it still applies here (git apply --check 0). At 95ebbaf, cargo test --test toyos-build -- usb_transport_break with it applied exited 1: the driver never said "… transport broke on SCSI 0x28: the port disconnected during the command phase; its port's teardown takes it from here" (log 588r3-control-gone-arm.log).

Independent oracles.

  • The ladder fix is judged against real hardware first: the T14's records at 95ebbaf are the recorded failure, metal_usb_judge carries them, and the T14 at 63a2a04 passed. The xHCI 1.2 sections above were read from the specification's text. The QEMU stagings' completions and detach order were read from QEMU 11.1.1's hw/usb/hcd-xhci.c, the instrument .github/qemu-version declares.
  • The crate's byte layouts and verdicts are written from the specifications' tables, not from the old code:
    • USB MSC BOT 1.0: §5.1 CBW, §5.2 CSW, §5.3.3 status retry, §6.3.1 valid and §6.3.2 meaningful CSW, §6.7.2 data stall.
    • SPC-4: §4.5.3 fixed sense, §6.4 INQUIRY, §6.29 REQUEST SENSE, §6.37 TEST UNIT READY.
    • SBC-3: §5.11 READ(10), §5.32 WRITE(10), §5.24 SYNCHRONIZE CACHE, and READ CAPACITY(10)/(16).
    • USB 2.0 §9.6.7 for LANGID.
  • The sector-size set is the one exception. It has no oracle but the driver it was carried over from.
  • QEMU's usb-storage device is a second implementation of the same protocol, reached through the guest USB suite. The T14's stick is a third.

What I am unsure of

🤖 Generated with Claude Code

https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L

…xhci machines

The Bulk-Only round trip and the SCSI above it were the one half of the xHCI
driver still hand-written in the kernel's wait module. Every decision in them
moves into toyos-xhci as pure code with host tests; kernel/src/drivers/xhci/
wait/msc.rs keeps the transfers, the waits, the phase publishing, the staged
actuators and the log lines, so a userland usbd can drive the same machines.

toyos_xhci::bot
- `cbw`: the 31-byte CBW (BOT 1.0 §5.1), LUN 0, direction from the CDB.
- `RoundTrip`: which transfer is owed (CBW, data, CSW), what each completion
  means, a stalled data phase restarted and its status read (§6.7.2), the one
  legal status-stall retry (§5.3.3), and `CswDue::judge`, the CSW believed
  only when valid and meaningful (§6.3), delivered = min(controller, device).
- `Broke<W>` with `left` and `event`, generic over the driver's silence reason.
  A disconnect is its own variant, `Gone`, where it was `Silence { why:
  Quiet::Gone }`: the crate has to tell it apart and the kernel's `Quiet` is
  not the crate's. `Broke::left` matches on a `Phase`, not on a phase's name.

toyos_xhci::scsi
- `Cdb`, built only by this module and carrying its own direction, so no
  caller passes a length or a direction beside it (the `cdb_len`/`data_in`
  parameters and the kernel's shape assert are gone).
- `Sense` (fourteen bytes or none), `Outcome`, `flushed` (INVALID COMMAND
  OPERATION CODE is no cache, not a failure), `Transfer` (READ/WRITE(10)
  batching, and only the first batch may answer "ask again"), and `BringUp`:
  TEST UNIT READY on a budget, sense, recovery, INQUIRY, READ CAPACITY(10)
  then (16), and every refusal by name — a stepped machine, driven blocking
  by the kernel as enumeration's boot scan drives its own.

toyos_xhci::ladder::Run holds a device's break count and highest rung;
identity::first_language reads descriptor zero's LANGID.

No behaviour change on the wire: the same commands, bytes, order and waits.
Every log line renders as before, and the two spellings toyos-blackbox holds
to the source are kept. What differs off the wire: the CBW is one 31-byte copy
where it was field writes through `Unaligned` (same bytes, same offset) and
the CSW one 13-byte copy; REQUEST SENSE's response is copied whenever the
round trip completed and discarded under 14 bytes; bring-up copies the whole
allocation rather than the bytes it reads; a status-stall retry publishes
`StatusOwed` twice (the same value); `Transfer::next` panics where the old
code truncated a sector number bring-up and the range check already make
unreachable; and `msc_flush`'s inner latch check, always true, is gone.

sourcegate: the `: u32 = 4096` exception for msc.rs goes with HOST_BLOCK,
which is the crate's now.

The issue's slug claimed the machine is hand-written in the kernel, which this
refutes; what it still owes is the bind, the one scheduling-pass call site, so
it is renamed to
issues/hardware/a-disk-plugged-in-after-boot-is-bound-inside-a-scheduling-pass.md
and both citations move with it.

kernel/src: 64984 -> 64697 lines (find kernel/src -name '*.rs' | xargs cat |
wc -l); msc.rs 2647 -> 2360.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu marked this pull request as ready for review September 28, 2026 19:34
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 1, at 627a715

CI host SUCCESS at 627a715 (run 36473180835, --ci host and --ci gate-stage steps success). git merge-tree origin/main 627a7150 is clean (exit 0). Guest: 19 USB/storage tests and Fast EXIT=0 (orchestrator logs 588-*.log). None of those runs reaches the ladder.

Growth, from git diff --shortstat origin/main...627a7150 (+1799 −601): production is about +559 (crate +849: bot +271, scsi +527, ladder +42, identity +8, lib +1; kernel −287; sourcegate −3), tests about +641, issues −2. I accept the growth for host-testable decisions that usbd can reuse. The deletions are named below.

BLOCKER

  • toyos-xhci/src/bot.rs:108, kernel/src/drivers/xhci/mod.rs:116 — CC_STALL = 6 is now declared twice, once in the crate and once in the kernel. The crate's copy sits in bot.rs rather than in job.rs beside CC_SUCCESS/CC_SHORT_PACKET, and it is a sibling of the kernel constant. Move it to job.rs, have the kernel (wait/mod.rs:485) import it, and delete mod.rs:116.
  • toyos-xhci/src/bot.rs:293-296 — two mutations on the data-stall route survive every host test. (a) Self { moved: self.data_len.saturating_sub(unmoved), ..self } → Self { moved: self.data_len, ..self }: after a stall the device's residue is trusted alone. (b) .status(false) → .status(true): a CSW STALL after a data-stall restart gets no §5.3.3 retry, which is a change on the wire. Add a walk with a Data STALL at residue 100 then CSW (TAG, 0, 0), asserting Done { delivered: 412 }. Add a walk with a Data STALL, then a Status STALL, then a whole CSW, asserting the second Status act and Ok. Show both mutations red.
  • kernel/src/drivers/xhci/wait/msc.rs:763, :1374 — this is the new seam that sends a disconnect to the teardown and not up the ladder. Deleting the Err(Quiet::Gone) => bot::Answer::Gone, arm passes every test in the tree. The PortGone actuator returns a ready-made Broke::Gone from bot before any transfer, so port_gone_is_left_to_the_teardown never goes through completed(). Stage the fault as the Command act's completion (Err(Quiet::Gone) fed through completed) so the actuator exercises the seam. Then show the arm-deletion mutation red in usb_transport_break.
  • tests/common/usb.rs:1414 (usb_transport_break, redlisted) — the paths this change moved most have no run at 627a715. That covers Run/skips_class_reset, Broke::Gone to the teardown, Told on OutOfStep, offline after "broke N times running", abandoned_write_is_taken_offline, and the moved sticks. The pcap wire oracle (command_blocks, every_port_reset_is_followed_by_a_test_unit_ready) is the only independent check of "no change on the wire" for the ladder, and it did not run either. The orchestrator must force it by name at 627a715 and at base bc9ccad, same run count, with the row removed in a scratch checkout only. Required: the first boot (pcap order, owed-data skip, port-gone), transport_gives_up and abandoned_write_is_taken_offline are green at head. Any red at head carries a signature from one of the four redlisted issues and also occurs at the base.
  • (hardware) — there is no metal reading at 627a715. This is the T14's boot-stick driver, and the PR body itself says the metal run is what would show a difference on silicon. Required: a T14 stick boot and transport_break_on_metal (tests/common/usb.rs:2273) at this head, with exit code and log. QEMU is not the hardware.

NOTE

  • toyos-xhci/src/scsi.rs:15 — pub type Nanos = u64; is a fifth copy of the alias the crate root already re-exports (port::Nanos). Use crate::Nanos.
  • toyos-xhci/src/scsi.rs:143-149,229 — the expect rests on Geometry coming only from geometry(), but its fields are pub and so is NONE. Make the fields private with accessors so the premise is enforced by the type rather than by a comment.
  • toyos-xhci/src/bot.rs:355-363 — the checks follow the old code's order, not §6.3.1. Status 02h is meaningful whatever the residue, but here a phase error with residue > length becomes Residue. The recovery is the same (both lead to Phase::Status), but the test claims §6.3 and pins the old order. PhaseError also carries statuses 3..=255.
  • toyos-xhci/src/bot.rs:278 — saturating_sub → wrapping_sub survives: no test has a controller residue larger than the transfer. One assertion closes it.
  • toyos-xhci/src/bot.rs:148-150 — Broke<Quiet>::Silence { why: Quiet::Gone } can still be built beside Broke::Gone, which gives one fact two representations. Only completed() keeps them apart.
  • toyos-xhci/src/scsi.rs:121 — scsi::Outcome has the same name as the crate root's toyos_xhci::Outcome (job::Outcome). Rename one.
  • toyos-xhci/src/scsi.rs:497 — the 512..=4096 sector set comes from the old code, not from SBC-3, which allows any logical block length. 256 divides HOST_BLOCK and is refused. The behaviour is unchanged, so keep it, but the test claims the spec as its oracle.

REMOVE

  • kernel/src/drivers/xhci/wait/msc.rs:8-11 — "Every decision is the crate's …" is false: the climb loop, the Gone-to-teardown verdict, offline: dev.failed and data_out are decided in this file.
  • toyos-xhci/src/scsi.rs:10-12 — "it is driven to its end in place today" describes the tree and marks a date; it will rot.
  • toyos-xhci/src/scsi.rs:387 — "A sector size that does not divide [HOST_BLOCK]; zero among them." is false: 256 divides it and lands here.
  • toyos-xhci/src/bot.rs:158-160 — "The CSW named somebody else's transfer" is false for what: "signature".
  • PR body "## Overlap with Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536" — not the merge's record.
  • PR body "## What I am unsure of" — review-time text; it goes stale in main's history.
  • PR body "The guest runs are queued below." — nothing is queued below.

SEND BACK

Japabu and others added 2 commits September 28, 2026 22:53
…ne seam exercised

- CC_STALL lives in toyos_xhci::job beside CC_SUCCESS and CC_SHORT_PACKET;
  the kernel's EP0 recovery imports it and its own copy is deleted.
- bot: two walks on the data-stall route. A Data STALL leaving 100 B unmoved
  then CSW (TAG, 0, 0) delivers 412 B; a Data STALL, a Status STALL and a
  whole CSW asks the status twice and ends Ok. A controller residue larger
  than the transfer delivers nothing, on both routes.
- bot: the CSW is judged in BOT 1.0 §6.3.2's order: status 2 is a phase
  error whatever the residue, 0 and 1 are refused for a residue past the
  transfer, and 3..=255 are Broke::Reserved, no longer a phase error.
- msc.rs: the staged PortGone is the Command act's completion,
  completed(Err(Quiet::Gone)), so the actuator goes through the arm that
  sends a disconnect to the teardown rather than past it.
- scsi: the crate's Nanos, not a fifth alias; Geometry's fields are private
  behind accessors, and a test pins that Geometry::NONE fits no transfer;
  Outcome is Reply, apart from the crate root's job::Outcome; the refusal of
  256-byte sectors is stated as this driver's set, not SBC-3's.
- Removed: msc.rs's "every decision is the crate's" paragraph, scsi.rs's
  "driven in place today" paragraph, SectorSize's doc and Broke::Csw's doc.

Mutations, each a checked patch built, run and reversed (cargo test -p
toyos-xhci EXIT=101 on each): stall moved = data_len; stall status(true);
data residue wrapping_sub; the CSW's old residue-first order; stall residue
wrapping_sub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 2, at f90b2d2

CI host SUCCESS at f90b2d2. git merge-tree --write-tree origin/main HEAD (origin/main 8a8fe27) exits 0. The merge 435e00b touched none of the branch's files. Guest evidence is the orchestrator's 588r2 runs at f90b2d2: 20 USB/xHCI/storage tests and Fast, all EXIT=0. Forced usb_transport_break runs: head 2/2 EXIT=0, head with the gone arm deleted EXIT=1, base e3a1cdc 2/2 EXIT=0.

Growth, from git diff --shortstat origin/main...HEAD (+1869 −607): production is about +510 and tests about +750 (bot 376, scsi 339, ladder 27, identity 10). Accepted, on round 1's grounds.

Round-1 BLOCKERs

  • CC_STALL declared twice: CLOSED. toyos-xhci/src/job.rs:54 is the only declaration left. wait/mod.rs:44 imports it, and mod.rs:116 is deleted.
  • Two data-stall mutations survived: CLOSED. m-a-stall-trusts-device goes red in a_stalled_data_phase_delivers_what_both_the_controller_and_the_device_say_arrived, and m-b-stall-no-status-retry goes red in a_status_stalled_after_a_data_stall_is_still_asked_for_again. Each built with EXIT=0 and tested with EXIT=101, and the tree was restored by cmp (r2/mutations.log). Each patch is exactly the round-1 line.
  • The Gone seam was never exercised: CLOSED. msc.rs:1417 feeds the staged PortGone through completed(Err(Quiet::Gone)), and gone-arm-deleted.patch deletes exactly msc.rs:761.
    • The mutated run exits 1 with the driver never said "... its port's teardown takes it from here". Its log shows the disconnect climbing the ladder instead: break 1 of 3 running, then Reset Recovery took.
    • Unmutated, the head is 2/2 EXIT=0.
  • Forced usb_transport_break runs: CLOSED. Round 1 required the head to be green, and any red at the head to occur at the base as well. It never required a red at the base.
    • Head 2/2 and base 2/2 print the same lines: the pcap wire oracle (4 Bulk-Only resets, each followed by both clears and a TEST UNIT READY), the owed-data skip, 3 taken offline behind a last reset, the abandoned write, and all four moved-stick boots.
  • No metal reading: OPEN. No T14 stick boot and no transport_break_on_metal exist at f90b2d2. This remains a landing condition, because QEMU is not the hardware. The branch owes no code for it; it waits for the T14.

The redlist

  • The PR does not claim to fix usb_transport_break, and src/redlist.rs is not in the diff. The green at base contradicts nothing the PR says.
  • The row stays, and so does issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md, unchanged.
    • The issue's red comes at a rate. The code it names (wait_for_return, and the disk call spinning with IF clear) is untouched by this diff. Two green runs on each side measure nothing about it.
    • The base did exercise the held-disk path. The defect the issue owes shows up identically at head and base: SameStick: the held call ended 2.000 s after the break on cpu1; the stick was bound on cpu0, after it ended. The test tolerates that shape; only the rarer interleaving that also loses the window goes red.

Mutation patches

  • All seven revert what they claim: unredlist-transport-break, gone-arm-deleted, and m-a through m-e. m-d moves the residue guard ahead of every status, which is the old order in full, as the commit states.

BLOCKER

  • (hardware): no metal reading at f90b2d2. Carried from round 1, not new.

NOTE

  • kernel/src/drivers/xhci/mod.rs:115-116 — CC_SUCCESS and CC_SHORT_PACKET are still declared both here and in toyos-xhci/src/job.rs:51-52. At msc.rs:1416 the kernel's copy answers the crate's machine. This predates the branch, but the CC_STALL move is only complete once both kernel lines are deleted and imported from toyos_xhci::job.
  • PR body, mutation table m01–m18: these were measured at 627a715, and CswDue::judge has changed since. Re-run them at the head, or state the head they were measured at.

REMOVE

  • PR body: "Guest: at 627a715, the previous head, … No guest run has been made at this head." It is false at f90b2d2.
  • PR body: "That run belongs to the orchestrator." This is review-time text, not main's record.
  • PR body: "The orchestrator forces it with the row removed at this head and at the base. Those are the orchestrator's runs." This is review-time text as well.

SEND BACK

Japabu and others added 2 commits September 29, 2026 21:54
src/sourcegate.rs takes main's side. The branch's only hunk removed the
msc.rs row from the `: u32 = 4096` ban, and main deleted the Ban table
itself in #624; the 4096 rule is the reviewer's now, and HOST_BLOCK no
longer lives in the kernel.

issues/build/the-swarm-is-not-yet-falsifiable.md is deleted, as main
deleted it. The branch's only hunk there swapped the citation of the
renamed issue inside that document. The new slug is still cited from
issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md,
and the old slug is cited nowhere, by the bare name as well as the path.

rust takes main's pin, 90697f14, and the fork checkout is moved to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both were declared in the driver's mod.rs as well as in the crate, so the
kernel's copy answered the crate's machine in msc.rs. The crate's job.rs is
the one declaration, as it already is for CC_STALL, and every consumer
imports from it. The driver's comment on the pair goes with the constants:
the crate's doc on them already says that Short Packet is a success with a
residue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Mutation patches at 95ebbaf

The patches behind the negative-control table in the description, exactly as applied. Each is one hunk against 95ebbafdd: git apply --check passes on it, git apply mutates the tree, and git apply -R restores it. m01 to m23 are the table's rows. gone-arm-deleted is the disconnect seam's guest control: with it applied, usb_transport_break must go red in port_gone_is_left_to_the_teardown.

m01-cbw-direction-inverted
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -116,7 +116,7 @@
     out[0..4].copy_from_slice(&CBW_SIGNATURE.to_le_bytes());
     out[4..8].copy_from_slice(&tag.to_le_bytes());
     out[8..12].copy_from_slice(&data_len.to_le_bytes());
-    out[12] = if cdb.data_in() { 0x80 } else { 0x00 };
+    out[12] = if cdb.data_in() { 0x00 } else { 0x80 };
     let bytes = cdb.bytes();
     out[14] = bytes.len() as u8;
     out[15..15 + bytes.len()].copy_from_slice(bytes);
m02-csw-tag-not-checked
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -348,7 +348,7 @@
         }
         // A mismatched tag would attribute one command's status to another — a
         // write reporting the read before it as success.
-        if tag != self.tag {
+        if tag != self.tag && false {
             return Err(Broke::Csw { what: "tag", got: tag, want: self.tag, status, residue });
         }
         match status {
m03-delivered-trusts-the-device-alone
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -356,7 +356,7 @@
             0 | 1 if residue > self.data_len => Err(Broke::Residue { unmoved: residue, of: self.data_len }),
             // Neither account is trusted alone: a caller may read only what
             // both the device and the controller say arrived.
-            0 => Ok(Bot::Done { delivered: self.moved.min(self.data_len - residue) }),
+            0 => Ok(Bot::Done { delivered: self.data_len - residue }),
             1 => Ok(Bot::Failed),
             status => Err(Broke::Reserved { status }),
         }
m04-status-stall-retried-without-bound
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -295,7 +295,7 @@
             (At::DataRestart { .. }, Answer::Restarted(false)) => Next::Broke(Broke::Stall { phase: Phase::Data }),
             // The class's one legal retry: a device may STALL the status
             // phase once (§5.3.3, Figure 2).
-            (At::Status { retried: false }, Answer::Moved { code: CC_STALL, .. }) => Next::Act(
+            (At::Status { .. }, Answer::Moved { code: CC_STALL, .. }) => Next::Act(
                 Self { at: At::StatusRestart, ..self },
                 Act::Restart { pipe: Pipe::In, then: Phase::StatusOwed },
             ),
m05-csw-residue-unbounded
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -353,7 +353,7 @@
         }
         match status {
             2 => Err(Broke::PhaseError),
-            0 | 1 if residue > self.data_len => Err(Broke::Residue { unmoved: residue, of: self.data_len }),
+            0 | 1 if residue > self.data_len && false => Err(Broke::Residue { unmoved: residue, of: self.data_len }),
             // Neither account is trusted alone: a caller may read only what
             // both the device and the controller say arrived.
             0 => Ok(Bot::Done { delivered: self.moved.min(self.data_len - residue) }),
m06-short-cbw-or-csw-accepted
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -318,7 +318,7 @@
 /// CSW on a 512-byte endpoint — so only the residue says it all moved.
 fn framed<W>(phase: Phase, pipe: Pipe, len: u32, answer: Answer<W>) -> Result<(), Broke<W>> {
     match answer {
-        Answer::Moved { code: CC_SUCCESS | CC_SHORT_PACKET, residue: 0 } => Ok(()),
+        Answer::Moved { code: CC_SUCCESS | CC_SHORT_PACKET, .. } => Ok(()),
         Answer::Moved { code: CC_SUCCESS | CC_SHORT_PACKET, residue } => {
             Err(Broke::Short { phase, moved: len.saturating_sub(residue), wanted: len })
         }
m07-data-stall-not-restarted
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -279,7 +279,7 @@
             // A stalled data phase is ordinary — an unsupported command, a read
             // past the end — and the status that follows its recovery turns it
             // into a clean refusal (§6.7.2).
-            (At::Data, Answer::Moved { code: CC_STALL, residue }) => Next::Act(
+            (At::Data, Answer::Moved { code: CC_STALL, residue }) if false => Next::Act(
                 Self { at: At::DataRestart { unmoved: residue }, ..self },
                 Act::Restart { pipe: data_pipe, then: Phase::Data },
             ),
m08-sense-believed-at-13-bytes
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -92,7 +92,7 @@
     /// The sense `response` carries, of which `delivered` bytes arrived. ASCQ
     /// is byte 13, so fourteen must have or none of it is believed.
     pub fn of(response: &[u8; SENSE_BYTES], delivered: u32) -> Self {
-        if delivered < 14 {
+        if delivered < 13 {
             return Self::NONE;
         }
         Self { key: response[2] & 0x0F, asc: response[12], ascq: response[13] }
m09-ask-again-after-blocks-moved
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -257,7 +257,7 @@
             }
             Reply::Ok { delivered } => Moved::Short { delivered },
             Reply::Refused(sense) => Moved::Refused(sense),
-            Reply::Budget if first => Moved::Ended(Fail::Budget),
+            Reply::Budget => Moved::Ended(Fail::Budget),
             Reply::Broken | Reply::Budget => Moved::Ended(Fail::Device),
         }
     }
m10-read-capacity-16-never-asked
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -487,7 +487,7 @@
                 Next::Disk(Self { at: At::Read(next), ..self }, Inquiry(said), Ask::Read(next))
             }
             // An all-ones last LBA says the disk needs the 16-byte form.
-            Query::Capacity10 if be32(0) == u32::MAX => {
+            Query::Capacity10 if be32(0) == u32::MAX && false => {
                 self.ask(At::Read(Query::Capacity16), Ask::Read(Query::Capacity16))
             }
             Query::Capacity10 => Next::Up(geometry(u64::from(be32(0)), be32(4))),
m11-disk-past-read-10-served
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -506,7 +506,7 @@
     if !matches!(sector_bytes, 512 | 1024 | 2048 | 4096) {
         return Up::Refused(Refusal::SectorSize(sector_bytes));
     }
-    if last_lba > u64::from(u32::MAX) {
+    if last_lba > u64::from(u32::MAX) && false {
         return Up::Refused(Refusal::PastRead10 { last_lba });
     }
     let sectors = last_lba + 1;
m12-no-cache-flush-is-a-failure
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -277,7 +277,7 @@
 
 pub fn flushed(reply: Reply) -> Flushed {
     match reply {
-        Reply::Refused(sense) if sense.unimplemented() => Flushed::NoCache,
+        Reply::Refused(sense) if sense.unimplemented() && false => Flushed::NoCache,
         Reply::Ok { .. } => Flushed::Emptied,
         Reply::Refused(sense) => Flushed::Refused(sense),
         Reply::Broken => Flushed::Ended(Fail::Device),
m13-ready-budget-ignored
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -442,7 +442,7 @@
 
     /// Another attempt, if the budget has room for one at `now`.
     fn again(self, now: Nanos, offline: bool) -> Next {
-        if offline || now >= self.give_up {
+        if offline {
             return Next::Up(Up::Unready { sense: self.sense, offline });
         }
         self.ask(At::Ready, Ask::TestUnitReady)
m14-sector-lba-unscaled
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -231,7 +231,7 @@
         }
         let blocks = (self.count - self.done).min(self.most);
         let block = self.lba + u64::from(self.done);
-        let sector = block * u64::from(self.sectors_per_block);
+        let sector = block;
         // `BringUp` refused a disk whose last sector does not fit 32 bits, and
         // `new` refused a transfer past the disk.
         let sector = u32::try_from(sector).expect("a sector past what READ(10) addresses");
m15-peripheral-qualifier-read-as-the-type
--- a/toyos-xhci/src/scsi.rs
+++ b/toyos-xhci/src/scsi.rs
@@ -477,7 +477,7 @@
         let be32 = |at: usize| u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]]);
         match query {
             Query::Inquiry => {
-                let peripheral = bytes[0] & 0x1F;
+                let peripheral = bytes[0];
                 if peripheral != 0 {
                     return Next::Up(Up::Refused(Refusal::NotADisk(peripheral)));
                 }
m16-run-over-keeps-its-rung
--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -138,7 +138,9 @@
 
     /// A round trip completed: the run is over. How many breaks it held.
     pub fn over(&mut self) -> u8 {
-        core::mem::replace(self, Self::NONE).breaks
+        let breaks = self.breaks;
+        self.breaks = 0;
+        breaks
     }
 }
 
m17-skip-class-reset-said-on-every-rung
--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -131,7 +131,7 @@
     pub fn broke(&mut self, left: Left) -> Climb {
         self.breaks = self.breaks.saturating_add(1);
         let rung = next(self.climbed, left);
-        let skips_class_reset = self.climbed.is_none() && rung != Rung::ClassReset;
+        let skips_class_reset = rung != Rung::ClassReset;
         self.climbed = Some(rung);
         Climb { rung, skips_class_reset }
     }
m18-langid-read-from-an-empty-descriptor
--- a/toyos-xhci/src/identity.rs
+++ b/toyos-xhci/src/identity.rs
@@ -102,7 +102,7 @@
 /// arrived; a device offering none names no string.
 pub fn first_language(arrived: &[u8]) -> Option<u16> {
     match *arrived {
-        [length, 3, lo, hi, ..] if length >= 4 => Some(u16::from_le_bytes([lo, hi])),
+        [_length, 3, lo, hi, ..] => Some(u16::from_le_bytes([lo, hi])),
         _ => None,
     }
 }
m19-stall-delivers-the-devices-residue-alone
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -289,7 +289,7 @@
             (At::Data, Answer::Silent(why)) => Next::Broke(Broke::Silence { phase: Phase::Data, why }),
             (At::Data, Answer::Gone) => Next::Broke(Broke::Gone { phase: Phase::Data }),
             (At::DataRestart { unmoved }, Answer::Restarted(true)) => {
-                let (trip, act) = Self { moved: self.data_len.saturating_sub(unmoved), ..self }.status(false);
+                let (trip, act) = Self { moved: self.data_len, ..self }.status(false);
                 Next::Act(trip, act)
             }
             (At::DataRestart { .. }, Answer::Restarted(false)) => Next::Broke(Broke::Stall { phase: Phase::Data }),
m20-data-stall-then-no-status-retry
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -289,7 +289,7 @@
             (At::Data, Answer::Silent(why)) => Next::Broke(Broke::Silence { phase: Phase::Data, why }),
             (At::Data, Answer::Gone) => Next::Broke(Broke::Gone { phase: Phase::Data }),
             (At::DataRestart { unmoved }, Answer::Restarted(true)) => {
-                let (trip, act) = Self { moved: self.data_len.saturating_sub(unmoved), ..self }.status(false);
+                let (trip, act) = Self { moved: self.data_len.saturating_sub(unmoved), ..self }.status(true);
                 Next::Act(trip, act)
             }
             (At::DataRestart { .. }, Answer::Restarted(false)) => Next::Broke(Broke::Stall { phase: Phase::Data }),
m21-data-residue-wrapping-sub
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -273,7 +273,7 @@
                 }
             },
             (At::Data, Answer::Moved { code: CC_SUCCESS | CC_SHORT_PACKET, residue }) => {
-                let (trip, act) = Self { moved: self.data_len.saturating_sub(residue), ..self }.status(false);
+                let (trip, act) = Self { moved: self.data_len.wrapping_sub(residue), ..self }.status(false);
                 Next::Act(trip, act)
             }
             // A stalled data phase is ordinary — an unsupported command, a read
m22-csw-residue-checked-before-status-2
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -352,8 +352,8 @@
             return Err(Broke::Csw { what: "tag", got: tag, want: self.tag, status, residue });
         }
         match status {
+            _ if residue > self.data_len => Err(Broke::Residue { unmoved: residue, of: self.data_len }),
             2 => Err(Broke::PhaseError),
-            0 | 1 if residue > self.data_len => Err(Broke::Residue { unmoved: residue, of: self.data_len }),
             // Neither account is trusted alone: a caller may read only what
             // both the device and the controller say arrived.
             0 => Ok(Bot::Done { delivered: self.moved.min(self.data_len - residue) }),
m23-data-stall-residue-wrapping-sub
--- a/toyos-xhci/src/bot.rs
+++ b/toyos-xhci/src/bot.rs
@@ -289,7 +289,7 @@
             (At::Data, Answer::Silent(why)) => Next::Broke(Broke::Silence { phase: Phase::Data, why }),
             (At::Data, Answer::Gone) => Next::Broke(Broke::Gone { phase: Phase::Data }),
             (At::DataRestart { unmoved }, Answer::Restarted(true)) => {
-                let (trip, act) = Self { moved: self.data_len.saturating_sub(unmoved), ..self }.status(false);
+                let (trip, act) = Self { moved: self.data_len.wrapping_sub(unmoved), ..self }.status(false);
                 Next::Act(trip, act)
             }
             (At::DataRestart { .. }, Answer::Restarted(false)) => Next::Broke(Broke::Stall { phase: Phase::Data }),
gone-arm-deleted
--- a/kernel/src/drivers/xhci/wait/msc.rs
+++ b/kernel/src/drivers/xhci/wait/msc.rs
@@ -754,7 +754,6 @@
 fn completed(completion: Result<(u32, u32), Quiet>) -> bot::Answer<Quiet> {
     match completion {
         Ok((code, residue)) => bot::Answer::Moved { code, residue },
-        Err(Quiet::Gone) => bot::Answer::Gone,
         Err(why) => bot::Answer::Silent(why),
     }
 }

Japabu and others added 3 commits September 30, 2026 13:36
…a break

The T14 reading of `usb_transport_break` at 95ebbaf: the boot stick sat on
USB2 port 1 at high speed. The staged WRITE(10) break left it owed Data-Out,
so the ladder entered at the port rung with no class reset
(`ladder::enters_at`). The rung's hot reset completed with the port Enabled
(PORTSC 0x00200e03); 50 ms later Address Device answered USB Transaction
Error. The rung ended Failed, the climb counted break 2 and entered the
Offline rung, which read port 1 Disconnected (0x000202a0), sent nothing and
said "is offline". The disk was held, the stick bound on port 13 (a USB 3.1
port, speed 4) as the same device, and the WRITE completed on it.

Why the rung could not see it leave: a USB2 port detects no disconnect while
it drives a reset (xHCI 1.2 §4.19.1.1.2, note 57) and advances to Enabled when
the reset ends (§4.19.1.1.4), and a peripheral uses one connection at a time
(§4.19.7). A SuperSpeed stick that trains on the USB3 half therefore leaves the
USB2 port after the reset's completion has been read, and the rung's next step
meets an empty port.

- toyos-xhci: `ladder::holds`, connected with no unconsumed connect change,
  the predicate `reset_port` already applied before writing a reset.
- The climb asks it again when a rung ends unverified. A device its port no
  longer holds ends the climb as one whose reset read the port empty always
  did: no break counted, no Offline rung, the device left for the hold a
  reset of this driver's earns it, and its slot the port's teardown's
  (§4.4). The Offline rung sent nothing to a port that no longer held its
  device, so nothing changes on the wire; a class rung whose device left no
  longer runs the port rung's quiesce.
- `usb-reset-moves-after` stages that leave under QEMU: the hold comes after
  the reset's completion is read with the device on the port. QEMU 11.1.1
  answers Address Device on a port with no device with TRB Error
  (hw/usb/hcd-xhci.c, `xhci_address_slot` and `xhci_lookup_uport`), a failed
  step as the T14's Transaction Error is. `usb_transport_break` gains
  `Moved::AfterItsReset`, and no same-stick shape may say the stick is offline.
- The metal verdict's kernel half is `usb::transport_break_recovered`: the
  ladder entered at the port rung, the stick is never offline, and either
  the port rung took and the WRITE completed, or the stick was held, came
  back as the same device and the WRITE completed on it, each in order. The
  "Reset Recovery took" alternative is gone, since this break never enters
  at the class reset. `metal_usb_judge` holds it to the records the T14
  wrote at 95ebbaf (refused) and to the shape this change writes (passed),
  beside four more.
- The SuperSpeed-stick issue gains the reading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
`cargo run -- --ci host` reddened on clippy::redundant_clone at the two
`held.clone()` calls 13ecd39 added to `a_stick_its_reset_moved_carries_on`:
each was the value's last use. The needle is a `&str` now, made a `String`
at each use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
#616 has the metal judges read the channel their line crosses on. It does
not touch `transport_break_on_metal` or its registration, and the merge is
clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Mutation patches at dc0863b

The patches behind this round's rows of the negative-control table in the description, exactly as applied. Each is one hunk against dc0863b11: git apply --check passes on it, git apply mutates the tree, and git apply -R restores it. h1, h2 are judged by cargo test -p toyos-xhci; j0 to j2 by cargo test --test toyos-checks metal_usb_judge; k1 builds in all six kernel clippy shapes, and its run is usb_transport_break, which must go red in Moved::AfterItsReset on is offline.

h1-holds-ignores-the-connect-change

diff --git a/toyos-xhci/src/ladder.rs b/toyos-xhci/src/ladder.rs
index dc9ddc936..f295d0269 100644
--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -198,7 +198,7 @@ pub const RESET_RECOVERY_NS: u64 = 50_000_000;
 /// no longer holds left: that is no break, no rung above reaches it, and its
 /// port's teardown owns what it held (§4.4).
 pub fn holds(port: Portsc) -> bool {
-    port.connected() && !port.connect_changed()
+    port.connected()
 }
 
 /// Whether a failed `step` ends [`Rung::PortReset`].

h2-holds-ignores-the-connection

diff --git a/toyos-xhci/src/ladder.rs b/toyos-xhci/src/ladder.rs
index dc9ddc936..a124096db 100644
--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -198,7 +198,7 @@ pub const RESET_RECOVERY_NS: u64 = 50_000_000;
 /// no longer holds left: that is no break, no rung above reaches it, and its
 /// port's teardown owns what it held (§4.4).
 pub fn holds(port: Portsc) -> bool {
-    port.connected() && !port.connect_changed()
+    !port.connect_changed()
 }
 
 /// Whether a failed `step` ends [`Rung::PortReset`].

j0-verdict-as-at-95ebbafdd

diff --git a/tests/common/usb.rs b/tests/common/usb.rs
index 1f98e47e7..45f4c1035 100644
--- a/tests/common/usb.rs
+++ b/tests/common/usb.rs
@@ -2156,25 +2156,25 @@ pub fn transport_break_recovered(kernel: &serial::Serial) -> Result<(), String>
         "transport broke on SCSI 0x2a: a staged break skipped the data phase wait; break 1 of ",
     )?;
     let under_test = broke_on(staged)?;
-    let entered = kernel.must_say_after(
-        staged,
-        &format!("usb-storage: {under_test} is owed the data of the command that broke"),
-    )?;
-    kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
-    kernel.must_not_say(" did not come back within ")?;
-    if let Ok(left) = kernel.must_say_after(entered, " after this driver reset it; it is held ") {
-        let back = kernel.must_say_after(left, " as the same device (USB ")?;
-        kernel.must_say_after(
-            back,
-            "is back, and the operation it was asked went out again on it: it completed",
-        )?;
+    if let Ok(left) = kernel.must_say(" after this driver reset it; it is held ") {
         eprintln!("  [usb] {left}");
+        let back = kernel.must_say(" as the same device (USB ")?;
         eprintln!("  [usb] {back}");
+        kernel.must_say("is back, and the operation it was asked went out again on it: it completed")?;
+        kernel.must_not_say(" did not come back within ")?;
         return Ok(());
     }
-    let took = kernel.must_say_after(entered, &format!("usb-storage: {under_test} the port reset took"))?;
-    kernel.must_say_after(took, &format!("usb-storage: {under_test} SCSI 0x2a completed after "))?;
+    let rungs = [
+        format!("usb-storage: {under_test} Reset Recovery took"),
+        format!("usb-storage: {under_test} the port reset took"),
+    ];
+    let took = rungs
+        .iter()
+        .find_map(|rung| kernel.must_say(rung).ok())
+        .ok_or_else(|| format!("neither {:?} nor {:?}: no rung verified", rungs[0], rungs[1]))?;
     eprintln!("  [usb] {took}");
+    kernel.must_say(&format!("usb-storage: {under_test} SCSI 0x2a completed after "))?;
+    kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
     Ok(())
 }
 

j1-verdict-lets-the-stick-go-offline

diff --git a/tests/common/usb.rs b/tests/common/usb.rs
index 1f98e47e7..dbc583377 100644
--- a/tests/common/usb.rs
+++ b/tests/common/usb.rs
@@ -2160,7 +2160,6 @@ pub fn transport_break_recovered(kernel: &serial::Serial) -> Result<(), String>
         staged,
         &format!("usb-storage: {under_test} is owed the data of the command that broke"),
     )?;
-    kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
     kernel.must_not_say(" did not come back within ")?;
     if let Ok(left) = kernel.must_say_after(entered, " after this driver reset it; it is held ") {
         let back = kernel.must_say_after(left, " as the same device (USB ")?;

j2-verdict-lets-the-class-reset-in

diff --git a/tests/common/usb.rs b/tests/common/usb.rs
index 1f98e47e7..4ecc1d192 100644
--- a/tests/common/usb.rs
+++ b/tests/common/usb.rs
@@ -2156,10 +2156,7 @@ pub fn transport_break_recovered(kernel: &serial::Serial) -> Result<(), String>
         "transport broke on SCSI 0x2a: a staged break skipped the data phase wait; break 1 of ",
     )?;
     let under_test = broke_on(staged)?;
-    let entered = kernel.must_say_after(
-        staged,
-        &format!("usb-storage: {under_test} is owed the data of the command that broke"),
-    )?;
+    let entered = staged;
     kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
     kernel.must_not_say(" did not come back within ")?;
     if let Ok(left) = kernel.must_say_after(entered, " after this driver reset it; it is held ") {

k1-climb-past-a-device-that-left

diff --git a/kernel/src/drivers/xhci/wait/msc.rs b/kernel/src/drivers/xhci/wait/msc.rs
index 9f29a8bbc..11ab6fbea 100644
--- a/kernel/src/drivers/xhci/wait/msc.rs
+++ b/kernel/src/drivers/xhci/wait/msc.rs
@@ -1134,15 +1134,6 @@ impl XhciController {
                 Climbed::Failed => None,
             };
             let ended = Unverified { rung, broke: out_of_step.as_ref() };
-            let port = self.read_portsc(dev.port_idx);
-            if !ladder::holds(port) {
-                log!("usb-storage: {slot} {ended}, and port {} no longer holds the device (PORTSC \
-                     {:#010x}): its port's teardown takes it from here",
-                    u32::from(dev.port_idx) + 1, port.raw());
-                dev.failed = true;
-                dev.left = true;
-                return false;
-            }
             log!("usb-storage: {slot} {ended}; break {} of {MAX_TRANSPORT_BREAKS} running",
                 dev.run.breaks().saturating_add(1));
             // After a step that was not answered the event is spent: the rung

@Japabu Japabu changed the title USB mass storage: the BOT round trip and the SCSI bring-up are toyos-xhci machines USB mass storage: the BOT round trip and SCSI bring-up are toyos-xhci machines, and a stick that leaves its port is no break Sep 30, 2026
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 3, at dc0863b

CI host SUCCESS at dc0863b (run 36713768883). git merge-tree --write-tree origin/main HEAD against origin/main deb8fa3 exits 0. The merge dc0863b resolved no conflict: --remerge-diff is empty.

Earlier BLOCKERs

  • Round 2, no metal reading: CLOSED. The orchestrator ran the T14 at dc0863b with the redlist row lifted in a scratch copy (588r4-metal-transport-break.log): [metal] 1 passed, 0 failed, PASS usb_transport_break, EXIT 0.
    • The stick started on port 1 at speed 3. The log shows the port reset was not answered, and port 1 no longer holds the device (PORTSC 0x000202a0), then slot 1 disabled from the port's teardown, then disk 0 came back on port 13 slot 6 as the same device, then … went out again on it: it completed.
    • Neither break 2 nor is offline appears.
  • The red reading at 95ebbaf: CLOSED.
    • Negative control k1 (the new climb arm deleted) exits 1: AfterItsReset: … slot 1 is offline … of a stick that came back as itself (588r4-k1.log).
    • Head, forced usb_transport_break: EXIT 0, with the AfterItsReset line (588r4-forced.log).
    • usb_ 10/10 and xhci_ 15/15: EXIT 0.
  • Round 2 NOTEs and REMOVEs: CLOSED.
    • 95ebbaf imports CC_SUCCESS and CC_SHORT_PACKET from toyos_xhci::job and deletes the mod.rs copies.
    • m01–m23 are stated at dc0863b.
    • The three body lines are gone.

Growth, from git diff --shortstat origin/main...HEAD (+2130 −663):

  • Production +627: toyos-xhci +877, kernel −250.
  • Tests +835: toyos-xhci +707, harness +128.
  • Issues +5.

This round (6211bb3e4..a2b229f4e, +268 −66): production +55, tests +140, issues +7. Accepted for a defect the T14 showed, less the exit that BLOCKER 2 folds.

BLOCKER

  • kernel/src/drivers/xhci/wait/msc.rs:1138 — the claim "the climb asks it again whenever a rung ends unverified" is pinned only for a rung that ended Failed (usb-reset-moves-after, where Address Device is refused).
    • No test reaches this arm through a rung whose TEST UNIT READY broke (OutOfStep, e.g. Broke::Gone) on a port its device has left.
    • So - if !ladder::holds(port) { / + if out_of_step.is_none() && !ladder::holds(port) { passes every suite. It brings back main's is offline for a stick that leaves between the port rung's Configure Endpoint and its TEST UNIT READY.
    • Show this patch red in usb_transport_break: stage a leave at that point, and assert transport broke on the port reset's TEST UNIT READY: …, and port 1 no longer holds the device and no is offline.
  • kernel/src/drivers/xhci/wait/msc.rs:1128-1145 — the new !ladder::holds arm repeats the Climbed::Gone arm six lines above it (dev.failed = true; dev.left = true; return false). That is two exits for one fact: the device left, and its port's teardown owns it.
    • Climbed::Gone comes only from the port rung's AfterReset::Left (msc.rs:1298).
    • Every AfterReset::Left also reads !holds at the climb's read: the port read empty, and only a new connect can make it read connected again, and a new connect raises CSC.
    • Fix, either way: AfterReset::Left ends the rung Failed and Climbed::Gone is deleted; or an unverified rung whose port no longer holds becomes Climbed::Gone before the match.

NOTE

  • kernel/src/drivers/xhci/wait/msc.rs:1137 — judged sound: no transport failure on a port that still holds its device is read as a leave.
    • holds is the port machine's own teardown condition (kernel/src/drivers/xhci/mod.rs:1183). It is also main's Offline rung's refusal to reset (reset_port's here, msc.rs:1230).
    • Between the rung's end and that rung's read, nothing acknowledges CSC. So every reading this arm ends as a leave is one main already ended as Left, under an is offline line.
    • A port that reads connected with no change unconsumed still counts a break: abandoned_write_is_taken_offline for OutOfStep (green in the forced run), log_flush_retry for Failed.
  • tests/common/volumes.rs:3047 — log_flush_retry is the only test of a Failed port rung on a port that still holds its device, which is the direction the new arm must not take. It is in neither usb_ nor xhci_ and has not run at dc0863b. The queued whole run must carry it green before landing.
  • toyos-xhci/src/ladder.rs:200 — holds is a PORTSC predicate declared in the MSC ladder. kernel/src/drivers/xhci/mod.rs:982 (HID recovery) and :1183 (the port service) spell the same !connected() || connect_changed() inline. Declare it once on Portsc (toyos-xhci/src/portsc.rs) and read it at all four sites.
  • toyos-xhci/src/ladder.rs:200 — a USB3 port whose device is present but untrained reads CAS with CCS 0 (xHCI 1.2 §5.4.8 bit 24, cleared by a warm reset), and so reads !holds.
    • Nothing in toyos-xhci names CAS or warm-resets such a port.
    • Such a stick is lost as having left, on main as here. File it.
  • kernel/src/drivers/xhci/wait/msc.rs:1138 — the arm also ends a Rung::ClassReset whose device left, and skips main's port-rung quiesce.
    • if rung == Rung::PortReset && !ladder::holds(port) passes every suite. The outcome equals main's: quiesce, then reset_port reads the port empty, then Climbed::Gone.
    • The body's "a class rung whose device left no longer goes on to the port rung's quiesce" has no test.
  • kernel/src/drivers/xhci/wait/msc.rs:1236-1242, 1273-1279 — two copies of one staging (log the held line, cue, wait for the port to read empty) that differ only in the line. Make one helper that takes HELD or HELD_AFTER.
  • tests/common/usb.rs:2164 — no case in metal_usb_judge needs must_not_say(" did not come back within "): "a stick that never came back" is refused first, by its missing same-device line. Either add a case where the write completed on the returned stick and a later did not come back within follows, or delete the line.
  • PR body, Metal and the k1 row — the head's readings exist, but the body says "owed" and "must go red". Main's record carries each with its command, exit and log:
    • T14 PASS with the new line and no is offline.
    • k1 EXIT 1.
    • Forced usb_transport_break EXIT 0.
  • src/redlist.rs:97 — the row stays in this PR:
    • Its issue (expected-red: a held call's CPU and the bind) is untouched by this diff.
    • Three more open issues name other red modes of this test.
    • One forced green per instrument measures no rate.

REMOVE

  • PR body, Metal: "The reading at this head is owed." — false: the T14 has read dc0863b.
  • PR body, Metal: "That run's harness also failed the verdict for finding no kernel output in the readback; Metal judges read the channel their line crosses on #616, merged here, reads that channel." — a harness story, not this merge's record.
  • PR body, "## Unsure" — review-time text, removed in round 1 as "What I am unsure of". Its open question belongs in the SuperSpeed issue.
  • tests/checks/usb.rs:6-8 — "as the ladder told it when it climbed on past the stick that had left, … and as it tells it now" — chronology in source; the cases' own names say which log is refused.
  • issues/kernel/a-disk-taken-offline-is-never-brought-back.md:24 — ", the T14 has not yet" — false: the T14 at dc0863b bound disk 0 back on port 13 as the same device.

SEND BACK

Japabu and others added 3 commits September 30, 2026 16:14
…eaks the rung's TEST UNIT READY on it

Answers the third review of #588 at dc0863b.

- BLOCKER 2, one exit for one fact: `Climbed::Gone` is deleted. The port
  rung's reset step now fails whenever `reset_port` answers anything but
  `Enumerate`, `AfterReset::Left` included, and the climb's `!holds` read is
  the one place a rung ends as its device leaving. Every `AfterReset::Left`
  reads `!holds` there: a port read empty reads connected again only on a new
  connect, which raises CSC, and the only acknowledge between the two reads is
  `enumeration_ack`, which clears CSC only after a warm reset whose `after`
  word carried it.
- BLOCKER 1: `usb-reset-moves-configured` holds the port rung once every step
  was answered and before its TEST UNIT READY, and cues the host to move the
  stick. QEMU 11.1.1 detaches the slot's port before it updates PORTSC
  (hw/usb/hcd-xhci.c, `xhci_detach` then `xhci_detach_slot`), and a doorbell
  on a slot whose port is detached kicks nothing (`xhci_kick_epctx`,
  `xhci_slot_ok`), so the CBW's wait reads the port disconnected: the rung
  ends `OutOfStep(Broke::Gone)` on a port that no longer holds the stick.
  `usb_transport_break` gains `Moved::BeforeItsTestUnitReady`, which asserts
  `transport broke on the port reset's TEST UNIT READY: the port disconnected
  during the command phase, and port 1 no longer holds the device` and, with
  every same-stick shape, no `is offline`.
- `holds` moves onto `Portsc`, and the port machine, the HID recovery, the
  climb and `reset_port` read it; the two site comments that restated it go.
- One helper, `hold_for_the_move`, for the three staging holds, and one
  `reset_moves::take` that takes the actuator it is asked for.
- The metal verdict no longer refuses `did not come back within`: no case
  needed it, since a stick that never came back is refused by its missing
  same-device line first.
- REMOVEs: the chronology in `transport_break_verdict`'s doc, and ", the T14
  has not yet" in the offline-disk issue.
- The SuperSpeed-stick issue carries the open question of why the port rung's
  reset moved the stick where the scan's did not.
- Filed: a USB3 device attached but untrained reads CAS with CCS clear, and so
  reads as gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
No conflict. The rust gitlink takes main's c4c65e3e8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Mutation patches at 63a2a04

Each is applied with git apply from the repository root. Results are in the PR body's negative-control table.

unredlist-transport-break

--- a/src/redlist.rs	2026-09-30 16:38:17
+++ b/src/redlist.rs	2026-09-30 16:38:31
@@ -84,10 +84,6 @@
     Disabled {
         test: "syscall_window_nmi",
         issue: "issues/kernel/syscall-window-nmi-shortfalls-on-a-contended-host.md",
-    },
-    Disabled {
-        test: "usb_transport_break",
-        issue: "issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md",
     },
     Disabled {
         test: "user_copy_races_munmap",

n1-a-broken-test-unit-ready-climbs-on-past-a-device-that-left

--- a/kernel/src/drivers/xhci/wait/msc.rs	2026-09-30 16:38:17
+++ b/kernel/src/drivers/xhci/wait/msc.rs	2026-09-30 16:38:31
@@ -1128,7 +1128,7 @@
             // disconnect while it drives a reset (xHCI 1.2 §4.19.1.1.2, note
             // 57) and reads Enabled once the reset ends (§4.19.1.1.4).
             let port = self.read_portsc(dev.port_idx);
-            if !port.holds() {
+            if out_of_step.is_none() && !port.holds() {
                 log!("usb-storage: {slot} {ended}, and port {} no longer holds the device (PORTSC \
                      {:#010x}): its port's teardown takes it from here",
                     u32::from(dev.port_idx) + 1, port.raw());
--- a/src/redlist.rs	2026-09-30 16:38:17
+++ b/src/redlist.rs	2026-09-30 16:38:31
@@ -84,10 +84,6 @@
     Disabled {
         test: "syscall_window_nmi",
         issue: "issues/kernel/syscall-window-nmi-shortfalls-on-a-contended-host.md",
-    },
-    Disabled {
-        test: "usb_transport_break",
-        issue: "issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md",
     },
     Disabled {
         test: "user_copy_races_munmap",

c1-only-the-port-rung-asks-whether-its-device-left

--- a/kernel/src/drivers/xhci/wait/msc.rs	2026-09-30 16:38:17
+++ b/kernel/src/drivers/xhci/wait/msc.rs	2026-09-30 16:38:31
@@ -1128,7 +1128,7 @@
             // disconnect while it drives a reset (xHCI 1.2 §4.19.1.1.2, note
             // 57) and reads Enabled once the reset ends (§4.19.1.1.4).
             let port = self.read_portsc(dev.port_idx);
-            if !port.holds() {
+            if rung == Rung::PortReset && !port.holds() {
                 log!("usb-storage: {slot} {ended}, and port {} no longer holds the device (PORTSC \
                      {:#010x}): its port's teardown takes it from here",
                     u32::from(dev.port_idx) + 1, port.raw());

h1-holds-ignores-the-connect-change

--- a/toyos-xhci/src/portsc.rs	2026-09-30 16:38:17
+++ b/toyos-xhci/src/portsc.rs	2026-09-30 16:38:31
@@ -115,7 +115,7 @@
     /// replugged between two looks reads connected again, and only CSC says
     /// the one that was here has gone.
     pub const fn holds(self) -> bool {
-        self.connected() && !self.connect_changed()
+        self.connected()
     }
 
     /// Whether reset signalling is still on the wire.

h2-holds-ignores-the-connection

--- a/toyos-xhci/src/portsc.rs	2026-09-30 16:38:17
+++ b/toyos-xhci/src/portsc.rs	2026-09-30 16:38:31
@@ -115,7 +115,7 @@
     /// replugged between two looks reads connected again, and only CSC says
     /// the one that was here has gone.
     pub const fn holds(self) -> bool {
-        self.connected() && !self.connect_changed()
+        !self.connect_changed()
     }
 
     /// Whether reset signalling is still on the wire.

j0-verdict-as-at-95ebbafdd

--- a/tests/common/usb.rs	2026-09-30 16:38:17
+++ b/tests/common/usb.rs	2026-09-30 16:38:45
@@ -2192,24 +2192,25 @@
         "transport broke on SCSI 0x2a: a staged break skipped the data phase wait; break 1 of ",
     )?;
     let under_test = broke_on(staged)?;
-    let entered = kernel.must_say_after(
-        staged,
-        &format!("usb-storage: {under_test} is owed the data of the command that broke"),
-    )?;
-    kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
-    if let Ok(left) = kernel.must_say_after(entered, " after this driver reset it; it is held ") {
-        let back = kernel.must_say_after(left, " as the same device (USB ")?;
-        kernel.must_say_after(
-            back,
-            "is back, and the operation it was asked went out again on it: it completed",
-        )?;
+    if let Ok(left) = kernel.must_say(" after this driver reset it; it is held ") {
         eprintln!("  [usb] {left}");
+        let back = kernel.must_say(" as the same device (USB ")?;
         eprintln!("  [usb] {back}");
+        kernel.must_say("is back, and the operation it was asked went out again on it: it completed")?;
+        kernel.must_not_say(" did not come back within ")?;
         return Ok(());
     }
-    let took = kernel.must_say_after(entered, &format!("usb-storage: {under_test} the port reset took"))?;
-    kernel.must_say_after(took, &format!("usb-storage: {under_test} SCSI 0x2a completed after "))?;
+    let rungs = [
+        format!("usb-storage: {under_test} Reset Recovery took"),
+        format!("usb-storage: {under_test} the port reset took"),
+    ];
+    let took = rungs
+        .iter()
+        .find_map(|rung| kernel.must_say(rung).ok())
+        .ok_or_else(|| format!("neither {:?} nor {:?}: no rung verified", rungs[0], rungs[1]))?;
     eprintln!("  [usb] {took}");
+    kernel.must_say(&format!("usb-storage: {under_test} SCSI 0x2a completed after "))?;
+    kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
     Ok(())
 }
 

j1-verdict-lets-the-stick-go-offline

--- a/tests/common/usb.rs	2026-09-30 16:38:17
+++ b/tests/common/usb.rs	2026-09-30 16:38:31
@@ -2196,7 +2196,6 @@
         staged,
         &format!("usb-storage: {under_test} is owed the data of the command that broke"),
     )?;
-    kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
     if let Ok(left) = kernel.must_say_after(entered, " after this driver reset it; it is held ") {
         let back = kernel.must_say_after(left, " as the same device (USB ")?;
         kernel.must_say_after(

j2-verdict-lets-the-class-reset-in

--- a/tests/common/usb.rs	2026-09-30 16:38:17
+++ b/tests/common/usb.rs	2026-09-30 16:38:31
@@ -2192,10 +2192,7 @@
         "transport broke on SCSI 0x2a: a staged break skipped the data phase wait; break 1 of ",
     )?;
     let under_test = broke_on(staged)?;
-    let entered = kernel.must_say_after(
-        staged,
-        &format!("usb-storage: {under_test} is owed the data of the command that broke"),
-    )?;
+    let entered = staged;
     kernel.must_not_say(&format!("usb-storage: {under_test} is offline"))?;
     if let Ok(left) = kernel.must_say_after(entered, " after this driver reset it; it is held ") {
         let back = kernel.must_say_after(left, " as the same device (USB ")?;

@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator runs at 63a2a04 (cargo test --test toyos-build -- <args>; logs orch/logs/588r5-*.log):

job args patch exit
n1 narrowing (the review's out_of_step.is_none() && !holds) usb_transport_break n1-a-broken-test-unit-ready-climbs-on-past-a-device-that-left.patch + redlist row lifted 1 — BeforeItsTestUnitReady: "... slot 1 is offline ..." of a stick that came back as itself
forced usb_transport_break unredlist-transport-break.patch 0
whole (none) — 0 — 498 passed, 498 total
T14 forced --metal usb_transport_break unredlist-transport-break.patch 0 — the port reset was not answered, and port 1 no longer holds the device (PORTSC 0x000202a0): its port's teardown takes it from here, then PASS usb_transport_break

CI host at 63a2a04: success.

@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review at 63a2a04 (round 5)

CI host SUCCESS at 63a2a04 (run 36732205365). The orchestrator's runs at 63a2a04 are in issuecomment-5914619271:

  • n1: EXIT 1.
  • usb_transport_break, forced: EXIT 0.
  • The whole suite: EXIT 0, 498/498.
  • The T14, forced: EXIT 0.

The merges fef8cfd and 63a2a04 resolved nothing (--remerge-diff is empty). None of #536's hunks in this branch's files touches the ladder. git merge-tree --write-tree origin/main HEAD against 3c24b6e exits 1 (NOTE).

Earlier BLOCKERs (the review at dc0863b)

  • msc.rs:1138, no staged leave for a rung whose TEST UNIT READY broke: CLOSED.
    • usb-reset-moves-configured stages it.
    • n1 (out_of_step.is_none() && !port.holds()), with the row lifted, exits 1: BeforeItsTestUnitReady: "… slot 1 is offline …" of a stick that came back as itself.
    • The head, forced, exits 0.
  • msc.rs:1128-1145, two exits for one fact: CLOSED.
    • Climbed::Gone is deleted, and AfterReset::Left now ends the rung Failed (msc.rs:1297).
    • Every usb-reset-moves boot reaches the one read, and the forced run exits 0 with all of them.
    • The T14 exits 0 with the no longer holds the device line.
  • The NOTE on log_flush_retry: met by the 498/498 run.

Growth

From git diff --shortstat origin/main...HEAD (+2222 −701):

  • Production +1268 −648: toyos-xhci +869; kernel +399 −648.
  • Tests +900 −37: toyos-xhci +707; harness +193 −37.
  • Issues +54 −16.

This round (fef8cfdb7..1aa3a1b81, +149 −95): production +60 −67, tests +53 −25, issues +36 −3. Accepted.

BLOCKER

NOTE

  • tests/checks.rs — conflicts with main at 3c24b6e (Metal timings are recorded per machine and judged against that machine's own record #630): merge-tree exits 1.
  • PR body — n1's EXIT 1, the forced EXIT 0, the 498/498 run and the T14's EXIT 0 are in a comment, not in the body. The body's gates carry each one with its command, exit and log (588r5-*.log).
  • msc.rs:1044, :1131, :2089-2102 — "the device left" has two exits: the Broke::Gone arm in scsi(), and the climb's read after a rung.
    • bring_up sends a Gone TEST UNIT READY into the climb, which then aims a class reset at the empty port before it reads.
    • Reading holds once, at the climb's entry and after each rung, makes that one exit.
    • Main has this too, and it is outside this round.
  • msc.rs:1131 — c1 (rung == Rung::PortReset && !port.holds()) still passes every suite, as at dc0863b. Its outcome is main's.
  • msc.rs:1096-1147 — the leave decision is kernel code.
    • Made an answer of toyos_xhci::ladder::Run, n1 and c1 become crate mutations that cargo test -p toyos-xhci turns red. That test outlives any guest test.
    • Only the port read would stay for a guest test to judge.

REMOVE

  • issues/hardware/the-t14s-superspeed-stick-enumerates-at-high-speed-under-toyos.md:42-43 — ", which nobody here has read" — not load-bearing.
  • tests/toyos.rs:1858-1861 — "comes back from the class's Reset Recovery on the machine's own controller" — false: the verdict this branch wrote for that row refuses a class reset on this break (j2).
  • PR body, Gates — "The guest runs at 63a2a04 are requested from the orchestrator: …" — stale.
  • PR body, the n1 row — "requested: usb_transport_break must exit 1 …" — stale.
  • PR body, Metal — "and its T14 run is requested" — stale.
  • PR body, the c1 row — "and the review read it green on every suite" — false: a review runs nothing.

SEND BACK

Japabu and others added 2 commits September 30, 2026 18:16
tests/checks.rs conflicted: #630 adds the metal_checks tests beside
metal_usb_judge and deletes the doc line naming tests/metal-profile.toml.
Both sides' hunks are kept: metal_usb_judge, then main's metal_checks
tests, and the doc line goes with main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
… fix's test

Review round 5 of #588: the fix's only test was usb_transport_break, which
is redlisted and which #639 deletes. Whichever landed first, main kept the
fix with nothing that could go red on it.

toyos_xhci::ladder now decides what a rung that did not verify leaves:
- `Run::unverified(&Unverified<W>, holds)` answers `AfterRung::Left` for any
  rung, however it ended, on a port that no longer holds its device, and
  otherwise the next climb with the transfer event the next rung quiesces
  against. The kernel reads PORTSC once the rung has ended and applies the
  answer; its `Climbed` enum is replaced by `Result<(), Unverified>`.
- `AfterReset::goes_on` is the port rung's decision to go on past its reset.
- `a_rung_that_did_not_verify_left_exactly_where_its_port_no_longer_holds_the_device`
  covers both rungs, every ending, and both port readings, so the review's
  n1 and c1 narrowings, the arm deleted, and B2's fold of `Left` into a reset
  that took are crate mutations `cargo test -p toyos-xhci` turns red.

usb_stick_left (tests/common/usb.rs) stages the leave on the gate's data
stick, which the host unplugs on the hold's cue and never plugs back: the
three holds `usb-reset-moves`, `-after` and `-configured`. Each boot judges
the leave line, no `is offline`, no `break 2`, and the teardown's
`slot N disabled` after the leave; the `usb-reset-moves` and `-configured`
boots refuse `Reset Device failed` and `Address Device (after the port
reset)` between the hold and the leave. It has no redlist row. The T14 row
judged by `transport_break_on_metal` moves onto it; its comment is deleted,
since the verdict refuses the class reset it named.

usb_transport_break and its redlist row are as on main again: the
`AfterItsReset` and `BeforeItsTestUnitReady` boots and the `is offline`
refusal leave it, and the actuators only it arms are untouched.

Filed: a bind whose device left climbs the ladder at its empty port, the
review's two-exits NOTE, which main has too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Japabu added a commit that referenced this pull request Sep 30, 2026
Each issue whose test this branch deleted names the commit that took out
what only that test used, beside the test's own, as the revert that brings
both back: `quiesce-last-teardown` (b82f45e), `console-queue-at-the-stop`
(2718404), `copy-meets-a-remap` (2f366a9), the ten USB actuators
(b5c59cb, and 9ebf080 for `usb-transport-break` and `usb-reset-break`),
`SYS_DEBUG` 17 and 18 (009db6d), and the IST-off control with the storm's
hold (5399770). Where a restore needs a later commit reverted first, the
revert names it and says what else comes back.

The `usb_transport_break` issue goes back to `status: open`: #588 keeps that
test red and does not hold it. Its text now holds whichever of #588 and
#639 lands second, and says what that lander does: `tests/common/usb.rs`
resolved as the deletion with every hunk of the other side accounted for,
#588's two actuators deleted with the rest, and the commit holding #588's
version of the test recorded there.

Each filed flake deleted this round has its issue name the commit: its own
issue where it has one, `parallel-tests-red-under-other-suites` and
`a-loaded-suite-reds-a-volume-checker-on-both-arms` otherwise, the first of
them also saying which of its names were not deleted and why. The
single-test flake issues become `kind: defect`, since a deleted test blocks
no landing; `usb_short_read`'s and `partition_claim_gives_up`'s gain an exit.
Issues that named a deleted test as their gate get one line pointing at
where its restore is recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Mutation patches at 989aec4

Each applies with git apply at 989aec4 (git apply --check 0). Results are in the pull request body.

n1-an-out-of-step-rung-on-an-empty-port-climbs-on

--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -150,7 +150,7 @@
     /// the device: its port's teardown owns what it held. Otherwise the rung
     /// is the next break.
     pub fn unverified<W>(&mut self, ended: &Unverified<W>, holds: bool) -> AfterRung {
-        if !holds {
+        if !holds && matches!(ended, Unverified::Failed) {
             return AfterRung::Left;
         }
         // After a step that was not answered the event is spent: the rung has

c1-only-a-port-rung-asks-whether-its-device-left

--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -150,7 +150,7 @@
     /// the device: its port's teardown owns what it held. Otherwise the rung
     /// is the next break.
     pub fn unverified<W>(&mut self, ended: &Unverified<W>, holds: bool) -> AfterRung {
-        if !holds {
+        if !holds && self.climbed == Some(Rung::PortReset) {
             return AfterRung::Left;
         }
         // After a step that was not answered the event is spent: the rung has

k1-no-rung-ends-as-its-device-leaving

--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -149,10 +149,7 @@
     /// however the rung ended.** That is no break, and no rung above reaches
     /// the device: its port's teardown owns what it held. Otherwise the rung
     /// is the next break.
-    pub fn unverified<W>(&mut self, ended: &Unverified<W>, holds: bool) -> AfterRung {
-        if !holds {
-            return AfterRung::Left;
-        }
+    pub fn unverified<W>(&mut self, ended: &Unverified<W>, _holds: bool) -> AfterRung {
         // After a step that was not answered the event is spent: the rung has
         // commanded the pair since, and only the fields speak for it now.
         let broke = match ended {

b2c-a-reset-that-read-the-port-empty-goes-on

--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -262,7 +262,7 @@
     /// was enumerated as, on its port. Any other reading ends the rung, and
     /// nothing is sent to a port that reads empty.
     pub fn goes_on(self) -> bool {
-        self == Self::Enumerate
+        matches!(self, Self::Enumerate | Self::Left)
     }
 }
 

b2k-the-kernel-folds-an-empty-port-into-a-reset-that-took

--- a/kernel/src/drivers/xhci/wait/msc.rs
+++ b/kernel/src/drivers/xhci/wait/msc.rs
@@ -1275,7 +1275,7 @@
                     self.quiesce_bulk_pair(dev, broke, "stopping it before its port is reset")
                 }
                 // `reset_port` has said which way it did not.
-                PortStep::Reset => self.reset_port(dev, RECOVERING).goes_on(),
+                PortStep::Reset => matches!(self.reset_port(dev, RECOVERING), AfterReset::Enumerate | AfterReset::Left),
                 PortStep::Settle => {
                     let _ = crate::clock::settles(
                         self.after_break

m16-run-over-keeps-its-rung

--- a/toyos-xhci/src/ladder.rs
+++ b/toyos-xhci/src/ladder.rs
@@ -138,7 +138,9 @@
 
     /// A round trip completed: the run is over. How many breaks it held.
     pub fn over(&mut self) -> u8 {
-        core::mem::replace(self, Self::NONE).breaks
+        let breaks = self.breaks;
+        self.breaks = 0;
+        breaks
     }
 
     /// The rung this run last climbed ended `ended`, below [`Rung::Offline`],

@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator runs at 989aec4 (logs orch/logs/588r6-*.log):

job args patch exit line
stick left, run 1 usb_stick_left — 0 PASS usb_stick_left (15s)
stick left, run 2 usb_stick_left — 0
stick left, run 3 usb_stick_left — 0
kernel-side B2 fold usb_stick_left b2k-the-kernel-folds-an-empty-port-into-a-reset-that-took.patch 1 FAIL usb_stick_left: BeforeItsCompletion: "[kernel 0.324 cpu0] xHCI: Address Device (after the port reset) failed: code 5 (TRB Error)" between the hold and the leave
whole (none) — 0 test result: ok. 499 passed, 499 total (313.4s)
T14 --metal usb_stick_left record rows for boot.testcases 0 PASS usb_stick_left

The T14 run also recorded three new rows for the usbbreak boot (boot.usbbreak.{complete_ms,panel_max_us,panel_us} = 1160 / 3905 / 21851). They are not committed; the orchestrator holds them.

@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review at 989aec4 (round 6, final)

CI host SUCCESS at 989aec4 (run 36772779867, pull_request, head 989aec4).

The orchestrator's runs at 989aec4 are in issuecomment-5919300334 (588r6-*.log):

  • usb_stick_left: EXIT 0, three times.
  • usb_stick_left with b2k applied: EXIT 1, BeforeItsCompletion: "… Address Device (after the port reset) failed: code 5 (TRB Error)" between the hold and the leave.
  • The whole suite: EXIT 0, 499/499.
  • --metal usb_stick_left on the T14: EXIT 0.

Crate controls, from the PR body: n1, c1, k1 and b2c each give cargo test -p toyos-xhci EXIT 101.

Merges: git merge-tree --write-tree origin/main HEAD against 649ea51 exits 0. The --remerge-diff of 92760ab keeps both sides' hunks in tests/checks.rs.

Earlier BLOCKERs (round 5, at 63a2a04)

Growth

Measured with git diff --shortstat origin/main...HEAD against base 3c24b6e: +2443 −720 in total.

part whole branch this round (989aec4)
production +1323 −671 (toyos-xhci +920, kernel +403 −671) +105 −73
tests +1040 −33 (toyos-xhci +759, harness +281 −33) +217 −73
issues +80 −16 +28 −2

Accepted. This round's 32 net production lines move the leave decision into the crate, where a host test can turn red on it.

BLOCKER

None open.

NOTE

REMOVE

  • PR body, Gates: "Requested from the orchestrator at 989aec4 (orch/588r6/jobs.txt): …". Stale.
  • PR body, the b2k row: "requested: usb_stick_left must exit 1 …". Stale.
  • PR body, "What I am unsure of", first bullet ("usb_stick_left has not booted …"). Stale: it booted.
  • PR body, "What I am unsure of", third bullet ("… the guest runs requested for this head have to show"). Stale.
  • PR body: the n1, forced and whole rows of the 63a2a04 table, and the "At dc0863b … k1" line.
    • They measure a decision that was then inline in the kernel, and usb_transport_break boots this head no longer has.
    • The T14 row stays.

LAND AFTER NAMED CHANGES

The `--metal usb_stick_left` run at 989aec4 passed and recorded three
new numbers for the `usbbreak` boot: complete_ms 1160, panel_max_us
3905, panel_us 21851. On `main` the redlist keeps that boot from
running; this branch is what runs it, so its records land here. The
same run's `boot.testcases` rows are not this branch's and are left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu
Japabu added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit e754dc8 Sep 30, 2026
1 check passed
@Japabu
Japabu deleted the wt/toyos-botscsi branch September 30, 2026 21:37
Japabu added a commit that referenced this pull request Oct 1, 2026
…toyos-noredlist

#588 landed first, and its `usb_stick_left` arms `usb-transport-break`,
`usb-reset-moves`, `usb-reset-moves-after` and `usb-reset-moves-configured`
again, its T14 row (`usbbreak`) arms `usb-transport-break`, and its metal
record carries the `usbbreak` rows. Those stay, with `msc::transport_break`,
`msc::reset_moves`, `RECOVERING` and `Quiet::Staged`. Everything else this
branch deleted with `usb_transport_break` (90ecefe, b5c59cb, 402107b,
9ebf080) stays deleted: no test on either side arms it.

Conflicts, every hunk:

- `kernel/src/actuator.rs`, two hunks. First: main's eight USB rows. Kept
  `usb_transport_break`, `usb_reset_moves`, `usb_reset_moves_after` and
  `usb_reset_moves_configured`, with `usb_reset_moves`' doc losing its
  citation of the deleted `usb_transport_break`; dropped
  `usb_transport_offline`, `usb_reset_break`, `usb_transport_break_owed` and
  `usb_transport_break_flushed`, armed only by the deleted
  `usb_transport_break`, `log_flush_retry` and `partition_claim_departure`.
  Second: dropped `watch_window` (`blocking_read_window`, deleted in
  dc62efc), kept #634's `handler_post`.
- `kernel/src/drivers/xhci/wait/msc.rs`, fourteen hunks, misaligned by #588's
  rewrite of the file. Resolved as main's file with this branch's deletions
  applied to it: `Asks` and `bot`'s `asks` argument at its five call sites;
  `transport_break`'s owed and flushed half (`WROTE`, `FLUSHED`,
  `AFTER_A_FLUSH`, `wrote`, `flushed`, their calls in `MscDevice::wrote` and
  the flush, and `arm`'s argument), so `arm` reads `usb-transport-break`
  alone; `return_silent` and its hook in `served`; `reset_break` and the
  wrapper around `climb`, whose body `climb_until_in_step` carries again;
  `short_read` and its hold and release around the data phase; `staged` and
  its checks in `bot`, the bad signature, the withheld CBW, the gone port and
  the unanswered wait; the bind's `slow_return` stall and INQUIRY staging, and
  `slow_return` itself; `read_serial`'s short ask. `reset_moves`, its three
  holds and `RECOVERING` are main's as they stand.
- `kernel/src/watch.rs`, one hunk: dropped the `window` module
  (`watch-window`), kept #634's `handler_post` module.
- `toyos-sched/src/watch.rs`, one hunk: the same, `window` dropped and
  `handler_post` kept.
- `src/lib.rs`, one hunk: kept #652's `pub mod n2`, dropped `pub mod redlist`.
- `tests/common/power.rs`, one hunk: main's only change there renamed
  `transport_break_chain`'s doc to `usb_stick_left`; its one caller is the
  deleted `usb_transport_break`, so it stays deleted.
- `tests/common/usb.rs`, one hunk holding main's whole `usb_transport_break`
  region. Kept #588's `Held`, `usb_stick_left`,
  `a_stick_that_left_under_its_rung`, `transport_break_on_metal`,
  `transport_break_recovered` (which `tests/checks/usb.rs` judges) and
  `broke_on`; the rest is `usb_transport_break`'s and stays deleted:
  `a_read_whose_first_wait_spent_its_budget_goes_out_again`,
  `serial_short_is_not_read`, `cpu_of`, `line_with`, `Moved`,
  `a_stick_its_reset_moved_carries_on`, `abandoned_write_is_taken_offline`,
  `no_command_was_refused`, `port_gone_is_left_to_the_teardown`,
  `control_requests`, `command_blocks`,
  `every_reset_is_followed_by_a_test_unit_ready`, `is_a_rungs_configuration`,
  `every_port_reset_is_followed_by_a_test_unit_ready`,
  `every_reset_is_followed_by_both_clears` and `transport_gives_up`.
- `tests/toyos.rs`, five hunks. `MACHINE_TESTS`: kept `usb_stick_left` and
  #634's `handler_post_without_a_pass`; dropped `usb_transport_break`,
  `xhci_full_speed_device` (5e42235), `blocking_read_window` and
  `user_copy_races_munmap` (7ea6be1). `METAL`: kept #588's `usb_stick_left`
  row. Dispatch: kept `usb_stick_left` and `handler_post_without_a_pass`;
  dropped `usb_transport_break`, `xhci_full_speed_device` and
  `smp_failed_ap_leaves_no_hole` (aedcf17).

Outside the conflicts:

- `kernel/src/drivers/xhci/wait/mod.rs`: `Quiet::Staged` and its line come
  back, which `transport_break::take` answers with; the auto-merge had taken
  9ebf080's deletion. The `reset_break` and `return_silent` hooks stay
  deleted.
- `src/metal.rs`: `FLASHABLE`'s `usb-transport-break` row comes back
  (b5c59cb took it), since `usb_stick_left`'s T14 arm flashes it.
- `issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md`:
  records `4f2bea143`, this merge's second parent, as the tree holding #588's
  version of `usb_transport_break`, in place of the instruction to record it.
- `issues/boot-media/logd-ends-the-boots-log-on-one-refused-create-and-nothing-durable-says-so.md`:
  drops its citation of `power::transport_break_chain`, deleted in 90ecefe.

The `rust` gitlink is main's.

On this tree: `cargo run -- --build-only` exit 0; the kernel's `cargo check`
for x86-64 and AArch64, bare, with `boot-actuators` and with
`boot-actuators,test-actuators`, exit 0 each; `cargo test --test toyos-build
--no-run` exit 0, no warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Japabu added a commit that referenced this pull request Oct 2, 2026
Main is 469 commits past the last merge (e754dc8, #588). Six files
conflicted, each resolved by accounting for both sides:

- kernel/src/actuator.rs: main (#639, #660) deleted every actuator in the
  region this branch added `windows-staged` to, `handler-post` among them.
  Main's deletions stand, and `windows-staged` sits after
  `usb-reset-under-load`. Its doc names `virt_mask_windows`, its one judge
  now.
- kernel/src/scheduler.rs: main deleted `dump-in-blocking-pass`'s lines in
  `exit_to_user`; this branch's `irqs_unmasking` hook followed them. The
  hook stays, the actuator's lines go.
- src/clippy.rs: main dropped `fpu-save-nothing` and `user-writable-gsbase`
  from both instrument lists; `mask-windows` is appended to what is left,
  and the two `MASK_WINDOWS_KERNEL` shapes are this branch's.
- src/build.rs: main cut `TEST_SUITE_KERNEL_BUILDS` to the shipping and the
  test kernel; `MASK_WINDOWS_KERNEL` is the third. `carries_the_actuators`
  and `refuse_an_unarmable_boot` merged without conflict.
- tests/common/qemu.rs: main shortened `DECLARED_KERNEL_BUILDS`' doc and
  deleted `kernel_of`'s paragraph on `boot_image`; this branch's sentence
  for the windows' build and `kernel_of`'s rule are kept over main's text.
- tests/toyos.rs: main's file (#660: 21 declared guest tests, `Sched` and
  the shared guest boot gone, `metal::Metal` a struct) taken whole, and
  this branch's additions put back onto it: `ring_park_herd` on
  `RUST_SKIP`, `virt_mask_windows` and its judge `mask_windows`, the
  `mask_windows` metal row with `WINDOWSCASE`, and `windows_on_metal`.

**The x86-64 guest test `mask_windows` does not come back.** Main refuses
one name in two registries (`registered`), and `mask_windows` is the T14's
row; `irq_census_conservation`, the census these lines sit beside, was cut
from the guest suite for its row the same way. Its `MACHINE_TESTS` entry,
its `CARRIES` row and its `run_machine_test` arm, which booted the shared
guest image main deleted, are not carried. What it held is held by:
the x86-64 hooks' checks by the metal row, which boots them under the
herd; the stage by `virt_mask_windows`; the verdict by
`mask_windows_verdict` on the host.

The census of raw masking instructions is unchanged by the merge: every
word of `cli`, `sti`, `popfq`, `iretq`, `sysretq`, `eret`, `daifset`,
`daifclr`, `daif`, `wfi` and `hlt` under `kernel/src`, comments apart, is
the same list at 737803d and here but for main's own two moves: the NMI
gate's `iretq` from `nmi_gate.rs` to `idt/nmi.rs`, and `IrqGuard::unclosed`,
a read of the flags, deleted on both architectures. The clause of the
guard's drop hook that named it goes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
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