Conversation
…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>
Review, round 1, at 627a715CI Growth, from BLOCKER
NOTE
REMOVE
SEND BACK |
…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>
Review, round 2, at f90b2d2CI Growth, from Round-1 BLOCKERs
The redlist
Mutation patches
BLOCKER
NOTE
REMOVE
SEND BACK |
The USB mass-storage BOT round trip and the SCSI bring-up above it become pure
toyos-xhcicode with host tests.kernel/src/drivers/xhci/wait/msc.rskeeps 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 also still decides the ladder's climb loop, that a disconnect goes to the port's teardown,offline: dev.failed, anddata_out. A userland usbd can drive the same machines unchanged. The crate isno_std, has no unsafe code and makes no x86 assumptions.What changed and why
toyos_xhci::botcbw()builds the 31-byte CBW (USB MSC BOT 1.0 §5.1): LUN 0, direction taken from the CDB.RoundTripis the round trip as a stepped machine. Its acts areCommand,Data(pipe),StatusandRestart { pipe, then }, and it is answered withMoved { code, residue },Silent,GoneorRestarted.CswDue::judgechecks 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 areBroke::Reserved. Delivered bytes aremin(controller's count, device's count), and a controller residue larger than the transfer delivers nothing.Broke<W>carriesleft()andevent()and is generic over the driver's silence reason.Gonevariant; it used to beSilence { why: Quiet::Gone }. The crate has to tell a disconnect apart, and the kernel'sQuietis not a type the crate has.Broke<Quiet>::Silence { why: Quiet::Gone }can still be built, and onlycompleted()inmsc.rskeeps it apart fromBroke::Gone. It is not made unrepresentable because the price is a second kernel enum that mirrors three ofQuiet's four variants (one of themcfg'd), with conversions both ways, all to guard one match in one function. Deleting that match'sGonearm is the negative control below, run in the guest.left()matches on aPhase. It used to match on a phase's name string.CC_STALLjoinsCC_SUCCESSandCC_SHORT_PACKETintoyos_xhci::job. The kernel's EP0 recovery imports it, and the kernel's own copy is deleted.toyos_xhci::scsiCdbcan only be built inside this module, and it carries its own direction. The kernel'scdb_len/data_inparameters and its CDB-shape assert are gone, because a mismatch can no longer be written.Sense::ofbelieves sense data only if at least 14 bytes arrived (ASCQ is byte 13; SPC-4 §4.5.3).Replyis the answer to one SCSI command. It is named apart from the crate root'stoyos_xhci::Outcome.flushed(): INVALID COMMAND OPERATION CODE means the device has no cache, which is not a failure.Transferdoes 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 theexpectinTransfer::nextdepends on that.Geometry::NONEfits no transfer, and a test checks it.BringUpis a stepped machine:Refusal).BringUpblocking, 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.Printablemoved here unchanged.toyos_xhci::ladder::Runholds a device's break count and highest rung. It used to be two loose fields.identity::first_languagereads the LANGID of string descriptor zero.Kernel shim:
msc.rsdrops from 2647 to 2359 lines, andkernel/srcfrom 64984 to 64695 (find kernel/src -name '*.rs' | xargs cat | wc -l, before and after). The stagedPortGonefault 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.src/sourcegate.rs: the: u32 = 4096exception formsc.rsis removed, sinceHOST_BLOCKnow lives in the crate.Issue: the slug
the-bot-scsi-machine-is-still-hand-written-in-the-kernelis 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 toissues/hardware/a-disk-plugged-in-after-boot-is-bound-inside-a-scheduling-pass.md, and both citations move with it (issues/build/the-swarm-is-not-yet-falsifiable.md,issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md).No behaviour change on the wire
The commands, bytes, order, waits and recovery are the same. The two source spellings that
toyos-blackboxchecks againstmsc.rsare kept.These things changed off the wire:
Unaligned: the same bytes at the same DMA offset. The CSW is read as one 13-byte copy.StatusOwedtwice, with the same value and ring positions.Transfer::nextnow 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 innerif !dev.no_write_cacheis removed. It was always true, because the latch is checked above it.CSW claims N B unmoved, and a reserved status (3..=255) logsthe CSW carries status 0x.., which the class reservesinstead of a phase error. Both break in the status phase and climb the same ladder as before.boot-actuators, the stagedPortGonenow takes a tag and writes its CBW into the pool before its Command act completes as the disconnect. Nothing reaches the controller.Gates (this head)
cargo test -p toyos-build --libcargo test --workspace --exclude toyos-buildcargo test --test toyos-build -- --listcargo run -- --clippy(x86_64 and aarch64 kernels, with and withoutboot-actuators)cargo run -- --build-onlycargo test -p toyos-xhci(148 tests, 33 of them new)Guest: at 627a715, the previous head, the orchestrator ran the 19 USB/storage tests and Fast, all EXIT=0. None of those runs reached the ladder. No guest run has been made at this head.
Metal is owed. The metal reading has not been made: a T14 stick boot and
transport_break_on_metal(tests/common/usb.rs) at this head. The T14 is offline, so this PR claims none.High-risk: negative controls and the independent oracle
Negative controls. Each is a checked patch:
git apply --check, apply, shown to build,cargo test -p toyos-xhci,git apply -R. The tree was clean afterwards. All 23 build (EXIT=0) and all 23 go red (EXIT=101):a_cbw_is_laid_out_as_the_class_defines_ita_csw_is_believed_only_when_it_is_valid_and_meaningfulwhat_is_delivered_is_what_both_the_controller_and_the_device_say_arriveda_stalled_status_is_asked_for_again_oncea_csw_is_believed_only_when_it_is_valid_and_meaningfula_command_or_status_block_that_moved_short_breaksbottestssense_is_believed_only_when_its_ascq_arrivedonly_the_first_batch_may_answer_ask_againa_disk_too_big_for_read_capacity_10_is_asked_the_16_byte_forma_flush_the_device_does_not_implement_is_no_failure_and_every_other_refusal_isa_disk_that_never_becomes_ready_is_given_up_on_at_its_budgeta_transfer_is_its_batches_in_order_and_only_a_whole_one_advancesa_device_that_is_not_a_disk_this_driver_serves_is_refused_by_nameRun::overkeeps its runga_run_climbs_one_rung_per_break_until_a_round_trip_ends_itladdertestsa_language_is_read_only_from_a_string_descriptor_that_carries_onemoved: self.data_len(the device's residue alone)a_stalled_data_phase_delivers_what_both_the_controller_and_the_device_say_arrived.status(true)(no §5.3.3 retry)a_status_stalled_after_a_data_stall_is_still_asked_for_againwrapping_subwhat_is_delivered_is_what_both_the_controller_and_the_device_say_arriveda_csw_is_believed_only_when_it_is_valid_and_meaningfulwrapping_suba_stalled_data_phase_delivers_what_both_the_controller_and_the_device_say_arrivedThe disconnect seam's control is a guest mutation, because no host test drives the kernel: delete
Err(Quiet::Gone) => bot::Answer::Gone,fromcompleted(). With that patch applied the tree passescargo run -- --clippy(EXIT=0). Its run isusb_transport_breakwith the arm deleted, which must go red inport_gone_is_left_to_the_teardown. That run belongs to the orchestrator.For "no behaviour change" as a whole, the whole-change control is the guest USB suite on this head compared with its green on
main.usb_transport_breakis on the redlist (issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md), and it drives the ladder paths this change moved most. The orchestrator forces it with the row removed at this head and at the base. Those are the orchestrator's runs.Independent oracle. The host tests' byte layouts and verdicts are written from the specifications' tables, not from the old code:
The sector-size set is the one exception. It has no oracle but the driver it was carried over from.
QEMU's
usb-storagedevice is a second implementation of the same protocol, reached through the guest USB suite. The T14's stick is a third, reached through the metal arm that is owed above.🤖 Generated with Claude Code