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
@@ -225,6 +225,26 @@ int blk_subsys_init(VM *vm, uint8_t *ram_base, size_t ram_size);
int blk_subsys_attach_device(struct blkio_dev *dev);
/* Milestone 2h hot-detach. Refuses (BLK_EINVAL) unless dev's slot is the
* current chain tail -- see this function's own doc comment in
* block_subsystem.c for why. Discards any dirty cache/BAM/vol_meta state
* rather than attempting to flush it (the device is already physically
* gone by the time this is called). Returns BLK_ENODEV if dev isn't
* attached, BLK_EINVAL if dev is NULL or not the chain tail.
*/
int blk_subsys_detach_device(struct blkio_dev *dev);
/* Monotonic counter, bumped on every attach/detach (Milestone 2h). A raw
* pointer comparison against a blk_get_buffer() result cannot reliably
* detect a same-address device swap (glibc's allocator can hand back the
* exact address just free()'d by a detach to the very next attach's
* calloc()) -- callers that cache a blk_get_buffer() result across calls
* (block_words.c's VM block window) must instead compare this epoch
* against the value they last observed, invalidating their whole cache on
* any change rather than trusting a stored pointer's identity.
*/
uint64_t blk_subsys_epoch(void);
int blk_subsys_shutdown(void);
uint8_t *blk_get_buffer(uint32_t block_num, int writable);