Skip to content

One block service and a driver per disk controller; input names each device; SCSI is its own crate (usbd step 10, PR 1 of 2) - #962

Merged
Japabu merged 5 commits into
mainfrom
wt/toyos-usbd
Oct 21, 2026
Merged

Japabu merged 5 commits into
mainfrom
wt/toyos-usbd

Conversation

@Japabu

@Japabu Japabu commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

PR 1 of 2 for step 10 of issues/the-kernel-is-small-interrupts-post-and-threads-wait.md (stage 4 of issues/a-device-function-is-driven-from-its-arrival-to-its-end.md). It builds what usbd's whole xHCI (PR 2) stands on, and deletes what it supersedes. The kernel's xHCI driver, its USB storage, keyboard.rs/mouse.rs, the partition claims and OwedLock go in PR 2.

The block path: one service, a driver per disk controller

Ruling (orchestrator, on this assignment; recorded in the device-function track and step 10's text, replacing #901's block@<location>, each, pci@ and several block services).

  • One shared block service, diskserver, serves block to its clients and disk to disk drivers. Clients are unchanged; their grants are still supervisor-minted badges.
  • A disk driver is a drives row with receives = ["disk"]. It reads its own GPT, offers each disk's partitions, and says when a disk is gone.
  • On an open the service checks the grant, then hands the client's connection and region to the offering driver. The driver answers MSG_OPENED on the client's own connection and serves the session itself, so no block passes through the service.
  • NVMe is split out now: system/nvme is a drives row, one process per controller. The supervisor starts disk drivers before the storage rows.

