Compare commits
2
Commits
e10fb76fb2
...
36f1d6ae9e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36f1d6ae9e | ||
|
|
d8a195b8d8 |
+151
@@ -1495,3 +1495,154 @@ failure-path logging itself hasn't been exercised live yet, only compiled. Three
|
||||
failure that motivated §IX.5 (`xhci: not a Mass Storage/SCSI/BOT device` on a 4th concurrent
|
||||
attach) remains unexplained and unfixed — tracked as the next item, separate from the four
|
||||
closures above.
|
||||
|
||||
## X. §IX.5 follow-on: xHCI concurrent enumeration, root-caused and CLOSED; identity VM heap
|
||||
capacity measured, one real leak found and fixed, capacity question still OPEN — 2026-09-06/07
|
||||
|
||||
Picks up exactly where §IX.5 left off. Two separate root causes, both real, found by actually
|
||||
attaching multiple devices live rather than reasoning about the driver in the abstract — same
|
||||
lesson §VII and §IX already taught this project twice over.
|
||||
|
||||
### X.1 — The "4th-device enumeration failure" was never a driver bug — CLOSED, commit `30c26ad`
|
||||
|
||||
Root cause, confirmed live via QEMU's own `info usb` monitor command *before touching any
|
||||
kernel code*: the default `qemu-xhci` controller (`p2=4,p3=4`) exposes only **4 real dual-role
|
||||
ports**, not 8 as the parameter names suggest (p2/p3 count the *same* physical ports from the
|
||||
USB2/USB3 register sets, not additive). Every device attach in this project used
|
||||
`-device usb-storage,bus=xhci0.0,drive=...` with no explicit `port=`; past the 4th device, QEMU
|
||||
silently auto-inserts an internal USB2 hub and everything past that is invisible to a driver
|
||||
with no hub-descent logic. The driver's `"not a Mass Storage/SCSI/BOT device"` warning was
|
||||
**correct** — the device really was a hub. The per-slot xHCI/BOT machinery from §VII was
|
||||
inspected and found structurally sound for real top-level-port devices; no live corruption bug
|
||||
was in it.
|
||||
|
||||
**Fix, entirely in the QEMU test harness, not the kernel:** new `XHCI_PORTS` Make variable
|
||||
(`Makefile.starkernel`, default 16, overridable) sizes `p2`/`p3` on all three arches with real
|
||||
headroom above the current 9-device identity roster — sized deliberately, not exactly matched
|
||||
to today's count, per the standing ruling against hardcoding a bound to today's scale (§VII.4).
|
||||
`ZUSEDISK_QEMU_ARGS` now gives Zuse's drive an explicit `port=1`; `QEMU_EXTRA`'s own doc comment
|
||||
shows the pattern for attaching more devices.
|
||||
|
||||
**Found and fixed in passing:** `ZUSEDISK`'s Makefile default (`disk/zuse.img`) was stale —
|
||||
deleted from git at `c3db963`, superseded by `disk/thumbdrives/zuse-thumb-ident.img`, but the
|
||||
default was never updated, so a plain `make qemu` silently booted with no Zuse device attached
|
||||
at all. Corrected to the real minted image.
|
||||
|
||||
**Flagged, not touched (not asked):** `scripts/bleach_zuse_img.sh` and `disk/README.md` still
|
||||
reference the deleted `disk/zuse.img` path.
|
||||
|
||||
Three-arch `clean qemu` acceptance (single Zuse device, port 1) passed on amd64/aarch64/riscv64
|
||||
with this change.
|
||||
|
||||
### X.2 — The real driver bug: Configure Endpoint completions dropped under concurrency — CLOSED, commit `e10fb76`
|
||||
|
||||
With X.1's port fix in place, actually booting the real kernel with Zuse + all 8 identity
|
||||
drives attached concurrently (9 devices, each its own real port) surfaced a second, genuine bug:
|
||||
only **1 of 9** devices ever reached `blkio_usb: MSC device ready`. No error and no success for
|
||||
the other 8 — silence, not a diagnosable failure.
|
||||
|
||||
Root-caused live via temporary WARN-level diagnostic probes (written, captured, fully reverted
|
||||
— the committed fix contains no probe code) at four points in `xhci.c`: the initial port scan,
|
||||
the connect handler, the Command Completion Event handler, and the deferred per-slot dispatch
|
||||
loop. The probes disproved the first hypothesis (a stalled `pending_events[]` connect queue —
|
||||
all 9 devices actually completed Enable Slot + Address Device correctly) and found the real one:
|
||||
`xhci_poll_events()`'s deferred dispatch loop submitted `XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT` (a
|
||||
Command Ring op) **unconditionally** for every slot with that action pending in a single pass —
|
||||
unlike Enable Slot/Address Device/Disable Slot, which are correctly gated behind
|
||||
`dev->connect_state == XHCI_CONN_IDLE` before ever submitting. With 2+ devices enumerating at
|
||||
once, this let multiple Configure Endpoint commands sit outstanding on the Command Ring
|
||||
simultaneously. Their completion is correlated purely via the single shared `dev->connect_state`
|
||||
field, not the completion event's own Slot ID — so whichever slot's completion happened to land
|
||||
while `connect_state` still read `AWAIT_CONFIGURE_ENDPOINT` got correctly chained into
|
||||
`SET_CONFIG`; every other slot's completion arrived after `connect_state` had already moved on
|
||||
and was silently swallowed by the handler's generic "unrelated command completion" catch-all —
|
||||
no error path exists for that catch-all, which is exactly why it produced silence rather than a
|
||||
diagnosable failure.
|
||||
|
||||
**Fix:** the same single-in-flight discipline this driver already uses for every other Command
|
||||
Ring op. If the Ring isn't free when a slot's Configure Endpoint action comes due, put the
|
||||
action back on that slot instead of racing a second command onto a busy Ring; the next tick's
|
||||
dispatch pass retries it.
|
||||
|
||||
**Verified live:** the same 9-device concurrent boot now produces 9 `MSC device ready` lines and
|
||||
zero xHCI errors (was 1 of 9). Three-arch single-device regression (the standard acceptance
|
||||
case) still passed on all three arches.
|
||||
|
||||
### X.3 — Live 9-device hotplug walkthrough, on request: dynamic attach path also verified, not just boot-time scan
|
||||
|
||||
Separate from X.1/X.2's boot-time-attach testing, Captain Bob directed a live walkthrough: one
|
||||
QEMU instance up with nothing attached, then Zuse, bob/rajames, and 00 through 06 hotplugged one
|
||||
at a time via QMP `drive_add`+`device_add` on distinct `XHCI_PORTS`, watching the kernel's live
|
||||
reaction after each. This exercises `xhci_poll_events()`'s Port Status Change *event* path
|
||||
(true hotplug), not `xhci_scan_ports_for_already_connected()`'s boot-time initial-scan path
|
||||
(X.1/X.2's scenario) — a genuinely different code path through the same driver.
|
||||
|
||||
Every hotplug through 04 succeeded cleanly: `blkio_usb: MSC device ready`, correct distinct
|
||||
per-device identity readback (a temporary probe read each drive's own `full_name`/`username`
|
||||
straight off its own devblock via `blkio_read()`, confirming e.g. slot 3 really said `"00"`, not
|
||||
Zuse's or bob's data — no cross-contamination), and a live `WIREBIND` birth per identity. Six
|
||||
identities (including Zuse, who costs no extra VM) were live simultaneously with zero errors.
|
||||
The 05 hotplug is where X.4 below picks up — its USB/xHCI attach itself succeeded fine (further
|
||||
confirming X.2's fix generalizes to the hotplug path, not just boot-time scan); what failed next
|
||||
was a different layer entirely.
|
||||
|
||||
### X.4 — Identity VM kernel-heap capacity: exact numbers measured live, one real leak found and FIXED (commit `d8a195b`), the capacity/fragmentation question itself is OPEN
|
||||
|
||||
X.3's 05 hotplug attached at the USB layer but then failed at `WIREBIND`'s VM-birth step with a
|
||||
kernel allocator exhaustion error (`vm_create_word: malloc failed`, `vm_init: rolling window
|
||||
malloc failed`, `PARITY:BIRTH_FAILED`) — graceful, no crash, boot continued to `(zuse) ok>`, but
|
||||
a real scalability wall. Measured precisely (not estimated) via a temporary allocator-stats
|
||||
probe in `capsule_run.c`'s `capsule_parity_log_birth`/`capsule_parity_log_birth_failed` (written,
|
||||
captured, fully reverted — no probe code committed), replaying the same hotplug sequence:
|
||||
|
||||
- Kernel heap arena (`alloc_kernel.c`, `SF_ARENA_SIZE`) is a **fixed 4 MiB (4,194,304 bytes)** —
|
||||
does not grow.
|
||||
- Boot baseline (Hera + Hermes + Artemis, the 3 system VMs): **781,904 bytes**.
|
||||
- Zuse costs **nothing extra** — she authenticates into the existing Hera session rather than
|
||||
birthing a new VM.
|
||||
- Every other identity (`WIREBIND`) costs **exactly 457,392 bytes, with zero variance**,
|
||||
confirmed identical across bob, 00, 01, 02, 03, 04. This is two VM births per identity: a
|
||||
"default personality" console VM (228,576 bytes) then the real user session (228,816 bytes).
|
||||
- **6 identities fit** (bob + 00 + 01 + 02 + 03 + 04): arena at 3,526,256 / 4,194,304 used,
|
||||
668,048 free.
|
||||
- **The 7th identity (05) failed — not from clean capacity exhaustion.** 668,048 bytes were
|
||||
free, more than the usual 457,392 needed, but the attempt died partway through (peak
|
||||
3,876,624, only 350,368 bytes into itself), with individual 128-136 byte word-registration
|
||||
allocations failing. 662 `sf_free()` calls happened *during* that single failed attempt —
|
||||
background heartbeat/physics activity from the 6 already-live VMs churning the same arena
|
||||
concurrently. The allocator's free-list is documented as first-fit with **no splitting or
|
||||
coalescing**, so fragmentation from that concurrent churn is the likely proximate cause, not
|
||||
raw capacity.
|
||||
|
||||
**The bug found and FIXED here:** the failure was *leaking* memory, permanently, on top of the
|
||||
fragmentation problem. `capsule_wirebind_try_attach()` births its two VMs (console, then user)
|
||||
in sequence; when the second failed, the console VM that had *already succeeded* was never torn
|
||||
down — an orphan nothing ever points a real user at, since `WIREBIND` only ever hands the caller
|
||||
the user VM's id. Confirmed live: after the failed attempt, `used_bytes` settled at 3,754,832 —
|
||||
228,576 bytes *above* the pre-attempt baseline, i.e. one whole console VM's worth, permanently
|
||||
gone. Every failed attach was making the arena strictly worse for the next attempt, not neutral.
|
||||
|
||||
Fix: `capsule_vm_kill(username)` on the now-orphaned console VM before returning from the
|
||||
failure path in `capsule_wirebind.c` — the same teardown `capsule_wirebind_eject()`/
|
||||
`capsule_wirebind_unclean_detach()` already use elsewhere in that file (`vm_cleanup()` +
|
||||
`sf_free()`, confirmed live in §IX.2/§IX.3 to actually reclaim per-word dictionary allocations).
|
||||
Verified live with the same probe, replaying the identical failure: `used_bytes` after the fix
|
||||
returns to **exactly** the pre-attempt baseline (3,526,256, matching precisely) instead of
|
||||
leaking 228,576 bytes. Three-arch acceptance passed with the fix included.
|
||||
|
||||
**Still open — the actual architecture question, not yet decided (Captain Bob, 2026-09-07):**
|
||||
"identity is everything in this OS." The leak fix makes retries neutral instead of compounding,
|
||||
but the underlying ~6-identity practical ceiling (itself a fragmentation artifact, not a hard
|
||||
capacity number) is unaddressed. Options on the table, none chosen yet:
|
||||
- Grow the heap / make the arena size dynamic instead of a fixed 4 MiB compile-time constant.
|
||||
- Shrink per-VM dictionaries — every `WIREBIND` user VM currently carries the full word set,
|
||||
including things like `TTF-TEXT`/`SCROLL-BACK`/`KBD-SCAN` a non-console user session may not
|
||||
need.
|
||||
- Address the fragmentation contributor directly (splitting/coalescing in the free-list, or
|
||||
serializing VM-birth allocation against concurrent background allocation) — this is *why*
|
||||
identity 05 failed even with more free bytes than it needed.
|
||||
|
||||
**Also flagged, not touched (not asked):** `Makefile.starkernel`'s
|
||||
`printf '$(KERNEL_ARGS)\n' > starforth.cfg` breaks when `KERNEL_ARGS` starts with `--` (dash's
|
||||
`printf` misreads it as an option). Worked around live during X.2's debug-level testing with a
|
||||
harmless prefix token (`KERNEL_ARGS="x --log-level=debug"`); not fixed in the Makefile itself.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-07T12:52:21Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-07T15:26:51Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -185,6 +185,20 @@ void capsule_wirebind_try_attach(struct blkio_dev *dev,
|
||||
if (capsule_runcap_birth(dev, sig, user_vm_name, mama_vm->stadium_vm_id, &user_id,
|
||||
&user_ctx) != CAPSULE_RUN_OK) {
|
||||
console_println("WIREBIND: user VM birth FAILED");
|
||||
/* The console VM above already succeeded and is live/registered
|
||||
* under `username` -- without this, it's an orphan: nothing ever
|
||||
* points a real user at it (WIREBIND only ever hands the caller
|
||||
* the user VM's id), so it just sits there permanently holding
|
||||
* its own kernel-heap arena allocation. Confirmed live 2026-09-07
|
||||
* (identity-heap-capacity testing): this is exactly what turns a
|
||||
* single failed identity attach into a permanent net loss of
|
||||
* heap, compounding every subsequent attempt's odds of failing
|
||||
* too, rather than a neutral retry. capsule_vm_kill() is the same
|
||||
* teardown capsule_wirebind_eject()/capsule_wirebind_unclean_
|
||||
* detach() already use elsewhere in this file -- vm_cleanup() +
|
||||
* sf_free(), verified to actually reclaim per-word dictionary
|
||||
* allocations (FABRIC-3.md §IX.2/§IX.3, 2026-09-06). */
|
||||
capsule_vm_kill(username);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user