Artemis Milestone 2g (partial): bulk endpoint discovery + 2e disconnect teardown

Picked up from a crashed session: xhci_driver.h/xhci.h already had the
bulk_in/out_ep_addr/max_packet fields and Endpoint-descriptor offset
macros scaffolded, but the actual walk that populates them was never
written. Added it: after 2f confirms a Mass Storage/SCSI/BOT interface,
a nested walk continues through the Endpoint descriptors that follow it
(bDescriptorType==5, stopping at the next Interface descriptor or end
of stream), keeping only Bulk-type endpoints and splitting IN/OUT by
bEndpointAddress bit 7. Also reset the four new fields in
xhci_bringup(), which the scaffolding had missed.

Also completed 2e's disconnect teardown, which was fully implemented
this session (not scaffolded): a Disable Slot command is now submitted
on a real disconnect, with the port's tracked slot ID captured and
cleared from port_slot_id[] immediately (before the command completes)
so a fresh connect on the same port isn't confused for one already in
progress, and DCBAA[slot_id] cleared only on a successful completion.

Verified live via QMP hotplug (deliberate device_add/device_del against
freshly launched, individually-tracked instances -- not whatever
happened to be attached at boot), all three architectures,
byte-identical: bulk IN endpoint=0x81, bulk OUT endpoint=0x02, then a
clean disconnect -> disable slot succeeded, no wedge. Caught and fixed
a documentation near-miss in the same pass: an initial draft cited the
probe-free three-arch acceptance boots as this feature's verification
evidence, but a stale leftover log directory from a pre-crash orphaned
QEMU process had been picked up by an `ls -dt | head -1` glob during
monitoring and mistaken for this session's own result -- the real
acceptance logs never had a device attached at all. Re-verified against
real PIDs and real log paths before writing FABRIC-2.md's final
writeup.

