Artemis Milestone 2h: hot-detach -- 2h complete

blk_subsys_detach_device() (block_subsystem.c) walks the device chain,
refuses removal of anything but the current tail (a mid-chain removal
would corrupt every later slot's start_lbn -- this architecture's own doc
already argues USB stays last specifically to avoid that), unlinks,
shrinks total_user_lbn, closes and frees the slot. Discards rather than
flushes dirty state -- the device is physically gone by the time this
runs (PORTSC disconnect only). Trigger wiring mirrors the attach path:
bot_msc_attached (set only once attach actually succeeds) gates a new
bot_msc_detach_pending flag set at PORTSC disconnect (not Disable Slot
completion, which is conditionally skipped and would miss concurrent
connect/disconnect pairs), consumed in sk_repl_idle().

Advisor flagged the real hazard ahead of time: block_words.c's VM block
window (blk_vm_lbn[]/blk_vm_cbuf[]) can go stale across a detach then a
same-LBN re-attach, and suggested a pointer-identity re-check in
blk_vm_load() as a minimal fix. That fix was implemented, then directly
falsified by its own designed-for-this test: attach a blank device, read
a block (populating the cache), detach, re-attach a device with distinct
content at the identical LBN, read again -- served stale content from
the first device. Root cause, confirmed live: glibc's allocator hands
free(slot) straight back to the very next same-size calloc(), so the
"fresh" and stale pointers were bitwise identical despite being two
different devices. Fixed properly with a monotonic blk_subsys_epoch()
counter (bumped on every attach/detach) checked by a new
blk_vm_check_epoch() helper at the one choke point (blk_vm_find(), plus
blk_vm_flush_all() which reads the same arrays directly) that covers
every path touching the window cache -- unfooled by address reuse.

Verified live with a new disk/usb-thumbdrive-test2.img fixture (distinct
content from the existing blank test image): attach A, read (cache hit
populated), detach, re-attach B at the same LBN, read again -- correctly
ran a fresh device read and returned B's real content, not A's stale
cached zeros. The failing pointer-comparison attempt's own capture log
kept as evidence, not deleted. All three architectures re-verified clean.
FABRIC-2.md Section X 2h marked complete -- enumeration through
hot-detach all live and verified; only WRITE(10) (2g's own still-open
item) remains unimplemented in the driver, not blocking anything here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
This commit is contained in:
Robert Allan James
2026-08-25 14:10:05 -04:00
co-authored by Claude Sonnet 5
parent 3b085dd875
commit af267a52a6
22 changed files with 46979 additions and 13 deletions
+20
View File
@@ -243,6 +243,26 @@ typedef struct {
* xhci_poll_events()" constraint) then blk_subsys_attach_device(). */
uint8_t bot_msc_attach_pending;
uint32_t bot_msc_attach_slot_id;
/* Set by sk_repl_idle() once blk_subsys_attach_device() actually
* succeeds (not by the SET_CONFIGURATION handler itself -- attach can
* still fail, e.g. a bad capacity query, in which case there is
* nothing to detach later). Read by the PORTSC disconnect handler
* below to decide whether this disconnect needs a block-subsystem
* detach at all -- a device that never successfully attached (or that
* was already detached) produces no spurious detach flag. */
uint8_t bot_msc_attached;
/* Set by the PORTSC disconnect handler (see xhci_poll_events()'s own
* disconnect handling) only when bot_msc_attached is set -- same
* flag+consume-in-sk_repl_idle() shape as bot_msc_attach_pending,
* chosen deliberately over hooking the Disable Slot completion:
* disconnect is the unambiguous signal, while Disable Slot is only
* even issued when connect_state == XHCI_CONN_IDLE (see the "command
* ring busy" skip path) and would silently miss a detach otherwise.
* No xhci_bot_wait_for_idle() call is needed for detach itself (no
* device round-trip -- it's local block_subsystem.c bookkeeping), but
* consuming it in sk_repl_idle() anyway matches the attach path's own
* shape and keeps xhci.c decoupled from block_subsystem.c. */
uint8_t bot_msc_detach_pending;
/* Deferred chaining: a doorbell ring (new control transfer) must
* never happen synchronously from inside xhci_poll_events()'s event-