Skip to content

usbd drives every xHCI controller; the kernel drives no USB - #971

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

Japabu merged 31 commits into
mainfrom
wt/toyos-usbd-2

Conversation

@Japabu

@Japabu Japabu commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

This PR is step 10 of issues/the-kernel-is-small-interrupts-post-and-threads-wait.md and stage 4 of issues/a-device-function-is-driven-from-its-arrival-to-its-end.md.

What changes

usbd is a drives row. It runs one process per xHCI controller (drives = ["pci:0c:03:30"], receives = ["disk", "input"]).

The whole xHCI leaves the kernel. Deleted, not bridged:

  • the kernel's xHCI driver, USB storage, block layer and GPT;
  • keyboard.rs, mouse.rs and input_merge_test;
  • partition claims and SYS_PARTITION_*;
  • usb_gate, irq_ring, drain_irqs and the scheduler's Operation framework;
  • sync::OwedLock, its loom test and the owed-fence-off feature;
  • every xHCI and USB actuator, with the harness rows that only they served.

From the ABI: the Keyboard and Mouse device and file types, Partition, and the USB, block and partition inventory records. A reset now stops every claimed function's bus mastering and messages (pcidev::stop_before_reset), where it used to run the xHCI stop.

The machine's stop hands every USB device back (the orchestrator's ruling in review, which keeps the owner's run-18 ruling).

  • After logkeeper's flush and every file server's sync, the supervisor tells each disk driver the machine stops (a stop pipe it closes). It waits up to DRIVERS_MS, 3 s, for each one to end.
  • usbd then runs Hc::stop:
    • it resets every connected port while the controller runs, then waits TRSTRCY;
    • it halts the controller, runs HCRST, and unpowers its ports;
    • it exits 0 only if every port's reset finished and the controller halted and reset.
  • nvme does a normal shutdown (CC.SHN).
  • If no usbd is running at the stop (it died, or the boot deadline, the hard-lockup detector or a panic is what stops the machine), the ports are not reset. The device sees the platform reset alone, which S1 of the xHCI move into usbd: a FAT entry that grows its file is written a cache flush after its chain and data, held by a module test that stops the disk inside a flush epoch #901 §6 accepted. stick_survives_a_reset_under_load measures that case.

The driver (system/usbd/xhci, moved from lib/xhci)

  • Tasks on one thread. Each task waits on one thing: the event that matches its TRB, a deadline, or a change another task made.
  • A deadline never cancels. Command Abort takes back a command the controller still holds. Stop Endpoint plus Set TR Dequeue Pointer takes back a transfer. Dropping an operation the controller never answered is a panic.
  • Events are read on each interrupt record. IMAN.IP is cleared before the ring is drained, and every drain writes ERDP with EHB, an empty one too (xHCI 1.2 §4.17.2, §5.5.2.3.3). Without this, an interrupt that found nothing would leave the next event unannounced.
  • Taking the controller from firmware follows §4.22.1. usbd sets HC OS Owned and waits up to 1 s for HC BIOS Owned to clear (§4.22.1: the wait "should not exceed '1' second"). A controller firmware keeps is refused by name and left as found.
  • Starting it follows iommu: lowest-first device addresses below each claim's stated limit; a claim masters only when its holder starts it #974's hand-over: usbd states its address limit (64 bits, or 32 where HCCPARAMS1.AC64 is clear), takes its grant, and starts bus mastering before it writes any address to the controller.
  • Settling waits for every port's attach window: TSIGATT, 100 ms on USB 2 (USB 2.0 §7.1.7.3), and tPollingLFPSTimeout, 360 ms on USB 3 (USB 3.2 §7.5.4.3). A port with a device settles once that device is enumerated or given up on by name. Without this the T14's stick was offered after LOG and BOOT had been answered absent.
  • A TD is put whole before the ring's Link, first TRB last, TD Size stated. usbd moves a client's region directly, so a data phase crossing 64 KiB is a TD of several TRBs (the kernel driver bounced every transfer through one TRB). Each TRB states the packets left after it (§4.11.2.4). A TD that would cross the Link starts on the ring's first TRB behind a Link TD (§4.11.7; "a TD Fragment shall not span segments", §4.11.5.1). Its first TRB is handed over last (§4.11.7.1). An early Link marks every slot it skips with the closing lap's cycle, so no slot two laps old is the controller's on the next lap (§4.9.2). A TD is at most half a ring, so the TD after an early Link never overwrites it.
  • Every refusal it can name ends usbd by name, and diskserver treats a driver that ends before settling as unusable at once instead of waiting its bound.
  • Host-tested against a stub xHC written from xHCI 1.2. The crate is generic over toyos-device-memory's traits. It has 110 tests, including the three the step's exit named and the one it owed: a stick whose bulk endpoint never answers leaves a keyboard on the same controller delivering every report.

HID and storage

  • HID (lib/usbhid, moved from kernel/usbhid) binds each collection by its report descriptor, in report protocol:
    • arrays are read as indexes and bitmaps as keys;
    • HUT's reserved usages and 00–03 name no key;
    • a collection the reader cannot bind costs only its own reports;
    • absolute axes are scaled onto the record's range.
  • Mass storage keeps its Bulk-Only round trip and its recovery ladder. When the ladder resets a disk's port, the disk is held for its return and given back only to a device with the same identity. A recovery whose CLEAR_FEATURE(HALT) fails takes back the transfer the ring was moved past, and does not panic.

Decisions

  • A PCI matcher may name a place (pci:0c:03:30/1022:1639@03:00.3). Test images use it so that the AMD bench's boot controller is driven and its other controller is left to the claim tests. Shipped images name the class alone.
  • The input port's holders are ps2server and usbd (HELD_ALONE holds a list now).
  • Spare-controller tests claim the controller no row names. Their control is a claim on the held boot controller, which is refused AlreadyExists. This replaces xhci-leave.

How a metal run's log reaches readback

The path is: logkeeper → the LOG file server → diskserver's block session → usbd's disk on the boot stick (Bulk-Only Transport) → the stick's log partition. toyos-metal reads that partition back (--fat32-check).

The kernel holds no part of this path any more. Every metal judge reads /log off the stick, so each staged row is also the proof of this path on its bench. usbd_serves_the_boot_stick (new, rides TESTCASES on both benches) checks four things:

  • the boot controller was handed over once, to usbd;
  • its message was armed through a remapping entry of its own;
  • its No-Op was answered;
  • usbd says the stick is serving it as disk 0.

On the benches:

  • T14 at 8a93782dd: all five rows pass, and the log reaches the stick through usbd.
  • AMD at 8a93782dd:
    • usbload passed: stick_survives_a_reset_under_load passes on its readback, with 7320 KiB of the load's lines on the stick.
    • testcases failed: its /log stopped mid-append at about 16 s while tone_beside_stick_write wrote 2000 lines a second, and the boot ran on to an orderly stop. A probe boot (r9) reproduced it; a second probe (r10) did not. The cause is not found yet (see Risks).

Checks

check revision exit
cargo run -- --ci host 421c2b3 0, "87 step(s), all green"
cargo run -- --build-only, then --arch aarch64 421c2b3 0, 0
cargo test --test toyos-build (68 rows) 421c2b3 1: 67 passed

The one red is crypto_instrument_prints_its_table: QEMU drops console bytes (issues/qemu-drops-console-output-the-harness-is-slow-to-read.md). That stall has the same signature at the base and in an image with no usbd.

Negative controls, each applied as a patch, red, and restored (mut/ in both evidence trees):

  • x1–x10, the driver. x1 was rerun on the final driver code and is red on the same assertion. x10 is the CLEAR_FEATURE failure left unreclaimed.
  • i1–i5, the input link.
  • h1–h10, the HID reader.
  • k1: usbd drops every key; the USB key smoke stalls.
  • l1: usbd's writes turned into reads; the stick holds no load line.
  • s1: the supervisor tells no driver; usbd does not end before the kernel's stop.
  • s2: the stop writes no port reset; red on the stub and in QEMU (exit 3).
  • t1: the old settle rule; both attach-window tests red.
  • d1: diskserver forgets a driver that ended before settling; red.
  • f1: a controller firmware keeps is taken anyway; red.
  • r1: TDs placed across the Link as before (Link with Chain); the stub refuses "a TD crosses the ring's Link".
  • z1: TD Size left 0; the hand-counted TD Size test is red.
  • b3: an early Link that marks nothing it skips; the ring walk takes slot 254 two laps old.
  • b4: a TD bound of 255 TRBs; the longest-TD walk stops on the overwritten Link.
  • b5: an r14 readback with a third 03:00.4 fault record; amdvi_usbd_drives_its_own_entry is red.

x8 (IP never cleared) is red on the stub's interrupt model. Under MSI or MSI-X, IP clears itself (§5.5.2.1).