What changed:

  • toyos-blockring::disk: the driver/service frames. A driver says its name, offers each disk (Offer, up to a GPT's 128 entries), settles once (Offered or Unusable), and says when a disk is gone. The service hands over a session with two handles. wire::Listed gains the service's number for the partition's disk.
  • lib/blockserve: a disk driver's side.
    • table: the GPT read, moved from diskserver.
    • link: offers and hand-overs. When the service ends, the link connects again at once (the supervisor keeps the port) and offers everything again. Sessions already handed over keep running.
    • sessions: each disk's sessions over a Device trait the driver implements (has_room, submit_io, submit_flush, dma_map/dma_unmap). This is diskserver's old serving code with admission left to the service.
    • region, moved from diskserver, since both ends map it.
  • system/nvme: the NVMe driver moved whole, plus a main loop. The row keeps QEMU's pci:01:08:02/1b36:0010, as diskserver's row did, so a metal boot leaves the bench's own NVMe alone (issues/diskserver-claims-qemus-nvme-by-its-pci-id.md, whose citations move here).
  • system/diskserver: the block service alone. Its decisions are pure functions over what drivers said (Known::listing, Known::open) and are host-tested.
    • A driver is the supervisor's badge on its disk connector: the function's arrival, which the kernel stamps on the connection. The supervisor names each disk driver it started by that badge (--driver). A driver's own name appears in log lines only.
    • Nothing is routed until every driver started before the service has settled, and every driver that ended has settled again. That includes an open of a partition already offered. A GUID a second disk carries is therefore refused on both, and a type-granted reopen after a driver's restart waits for the offer.
    • The wait is bounded by DRIVER_BOUND (30 s, a stated policy, wire::DRIVER_BOUND_NS), counted from the service's start or a settled driver's last end. Past it every answer is immediate, with what is known, and the line names the drivers that never settled.
    • Something not offered is refused Unusable while a driver is missing, unusable or ended. It is refused ClaimRefused while a disk controller got no driver (--undriven), and NotFound only otherwise. So "not yet" is never answered as "absent".
    • A client bounds its wait for a listing's or an open's answer by wire::ANSWER_BOUND_NS (twice that), and answers Unanswered past it.
  • Supervisor:
    • It starts disk-driver rows before the storage rows.
    • It mints each disk driver's disk connector with its arrival as the badge, and passes the block service --driver <arrival> for each running disk driver and --undriven <entry at PCI x>, at every start of it, restarts and swaps included.
    • Disk drivers count as storage, so they get no file view.
    • The slot table update is granted is the one on the running ROOT's disk, now that a listing spans several disks.
    • The old --claim-refused path in start() is gone.
  • SDK: Connection::received and Connection::into_owned, so a connection end can be passed on. The kernel refuses each connection call on a handle of another type, so trust stops at what the sending protocol vouches for.

Two consequential choices, mine to flag:

input: each device its own source and gone

  • toyos-input now owns its wire records. They are no longer toyos_abi types; the kernel streams' readers stay until PR 2 deletes the streams.
    • Keys carry a u16 source, pointers' source widens to u16, and MSG_GONE{source} replaces LOST.
    • Merge and Pointer hold each connection's devices by source. An entry holds a place only while its device holds something, up to DEVICES (16).
    • A sender past that is refused by name (Full), and the owners drop its connection.
  • ps2server's pointer is source 0 and its keyboard source 1, whose overrun sends gone for source 1.
  • The owed test: a_connections_end_releases_every_device_it_carried_and_a_gone_only_its_own_source. It is red with Merge's release on a connection's end removed, and red with gone releasing its whole connection (below).

toyos-scsi

lib/xhci/src/scsi.rs moved whole to lib/scsi: no_std, no xHCI dependency, its host tests with it.

  • Sense::of now reads only fixed format (SPC-4 §4.5.1, table 27: 0x70 current, 0x71 deferred).
  • It refuses descriptor format and vendor codes by name, and an additional sense length that ends before ASCQ.
  • A deferred error is carried as one, so it never reads as the current command's INVALID COMMAND OPERATION CODE.

This closes issues/scsi-is-decoded-inside-the-xhci-crate.md and issues/sense-data-is-read-as-fixed-format-whatever-its-response-code-says.md. The kernel's one caller logs a refused sense and reads it as Sense::NONE, the failing side.

A defect a test missed, and the test now holding it

The first guest run of nvme_disk_boots passed with LOG, BOOT and DATA all absent. The driver called describe twice, and a claim answers it exactly once. The test only checked a clean boot. It now requires each role served:

  • Log serving /log — FAT32,
  • Boot serving /boot — FAT32 read-only,
  • Data serving ... — bcachefs,

That double describe is its negative control (below). In the green run every role mounts off sessions the driver serves, BOOT read-only.

Checks

At head 27b75c355 (one commit on origin/main 9b11608ab):

check command exit
host cargo run -- --ci host 0, "87 step(s), all green"
x86-64 image cargo run -- --build-only 0
aarch64 image cargo run -- --build-only --arch aarch64 0
guest, QEMU x86-64 cargo test --test toyos-build -- --nocapture nvme_disk_boots iommu_virtio_platform acpi_mediated_access a_drives_row_holds_one_process_per_function usbd_drives_the_spare 0, 5 passed

Why each guest row:

  • nvme_disk_boots: the whole block path on an NVMe-only machine.
  • iommu_virtio_platform: a refused drives start's line, which its no-unit arm reads.
  • acpi_mediated_access: its shared string.
  • a_drives_row_holds_one_process_per_function and usbd_drives_the_spare: the supervisor's new order of drives starts.

Negative controls, each a checked patch applied, run and restored in one script (RESTORED_CLEAN=yes in every log):

patch test exit
Merge::end releases nothing a_connections_end_releases_… 101
gone releases its whole connection the same 101
descriptor format read as fixed sense_is_read_only_in_the_fixed_format… 101
a deferred error read as current the same 101
the service answers an unoffered open without waiting what_no_disk_offers_waits_for_every_driver_started_at_boot 101
the NVMe driver describes its claim twice nvme_disk_boots (guest) 1, "fileserver: Log serving /log — FAT32, " never reached the boot console

The controls ran on the uncommitted tree just before the commit. Later edits touched only the supervisor's drive_where and the --claim-refused → --undriven rename, neither in a mutated line.

Evidence (logs and patches): https://github.com/ToyOSOrg/evidence/tree/b167cf5d6e0ae745e9e1e34e2599112f29767e77/pr1 (the first nvme_disk_boots run, roles absent, is pr1/nvme-disk-boots-first-run-roles-absent.log)

Metal (staged, not run; the orchestrator runs every boot)

The judges read /log off the stick, which in this PR is still the kernel's. These boots therefore show that the new supervisor order and the block service with no disk drivers leave the log path whole on both benches.

  • T14: usbd_drives_the_type_c_controller (its testcases-xhci-leave boot).
  • AMD bench: amdvi_translates_the_stick_controller (its testcases boot); its request needs the bench's --host.

Run by the orchestrator at 27b75c355: the T14 row passed. The AMD row failed only on the recorded HDMI-audio DMA truncation (03:00.1). On both benches the log partition and the kernel log read back whole. Readbacks, with USB serials masked: https://github.com/ToyOSOrg/evidence/tree/00757415fbf704cec4092b98a0c4bc7413aee14e/pr1 (bench-t14/, bench-amd/).

649e0cbe3 changes only issues/diskserver-claims-qemus-nvme-by-its-pci-id.md, which records the nvme row's QEMU id as a compromise with its owner and exit. No check above can change.

Review round 1

Answered in e07354fb1; checks and controls are in the reply on this PR (#962 (comment)). Evidence: https://github.com/ToyOSOrg/evidence/tree/ddc16d3eaf90590530f85e1ab82d3a8ed4f330d7/pr1-r1

Risks

  • DRIVER_BOUND (30 s) is a policy, not a specification. It is spent only when a driver never settles, and then once.
  • LOG and BOOT now wait for every disk driver to settle before they open, as DATA's listing already did.
  • A listing that waits holds a connection. At most MAX_WAITING (16) wait; past that the acceptor goes unwatched and connects queue in the port.
  • PR 2 is where the stick moves to usbd. Until then the T14's /log and /home stay on kernel partition claims, which this PR leaves as they were.

🤖 Generated with Claude Code

…device; SCSI is its own crate

Step 10's first half under the orchestrator's block ruling (recorded in the
device-function track): a disk driver is a `drives` row receiving `disk`,
reads its own table and offers its disks; diskserver is the block service
alone, and hands each opened session's client connection and region to the
offering driver, which answers the open and serves it. No block passes
through the service.

- toyos-blockring: `disk`, the driver/service frames; `Listed` names its disk.
- lib/blockserve: a disk driver's side — its table, its link (it connects
  again and offers again when the service restarts), each disk's sessions
  over a `Device` it implements.
- system/nvme: the NVMe driver, moved out of diskserver whole, one process
  per controller; it describes its claim once (the claim answers once).
- diskserver: what is listed and admitted, by grant, across every offer; a
  listing and an open of what no disk offers wait until every boot-started
  driver has settled, bounded by DRIVER_BOUND (30 s) and naming the driver
  that did not.
- supervisor: disk drivers start before the storage rows; the block service
  is told how many (`--drivers`) and which controllers got none
  (`--undriven`); the slot table is the one on the running ROOT's disk.
- SDK: `Connection::received` and `into_owned`, to pass a connection on.
- nvme_disk_boots requires each role served: it passed with all three
  absent while the driver's second describe was refused.

toyos-input's records carry a u16 `source` on keys and pointers and a
`gone` per source in place of `LOST`; the owners hold each connection's
devices by source, at most `DEVICES` holding something, and drop a sender
past it. ps2server's pointer is source 0 and its keyboard source 1.

toyos-scsi is `lib/xhci/src/scsi.rs` moved, `no_std`, with no xHCI
dependency; `Sense::of` reads only fixed format (SPC-4 table 27), refuses a
short additional length, and carries a deferred error as one, which never
reads as the current command's INVALID COMMAND OPERATION CODE. Closes
scsi-is-decoded-inside-the-xhci-crate and
sense-data-is-read-as-fixed-format-whatever-its-response-code-says.

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

Japabu commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator, bench at 27b75c3 (one boot per bench, image hashes verified): T14 testcases-xhci-leave — usbd_drives_the_type_c_controller PASS; AMD testcases — amdvi_translates_the_stick_controller FAIL only on the recorded, independent HDMI-audio DMA truncation (iommu: DMA FAULT … stream=03:00.1 addr=0x…1000…, issues/a-function-that-truncates-its-dma-addresses-faults-in-its-domain.md). On both benches the log partition and kernel log were read back whole, so the new supervisor order and the empty block service leave the /log path intact; the path through usbd is PR 2's to prove.

#962 names NVMe by QEMU's id in every image so no metal boot drives the
bench's own disk, which holds the Ubuntu the metal loop reboots through.
The record now says what a shipped image should drive (every NVMe by class),
what keeps another system's disk unwritten under a class row (no grant
reaches its partitions; read, not measured), and what closes it.

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