FABRIC-2.md Section X Milestone 2 updated: 2e's disconnect-teardown
checklist item marked done, 2g's endpoint-identification item marked
partially done (identification only -- Configure Endpoint / EP Context
wiring is still open).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4
This commit is contained in:
Robert Allan James
2026-08-25 08:17:50 -04:00
co-authored by Claude Sonnet 5
parent b4bbd043d0
commit 96d55fcd87
15 changed files with 54535 additions and 13 deletions
+74 -6
View File
@@ -3211,9 +3211,10 @@ Final three-arch acceptance (probe-free, `clean qemu`): all three boot to `ok>`
the device (Address Device command) — **both done 2026-08-22**, see writeup below.
Device descriptor read is 2f's job and depends on this (not the reverse — an earlier
version of this note had the dependency backwards)
- [ ] On disconnect: tear down the corresponding device slot and signal to whatever higher-
level code (Section U's identity/VM logic) that the device is gone — **this is the
first point where Section U/V's design actually gets a real trigger to hang off**
- [x] On disconnect: tear down the corresponding device slot **done 2026-08-25**, see
writeup below. Signaling to Section U's higher-level code is still open — a real
callback surface doesn't exist yet, this item only covers the xHCI-side teardown
(Disable Slot command, DCBAA entry clear)
- [ ] Decide and implement where the hotplug event surfaces to the rest of the kernel —
likely a callback registered by whatever owns the home-blocks logic, not xHCI code
calling into block_subsystem.c directly (matching the existing "kernel/Artemis
@@ -3319,8 +3320,43 @@ no debugging needed. `logs/20260822-113852/amd64/`, `logs/20260822-114043/aarch6
Still open: correlating Command Completion Events via the real Command TRB Pointer (not needed
yet, see above), device descriptor read (2f), USB2 port-reset handling (untested — this
increment's test device was SuperSpeed and self-enabled), disconnect teardown (Disable Slot
command, DCBAA entry clear), and the callback surface into Section U's higher-level code.
increment's test device was SuperSpeed and self-enabled), and the callback surface into
Section U's higher-level code.
**Disconnect teardown (Disable Slot command, DCBAA entry clear), done 2026-08-25.** New
`XHCI_CONN_AWAIT_DISABLE_SLOT` state and `dev->pending_disable_slot_id`, same
single-outstanding-command pattern as Enable Slot/Address Device above. On a real disconnect
(`PORTSC.CCS` clear), `port_slot_id[port_id-1]` is captured into
`pending_disable_slot_id` and cleared from the tracking array *immediately* — before the
Disable Slot command's completion is known — so a fresh connect on the same port during the
brief in-flight window isn't confused for one already in progress; the command's own
completion handler is the only thing that later touches `DCBAA[slot_id]`, zeroing it on
success only (a failure completion leaves it alone rather than zeroing a context pointer the
controller might still consider live). If a disconnect arrives while another Command Ring
command is already outstanding, the Disable Slot is dropped rather than queued — same
documented limitation as Enable Slot's "already pending" case, not a new gap.
Verified live via QMP hotplug, all three architectures — a real connect→full-enumeration→
disconnect cycle exercised the new teardown path directly, not a synthetic disconnect: `xhci:
port status change -- device connected` through `xhci: device configured` (full 2f sequence)
followed immediately by `xhci: port status change -- device disconnected`, `xhci: disable slot
command submitted`, `xhci: disable slot succeeded`, no wedge. `logs/20260825-081058/amd64/`,
`logs/20260825-081300/aarch64/`, `logs/20260825-081517/riscv64/`.
**A documentation near-miss caught before commit, worth recording.** The first draft of this
writeup cited the probe-free three-arch acceptance boots (`logs/20260825-075759/amd64/` etc.)
as the verification evidence for this feature — those boots never had a USB device attached at
all (`grep xhci` on them shows only the four controller-bring-up lines, nothing past that), so
the claim was false. The mixup came from an earlier monitoring mistake in-session: a
leftover, still-running QEMU process from before a session crash left a *stale* log directory
on disk, and a `ls -dt | head -1` glob picked that stale directory instead of the freshly
launched run's — the stale log happened to already contain a full connect/enumerate/disconnect
sequence from whatever testing produced it originally, which read as this session's own result
until checked against the actual PID and the actual new log path. Caught by re-grepping the
three acceptance logs directly before writing this paragraph and finding them empty of device
activity; fixed by re-running the verification as a real, deliberate QMP hotplug (not relying
on whatever happened to be attached at boot) against freshly launched, individually-tracked
instances.
**2f. USB device enumeration (post-connect, before it's usable as storage)**
- [x] Request and parse the device descriptor — **done 2026-08-22**, see writeup below.
@@ -3415,7 +3451,11 @@ arch afterward, no wedge. `logs/20260825-073235/amd64/`, `logs/20260825-073417/a
`logs/20260825-073728/riscv64/`.
**2g. Bulk-Only Transport (BOT) — the actual read/write path**
- [ ] Identify and configure the device's bulk IN and bulk OUT endpoints
- [~] Identify the device's bulk IN and bulk OUT endpoints — **identification done
2026-08-25**, see writeup below; *configuring* them (a Configure Endpoint command,
wiring their EP Contexts/DCIs so the controller will actually run transfers on them)
is still open — this increment only reads the addresses out of the descriptor, it
doesn't yet act on them
- [ ] Implement CBW (Command Block Wrapper) construction and send, for a SCSI READ(10)
- [ ] Implement CSW (Command Status Wrapper) receive and status check
- [ ] Get one real SCSI READ(10) working end to end — first proof the whole stack works,
@@ -3425,6 +3465,34 @@ arch afterward, no wedge. `logs/20260825-073235/amd64/`, `logs/20260825-073417/a
- [ ] Implement basic error/stall recovery (CSW failure status, endpoint stall clear) — at
minimum enough to not wedge the controller on a single bad transfer
**Bulk endpoint identification, done 2026-08-25.** Extends the Interface-descriptor walk
already built in 2f: once a Mass Storage/SCSI/BOT interface is confirmed, a second, nested
walk continues forward from that interface's own offset through the Endpoint descriptors that
follow it (`bDescriptorType == 5`, USB 2.0 spec table 9-13), stopping at the next Interface
descriptor or end of the concatenated descriptor stream — matching the same "found by type,
not assumed at a fixed offset" discipline the outer walk already uses. Only endpoints whose
`bmAttributes` bits 1:0 read Bulk (`0x02`) are kept; `bEndpointAddress` bit 7 picks IN vs. OUT.
Four new `xhci_dev_t` fields (`bulk_in_ep_addr`/`bulk_in_max_packet`/`bulk_out_ep_addr`/
`bulk_out_max_packet`) hold the result — address `0` doubles as the "not found yet" sentinel
for both, since endpoint address 0 is always EP0 and can never legitimately be a bulk
endpoint's own address. A device confirmed BOT but missing either direction logs a warning
rather than failing silently.
This work was already scaffolded — the four struct fields, the descriptor-offset macros, and
their doc comments were all written and uncommitted before an earlier session ended abruptly
mid-edit — but the walk itself, the piece that actually populates them, hadn't been written
yet; this increment is that missing piece (plus resetting the four fields in
`xhci_bringup()`, which the scaffolding had also missed).
Verified live via QMP hotplug (deliberate `device_add`/`device_del` against a freshly booted,
individually-tracked instance — see the disconnect-teardown writeup above for why this
matters), all three architectures, byte-identical results everywhere: `bulk IN
endpoint=0x81`, `bulk OUT endpoint=0x02`, immediately followed by the existing `set
configuration submitted` → `device configured` chain, unaffected. `logs/20260825-081058/amd64/`,
`logs/20260825-081300/aarch64/`, `logs/20260825-081517/riscv64/`. *Configuring* these
endpoints (a Configure Endpoint command, EP Context setup) is the next open item in this
milestone — this increment only identifies them.
**2h. Integration with the existing block subsystem**
- [ ] Wire a working USB MSC device into `blk_subsys_attach_device()` (or
`blk_subsys_add_raw_device()`, whichever fits — confirm which, since USB is