Integration tests and where they run

  • QEMU (x86-64 q35, nec-usb-xhci and qemu-xhci):
    • The handover, claim and refusals of a real QEMU xHC, which no host model stands in for: usbd_drives_the_spare, usbd_refused_without_amdvi_remapping, usbd_refused_without_smi_filter, amdvi_firmware_left, iommu_virtio_platform, a_drives_row_holds_one_process_per_function and the other testcases rows.
    • the_log_lands_on_the_boot_stick: a stick boot whose log volume is read back on the host, and whose stop under load is judged.
    • a_usb_key_reaches_the_console: a key from a real xHC reaches the owner of input.
  • T14: usbd_serves_the_boot_stick, usbd_drives_the_type_c_controller, stick_survives_a_reset_under_load, tone_beside_a_stick_write, mask_windows.
  • AMD bench: usbd_serves_the_boot_stick, amdvi_usbd_drives_its_own_entry, amdvi_translates_the_stick_controller, amdvi_remaps_the_stick_controller, stick_survives_a_reset_under_load, tone_beside_a_stick_write.
  • No metal row stages a machine that cannot remap (the orchestrator's ruling). Without remapping every claim is refused, usbd's included, so that boot leaves no /log to judge. amdvi_claim_refused_without_remapping and claim_refused_without_remapping are deleted, and with them the kernel's iommu-no-remap actuator and the usbd_spare_unhanded binary. QEMU holds the refusal, by usbd_refused_without_amdvi_remapping and amdvi_firmware_left.

These rows exercise real controllers, their interrupt remapping and the stick that carries /log. Neither the stub nor QEMU can establish that.

Risks and gaps

  • No USB at all without remapping. A machine with no IOMMU, or one that cannot remap interrupts (QEMU's AMD-Vi included), has no USB now: the claim is refused, by design. amdvi_firmware_left expects that refusal.
  • The AMD stick stall, and which of three xHCI rules it was, is not separated. At 2000 log lines a second the AMD testcases boot's /log stopped mid-append: r8, r9, then 6 of 7 probe boots (r12, r13); main with only the tone row added, 0 of 2. Probes that report by exit codes the kernel records showed usbd's: a stick transfer unanswered, no event on the ring, the endpoint Running, and in 2 of 3 boots USBSTS HCH, HSE or HCE set. The multi-TRB TDs broke three xHCI rules (above). Since 0e0c37e follows them, 6 of 6 AMD testcases boots are clean (r14). That is a rate from 6/7 to 0/6, not a proof of which rule this controller needs.
  • metaldevicecase is ridden by no row. Finding filed: issues/the-device-boot-is-flashed-by-no-metal-row.md.
  • A usbd row without input binds HID until its first open answers NotGiven. That is a few control transfers per keyboard.
  • RawKeyEvent still lives in sdk/abi. Finding filed: issues/the-key-record-the-surface-protocol-carries-is-defined-in-the-kernel-abi.md.
  • A hot-plugged stick is routed from its own offer alone. Recorded with an owner: issues/a-disk-plugged-in-after-settling-is-routed-as-soon-as-it-is-offered.md.
  • The stop is protected only on the orderly path. It does not cover a usbd that died, or a deadline, lockup or panic reset.

Metal

  • T14 at 8a93782dd: usbd_serves_the_boot_stick, usbd_drives_the_type_c_controller, stick_survives_a_reset_under_load, tone_beside_a_stick_write, mask_windows: all PASS.
  • AMD at 8a93782dd: stick_survives_a_reset_under_load PASS. The testcases boot is open (above).
  • b07a9b1ac changes, from 8a93782dd, only the refusal of a controller firmware keeps. That path ran on neither bench: firmware released 03:00.3 in about 11 ms on every boot after iommu: lowest-first device addresses below each claim's stated limit; a claim masters only when its holder starts it #974.
  • r14 at 0e0c37e66: T14 five rows PASS; AMD usbload PASS. AMD testcases six boots: all completed with clean FAT checks, and usbd_serves_the_boot_stick and tone_beside_a_stick_write PASS on all six. The three amdvi rows were red on every boot from judges, not the machine. This branch folded main's separate xhci-leave boot into testcases, so test_rs_usbd_spare_fault's staged fault met the no-fault rule of the translate and remap rows. And usbd's line now begins ENDED — . The fix is in the judges: the translate and remap rows refuse every fault but 03:00.4's, and amdvi_usbd_drives_its_own_entry requires exactly 03:00.4's two staged records. The six r14 readbacks re-judged pass the amdvi rows. A copy with a fault on 03:00.3 added is red on the no-fault rows, and one with a third 03:00.4 record is red on the spare's row.
  • 421c2b32a changes the ring again (review r2, B3 and B4), so both benches are staged again there: AMD testcases six boots and usbload, and the T14's three images.

🤖 Generated with Claude Code

Japabu and others added 3 commits October 21, 2026 07:36
Step 10 of the small-kernel track, stage 4 of the device-function track. usbd
is a `drives` row, one process per xHCI controller: its sticks are disks
offered to the block service on `disk`, whose sessions it serves straight out
of each client's region, and its keyboards and pointers report to the owner
of input over `input`, each bound collection a `source` of its own.

The driver (system/usbd/xhci, moved from lib/xhci) is rewritten as tasks on
one thread, each waiting on one thing: the answer its TRB is matched by, a
deadline, or a change another task made. A deadline is never a cancellation:
an operation the controller holds is taken back by Command Abort or by Stop
Endpoint and Set TR Dequeue Pointer, and dropping one unanswered is a panic.
It is generic over toyos-device-memory's traits and tested against a stub
xHC written from xHCI 1.2, with the three tests the step's exit named and
the one it owed: a stick whose bulk endpoint never answers leaves a keyboard
on the same controller delivering every report. Events are taken on each
interrupt record: IMAN.IP is cleared before the ring is read and ERDP is
written with EHB on every pass (xHCI 1.2 §4.17.2, §5.5.2.3.3), so an
interrupt that found nothing still leaves the next one announced.

HID interfaces are bound by their report descriptor in report protocol
(lib/usbhid, moved from kernel/usbhid): arrays read as indexes, bitmaps as
keys, HUT's reserved usages and 00-03 named no key, a collection this reader
cannot bind costing only its own reports, absolute axes scaled onto the
record's range. Mass storage keeps its Bulk-Only round trip and ladder; a
disk whose port its ladder reset is held for its return and given back only
to a device of the same identity.

Deleted from the kernel: the xHCI driver, USB storage, the block layer, GPT,
the keyboard and mouse streams, partition claims and SYS_PARTITION_*,
usb_gate, irq_ring, drain_irqs, the scheduler's Operation framework,
sync::OwedLock with its loom test and fence feature, and the xHCI and USB
actuators. A reset stops every claimed function's bus mastering and messages
(pcidev::stop_before_reset) in place of the xHCI stop. From the ABI: the
Keyboard and Mouse device and file types, Partition, and the USB, block and
partition inventory records.

Beside it: TrySendError::Busy says a frame went nowhere, so a full peer is
waited for rather than dropped; a PCI matcher may name a place
(`@03:00.3`), so the AMD bench's boot controller is driven and its other one
left to the claim tests; the input port's holders are ps2server and usbd.
The harness loses the rows whose kernel actuators went and gains
`usbd_serves_the_boot_stick`; the spare tests claim the controller no row
names, with a claim on the held boot controller as their control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crypto_instrument_prints_its_table hung on most runs beside eleven other
rows under host load. Temporary probes placed every byte on the guest's side
of the pipe. logkeeper read the runner's ring through to its head and wrote
every rendered byte to its console. The kernel's console queue was empty,
klogd owed no record, and the runner's own watchdog showed that it had
printed its end marker. The hang also happened at the branch's base and with
no usbd in the image. This commit records the sighting in the issue that
already owns the loss. The probes are not committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every metal boot's /log now goes logkeeper -> the LOG file server ->
diskserver -> usbd -> the boot stick. No QEMU row read that path: their
console is the serial line, and the metal judges are the only readers of
the stick's log volume. #971's first metal boots came back with no log
file on the stick, which nothing before metal could have seen.

the_log_lands_on_the_boot_stick boots testcases from a stick on
Profile::Metal, asks for the shutdown, and reads the stick's log volume
back on the host (toyos_build::image::log_volume_files, read with the
kernel's FAT driver). It judges it with the metal judge's own verdict. Its
negative control turns every write into a read in usbd's Disk adapter, so
the writes never reach the stick. It is red with the metal boots'
signature: no logkeeper file on the volume.

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

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #971 at bab43d811 (diff origin/wt/toyos-usbd...origin/wt/toyos-usbd-2)

Architecture

The shape matches #901's accepted design. usbd owns each controller whole. HID goes to the owner of input over input, with a source and a gone per collection. Sticks are offered on disk and served out of each client's region through toyos-blockserve. The kernel stop is now generic over claimed functions (pcidev::stop_before_reset). toyos-xhci moved under system/usbd, where its one program lives. toyos-usbhid stays in lib/ as a pure decoder of device-chosen bytes.

The deletions are complete. git grep outside issues/ finds nothing for OwedLock, owed-fence, irq_ring, drain_irqs, usb_gate, poll_if_pending, port_work_pending, input_merge_test, DeviceType::Keyboard/Mouse, FileType::Keyboard/Mouse, DEADMAN, between_attempts, until_answered, probe_usb_disks, usb_storage, either selftest actuator, kernel/usbhid, lib/xhci or toyos-xhci-sim. What remains is the stale references listed under finding 5. Every deleted issue slug has no citation left.

Size: +8895/−20753 overall. The kernel is +197/−10115. Test files are roughly +2.8k/−3.0k.

Trust model: a machine without remapping now refuses the claim, and it says so twice: pcidev's NOT HANDED OVER — its interrupts would not be remapped and the supervisor's usbd was not started on PCI …. iommu_virtio_platform's no-unit arm and amdvi_firmware_left assert that refusal, and a-machine-without-an-iommu-refuses-every-claim.md records the consequence. That is consistent with root's isolation policy.

Interrupt handling is as claimed. take_events writes IMAN (IP W1C, IE kept) before draining, and writes ERDP|EHB on every drain, empty ones included (hc.rs:536-568). an_interrupt_that_finds_no_event_leaves_the_next_one_announced holds the EHB half, with x9 as its control.

The owed tests exist:

The crypto red's independence holds on the base run alone: base-e07354fb1-run.log reads STALLED: 973s at the base. no-usbd-run.log ends in a SIGTERM with no verdict on that row, so it shows nothing either way and should not be cited as a reproduction.

The place matcher (@03:00.3) is backed by #901 (claims by location; roast 8 on the AMD twins), so it is not test-only machinery.

Findings

1. BLOCKER: the approved stage's metal rows are deferred, the one guarding the deleted reset stop among them.

2. BLOCKER: one recovery path panics usbd, which drops every device on the controller.

  • Where: system/usbd/xhci/src/usb.rs:949-950.
  • What happens: when the CLEAR_FEATURE(HALT) fails, restart returns Ok(Err(())) without calling op.reclaimed() on held. Op::drop (hc.rs:774-786) then panics, because ending is false.
  • How it is reached: from msc.rs:732, when a bulk transfer goes silent on an endpoint whose output context reads Halted. That happens if the halt's event was not yet announced, or if the device stalls or leaves (control_held answers Gone at once once leaving is set). The route is Reset Endpoint → Set TR Dequeue → ClearHalt.
  • Why it is a defect: by that point Set TR Dequeue has moved the ring past the TRB, so the controller holds none of it, and the panic is wrong. It breaks the "a device that stops answering costs only itself" invariant the PR claims.
  • Fix: reclaim held before that return. A stub case for this path (an endpoint Halted with its event unread, and CLEAR_FEATURE stalled) would be cheap if you want it held.

3. BLOCKER: the PR's account of the x1 control is false.

  • What the PR says: "x1–x7 … ran before the IP/EHB change. That change touches only take_events and the stub's interrupter, which none of those paths reads."
  • What x1 is: a mutation inside take_events (mut/x1-dispatch-awaits-the-stick.patch). It is the negative control of the owed test.
  • Why the result may differ: with the old code an empty drain wrote no ERDP; the final code clears EHB on every drain. x1's red may now come from a different cause, or not at all.
  • Cheapest check: rerun x1 against bab43d811. It is one host test, in seconds.

4. BLOCKER: the usbd_serves_the_boot_stick judge contradicts its own comment.

5. BLOCKER: records and comments this change made false, and dead code it left.

  • The small-kernel track (issues/the-kernel-is-small-interrupts-post-and-threads-wait.md):
    • Lines 75-77 ("which the kernel drives until step 10; the kernel's USB storage, block layer …") are stale.
    • Lines 113-119 (step 4's "Left") and 142-144 (step 7, "which step 10 deletes") are stale.
    • Line 103's step tally is stale.
    • Step 10's "Done" at line 190 claims "every usb-kbd and usb-tablet go through usbd". That is false for every test image: their usbd rows receive no input, so HID binds and is dropped at NotGiven.
    • Step 10's /log evidence (line 203) drops the client test issues/nothing-reopens-a-diskserver-session-over-a-used-page.md owes, which the exit named and which is still open. List it under "Left".
    • Line 206 has trailing whitespace.
  • issues/a-cut-bulk-only-command-does-not-brick-the-benchs-stick.md:95-104: "The present state" describes a standing guard and a reset path ("drove every port reset and took the power away") that this PR deletes. The table's survivals were all measured with that port reset in place. Say so, rather than only appending to the exit.
  • lib/bcachefs/tests/integration.rs:1339 cites "kernel/CLAUDE.md's BudgetExpired rule", which this PR deletes from kernel/CLAUDE.md.
  • kernel/src/drivers/pci.rs:103-105: the CONFIG_BYTES rationale ("the reset-time xHCI stop rebuilds one") is false now.
  • kernel/src/drivers/pci.rs:328: links [Self::message], which is deleted.
  • kernel/Cargo.toml:362-363: "The block path's half went with the kernel's xHCI." is history. Delete it.
  • sdk/abi/src/syscall.rs:1276-1282: PciId::parse is now dead. Its doc names the deleted xhci-leave=, and its only caller is its own test (:2553-2560, the_xhci_leave_parameter_…). Delete both.
  • tests/guest/rust-tests/src/bin/end_returns_claims.rs:2: still says "the function xhci-leave= leaves".
  • system/usbd/xhci/Cargo.toml:2: names src/stub.rs; the stub is src/stub/mod.rs.
  • Dead code:
    • Hc::change_waiting (hc.rs:491) has no caller.
    • Inputs::not_given() (input.rs:184) has no caller.
  • New findings with no owner: issues/the-device-boot-is-flashed-by-no-metal-row.md and issues/the-key-record-the-surface-protocol-carries-is-defined-in-the-kernel-abi.md record evidence and an exit but no owner. Root's "evidence, owner and exit" asks for all three.

6. BLOCKER: USB HID is unverified on any non-stub controller.

Suggestions

  • main.rs:178, Config.input: endow::namespace().is_some() binds HID on every test-image usbd until the first open answers NotGiven. Deciding from the endowment itself at start would remove Inputs::not_given and the stated risk.
  • hc.rs:409: recomputes grant_bytes() inline. Call it instead.
  • hc.rs:805, the firmware handoff: spins spin_loop for up to 1 s. Polling is justified, since firmware raises nothing. Sleeping between reads would not burn the CPU.
  • x8 (IP never cleared): my reading of xHCI 1.2 §5.5.2.1 is that IP clears by itself under MSI/MSI-X; please check that against the text. If so, x8 reds on the stub's interrupt model rather than on a hardware-visible defect. Clearing IP is still correct.
  • README.md:37 and :197: they still describe the kernel calling the xHCI port machine and driving the stick. The primary checkout has uncommitted README edits, so coordinate before touching it.

SEND BACK

Japabu and others added 8 commits October 21, 2026 08:20
Review round 1 of #971.

- Device::restart returned from a failed CLEAR_FEATURE(HALT) without
  reclaiming the held transfer, so dropping its Op panicked usbd. That
  dropped every device on the controller. It now reclaims it: Recovery
  orders the clear after Set TR Dequeue Pointer, so the ring is already
  past the TRB. The stub can now halt an endpoint with its event lost
  (Reply::StallUnannounced) and make a stick stall CLEAR_FEATURE.
  a_halt_whose_clear_feature_stalls_costs_only_its_stick passes. With the
  fix reverted it panics at the Op drop.
- Dead code and stale text: PciId::parse and its xhci-leave test,
  Hc::change_waiting, Inputs::not_given's accessor, CONFIG_BYTES's reset-stop
  rationale (now private), a link to the deleted PciDevice::message, the
  kernel Cargo.toml's history line, bcachefs's citation of the deleted
  BudgetExpired rule, end_returns_claims' xhci-leave doc, the stub's path
  in toyos-xhci's Cargo.toml.
- The two new findings and the hot-plug defect name their owners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #971. The kernel's USB keyboard path and
usb_keyboard_rollover went with this PR. Before this row, no guest or metal
row carried a report from a real xHC through usbd to an owner of input.

a_usb_key_reaches_the_console boots images/console.toml, whose usbd
receives `input`, on Profile::MetalUsbKeyboard (Metal with a usb-kbd, which
every QMP key then goes to). It types `ls` and Enter, and waits for the
console's shell to spawn toybox. QEMU's xHC and keyboard are the
independent end the stub cannot be. Its negative control has usbd's input
port drop every key record, and the row stalls.

usbd_serves_the_boot_stick no longer judges the log volume's mount. It
holds the kernel's part only, as its comment and #901 say.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and its controller

Review round 1 of #971 (finding 1), on the orchestrator's ruling: the stick
protection the kernel's xHCI stop gave, after the owner's run-18 ruling
("ToyOS must not brick a USB device"), moves into usbd's stop path.

- The supervisor endows every disk-driver instance a `stop` pipe
  (toyos_quiesce::DRIVER_STOP). After logkeeper's flush and every file
  server's sync, it closes the pipes and waits for each driver to end,
  within DRIVERS_MS (3 s). A driver that does not end in time is named, and
  the machine stops without it. If the kernel refuses the stop, the
  drivers' functions are driven again.
- usbd, told: its driver ends (`ending`, so the operations its tasks held
  are let go), and Hc::stop runs within STOP_NS (2 s). It resets every
  connected port at once while the controller still runs, which returns
  each device to Default with no command outstanding (USB 2.0 §7.1.7.5).
  TRSTRCY follows if a USB 2 device was reset. Then RS is cleared and HCH
  awaited, HCRST runs, and the ports are unpowered where the controller has
  Port Power Control. Its exit code is the account the kernel always
  records: 0 only if every connected port's reset finished (PR clear and
  PRC set after this write) and the controller halted and reset.
- nvme, told: a normal shutdown (CC.SHN, awaited on CSTS.SHST, NVMe Base
  2.0 §3.6.2), and it ends.
- If no usbd is running when the machine stops (it died, or the boot
  deadline, the hard-lockup detector or a panic is what stops it), nothing
  resets the ports. A usbd that died had its function reset by its claim's
  release, FLR or the D3hot round trip, and the kernel's stop clears Bus
  Master. The device sees the platform reset alone, which #901 §6
  accepted and stick_survives_a_reset_under_load measures.
- SDK: a pipe end can be taken from the endowment table (FromHandle).

Tests:
- the_stop_resets_every_connected_port_then_halts_resets_and_unpowers_the_controller,
  on the stub, with a stick command left open.
- the_log_lands_on_the_boot_stick now also judges that usbd ended with 0
  between the stop's request and the kernel's stop.
- Controls: the supervisor telling no driver (red on the order), and the
  stop writing no port reset (red on the stub and in QEMU, exit 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r load

Review round 1 of #971 (finding 1). #901 §9's row lands with the stage that
deletes the kernel's port-reset stop.

- stick_load (a guest job) writes /home on the stick for ever through usbd:
  an 8 MiB file rewritten in place, fsync'd after every MiB.
- stick_survives_a_reset_under_load (metal, T14 and AMD) boots testcases
  with that job alone. A boot carrying it is given a 40 s deadline,
  below its runner's list bound, so the kernel's reset path, which touches
  no USB register, lands while the job writes. Judged: the deadline expired
  (the pass after the reset says so), the job's lines reached /log, and the
  stick enumerated on the next host within the bench's record
  (boot.usbload.stick_secs, timings::ceiling). A host check feeds the
  judge a passing page, a page with no load, and one handed back.
- stick_load is kept off the shared boot (RUST_SKIP).
- the_log_lands_on_the_boot_stick now asks for the stop under the same
  load (the power button, pressed while stick_load writes), so usbd's stop
  is exercised with writes in flight. Its three controls (usbd's writes
  turned into reads, the supervisor telling no driver, the stop writing no
  port reset) are each red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #971 (finding 1). These are #901 §9's other two rows.
Each rides a boot that already runs, so neither costs a boot of its own.

- tone_beside_a_stick_write (metal, T14 and AMD, on testcases):
  tone_beside_stick_write plays hda_tone's tone while a thread of its
  process writes /home on the stick through usbd, fsync'd every MiB. It
  owes at least the mebibyte the issue names. The verdict is soundserver's
  underruns=0 over the job's window: the exit of
  issues/a-megabyte-written-to-the-stick-starves-a-tone-beside-it.md.
- mask_windows also reads every CPU's longest windows under stick_write
  (32 MiB through usbd, then an end), run after the herd on windowscase.
  They are judged against the bench's record
  (boot.windowscase.stick_write.*, timings::ceiling), which is the base
  #901 asks for.
- Both jobs are kept off the shared boot. In QEMU (MetalTwoHda), checked by
  hand and not committed: stick_write wrote 32 MiB and ended 0, and
  tone_beside_stick_write wrote 251904 KiB beside the tone and ended 0
  with underruns=0. QEMU's timings judge nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #971 (finding 5).

- The small-kernel track: stage 4's Left, the step tally, step 4 (now done:
  the stick's partitions are diskserver's through usbd, and
  SYS_PARTITION_* are gone), and step 7 (now done on the stick too). Step
  10's claim about HID was false for every test image, whose usbd receives
  no `input`; it now says which image delivers HID and what the stop does.
  Its evidence gains the stick row, the stop test and the USB key smoke.
  Its Left names the metal rows that have not run, the missing log file
  of #971's first metal boots, and the client test that /log's evidence
  still owes.
- a-cut-bulk-only-command-does-not-brick-the-benchs-stick: every survival
  in its table was measured with the kernel's port reset in place. Its
  present state now says what replaced that reset and what does not reset
  ports at all, and names the guard that replaced the old one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clippy's double_ended_iterator_last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of #971's suggestion: start recomputed it inline.

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

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Round 1 is answered on wt/toyos-usbd-2 up to 45b247594. Evidence: https://github.com/ToyOSOrg/evidence/tree/13597a46531bb0ee3250c03c075fdc980da2f478/pr2-r1

1. The stick protection (on the orchestrator's ruling), 9bdc96abf.

  • The supervisor tells the disk drivers. Every disk-driver instance is endowed a stop pipe (toyos_quiesce::DRIVER_STOP). After logkeeper's flush and every file server's sync, the supervisor closes those pipes and waits for each driver to end, within DRIVERS_MS (3 s). A driver still running at the bound is named, and the machine stops without it. If the kernel refuses the stop, the drivers are started again.
  • usbd stops the controller (Hc::stop, bounded by STOP_NS, 2 s):
    • it resets every connected port while the controller still runs, then waits TRSTRCY if a USB 2 device was reset;
    • it clears RS and waits for HCH, runs HCRST, and unpowers the ports where the controller has PPC;
    • its exit code is 0 only if every port's reset finished (PRC set after this write) and the controller halted and reset. The kernel always records the code, while usbd's own line may reach no log.
  • nvme shuts down normally (CC.SHN, awaited on CSTS.SHST).
  • If usbd died: its claim's release already reset the function (FLR or the D3hot round trip), and the kernel's stop clears Bus Master. Nothing resets the ports; the device sees the platform reset alone. The boot deadline, the hard-lockup detector and a panic are the same case, which S1 of the xHCI move into usbd: a FAT entry that grows its file is written a cache flush after its chain and data, held by a module test that stops the disk inside a flush epoch #901 §6 accepted.

How the protection is shown before the stick test runs:

  • the_stop_resets_every_connected_port_then_halts_resets_and_unpowers_the_controller (stub). The stub leaves a stick command open, and the test checks every step read back.
  • the_log_lands_on_the_boot_stick (QEMU, stick boot):
    • stick_load writes the stick, and the power button is pressed while it does;
    • the stick's log volume is read back on the host and judged with the metal verdict;
    • usbd must end with code 0 between the stop's request and the kernel's stop: record.
  • Controls:
    • the supervisor telling no driver: red on the order;
    • the stop writing no port reset: red on the stub (reset 0 of 2) and in QEMU (exit 3);
    • usbd's writes turned into reads: red, with the same "no logkeeper file" signature the metal boots showed.

The three #901 §9 rows are in this PR, none deferred (e185b68cc, ecbb0ead5):

  • stick_survives_a_reset_under_load boots testcases with stick_load alone. The boot deadline is 40 s, below the runner's bound, so the reset touches no USB register while the stick is being written. The judge needs DEADLINE_EXPIRED, the load's lines in /log, and boot.usbload.stick_secs against the bench's record. A host check covers the judge.
  • tone_beside_a_stick_write rides testcases: the tone plays while a thread of the same process writes ≥1 MiB to the stick. Verdict: underruns=0.
  • mask_windows also reads windows under stick_write (32 MiB) on windowscase. Judged against the record (boot.windowscase.stick_write.*).
  • None of them has run.

2. d4459fc14: a failed CLEAR_FEATURE(HALT) reclaims the held transfer. a_halt_whose_clear_feature_stalls_costs_only_its_stick covers it: the stub halts the endpoint with its event lost and stalls CLEAR_FEATURE. With the fix reverted, the test panics at the Op drop (mut/x10).

3. x1 was rerun on the final driver code. It is red on the same assertion (collection 0: a Keyboard never said). The PR's earlier account was wrong to say IP/EHB touched no path x1 reads, and the body now says what the rerun showed.

4. The judge no longer requires the mount, as its comment and #901 say.

5. All of finding 5 is done (d4459fc14, f40887c0f):

  • the track's stale lines, the step tally, step 10's HID claim, the client test now listed under Left, and the trailing whitespace;
  • the cut-command issue's present state now says its table was measured with the old port reset in place;
  • the bcachefs citation, CONFIG_BYTES, the message link and the Cargo history line;
  • PciId::parse and its test, end_returns_claims' doc, and the stub path;
  • change_waiting and not_given();
  • owners on the new issues.

README.md is untouched. It needs two edits, which I have passed to the orchestrator:

  • :37 lists "the xHCI port state machine" among kernel decisions; it is now usbd's, in userland.
  • :197 says the kernel's own xHCI and USB storage drivers read the stick and append the log through their FAT32 write path. Now usbd drives the stick, diskserver and fileserver serve it, and logkeeper writes /log.

6. f4c34d89d: a_usb_key_reaches_the_console. It boots images/console.toml on Profile::MetalUsbKeyboard, types ls and Enter through QMP, and waits for the shell to spawn toybox. Control: usbd drops key records, and the row stalls (mut/k1).

Suggestions:

  • Taken: grant_bytes (45b247594).
  • x8: §5.5.2.1 does have IP clear itself once an MSI/MSI-X write completes. So x8 reds on the stub's interrupt model (INTx-like), not on a defect visible under MSI-X. Clearing IP stays, since it is correct for either mode.
  • Config.input stays decided at the first open. Deciding it at start would need a lookup the namespace does not offer.
  • The firmware handoff spin is not changed in this round.

Still open.

  • The metal log failure. The QEMU stick boot carries the log, so its cause is still unknown. The raw partitions hold no logkeeper text at all: no file, no stray clusters. AMD's testcases boot shows dmafault=3. Diagnostic images (a kernel that keeps the storage path's program lines and records in the black box) are staged for one boot per bench, with the stop protection in.
  • amdvi_claim_refused_without_remapping cannot be judged on metal any more. Without remapping there is no USB, so there is no /log. I'd propose deleting the metal row and keeping the QEMU refusal rows (usbd_refused_without_amdvi_remapping, amdvi_firmware_left). That is the orchestrator's call.

Japabu and others added 5 commits October 21, 2026 09:39
…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>
On the orchestrator's ruling in #971's review. On a bench whose units remap
no message, every claim is refused, usbd's included. That machine drives no
USB, so its stick receives no /log, and a judge that reads /log can only
fail. amdvi_claim_refused_without_remapping is deleted, as asked, and so is
claim_refused_without_remapping: the same `iommu-no-remap` boot, for the
same reason. With them go everything only they used: the kernel's
iommu-no-remap actuator (VT-d's and AMD-Vi's arms), its FLASHABLE entry,
claims::refused_unremapped, and the usbd_spare_unhanded binary. QEMU keeps
the refusal, by usbd_refused_without_amdvi_remapping and
amdvi_firmware_left. The track, the IOMMU issue and the two issues that
named the rows say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The T14's diagnostic boot (19c3beb56) showed the race. usbd said "settled,
0 device(s) named" at 12.622, as soon as it ran. Its ports had been
powered again a moment before and its stick did not read connected yet.
The block service then answered LOG and BOOT NotFound, logkeeper had no
/log, and the stick's disk was offered at 12.868, 246 ms too late. QEMU's
ports connect the instant they are powered, so no QEMU boot showed it.

Every port is now unsettled at the start, whether or not it reads
connected. A connected port settles once its device is enumerated or
given up on by name, as before. An empty port settles once its attach
window has passed with nothing connected. The window is the most a device
present at power-on may take to be seen: TSIGATT, 100 ms on a USB 2 port
(USB 2.0 §7.1.7.3, Table 7-14), and tPollingLFPSTimeout, 360 ms on a USB 3
port (USB 3.2 §7.5.4.3). The port task waits for a port status change or
the window's end. A device that arrives later is a hot-plug.

EMPTY_BUS_NS and SLOW_CONNECT_NS, which nothing read, are gone.

Tests:
- a_stick_that_connects_after_the_start_is_offered_before_the_bus_settles
- an_empty_bus_settles_when_its_attach_window_ends

Both are red on the old rule, which settled a port that read disconnected
at the start (mut t1).

QEMU cannot reproduce this. A stick hot-added there is enumerated about
600 ms after the plug, past both windows, so no QEMU row reds without the
fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AMD bench's diagnostic boot (19c3beb56): usbd panicked with a
backtrace on "the kernel refused a DMA grant: Faulted". diskserver then
waited its whole 30 s bound for the driver that had died, and answered
DATA, BOOT and LOG Unusable.

- usbd: every refusal before it serves is said once ("NOT SERVING — ..."),
  the block service is told the controller is unusable, and it idles until
  the machine stops. This covers the claim's description, the register map,
  the controller, the grant and the run. A controller lost while served
  (Dead, or the claim refusing its interrupt record) is said ("ENDED —
  ...") and the process exits 2, which releases the claim. Only invariant
  breaks still panic.
- diskserver: Known::ended. A driver that never settled is Unusable as soon
  as its connection ends, so a listing or open waiting on it is answered
  then. A settled driver's end is still awaited until it settles again.
  Test: a_driver_that_ends_before_it_settles_is_unusable_at_once. The old
  rule, which forgot the driver, is red (mut d1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m the first hand-over

The AMD bench's diagnostic boot (19c3beb56): firmware had not let go of
the boot xHC after usbd's 1000 ms (USBLEGSUP 0x00010401 -> 0x01010401,
BIOS Owned still set). The kernel attached the claim's domain to a
function firmware left mastering the bus. The controller's own accesses
were refused (cpu0 dmafault=3). The fault latched on the claim before
usbd's first grant, so the grant was refused Faulted.

bring_up now clears Bus Master Enable, and drains what the function posted
before it, before the domain is attached. With the bit clear a function
issues no memory request (PCIe Base 6.0 §7.5.1.1.3). This makes
Bound::mastering, false until the first grant, true of the function and
not only of the record, on a first hand-over as on every later one, where
release had already cleared it. The first grant sets the bit again, after
usbd has halted and reset the controller.

It is not a reset, and it writes only the one bit every function has. It
prevents the stale access rather than excusing a fault that was recorded,
so the fault policy is unchanged: a fault after the first grant is still
the holder's.

The black box kept no DMA FAULT record (the probe's section was full), so
03:00.3 as the faulting function is read off the latch that refused its
grant, not off a record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from wt/toyos-usbd to main October 21, 2026 08:30
Japabu and others added 3 commits October 21, 2026 10:35
…ant, from the first hand-over"

This reverts commit cc789d1. The owner's K2 direction (2026-10-21) puts
safe hand-over before DMA, with its hold-off and activation, under K
(wt/toyos-dma-limit). #971 builds no kernel mechanism for it. The AMD
bench's boot xHC is left unavailable by name in usbd instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eft as found

The owner's K2 direction (2026-10-21) says a device without a safe path
stays unavailable, with the reason stated. The AMD bench's boot xHC kept
HC BIOS Owned set past usbd's 1000 ms wait (USBLEGSUP 0x00010401 ->
0x01010401), and usbd reset it anyway, under firmware that may still have
been driving it.

take_from_firmware follows xHCI 1.2 §4.22.1: it sets HC OS Owned and waits
HANDOFF_NS for HC BIOS Owned to clear (USBLEGSUP, §7.1.1; the specification
gives no bound, so this one is policy). If firmware releases the controller
or never claimed it, its SMIs are turned off and the controller is usbd's.
If firmware keeps it, Reset::new refuses with Refusal::FirmwareKeepsIt.
Nothing more is written: no SMI change, no halt, no HCRST. usbd says
NOT SERVING with that reason, tells the block service the controller is
unusable, and serves nothing.

A safe way to take such a controller is the kernel's to give: K's
hand-over and activation (wt/toyos-dma-limit). This PR builds no kernel
mechanism for it.

The stub can now model firmware that keeps the semaphore.
a_controller_firmware_keeps_is_refused_and_left_as_found holds the rule:
USBCMD untouched, no SMI write. The old reset-anyway rule is red against
it (mut f1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…judged by its seal

The T14's round 3 at e729c3e found two defects.

- "32204 MiB written and synced" in a 40 s boot is about 800 MB/s, far
  beyond any stick. The readback shows why: "this machine has no DATA
  partition; /apps, /config, /home and /state are in memory". A metal test
  image carries no DATA partition, so stick_load, stick_write and
  tone_beside_stick_write all wrote memory, and no row tested what it
  claimed. All three now write lines to /log (stick_lines.rs):
  - 900-byte records at 2000 a second, under logkeeper's per-child
    allowance, so none is suppressed;
  - logkeeper writes them to the stick through usbd and syncs every round.
  What reached the medium is counted by each judge in the file read back
  off the stick (metal::bytes_of_lines), never from the job's own claim.
  Each judge needs at least a mebibyte: stick_survives_a_reset_under_load,
  tone_beside_a_stick_write, and mask_windows' stick write.
- usbload came back refused by the readback gate, which asks every
  unarmed boot for the supervisor's stop line, and a deadline boot never
  reaches it. ended_by_its_deadline (toyos-metal) declares a boot from its
  own artifact: armed to wedge, or carrying a deadline below the least list
  bound a runner gives, as only STICK_LOAD_JOB's boot does. Such a boot is
  judged as a wedge is, by the record its bound sealed, and it is refused if
  it handed back. Host test:
  a_boot_its_deadline_ends_on_purpose_is_judged_by_the_record_it_sealed.
  Applied to the real readback, the gate now answers Ok.
- the_log_lands_on_the_boot_stick waits for 1500 of the load's lines and
  judges what reached the volume. It now reads the stop's request from the
  file and usbd's end and the kernel's stop from the console, ordered on the
  boot's own clock. Under a flood the console can drop the supervisor's line.
  Controls rerun: s1 and s2 red as before. l1 (writes made reads) now stalls
  waiting for the load, which is red too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu and others added 2 commits October 21, 2026 11:34
T14 round 4 at fa0f434: stick_survives_a_reset_under_load was refused
because "usbload's own log is missing its parts 2 to 56". The parts were
not lost to the reset. logkeeper keeps at most 16 files of a mebibyte
(MAX_LOG_FILES) and deletes the oldest of this boot's own parts to stay
there, as its log says ("/log holds more than 16 logs, so
..._0042.log was deleted"). At 2000 lines a second (1.8 MB/s) the load
wrote 72 parts before the deadline. The 16 kept parts are on the medium
whole: the readback holds 17727 of the load's lines, about 16 MB, and
passed its FAT check.

stick_load now says 200 lines a second, about 180 KB/s. That is still a
write and a sync on the stick every logkeeper round, and a boot's log stays
inside retention until the deadline ends it. The other two loads end within
their boots and keep 2000 a second; say takes the rate per caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leaves, and no more

T14 r5 at d36af38: the usbload boot ran until its deadline, and the
readback refused its log partition: "/2026-10-21-094046_0008.log:
DIR_FileSize is 200402 bytes, which needs 392 clusters, and the chain
holds 394; FSInfo: FSI_Free_Count is 53763 and the FAT has 53761 free
clusters".

That is the designed crash state, not a writer-ordering defect.
toyos-fat32 claims a cluster as end-of-chain before linking it and writes
the size last (lib/fat32/src/repair.rs, the open windows), so a stop inside
an append leaves a chain longer than its size and a free count that is a
hint (fatgen103 §5: FSI_Free_Count "is not necessarily correct"). No byte
the size names is lost. The fileserver crash test reaches this state, and
it now asserts that it does: a stop fell between the chain's growth and the
size's write.

- toyos_fat32_check::unexplained_by_a_stop says what of a check no stop can
  explain. It lets lost chains, one entry's copies disagreeing, the free
  count, and one file's chain too long through. It refuses a second grown
  file, a short chain, a cross-link, a cycle and every broken entry.
- toyos-metal applies it only to a boot its deadline ends
  (ended_by_its_deadline) and prints each state it lets through. Every other
  boot still owes a volume with nothing to say about it.
- Tests: the r5 complaints accepted for a deadline boot and refused for an
  ordinary one, and a short chain refused for both. The checker crate's own
  test covers the rule.
- Nothing on the mount path takes the leak back. That is recorded with its
  exit in issues/a-stop-mid-append-leaves-a-fat-volume-s-leak-for-ever.md.

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

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator, T14 rounds 4–6 (hashes verified): at fa0f434 usbd_serves_the_boot_stick, usbd_drives_the_type_c_controller, tone_beside_a_stick_write, mask_windows PASS (loads now write /log on the stick and judges count bytes read back off it); at 56bd104 stick_survives_a_reset_under_load PASS (deadline reset at 51.5 s mid-write; the FAT left the designed crash state the checker now accepts only on deadline boots). All five T14 rows pass. Landing waits on #974 (K's DMA hold-off/activation), since the AMD bench's boot stick is on 03:00.3.

Japabu and others added 2 commits October 21, 2026 13:40
#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>
…troller firmware keeps

On #974's hand-over contract. A claim's function masters nothing until its
holder starts it (SYS_DEVICE_DMA_START), and every grant and lend is placed
below the limit the holder states (SYS_DEVICE_DMA_LIMIT).

- After the firmware handoff and the controller's halt and reset, usbd
  states its limit. That is 64 bits, or 32 where HCCPARAMS1.AC64 is clear
  (xHCI 1.2 §5.3.6). It then takes its grant and starts mastering before it
  writes any address to the controller. A controller without AC64 is no
  longer refused: #974 places its grants below 4 GiB.
- A controller firmware keeps (HC BIOS Owned still set after HANDOFF_NS,
  §4.22.1) is now taken, by name, with its SMIs off. Until usbd starts it,
  its bus mastering is off, so the halt and reset cut no transfer of
  firmware's that could reach memory (PCIe Base 6.0 §7.5.1.1.3). That is
  the AMD bench's 03:00.3. The previous commit's refusal stood in for this
  contract until it landed.
- A refused limit or start ends usbd by name, as every other refusal does.
- Test: a_controller_firmware_keeps_is_taken_by_name_with_its_smis_off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
The next commit deletes `MAX_CPUS`, which this assertion names. The owed
word's own runtime bound stays: `OwedLock::try_lock_or_owe` refuses a cpu
id past bit 63 by name. PR #971 deletes the kernel xHCI driver and
`OwedLock` with it; once it lands this commit has nothing left to change
and goes in the rebase onto it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
The next commit deletes `MAX_CPUS`, which this assertion names. The owed
word's own runtime bound stays: `OwedLock::try_lock_or_owe` refuses a cpu
id past bit 63 by name. PR #971 deletes the kernel xHCI driver and
`OwedLock` with it; once it lands this commit has nothing left to change
and goes in the rebase onto it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu and others added 4 commits October 21, 2026 16:01
usb_stopped_before_the_reset matched "kernel] exit: usbd pid=" in the
console's text, but the kernel tags a record made on a thread's context
with its tid ("[15.227 cpu1 kernel tid=1] exit: usbd ..."), so a correct
stop whose exit was recorded that way was refused as missing. Each line
is now parsed by toyos_logstream and matched by its source and the start
of its text, as every other reader does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kept this branch's deletions of the kernel xHCI, irq_ring, log recovery
and the scheduler's Operation framework, and with them #976's per-CPU
fields for irq_ring and the no-task operation slot. fileserver takes
#975's descriptions without the partition claims this branch removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ize stated

usbd stalled the AMD bench's boot stick (1022:1639) in six of seven boots
under 2000 log lines a second: a bulk transfer the controller never
answered, with no event on the ring, and in two of three probed boots the
controller halted or in error (USBSTS HCH|HSE|HCE). The kernel's xHCI it
replaces moved every transfer through a bounce buffer as one TRB; usbd
moves a client's region directly, so a data phase that crosses 64 KiB is
a TD of several TRBs, and those broke three rules of xHCI 1.2:

- TD Size (§4.11.2.4) "shall be valid in all Transfer TRBs" (§4.11.7):
  it was 0 in every TRB, which names each the last of its TD while its
  Chain bit says another follows. hc::normal_td states the packets left
  after each TRB.
- "A TD Fragment shall not span segments" (§4.11.5.1, §4.11.7.1): a TD
  could cross the ring's Link. One that would not fit now starts on the
  ring's first TRB behind a Link TD put where it would have begun, which
  §4.11.7 allows ("One or more Link TDs may precede or follow a TD"); the
  Link at the ring's end never carries a Chain bit now.
- "The first TRB of a TD Fragment is written last" (§4.11.7.1): each TRB
  was handed over as it was written. The first is now put with the
  controller's cycle state inverted and handed over once the rest is.

Which of the three this controller trips is not separated: the probes
read the controller's state, not the rule. The stub now refuses a TD that
crosses the Link; a_td_that_would_cross_the_rings_link_starts_behind_it
laps a bulk-out ring with TDs at every offset (red with the old placement:
"a TD crosses the ring's Link"), and a_tds_size_counts_the_packets_left_
after_each_trb checks hand-counted TD Sizes (red with TD Size 0).

A bulk endpoint stating a max packet size of 0 is refused at its binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folding amdvi_usbd_drives_its_own_entry into the shared testcases boot
(this branch dropped main's separate xhci-leave boot, as the kernel no
longer drives a controller to leave) put its staged faults beside
amdvi_translates_the_stick_controller and amdvi_remaps_the_stick_controller,
which refused any DMA FAULT on the boot. The first AMD boots to reach
those judges (r14) went red on 03:00.4's aimed read at 0x50400000: the
fault test_rs_usbd_spare_fault stages, past the spare's grant on purpose.
Those two now refuse every fault but the spare's, which
amdvi_usbd_drives_its_own_entry judges by address and access.

That row also waited for "usbd 03:00.4: the claim refused its interrupt
record (Io)", which usbd says as "usbd 03:00.4: ENDED — the claim refused
its interrupt record (Io); ..." since it ends by name.

The six r14 readbacks, whose image this commit leaves byte-identical,
judge green on all five AMD testcases rows; one with a fault on 03:00.3
added is red on both no-fault rows.

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

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

r2: focused review at 5461e947c (new since 8a93782dd: b07a9b1ac, merge 4ee2dcadf, 0e0c37e66, 5461e947c)

Round 1's blockers: resolved

  • BLOCKER 1, resolved at b07a9b1ac. Handoff::Kept returns Refusal::FirmwareKeepsIt again, before any SMI write, halt or HCRST. Its doc quotes §4.22.1, Reset's "firmware has let go of it" is true again, and a_controller_firmware_keeps_is_refused_and_left_as_found is restored with control f1.
  • BLOCKER 2, resolved. HANDOFF_NS now quotes "should not exceed '1' second". Both round-1 suggestions were taken as well: the AC64-clear stub case and the settle doc.

Findings

BLOCKER 3. system/usbd/xhci/src/hc.rs:274-281 (Ring::link), called early from :254-256: an early Link leaves TRBs from two laps back that the controller treats as its own on the next lap.

Before this change every lap wrote every slot from 0 to 254. Each slot therefore held the previous lap's cycle bit, which never matches the controller's next Consumer Cycle State (§4.9.2). An early Link at slot t now ends lap L (cycle c) without rewriting slots t+1..=254. Those slots keep lap L−1's cycle !c, and !c is exactly the CCS the controller holds on lap L+1. On lap L+1, once the driver's tail rests at some p in t+1..=254, the controller finishes the TD before p, reads slot p, finds the cycle bit matching, and executes a TRB that lap L−1 wrote.

A concrete sequence:

  1. Lap A (cycle 1): 1-TRB TDs run to the end, so slots 0..254 hold cycle 1.
  2. Lap B (cycle 0): 1-TRB TDs run to tail 253. A 3-TRB TD then fails 253 + 3 > 255, so link() writes a Link at 253 and slot 254 still holds lap A's cycle 1.
  3. Lap C (cycle 1): TDs end exactly at tail 254. The controller, with CCS 1, executes lap A's slot 254.

The same applies after a Set TR Dequeue to ring.dequeue() at such a p (usb.rs:949). This sequence is reachable now:

  • An unaligned MAX_REQUEST_BLOCKS (32 × 4 KiB) data phase is 3 TRBs (msc.rs pieces).
  • A control transfer is Setup, Data and Status: 3 TRBs on EP0.

The consequence is a stale Normal TRB pointing at a buffer from two laps ago. On bulk-out that can be an old CBW, possibly a WRITE, or old data. The result is a Bulk-Only phase desync or stale data written to the stick, and if the buffer is unmapped, an IOMMU fault.

a_td_that_would_cross_the_rings_link_starts_behind_it cannot expose this. Its bulk-out TDs are 1 or 2 TRBs, so an early Link only ever happens at 254 and leaves no gap. The stub would execute such a stale TRB, because next_trb follows the cycle bit alone.

Fix: when link() is called with tail < RING_TRBS - 1, rewrite slots tail+1..RING_TRBS-1, the end Link included, so that they carry the closing lap's cycle (self.cycle before the toggle). Each early Link then invalidates whatever it skips, and the invariant "every slot holds a cycle other than the next lap's CCS" holds again.

Cheapest check: a host test of Ring::enqueue against a cycle-bit walker that consumes from the last dequeue with its CCS. Run 1- and 3-TRB TDs that park the tail inside a gap left by an early Link, and assert that the walker stops exactly at the tail after every TD. It is red today by the trace above. A stub case with unaligned 128 KiB writes would also show it, as stray events or a phase error.

BLOCKER 4. hc.rs:283-288 (Ring::room) and the assert at :253: the stated capacity is false once early Links exist. room() says that "only the operation's own length matters" and allows a TD of up to 255 TRBs. After an early Link at t, a TD of n > t TRBs overwrites slot t, the Link the controller still has to follow. The controller then stops on a TRB that is not its own, and the ring deadlocks. Every caller today is at most 3 TRBs, so nothing reaches this. But the contract and its assert promise what the ring cannot deliver.

Fix: bound a TD to (RING_TRBS - 1) / 2 TRBs, or any bound for which the early-Link condition t + n > 255 implies n ≤ t, and state that in room().

BLOCKER 5. tests/toyos.rs:4518-4529 (no_fault_but_the_spares) together with amdvi_usbd at :4415-4425: the 03:00.4 exemption is too broad. The no-fault rows now skip every DMA FAULT line on stream=03:00.4. amdvi_usbd only requires that one record names the aimed address and that one is access=none … IO_PAGE_FAULT. It refuses no others. So any further process-owned fault on 03:00.4 passes all three rows. That includes a fault from usbd driving the spare before the staged arm, or a second read past the grant. The r14 readbacks are deterministic: each of the six boots has exactly two 03:00.4 records, addr=0x50400000 access=read … streamfaults=1 and access=none … streamfaults=2.

Fix: have amdvi_usbd refuse any 03:00.4 record other than those two, for example records.len() == 2 with each matched, and reuse the rejudge-r14 control with a third 03:00.4 record added. owner=kernel lines are not hidden, since alive() still reads them as a death.

Verified, no finding

  • TD Size (§4.11.2.4) follows the spec's formula: packets minus floor(bytes so far ÷ MPS), capped at 31, and 0 in the last TRB. The hand-counted cases are right.
  • First TRB last is correct (§4.11.7.1). The first TRB is written with !cycle, the rest and any trailing Link are written, then publish, then the first control word with cycle.
  • The early Link's own bits are correct. It carries the closing lap's cycle and TC, and no Chain bit, which is consistent with §4.11.7.
  • The tail never rests at 255, so a 1-TRB TD, every command among them, never links early.
  • dequeue() is correct, including after an early Link: tail 0, cycle toggled.
  • Bulk MPS 0 is refused at binding.

Folding xhci-leave into testcases: acceptable

The separate boot existed only for the kernel xHCI's xhci-leave actuator. With the place matchers, usbd drives 03:00.3 and leaves 03:00.4 to the claim tests by configuration, so no boot-only property remains. The root rule on amortizing boots favours sharing. The staged faults stay in 03:00.4's own domain, with BME cleared on the first fault, and do not reach the stick's controller. The r14 readbacks show only those two records. With BLOCKER 5 fixed, sharing the boot costs no coverage.

Evidence

  • Metal: the AMD and T14 runs at 0e0c37e66 are the orchestrator's report, and I did not re-read them. They establish that the stall is gone on these boots. They do not exercise BLOCKER 3 deterministically: it needs a 3-TRB TD that ends a lap one slot short, followed by a lap that rests in that slot.
  • The rejudge: pr2/rejudge-r14/ shows 6 of 6 green at 5461e947c, and the control with an added 03:00.3 fault is red. That control does not cover a hidden 03:00.4 fault (BLOCKER 5).

SEND BACK

… amdvi_usbd refuses an unstaged fault

Review r2 of #971 (B3-B5).

B3. Before early Links every lap wrote slots 0..=254, so each held the
last lap's cycle, never the one the controller expects next (§4.9.2). A
Link put early at slot t closed a lap leaving t+1..=255 with the cycle
of the lap before, which is the next lap's: once that lap's tail rested
in the gap, or a Set TR Dequeue put it there, the controller would run a
TRB two laps old. Ring::link now writes the closing lap's cycle into
every slot it skips, the ring's own Link included.
the_controller_takes_each_td_and_stops_at_the_tail walks the ring as the
controller does over 20000 TDs of 1 to 3 TRBs, the reviewer's lap first,
and after each requires that exactly that TD is taken and the walk stops
at the tail; without the marking it takes slot 254 two laps old.

B4. An early Link at t followed by a TD of more than t TRBs overwrites
the Link before the controller follows it. Ring::room is now
(RING_TRBS - 1) / 2, so a TD that forces an early Link fits before it;
the_longest_td_leaves_the_early_link_before_it_whole is red at the old
bound of 255. Every TD usbd puts is at most 3 TRBs.

B5. amdvi_usbd_drives_its_own_entry now counts every 03:00.4 record and
requires exactly the staged two: the aimed read at the address the job
names, and the foreign vector's access=none IO_PAGE_FAULT. The six r14
readbacks pass; one with a third 03:00.4 record added is red.

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

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Review r2, at 421c2b3:

  • B3, fixed. Ring::link now writes the closing lap's cycle into every slot it skips, the ring's own Link included. hc::ring_tests::the_controller_takes_each_td_and_stops_at_the_tail walks the ring as the controller does, from its dequeue with its CCS and following Links. It runs over 20000 TDs of 1, 2 and 3 TRBs, starting with your lap (255 single TRBs, then 253, then a 3-TRB TD, then 254 single TRBs). After every TD it requires that the walk takes exactly that TD and stops at the tail, ring.dequeue(), which is also where a Set TR Dequeue resumes. Control b3, with the marking removed, is red: the walk takes slot 254 (offset 4064) two laps old.
  • B4, fixed. Ring::room() is (RING_TRBS - 1) / 2 and says why. the_longest_td_leaves_the_early_link_before_it_whole puts room()-long TDs after every lead length. Control b4, at the old bound of 255, is red: the walk stops on the overwritten Link.
  • B5, fixed. amdvi_usbd collects every 03:00.4 record, of any owner. It requires the aimed read (owner=process, access=read, at the job's address) and the foreign message (owner=process, access=none, IO_PAGE_FAULT), and no other. The six r14 readbacks pass. Control b5, run-1 with a third 03:00.4 record, is red: "03:00.4 has 3 fault records".

Checks at 421c2b3: --ci host 0 (87 green); --build-only x86_64 0 and aarch64 0; the guest suite 67/68, where the red is crypto, the known QEMU console stall. AMD testcases ×6 plus usbload and the T14's three images are staged for the orchestrator.

@Japabu

Japabu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

r3 at 421c2b32a (only 421c2b32a: hc.rs, tests/toyos.rs)

All three r2 blockers are resolved.

  • B3 (cycle state after an early Link): resolved. Before it writes an early Link, Ring::link now marks every slot from tail+1 to RING_TRBS-1, including the ring's own end Link, with the closing lap's cycle bit, so none of them matches the next lap's CCS (§4.9.2). On a normal close the range is empty. Overwriting slot 255's control word with a bare cycle bit is safe:

    • it is never the controller's on the next lap, because its cycle differs;
    • before any later lap can reach it, it is either rewritten as a Link (when the tail reaches 255, inside enqueue, before the first TRB is handed over) or marked again by the next early Link.

    the_controller_takes_each_td_and_stops_at_the_tail walks the ring the way the controller does: by cycle bit, following Links and toggling on TC. After every TD it requires that the walk takes exactly that TD and stops at the tail, and that dequeue() names the same place. Its first lengths are r2's trace: 255+253 single-TRB TDs, then one of 3 TRBs, then 254 singles. The tail therefore rests on slot 254 inside the gap, which is where the unmarked ring takes the two-laps-old TRB. After that come 20,000 seeded TDs of 1, 2 or 3 TRBs, with more than 20 early Links required. The test is deterministic and fits the defect.

  • B4 (TD length bound): resolved. room() is (RING_TRBS-1)/2 = 127. An early Link happens when t + n > 255, so with n ≤ 127 it means t ≥ 129 > n and the TD after it cannot reach slot t. The doc now states that reason. the_longest_td_leaves_the_early_link_before_it_whole places 127-TRB TDs after every lead-in offset, so a Link the TD overwrote would stop the walk short. Its control is the old bound of 255.

  • B5 (03:00.4 fault exemption): resolved. amdvi_usbd collects every DMA FAULT line on stream=03:00.4 with the same match no_fault_but_the_spares exempts, kernel-owned lines included. It requires that record count to be exactly 2:

    • a process-owned access=read at the aimed address;
    • a process-owned access=none … IO_PAGE_FAULT.

    An unstaged spare fault can no longer pass all three rows.

Evidence. I read the agents/usbd/pr2/metal-amd-r15 readbacks. The request names 421c2b32a. run-1 to run-6 each record exactly two DMA faults, both on 03:00.4, and every verdict says passed. run-ul records none. I did not re-read the T14 r15 readbacks, so that result is the orchestrator's report. The host tests' negative controls are the implementer's claim and I did not re-run them. By inspection, the B3 test's opening sequence reaches slot 254 two laps old without the marking, and the B4 test reaches an overwritten Link at a bound of 255.

No new findings. Nothing earlier is reopened.

LAND

@Japabu
Japabu marked this pull request as ready for review October 21, 2026 17:04
@Japabu
Japabu added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit af1c5af Oct 21, 2026
8 checks passed
@Japabu
Japabu deleted the wt/toyos-usbd-2 branch October 21, 2026 17:36
Japabu added a commit that referenced this pull request Oct 21, 2026
A one-off accuracy audit of 117 open defect records (batch 0 of 4). Each
claim was checked against origin/main at
6f4d8db and the owner's uncommitted
planning in the primary checkout, then brought onto
db0cf63 (#971, #978, #980) and the records
whose cited code #971 removed checked again there. The per-record
classification, with file:line evidence, is in the PR.

Deleted as fixed. For each, which halves of the exit were met, and which
were judged unnecessary and why:

- a-file-server-killed-mid-sync-leaves-a-half-written-btree: met whole.
  3adcf42 made DATA's commit shadow paging, the superblock written last
  after a flush, and its host test, lib/bcachefs/tests/crash.rs, stops a
  commit at every block write, torn sectors and a lost block 0 included,
  and mounts what is left: the exit's own test. The residual defects are
  owned by a-write-over-a-files-committed-page-is-not-shadowed and
  a-crash-leaks-the-blocks-of-its-last-commit.
- a-long-name-entry-whose-write-was-refused-is-on-the-volume-and-the-create-erases-nothing:
  the QEMU half is met by a cheaper test, the T14 half judged unnecessary.
  0d73b99 removed erase_inserted; write_entry_at queues the old bytes as
  a repair before every write, so an entry write refused after it landed
  is written back. lib/fat32/tests/refused_writes.rs models the device the
  exit asked QEMU to stage, a write that lands and is answered with an
  error, as a host test: `cargo test -p toyos-fat32 --test refused_writes
  every_refused_write_of_a_long_name_create_is_undone` exit 0 at
  6f4d8db, 24 refused and landed cases on the record's own file name,
  each checked by toyos-fat32-check. The T14 fat check of a
  usb-transport-break boot was not run: the mechanism is the crate's and
  the host model reaches every write, and since #971 that actuator no
  longer exists, so the arm cannot be staged.
- a-replacement-netstack-reuses-its-predecessors-ports: met. Since
  4b96de0 netstack runs ToyOS's own stack, whose TCP ephemeral ports are
  RFC 6056 keyed draws from keys each process takes from the system's
  randomness, the remedy the record named; NetDaemon's fixed 49152 is
  gone. Its citation in netstack-ends-for-the-boot-on-a-nic-it-cannot-drive-on.md
  goes with it.
- a-disk-whose-port-went-away-panics-the-boot-at-roots-hold was fixed by
  602cff0: its exit's code half is met, and its "green on a nightly"
  half by that commit's three local `--nightly` runs of
  usb_transport_break with a reverted negative control, not by a CI
  nightly. #971 deleted the record on main first, so this change no longer
  carries the deletion.

Deleted as superseded:

- a-deliberate-wedge-inside-a-driver-panics-every-other-cpu-that-wants-its-lock:
  the usb-wedge-* actuators it was about went in 29e1006, and 13ed7b4
  (#971) took the kernel's USB driver out and its restoration out of stage
  E of the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md. The one
  staged wedge left, wedge-before-reset, holds no lock when it stops.

Corrected where the text no longer matched the code, the defect kept:
renamed programs (init, netd, logd, fsd, soundd), moved functions and
files, line numbers that drifted, guest tests cut since, citations of
deleted files, and statements the tree refutes (the console's blocking
read, the DMA address window's arithmetic, the mio half of the
TCP_NODELAY record, the firmware-call record's account of when AML
writes first reached the kernel, d2006cc, and its widening, 15536e2).
On the new base the console-watch and disk-offline records are main's
text, the console's carrying the residual correction to its blocking
read; held-disk, the NVMe boot wait and reset-stops-xHCI are main's
deletions, and the job-list record's citation of the kernel's xHCI stop
now names pcidev::stop_before_reset.

Left untouched because the owner's uncommitted planning edits them:
a-driver-that-reflashes-its-device-escapes-its-isolation,
a-file-held-across-a-file-servers-restart-answers-gone,
a-machine-without-an-iommu-refuses-every-claim, and
a-memory-overwrite-request-ubuntu-left-set-stays-set-under-toyos, which
the owner deleted locally as superseded (claude-questions.md, item 12)
together with its citation in the loader-wipe issue.

Also removes a home-directory path from
a-job-list-hangs-with-interrupts-on-and-the-deadline-ends-it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
A one-off accuracy audit of batch 1 (117 open `kind: defect` records).
Each claim was checked against origin/main at 6f4d8db. Every record
that #971, #978 and #980 touched was then re-audited against db0cf63,
the base this lands on. The primary checkout's uncommitted planning
(requirements/, claude-questions.md) was read and not edited.

Counts at db0cf63: 2 fixed, 43 partly stale, 60 still valid,
8 uncertain, and 4 already deleted by #971 (hotplug-blocks-a-scheduler-
pass, a-shutdown-on-a-held-usb-disk-left-a-cpu-deaf-to-a-tlb-shootdown,
an-acknowledge-after-an-enumeration-clears-a-replug-no-look-has-seen,
an-xhci-storm-starves-the-cpu-that-takes-it). Still-valid and uncertain
records are unchanged, except for the two extended below.

Deleted, fixed:

- a-second-launch-of-a-serves-program-panics-the-supervisor: no launcher
  client can reach the panic. `may_start` refuses a target the caller's
  `starts` does not list (4e20480). The build refuses a `starts` entry
  naming a row that serves a port or a role
  (`starts_name_what_a_launch_can_start`, host-tested), and a package's
  synthesized row serves nothing. The panic is now an internal
  invariant; a-comment-says-a-second-launch-is-refused-where-the-
  supervisor-panics records the comment above it, which still says
  "refused".
- bus-mastering-rides-memory-decode: #971 removed the kernel xHCI
  driver, the last kernel site that armed bus mastering before its
  refusals. virtio arms in `accept` after `offer` has parsed; amdvi arms
  after programming, with no refusal after it; a claimed function starts
  mastering through its own driver's `dma_start`, after its parse
  (usbd, nvme). build/sourcegate.rs's `BUS_MASTER_SITES` comment no
  longer cites the record.

Renamed, the slug's claim refuted, a residual kept, every citation
moved:

- a-safe-port-read-reaches-any-io-port -> a-safe-port-read-reaches-any-
  declared-port: a `pio::Port` is made only from a declared run, but the
  fixed runs are pub consts, so a safe read still bypasses
  `serial::Registers`.
- a-wake-to-a-busy-cpu-waits-for-its-next-pass-and-no-idle-cpu-can-take-
  it -> no-row-holds-a-wake-beside-seven-spinners-to-100-us. #874's
  `wake_onto_busy` read p99 5 us (T14) and 25 us (AMD bench), with 200 of
  200 wakes run off the busy CPU. What is left is the 7-spinner arm's
  100 us bound, which no row holds. The 8-spinner arm moves to
  a-woken-thread-waits-out-a-sibling-spinners-quantum-when-every-cpu-is-
  busy, its owner. The speed track's two citations moved, and its stale
  "whole 10 ms quantum" line was corrected.
- diskserver-resets-a-drive-that-is-slow-and-healthy -> the-nvme-driver-
  resets-a-drive-that-is-slow-and-healthy: `COMMAND_SILENCE` is the nvme
  driver's (system/nvme/src/main.rs), a row apart from diskserver.

Extended: a-spawn-drops-an-empty-argument now covers the supervisor's
`serve_launch`, which drops empty and non-UTF-8 arguments from the same
blob; its exit asks for a host test of that decode.

Corrected to the current code, residual defect kept: paths and names
after the userland moves (fsd -> fileserver, init -> supervisor, soundd
-> soundserver, netd -> netstack, blockd -> nvme); line numbers replaced
by names; tests cut since filing recorded as cut (consent_prompt in
ff1a11d, screen_gop_firmware_mode in 520c0d1). Some mechanisms
changed under a record: netring stage 3's stream refusals; the DMA
ordering behind `DmaBuffers::publish`/`observe`; blocked-time
accounting's one caller; `SpawnArgs::image`; the blackbox tail keeping
the newest records; the T14's HDA now driven; and `close_ends_polls`'s
arms at db0cf63. a-usb3-device-attached-but-untrained-reads-as-gone
takes #971's rewrite as it stands.

The per-record table (class, evidence, action) is in the PR's evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
A one-off accuracy audit of batch 1 (117 open `kind: defect` records).
Each claim was checked against origin/main at 6f4d8db. Every record
that #971, #978 and #980 touched was then re-audited against db0cf63,
the base this lands on. The primary checkout's uncommitted planning
(requirements/, claude-questions.md) was read and not edited.

Counts at db0cf63: 2 fixed, 43 partly stale, 60 still valid,
8 uncertain, and 4 already deleted by #971 (hotplug-blocks-a-scheduler-
pass, a-shutdown-on-a-held-usb-disk-left-a-cpu-deaf-to-a-tlb-shootdown,
an-acknowledge-after-an-enumeration-clears-a-replug-no-look-has-seen,
an-xhci-storm-starves-the-cpu-that-takes-it). Still-valid and uncertain
records are unchanged, except for the two extended below.

Deleted, fixed:

- a-second-launch-of-a-serves-program-panics-the-supervisor: no launcher
  client can reach the panic. `may_start` refuses a target the caller's
  `starts` does not list (4e20480). The build refuses a `starts` entry
  naming a row that serves a port or a role
  (`starts_name_what_a_launch_can_start`, host-tested), and a package's
  synthesized row serves nothing. The panic is now an internal
  invariant; a-comment-says-a-second-launch-is-refused-where-the-
  supervisor-panics records the comment above it, which still says
  "refused".
- bus-mastering-rides-memory-decode: #971 removed the kernel xHCI
  driver, the last kernel site that armed bus mastering before its
  refusals. virtio arms in `accept` after `offer` has parsed; amdvi arms
  after programming, with no refusal after it; a claimed function starts
  mastering through its own driver's `dma_start`, after its parse
  (usbd, nvme). build/sourcegate.rs's `BUS_MASTER_SITES` comment no
  longer cites the record.

Renamed, the slug's claim refuted, a residual kept, every citation
moved:

- a-safe-port-read-reaches-any-io-port -> a-safe-port-read-reaches-any-
  declared-port: a `pio::Port` is made only from a declared run, but the
  fixed runs are pub consts, so a safe read still bypasses
  `serial::Registers`.
- a-wake-to-a-busy-cpu-waits-for-its-next-pass-and-no-idle-cpu-can-take-
  it -> no-row-holds-a-wake-beside-seven-spinners-to-100-us. #874's
  `wake_onto_busy` read p99 5 us (T14) and 25 us (AMD bench), with 200 of
  200 wakes run off the busy CPU. What is left is the 7-spinner arm's
  100 us bound, which no row holds. The 8-spinner arm moves to
  a-woken-thread-waits-out-a-sibling-spinners-quantum-when-every-cpu-is-
  busy, its owner. The speed track's two citations moved, and its stale
  "whole 10 ms quantum" line was corrected.
- diskserver-resets-a-drive-that-is-slow-and-healthy -> the-nvme-driver-
  resets-a-drive-that-is-slow-and-healthy: `COMMAND_SILENCE` is the nvme
  driver's (system/nvme/src/main.rs), a row apart from diskserver.

Extended: a-spawn-drops-an-empty-argument now covers the supervisor's
`serve_launch`, which drops empty and non-UTF-8 arguments from the same
blob; its exit asks for a host test of that decode.

Corrected to the current code, residual defect kept: paths and names
after the userland moves (fsd -> fileserver, init -> supervisor, soundd
-> soundserver, netd -> netstack, blockd -> nvme); line numbers replaced
by names; tests cut since filing recorded as cut (consent_prompt in
ff1a11d, screen_gop_firmware_mode in 520c0d1). Some mechanisms
changed under a record: netring stage 3's stream refusals; the DMA
ordering behind `DmaBuffers::publish`/`observe`; blocked-time
accounting's one caller; `SpawnArgs::image`; the blackbox tail keeping
the newest records; the T14's HDA now driven; and `close_ends_polls`'s
arms at db0cf63. a-usb3-device-attached-but-untrained-reads-as-gone
takes #971's rewrite as it stands.

The per-record table (class, evidence, action) is in the PR's evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
A one-off accuracy audit of 117 open defect records. Each was read
against the code it cites at origin/main db0cf63,
with the owner's uncommitted requirements/ and claude-questions.md beside it.
After rebasing onto a8ea09d (#978, #981), each record whose cited code
#978 changed was read again.

Deleted, fixed:
- netstack-watches-both-pipes-of-every-connection-on-every-pass: c91e72b
  gave a pipe end's leaving its own watch (kernel/src/pipe.rs) and made
  netstack submit only a changed or answered watch (watches.rs `Armed`).
  The commit named the record and left it.
- shell-c-discards-the-cwd-its-launch-carried: `shell -c` runs in its
  launch's directory since b3770cf.
- std-answers-every-spawn-refusal-but-notfound-as-other: std's spawn maps
  each refusal by name, and every SyscallError through to_io_error, since
  61a283a.

Deleted, superseded (the subject's code or test no longer exists):
- os-toyos-io-traits-keep-a-posix-name: #978 makes a RawFd a number in the
  process's one descriptor table, as on unix, and gives the kernel handle
  its own word, std::os::toyos::io::AsRawHandle.
- kernel_stack_addr-names-an-offset-not-an-address: #583 merged without
  the rename; the rename is a step of
  the-loader-does-only-what-must-precede-the-handover.md.
- log-flush-retry-deadman-arm: the kernel's fsync deadman went with the
  durability ledger (80a1f1c), its kernel USB with usbd (13ed7b4), and
  the test with 9ebf080.
- metal-device-probe-usbread-answered-iofailed-once-beside-other-guests: one
  sighting through the kernel's usb-storage, gone with 13ed7b4; test
  deleted 24aa318.
- netstack-refused-accept-hung-waiting-for-a-wake-that-never-came: netd and
  its listen.rs are gone, the test was deleted (7967fba), and so is
  netcase_against_host.
- process-stats-exits-101-beside-other-guests: both recorded reds were the
  test's own premise; the test is deleted (4a5b228).
- screen-diag-boot-leaves-no-i8042-line-on-the-panel: the kernel's i8042
  and its health line left for ps2server (febfd21); test deleted.

Corrected and kept, each with what remains:
- io-uring-enter-trips-the-one-queue-invariant becomes
  a-wait-was-registered-while-its-task-still-read-waiting: the assertion is
  now Watch::register's; every recorded route is gone; the cause never was
  found.
- libc-loses-the-kernels-word-on-three-write-paths becomes ...-two-...:
  send's stream arm answers through stream_errno; sendto's datagram arm and
  stdio's sys_write still drop the kernel's word.
- logkeeper-flushes-the-records-its-own-refused-flush-made becomes
  logkeeper-flushes-the-records-its-own-flush-made: the measured kernel
  retry is gone; whether any flush now makes records is unmeasured.
- std-calls-undated-what-it-did-not-stat becomes
  std-set-times-answers-ok-and-sets-nothing: the mtime half is fixed.
- remove-dir-all-empties-a-directory-and-leaves-it: std's is fixed; pkg's
  remove_tree still gives that as its reason.
- netstack-holds-a-pending-request-for-a-client-that-left: the connect half
  is gone; the waiting receive is still unwatched.
- netstack-keeps-receiving-for-a-client-that-closed-its-receive-end,
  netstack-socket-ids-are-ambient, netstack-lookup-slots-have-no-per-client-share,
  netstacks-places-are-one-number-for-every-client, most-t14-leases-land-one-dhcp-retry-late:
  restated for regions, badges and netstack.
- smp-ap-hole-and-log-reserve-window-red-under-a-loaded-host: the ROOT read
  half is gone; the first spawn's WouldBlock is kept.
- soundserver-past-due-wake-max-1: only the null sink's wait still does it.
- std-says-a-launch-moves-its-handles-...: the std comment moved into
  toyos::spawn, where it is right; provide's doc is still wrong.
- std-maps-a-device-error-to-other-not-uncategorized: the consumers that
  kept Other load-bearing are gone.
- sharedmemory-slices-rest-on-a-size-nobody-checks: a peer's region maps
  through `lent`, which checks it; `adopt` does not.
- sshserver-authorized-keys-unprotected: installed packages no longer see
  /state; every shipped program still does.
- libc-munmap-refuses-part-of-a-mapping: the owner's 2026-10-21 memory
  checkpoint revises the refusal, so the "rule it final" exit goes.
- libc-refuses-what-toyos-cannot-yet-answer: after #978 F_DUPFD_CLOEXEC,
  F_DUPFD's lowest free and F_GETFD's EBADF hold; the fcntl bullet keeps
  record locks and F_GETFL, F_SETFL, F_GETOWN, F_SETOWN.
- Sites, names and paths moved for: kernel-text-is-writable...,
  libc-answers-einval..., libc-answers-epipe..., libc-host-tests...,
  listing-home..., logkeeper-ends..., map-mmio..., metalprobes-usb-read...,
  mio-deregister..., no-console..., one-diskserver-session...,
  one-write-far-past..., probe-mounts-on-a-checksum, sshserver-holds-one-channel...,
  sshserver-still-speaks-rsa, std-lookup-host..., null-sink-applies-one-connect
  (annotated, not re-seen).

parallel-tests-red-under-other-suites.md loses its pointer to a deleted
record. no-physical-memory-fairness is owner-held and untouched; 58 records
are still valid and 9 uncertain, both unchanged; four were already deleted
on main (#971, #980, #978). The per-record table with
file and line evidence is in the pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Oct 21, 2026
A one-off accuracy audit of 117 open defect records. Each was read
against the code it cites at origin/main db0cf63,
with the owner's uncommitted requirements/ and claude-questions.md beside it.
After rebasing onto a8ea09d (#978, #981), each record whose cited code
#978 changed was read again.

Deleted, fixed:
- netstack-watches-both-pipes-of-every-connection-on-every-pass: c91e72b
  gave a pipe end's leaving its own watch (kernel/src/pipe.rs) and made
  netstack submit only a changed or answered watch (watches.rs `Armed`).
  The commit named the record and left it.
- shell-c-discards-the-cwd-its-launch-carried: `shell -c` runs in its
  launch's directory since b3770cf.
- std-answers-every-spawn-refusal-but-notfound-as-other: std's spawn maps
  each refusal by name, and every SyscallError through to_io_error, since
  61a283a.

Deleted, superseded:
- kernel_stack_addr-names-an-offset-not-an-address: the field is still
  `kernel_stack_addr` (sdk/abi/src/boot.rs:9), and #583, its named owner,
  merged without the rename. The rename is a stated step of
  the-loader-does-only-what-must-precede-the-handover.md, which carries it.
- os-toyos-io-traits-keep-a-posix-name: the traits still exist, and now name
  a real descriptor. The owner's 2026-10-21 answers supersede the 2026-08-19
  "fds belong only in libc jargon" ruling (64f806d) the record rested on:
  keep C numbering and dup2 conventions in userland, including Rust/C
  descriptor interoperability; Rust std's C-style descriptor interoperation
  uses the same userland adaptation; one descriptor-inheritance rule honored
  by both libc spawning and Rust Command. #978 implements that, and the
  kernel handle has its own word, std::os::toyos::io::AsRawHandle.
- logkeeper-flushes-the-records-its-own-refused-flush-made: the measured
  loop was the kernel's retry of a budget-refused fsync, whose four records
  made logd's next round flush again. That retry and its records went with
  the durability ledger (80a1f1c) and the kernel's USB storage
  (13ed7b4). logkeeper ends its volume on a refused append or flush
  (system/logkeeper/src/main.rs:625-632) and warns of a slow round once,
  behind its `degraded` latch (:633-643); the file server logs only an
  operation that failed (system/fileserver/src/data.rs:136, 461), and a
  failed flush ends the volume. No current route is known.
The rest have code or a test that no longer exists:
- log-flush-retry-deadman-arm: the kernel's fsync deadman went with the
  durability ledger (80a1f1c), its kernel USB with usbd (13ed7b4), and
  the test with 9ebf080.
- metal-device-probe-usbread-answered-iofailed-once-beside-other-guests: one
  sighting through the kernel's usb-storage, gone with 13ed7b4; test
  deleted 24aa318.
- netstack-refused-accept-hung-waiting-for-a-wake-that-never-came: netd and
  its listen.rs are gone, the test was deleted (7967fba), and so is
  netcase_against_host.
- process-stats-exits-101-beside-other-guests: both recorded reds were the
  test's own premise; the test is deleted (4a5b228).
- screen-diag-boot-leaves-no-i8042-line-on-the-panel: the kernel's i8042
  and its health line left for ps2server (febfd21); test deleted.

Corrected and kept, each with what remains:
- io-uring-enter-trips-the-one-queue-invariant becomes
  a-wait-was-registered-while-its-task-still-read-waiting: the assertion is
  now Watch::register's; every recorded route is gone; the cause never was
  found.
- libc-loses-the-kernels-word-on-three-write-paths becomes ...-two-...:
  send's stream arm answers through stream_errno; sendto's datagram arm and
  stdio's sys_write and sys_read still drop the kernel's word.
- std-calls-undated-what-it-did-not-stat becomes
  std-set-times-answers-ok-and-sets-nothing: the mtime half is fixed.
- remove-dir-all-empties-a-directory-and-leaves-it: std's is fixed; pkg's
  remove_tree still gives that as its reason.
- netstack-holds-a-pending-request-for-a-client-that-left: the connect half
  is gone; the waiting receive is still unwatched.
- netstack-keeps-receiving-for-a-client-that-closed-its-receive-end,
  netstack-socket-ids-are-ambient, netstack-lookup-slots-have-no-per-client-share,
  netstacks-places-are-one-number-for-every-client, most-t14-leases-land-one-dhcp-retry-late:
  restated for regions, badges and netstack.
- smp-ap-hole-and-log-reserve-window-red-under-a-loaded-host: the ROOT read
  half is gone; the first spawn's WouldBlock is kept.
- soundserver-past-due-wake-max-1: only the null sink's wait still does it.
- std-says-a-launch-moves-its-handles-...: the std comment moved into
  toyos::spawn, where it is right; provide's doc is still wrong.
- std-maps-a-device-error-to-other-not-uncategorized: the consumers that
  kept Other load-bearing are gone.
- sharedmemory-slices-rest-on-a-size-nobody-checks: a peer's region maps
  through `lent`, which checks it; `adopt` does not.
- sshserver-authorized-keys-unprotected: installed packages no longer see
  /state; every shipped program still does.
- libc-munmap-refuses-part-of-a-mapping: the owner ruled on 2026-10-21 that
  whole-mapping-only unmap is not the final contract and the refusal is to
  be revised, so the "rule it final" exit goes.
- libc-refuses-what-toyos-cannot-yet-answer: after #978 F_DUPFD_CLOEXEC,
  F_DUPFD's lowest free and F_GETFD's EBADF hold; the fcntl bullet keeps
  record locks and F_GETFL, F_SETFL, F_GETOWN, F_SETOWN.
- Sites, names and paths moved for: kernel-text-is-writable...,
  libc-answers-einval..., libc-answers-epipe..., libc-host-tests...,
  listing-home..., logkeeper-ends..., map-mmio..., metalprobes-usb-read...,
  mio-deregister..., no-console..., one-diskserver-session...,
  one-write-far-past..., probe-mounts-on-a-checksum, sshserver-holds-one-channel...,
  sshserver-still-speaks-rsa, std-lookup-host..., null-sink-applies-one-connect
  (annotated, not re-seen).

parallel-tests-red-under-other-suites.md loses its pointer to a deleted
record. no-physical-memory-fairness is owner-held and untouched; 58 records
are still valid and 9 uncertain (stdio-buffering's deleted-file citation
now names its deleting commit), both unchanged; four were already deleted
on main (#971, #980, #978). The per-record table with
file and line evidence is in the pull request.

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

pkg read a whole archive into memory under a 256 MiB ceiling and refused
every link, so the toolchain M2 needs (clang, LLD and the C sysroot, 279 MB
installed) could not be installed.

pkg now reads an archive twice and never holds it: a survey hashes the
whole file and plans it header by header within the room the volume under
/apps has, and the unpack meets that plan header for header and the digest
again at the end, so an archive changed between the reads installs nothing
and writes nothing the plan did not check. The room replaces MAX_INFLATED as
the bound on what a survey inflates. Before the manifest is written the
volume is synced through /apps's capability, which refuses if any file was
left unwritten: a file's last close can be refused, and std's File drops
that answer.

The room is a new file-server request, ROOM: the free bytes of the volume
behind a capability and its allocation unit, answered by bcachefs's spare
blocks, FAT32's free clusters, and none for an absent or read-only volume.

A package may carry symlinks that resolve inside it: relative, `..` only
first and never past the package's own directory, and nothing is written
beneath a link or a file. The package's directory is no link, and the
manifest is the installer's alone. Hard links, pax and GNU long names stay
refused. A package's program is `<name>/<name>` or `<name>/bin/<name>`, so
a manifest's program may be nested inside its directory. pkg asks ToyOS's
file server for room, so it is declared exempt as ToyOS-only.

`--hosted-clang` packages its clang and LLD with clang's resource headers,
the C sysroot and LLVM's licence as clang-v<version>-toyos-x86_64.tar.gz,
deterministic, with `cc` and `clang++` linked to clang and a `<triple>.cfg`
naming the sysroot beside the binaries. hosted_clang_hello installs that
package with pkg on a 1 GiB DATA and compiles and links hello.c with
/apps/clang/bin. Its image starts the block service and the NVMe driver as
the split of #971 has them.

libc's posix_spawn reads a program a file server holds into a memory object
the kernel pages the child from, as std's Command does, since the kernel
opens only what it serves; clang's driver starts cc1 and ld.lld that way.

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

pkg read a whole archive into memory under a 256 MiB ceiling and refused
every link, so the toolchain M2 needs (clang, LLD and the C sysroot, 279 MB
installed) could not be installed.

pkg now reads an archive twice and never holds it: a survey hashes the
whole file and plans it header by header within the room the volume under
/apps has, and the unpack meets that plan header for header and the digest
again at the end, so an archive changed between the reads installs nothing
and writes nothing the plan did not check. The room replaces MAX_INFLATED as
the bound on what a survey inflates. Before the manifest is written the
volume is synced through /apps's capability, which refuses if any file was
left unwritten: a file's last close can be refused, and std's File drops
that answer.

The room is a new file-server request, ROOM: the free bytes of the volume
behind a capability and its allocation unit, answered by bcachefs's spare
blocks, FAT32's free clusters, and none for an absent or read-only volume.

A package may carry symlinks that resolve inside it: relative, `..` only
first and never past the package's own directory, and nothing is written
beneath a link or a file. The package's directory is no link, and the
manifest is the installer's alone. Hard links, pax and GNU long names stay
refused. A package's program is `<name>/<name>` or `<name>/bin/<name>`, so
a manifest's program may be nested inside its directory. pkg asks ToyOS's
file server for room, so it is declared exempt as ToyOS-only.

`--hosted-clang` packages its clang and LLD with clang's resource headers,
the C sysroot and LLVM's licence as clang-v<version>-toyos-x86_64.tar.gz,
deterministic, with `cc` and `clang++` linked to clang and a `<triple>.cfg`
naming the sysroot beside the binaries. hosted_clang_hello installs that
package with pkg on a 1 GiB DATA and compiles and links hello.c with
/apps/clang/bin. Its image starts the block service and the NVMe driver as
the split of #971 has them.

libc's posix_spawn reads a program a file server holds into a memory object
the kernel pages the child from, as std's Command does, since the kernel
opens only what it serves; clang's driver starts cc1 and ld.lld that way.

The guest row installs and compiles, and stops at the link: clang's driver
starting cc1 from /apps is refused ENOMEM, the kernel's ResourceExhausted,
while a small std parent starts ld.lld from there. The image object is
allocated physically contiguous, about 148 MB for clang, and the memory
track's stages 1 to 3 remove that; the track and the memory issue record it
as the row's blocker.

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

pkg read a whole archive into memory under a 256 MiB ceiling and refused
every link, so the toolchain M2 needs (clang, LLD and the C sysroot, 279 MB
installed) could not be installed.

pkg now reads an archive twice and never holds it: a survey hashes the
whole file and plans it header by header within the room the volume under
/apps has, and the unpack meets that plan header for header and the digest
again at the end, so an archive changed between the reads installs nothing
and writes nothing the plan did not check. The room replaces MAX_INFLATED as
the bound on what a survey inflates. Before the manifest is written the
volume is synced through /apps's capability, which refuses if any file was
left unwritten: a file's last close can be refused, and std's File drops
that answer.

The room is a new file-server request, ROOM: the free bytes of the volume
behind a capability and its allocation unit, answered by bcachefs's spare
blocks, FAT32's free clusters, and none for an absent or read-only volume.

A package may carry symlinks that resolve inside it: relative, `..` only
first and never past the package's own directory, and nothing is written
beneath a link or a file. The package's directory is no link, and the
manifest is the installer's alone. Hard links, pax and GNU long names stay
refused. A package's program is `<name>/<name>` or `<name>/bin/<name>`, so
a manifest's program may be nested inside its directory. pkg asks ToyOS's
file server for room, so it is declared exempt as ToyOS-only.

`--hosted-clang` packages its clang and LLD with clang's resource headers,
the C sysroot and LLVM's licence as clang-v<version>-toyos-x86_64.tar.gz,
deterministic, with `cc` and `clang++` linked to clang and a `<triple>.cfg`
naming the sysroot beside the binaries. hosted_clang_hello installs that
package with pkg on a 1 GiB DATA and compiles and links hello.c with
/apps/clang/bin. Its image starts the block service and the NVMe driver as
the split of #971 has them.

The guest row installs and compiles, and stops at the link: clang's driver,
starting cc1 from /apps through libc's posix_spawn of a served image
(toyos::files::read_image), is refused ENOMEM, while a small std parent
starts ld.lld from there. The image object is
allocated physically contiguous, about 148 MB for clang, and the memory
track's stages 1 to 3 remove that; the track and the memory issue record it
as the row's blocker. The package's licences and a command reached by its
name are recorded as owed.

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