Japabu commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #962 at 27b75c355 (one commit on origin/main 9b11608ab)

Architecture

The split follows orchestrator ruling A. diskserver is now only the block service: it admits by grant and routes. Each disk driver is a drives process that reads its own GPT and serves its sessions directly out of the client's region. The handover has the right shape. The service checks the supervisor-minted badge. The driver validates the region itself (Region::lent: kernel-allocated, exact size). The handle batch goes ahead of MSG_SESSION, and a refused frame drops the driver, so an orphaned batch is never paired with a later session. The driver never sees the client's badge, and a driver that dies takes only its own sessions with it. Sessions keep running across a service restart, which is an improvement on main.

Superseded paths: #901's block@/each/pci@ exist only in records, and both records are rewritten. Kernel partition claims and SYS_PARTITION_* stay, because the T14's /log and /home still use them until PR 2. That is consistent with the stated scope. --claim-refused is gone with no remaining reference. The nvme row's QEMU-only ID is recorded with an exit in issues/diskserver-claims-qemus-nvme-by-its-pci-id.md.

The input change is direct: toyos-input owns its wire records, devices are keyed by (slot, source), and Full is refused by name. The SCSI fix matches SPC-4: the 0x7F response-code mask, codes 0x70/0x71, and an additional length of at least 6 so the data reaches ASCQ at byte 13.

Size: +2519/−1134. Roughly 900 lines are moves (nvme.rs, region.rs, scsi). New production code is about 230 lines for the disk protocol, about 600 for blockserve, 226 for the nvme main loop and about 70 in the supervisor. Tests grow by about 250 lines across disk, sessions, diskserver Known, input and scsi.

The routing and waiting core needs the fixes below.

Findings

BLOCKER 1. A DATA reconnect after its driver restarts is refused NotGranted.

  • Where: system/diskserver/src/main.rs:148-153.
  • What happens:
    • DATA's grant is Scope::Kind(TOYOS_DATA) (system/supervisor/src/main.rs:3132).
    • When the nvme driver ends, the client's connection, which the driver holds, closes. Served::asked then calls Session::reconnect (system/diskserver/src/disk.rs:96, session.rs:357).
    • drop_driver has already removed the dead driver's offers (main.rs:405). So carrying is empty, kind is [0; 16], and a Kind grant never admits it. The reconnect is answered NotGranted immediately, without waiting, and the file server gets DiskError::Gone.
  • Why it matters:
    • This is the case the code says it handles: "as a file server reopening after a driver's restart needs" (main.rs:616).
    • On main it worked, because the restarted diskserver read its table before it accepted.
    • a_driver_that_ended_is_awaited_until_it_settles_again only tests a Unique grant.
  • Fix: while the drivers are unsettled, an unoffered GUID waits whatever the grant's scope, and the grant is judged when the answer is given. Alternatively, keep the kinds the ended driver offered. Add the Kind case to the host test.

BLOCKER 2. An offered partition is routed immediately while drivers are unsettled, which defeats the duplicate-GUID rule.

  • Where: system/diskserver/src/main.rs:155-158, against :391 and :160.
  • What happens:
    • The service refuses a GUID on two disks with Unusable ("neither is served").
    • But [one] is answered as soon as one disk offers it, even while another boot driver hasn't settled or a driver is Ended.
  • Concrete cases:
    • Two NVMe controllers whose disks carry the same image (same GUIDs) send LOG and BOOT to whichever driver offered first. The second offer then logs "neither is served" while the first session keeps running. Which disk you get depends on startup timing.
    • The same window opens on every driver restart: the ended driver's offers are gone, so another disk carrying the GUID is answered at once.
    • In PR 2 the other disk is a USB stick whose GPT anyone can write. Since an image's GUIDs are public, this becomes partition impersonation.
  • Fix: route an open only once settled() holds (at the bound, answer with what is known). This costs LOG/BOOT the same settle wait that DATA's listing already pays.

BLOCKER 3. Driver identity is the driver's own name.

  • Where: system/diskserver/src/main.rs:93,102,339-341,365,406-407, with the label built in system/nvme/src/main.rs:110.
  • What happens: fates is keyed by the label each driver chooses for itself, and settled() compares the number of distinct labels with --drivers.
  • Why it's a violation:
    • It contradicts PciFunctionInfo's own contract: bus/dev/func is "identity for a log line, never a name a claim is looked up by" (sdk/abi/src/pci.rs:35-36).
    • It also contradicts root's rule that names never establish trusted-driver identity.
  • Concrete consequences:
    • Two functions with the same bus:dev.func in different PCI segments get one label. fates.len() then never reaches expected, so every listing and every unoffered open waits 30 s, on every boot. One driver's end also marks the other Ended.
    • A driver started after the service counts toward the boot expectation. It can make settled() true while a boot driver never connected, and the answer is then NotFound. That is "not yet" answered as "absent", which the design forbids.
  • Fix: take identity from the supervisor. For example, mint each disk driver's disk connector with a badge naming its instance (the supervisor already mints block badges), pass those identities in --drivers instead of a count, and key fates by badge.

BLOCKER 4. False or contradictory comments.

  • lib/blockring/src/disk.rs:173: "A MSG_DRIVER or MSG_GONE payload" is wrong. MSG_GONE carries a disk number, which disk() decodes.
  • lib/input/src/wire.rs:4-5: "never reuses one for the life of its connection" contradicts MSG_GONE's "or lost a transition" (:29-32). ps2server sends gone for KEYBOARD on an overrun and then keeps sending KEYBOARD (system/ps2server/src/stream.rs:215-218). What holds is that a source names one device for the connection's life.
  • system/diskserver/src/main.rs:47-48: DRIVER_BOUND is described as "from its start". The code measures it from each request's arrival (:249, :436). So a driver that never settles costs every later request another 30 s. served_slots lists three times in sequence, so up to 90 s. Either fix the comment or, more simply, bound from the service's start or the driver's end, so that once the bound has passed every answer is immediate.
  • lib/blockring/src/wire.rs:207-210 and :216-218 say a listing is "refused the same". Under the accepted listing choice (main.rs:136-140), a listing that finds admitted partitions answers them. Say it is refused only when it finds none.

BLOCKER 5. Dead code. lib/blockserve/src/sessions.rs:181 Sessions::busy has no caller. Link::gone also has no caller yet, but it is protocol PR 2's usbd needs and the service already handles MSG_GONE, so it is acceptable at this stage.

SUGGESTION.

  • system/diskserver/src/main.rs:159-160: the [_] and _ arms give the same answer and can be merged.
  • lib/blockserve/src/link.rs:45-75: connect → ended → connect recurses without a bound if a fresh connection's sends keep failing, for example when page allocation fails. A loop, or a retry on the next event, avoids a stack overflow.
  • The client's handshake (system/diskserver/src/session.rs:451) blocks on recv_header with no failure timeout. A driver that wedges after a handover now hangs its client for good. The shape is pre-existing, but the handover adds a party; bound it or record it.
  • Both NVMe issue slugs (diskserver-claims-qemus-nvme-by-its-pci-id, diskservers-nvme-bring-up-open-codes-…) are now refuted by their own new headings. A rename is optional under issues/README.md ("renamed by the work that takes it up").

Evidence

  • Verified in ToyOSOrg/evidence b167cf5d6, pr1/:
    • Every guest boot reports supervisor: build 27b75c3556… clean, and the run ends with 5 passed, exit 0.
    • nvme_disk_boots shows the handover working: diskserver says the driver settled, nvme 00:01.0 opens sessions 0–2 (BOOT read-only), and all three fileserver: … serving lines appear.
    • Host CI: 87 steps, exit 0, and the new tests appear in it.
    • Both images build, exit 0.
  • The six negative controls apply, each fails its claimed test (exit 101, or exit 1 for the guest one), and each restores clean. They ran on the pre-commit tree. I compared the hunks with the head and accept the claim that later edits didn't touch the mutated lines.
  • The new tests catch the defects they name. Nothing covers BLOCKERs 1–3. The cheapest sufficient checks are host tests on Known:
    • a Kind-grant open of an unoffered GUID while a driver is Ended;
    • an open of a GUID one disk offers while another driver is unsettled;
    • a fate key that two drivers share, or that a late driver supplies (once identity is fixed).
  • Metal (T14 usbd_drives_the_type_c_controller, AMD amdvi_translates_the_stick_controller) is only staged so far. stage-t14.log says "The machine was not touched".

SEND BACK

…r by its badge

Review round 1 of #962.

- Nothing is routed until every disk driver started before the service has
  settled and every driver that ended has settled again. An open the grant
  admits on a disk already offered waits too, so a GUID a second disk also
  carries is refused on both rather than sent to whichever offered first,
  and a type-granted DATA reopen after its driver's restart waits for the
  offer rather than being refused NotGranted for a type no offer carries.
- A driver is the badge the supervisor minted its `disk` connector with
  (its function's arrival, `toyos_blockring::disk::badge`), never the name
  it gives itself. The supervisor names each running disk driver by it
  (`--driver`), so a late driver never stands in for one that never spoke,
  and a second connection with a live badge is let go.
- DRIVER_BOUND counts from the service's start or a settled driver's last
  end, so past it every answer is immediate; the client bounds its wait for
  a listing's or an open's answer by ANSWER_BOUND_NS, twice that.
- The driver's link connects again in a bounded loop, not by recursion.
- Comments that said what the code does not: MSG_GONE's payload, a
  `source`'s life, the listing's refusals. Sessions::busy is deleted.

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

Japabu commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Round 1 is answered in e07354fb1, a new commit on wt/toyos-usbd.

BLOCKER 1 (a type-granted reopen) and BLOCKER 2 (routing before settle): one rule. Known::open now routes nothing until settled() holds or the bound has passed. The grant is then judged against every offer.

  • A Kind-granted DATA reopen while its driver is Ended waits for the new offer.
  • A GUID that a second disk turns up carrying is refused Unusable on both disks. It is never sent to whichever disk offered first.
  • A connection with no grant is still refused at once.
  • Tests: a_driver_that_ended_is_awaited_until_it_settles_again_under_either_grant and a_guid_a_late_disk_also_carries_is_never_routed_to_the_first.
  • LOG and BOOT now pay the same settle wait that DATA's listing pays. In nvme_disk_boots the driver settles 0.3 s after its start.

BLOCKER 3 (driver identity): the approved approach.

  • The supervisor mints each disk driver's disk connector with toyos_blockring::disk::badge(arrival) (Grants::disk, called in start_instance).
  • It passes --driver <arrival> once per running disk driver, instead of a count.
  • The service keys fates by the badge the kernel stamps on the connection, and lets go of a second connection carrying a live badge.
  • The name a driver gives itself is used in log lines only.
  • Test: a_late_driver_never_stands_in_for_one_started_before_the_service. A driver the service did not expect settles, and the wait goes on for the expected one that never spoke.
  • Two drivers that share a name can no longer collide, because no name reaches Known. That is held by the types rather than by a test.
  • In the guest run: diskserver: serving the disks of the driver(s) started before it, [2] … disk driver 2 (nvme 00:01.0) settled.

BLOCKER 4 (the comments).

  • disk::label and disk::disk each now document their own payload.
  • input's header now says that a source names one device for its connection's life, and that a gone source may go on.
  • Refusal::Unusable and ClaimRefused now say a listing is refused only when it finds nothing it admits.
  • DRIVER_BOUND is fixed in the code, not in the comment. It now counts from the service's start or a settled driver's last end (awaited_since), so once it has passed every answer is immediate; served_slots's three listings cost at most one bound.
  • The bound moved to toyos_blockring::wire::DRIVER_BOUND_NS, which the service reads.

BLOCKER 5. Sessions::busy is deleted.

Suggestions taken:

  • The two Unusable arms are merged.
  • Link::connect is a loop of at most three fresh connections. After that the driver ends by name.
  • The client's handshake and listing now wait for readability up to wire::ANSWER_BOUND_NS (twice the service's bound, which leaves room for the driver's answer) and answer Error::Unanswered past it. A wedged driver no longer hangs its client.
  • The slug renames are declined.

Checks at e07354fb1 (the tree, built before the commit, which adds nothing):

check exit
cargo run -- --ci host 0, "87 step(s), all green"
cargo run -- --build-only, and --arch aarch64 0, 0
guest: nvme_disk_boots iommu_virtio_platform acpi_mediated_access a_drives_row_holds_one_process_per_function usbd_drives_the_spare 0, 5 passed

Negative controls, each applied, run (cargo test -p diskserver --bin diskserver) and restored clean:

patch exit red tests
open routes before settle 101 the four routing tests
round 0's rule: only an unoffered open under a unique grant waits 101 the ended-driver, late-disk and settle tests
settled() counts drivers instead of naming them 101 a_late_driver_never_stands_in_for_one_started_before_the_service

Evidence: https://github.com/ToyOSOrg/evidence/tree/ddc16d3eaf90590530f85e1ab82d3a8ed4f330d7/pr1-r1

The metal rows ran at 27b75c355 (T14 pass; AMD red only on the recorded HDMI-audio DMA truncation; the log read back whole on both). This round changes nothing either judge reads: no disk driver runs on either bench.

@Japabu

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 review of #962 at e07354fb1 (round 1's fixes, 649e0cbe3 and e07354fb1)

This round checks only the round-1 required items and the bounded open handshake. Earlier points are not reopened.

Assessment

The fixes are sound, and the representation is now the right one. A driver's identity is a badge the supervisor mints and the kernel stamps on the connection. One rule, "route nothing until settled", closes both the type-granted reopen and the first-offer race. No mechanism was added to do it.

Required items, verified

1. DATA reconnect after a driver restart: fixed. Known::open returns None while !settled() before any grant or offer is judged (system/diskserver/src/main.rs:341-343). A Kind-granted reopen during a driver's Ended window therefore waits for the new offer. A connection with no grant is still refused at once. The test a_driver_that_ended_is_awaited_until_it_settles_again_under_either_grant covers the Kind case, and the control that restores round 0's rule makes it red.

2. Routing before settle: fixed. The same rule applies here. a_guid_a_late_disk_also_carries_is_never_routed_to_the_first shows that a GUID turning up on a second disk is refused on both and never routed to the first. Both controls make it red.

  • A driver that is neither expected nor yet settled doesn't block settled(). So a disk plugged in after settling can still be routed before it speaks. That case is outside PR 1, and a later duplicate is still refused, so I'm not counting it here.

3. Identity by supervisor-minted badge: fixed.

  • Supervisor::start_instance mints the disk connector with disk::badge(arrival) (system/supervisor/src/main.rs:846-848, Grants::disk at :2928).
  • build_namespace filters disk out of plain receives (:2866), so no other row gets an unbadged or self-chosen connector.
  • block_args passes one --driver <arrival> per running disk-driver instance, not a count (:960-967).
  • The service keys fates by the badge the kernel stamps on the connection. It lets go of an unbadged connection or a second connection for a live badge (main.rs:429-441).
  • settled() requires every expected badge (:305), so a late driver cannot fill a slot. a_late_driver_never_stands_in_for_one_started_before_the_service checks this, and the "counts drivers" control makes it red.
  • Two drivers can no longer collide on a shared name: the type rules it out, because no label reaches Known.
  • A restart on the same function keeps its arrival and therefore its badge, which is what matching an Ended driver needs.
  • In the guest log the badge is visible: serving the disks of the driver(s) started before it, [2], then disk driver 2 (nvme 00:01.0) settled.

4. Comments: fixed, with one leftover (below).

  • disk::label and disk::disk each document their own payload.
  • The input header now says a source names one device and may continue after gone.
  • Unusable and ClaimRefused match the listing rule.
  • DRIVER_BOUND is fixed in code, not just reworded. It now counts from awaited_since, which is the service's start, reset when a settled driver ends. Once the bound has passed, every answer is immediate.

5. Sessions::busy: deleted.

Open handshake bound: fixed. answer() (system/diskserver/src/session.rs:444-455) waits for readability up to ANSWER_BOUND_NS, which is 2 × DRIVER_BOUND_NS and leaves room for the driver's own answer after a handover. Past that it returns Error::Unanswered. Both handshake and list use it. Link::connect is now a loop bounded at three fresh connections and then panics by name, so the recursion is gone.

Findings

BLOCKER. system/diskserver/src/main.rs:432-433: drop_driver's doc still says an ended driver "is awaited until a driver of its name settles again". That is the identity-by-name model this round removed; the code awaits the same badge, and only for a driver that had settled. The comment is false at the boundary BLOCKER 3 was about. It is a one-line fix and needs no rerun.

SUGGESTION. main.rs:242-245: "Below the client's own bound on an answer, which it is taken from" can be read either way. The client's bound is derived from this one (ANSWER_BOUND_NS = 2 * DRIVER_BOUND_NS), so say that directly or drop the clause.

Evidence

ToyOSOrg/evidence ddc16d3ea, pr1-r1/:

  • Host: --ci host gives 87 steps all green, exit 0. Both images build.
  • Guest: 5 passed, exit 0. The guest boots report build 649e0cbe3… dirty, meaning the pre-commit tree that e07354fb1 commits. Badge args, settle lines and all three roles served appear in it.
  • Negative controls: all three apply on top of 649e0cbe3 and restore clean. They are routing before settle, round 0's open rule, and counting drivers. Each makes the tests it claims red (exit 101), so the new tests hold behaviours 1–3.

SEND BACK (on the single stale comment above; nothing else remains)

Review round 2 of #962. drop_driver's doc still described identity by name.
The code waits for the same badge, and only for a driver that had settled.
DRIVER_BOUND's doc now says how the client's bound derives from it.

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

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 answered in b21b38cdd, which changes comments only, so no check needs rerunning.

  • BLOCKER (drop_driver): its doc now says that a driver which had settled is awaited until a driver with its badge settles again, and that one which never settled is forgotten.
  • SUGGESTION (DRIVER_BOUND): its doc now says directly that a client waits twice this bound (wire::ANSWER_BOUND_NS), which leaves the driver room for its own answer.
  • Hot-plugged disks: they are usbd drives every xHCI controller; the kernel drives no USB #971's case, and that PR will answer how they are handled.

Japabu added a commit that referenced this pull request Oct 21, 2026
Noted by #962's round-2 reviewer. It applies once #971 adds USB hot-plug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main moved lib/blockring/transport to sdk/transport (191dd46). The workspace
keeps lib/blockserve beside it. The supervisor took #968's null stdio slots
with no conflict, beside the disk drivers' order and badges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
…bd-2

Each conflict was resolved for both intents:
- toyos-untrusted is gone (main, 6699efb) and so is kernel/usbhid (this
  branch). The workspace keeps lib/usbhid, and the kernel depends on
  neither.
- kernel/src/mm/dma.rs keeps main's comment, without copy_to, which only the
  deleted xHCI driver called.
- SYS_PARTITION_* stays deleted (dispatch.rs), as do kernel/src/drivers/xhci
  and the-boot-volumes-server-holds-a-claim-that-writes.md. Main's edits to
  them are untrue now: the boot volume is a read-only session through usbd.
- tests/toyos.rs takes main's stdio and launch rows with this branch's spare
  wording.

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

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator: re-review LAND apart from one stale comment, fixed at b21b38c (verified). Merged origin/main at 867d0e6 (the only real conflict: Cargo.toml workspace members after #963; #968's null stdio slots merge beside the drives-before-storage order and badges); --ci host exit 0 and six guest rows green at the merge. Bench at 27b75c3: T14 PASS, AMD red only on the recorded HDMI DMA truncation, log path whole. Landing.

@Japabu
Japabu marked this pull request as ready for review October 21, 2026 08:05
@Japabu
Japabu added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit b580e00 Oct 21, 2026
8 checks passed
@Japabu
Japabu deleted the wt/toyos-usbd branch October 21, 2026 08:30
Japabu added a commit that referenced this pull request Oct 21, 2026
#962 rewrote blockring's crate header; it keeps its text with `toyos::transport` for
`toyos-transport`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
#974 changed the kernel xHCI driver and the old usbd binary (system/usbd/src/hc.rs),
both of which this branch deletes. They stay deleted, and their new limit and
start calls are carried into the new usbd in the next commit. dispatch.rs
takes #974's two DMA syscalls, and SYS_PARTITION_* stays deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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