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:
co-authored by
Claude Sonnet 5
parent
3b085dd875
commit
af267a52a6
+76
-3
@@ -3850,9 +3850,82 @@ call site of its own — USB is inherently hotplug, so a boot with no device con
|
||||
none of this new code, same as every earlier BOT increment's own probe-free acceptance runs):
|
||||
`logs/20260825-124143/amd64/`, `logs/20260825-124323/aarch64/`, `logs/20260825-124909/riscv64/`.
|
||||
|
||||
Still ahead for 2h: only the hot-**detach** path remains — `chain_append()` only adds, there is
|
||||
no removal function yet (Section V area A's identity-derived-offset work in Milestone 3 is a
|
||||
separate, later concern, not blocking here).
|
||||
**Hot-detach, done 2026-08-25 — 2h is complete.** `blk_subsys_detach_device()`
|
||||
(`block_subsystem.c`) walks `g.head` matching `slot->dev == dev`, refuses (`BLK_EINVAL`) unless
|
||||
the matched slot is the chain tail (this file's own architecture doc already argues USB/future
|
||||
devices are always last precisely so a removal never renumbers another slot's `start_lbn` — a
|
||||
mid-chain removal would corrupt everything after it, so this is refused outright rather than
|
||||
attempted), then unlinks, `g.total_user_lbn -= slot->user_blocks`, `blkio_close()`,
|
||||
`free(slot->bam)`, `free(slot)`. Deliberately discards rather than flushes any dirty cache/BAM/
|
||||
vol_meta state — the device is physically gone by the time this runs (called only after a real
|
||||
PORTSC disconnect), so a flush attempt cannot succeed; revisit if a future graceful-unmount path
|
||||
(as opposed to today's only path, a surprise removal) wants a best-effort flush first.
|
||||
|
||||
Trigger wiring mirrors the attach path's own shape: a new `bot_msc_attached` flag (`xhci_dev_t`)
|
||||
is set once `blk_subsys_attach_device()` actually succeeds (not by SET_CONFIGURATION itself —
|
||||
attach can fail, e.g. a bad capacity query, in which case there's nothing to detach later); the
|
||||
PORTSC disconnect handler sets `bot_msc_detach_pending` when `bot_msc_attached` is set, and
|
||||
`sk_repl_idle()` consumes it by calling `blk_subsys_detach_device()`. Deliberately hooked at
|
||||
disconnect itself, not Disable Slot completion — Disable Slot is only even issued when
|
||||
`connect_state == XHCI_CONN_IDLE` (see the existing "command ring busy" skip path a few lines
|
||||
above), so hooking there would silently miss a detach on a concurrent connect/disconnect pair.
|
||||
Unlike attach, detach needs no device round-trip (pure `block_subsystem.c` bookkeeping), so it
|
||||
doesn't strictly need `xhci_poll_events()`'s own call frame to have already returned — handled
|
||||
in `sk_repl_idle()` anyway, for shape symmetry and to keep `xhci.c` decoupled from
|
||||
`block_subsystem.c`.
|
||||
|
||||
**A second, more serious bug found live, past what advisor review alone caught.** Advisor
|
||||
flagged the real hazard correctly ahead of time: `vm->blk_vm_lbn[]`/`blk_vm_cbuf[]`
|
||||
(`block_words.c`'s VM block window, `BLOCK`/`BUFFER`/`UPDATE`'s cache) can go stale across a
|
||||
detach-then-reattach-at-the-same-LBN, since `block_subsystem.c`'s chain always appends at the
|
||||
current tail — and recommended a pointer-identity check in `blk_vm_load()`'s cache-hit path
|
||||
(re-resolve `blk_get_buffer()`, compare against the stored `cbuf`, treat a mismatch as a miss) as
|
||||
a minimal fix requiring no new API. That fix was implemented, then **directly falsified by its
|
||||
own designed-for-this test**: attach device A (blank), detach, re-attach device B (distinct
|
||||
content) at the identical LBN range, read that LBN — pointer comparison passed the check (i.e.
|
||||
called it a hit) and served **stale content from device A** anyway. Root cause: glibc's
|
||||
allocator hands `free(slot)` in `blk_subsys_detach_device()` straight back to the very next
|
||||
same-size `calloc(1, sizeof(*slot))` in `blk_subsys_attach_device()`, with nothing else allocated
|
||||
in between — confirmed live in this exact session, not inferred — so the "fresh" pointer and the
|
||||
stale one were bitwise identical despite belonging to two different physical devices. A pointer
|
||||
comparison cannot distinguish "still the same live device" from "a different device that
|
||||
happened to land at the same address" when the allocator is this deterministic.
|
||||
|
||||
Fixed properly with a monotonic `blk_subsys_epoch()` counter (`block_subsystem.c`, `uint64_t
|
||||
g.epoch`, bumped in `blk_subsys_attach_device()`, `blk_subsys_add_raw_device()`, and
|
||||
`blk_subsys_detach_device()`) that cannot be fooled by address reuse the way a pointer comparison
|
||||
was. `block_words.c` gained `blk_vm_check_epoch()`, called at the top of both `blk_vm_find()` and
|
||||
`blk_vm_flush_all()` (the only two functions reading `vm->blk_vm_lbn[]`/`vm->blk_vm_cbuf[]`
|
||||
directly rather than through `blk_vm_find()` first) — any epoch change since the VM's window was
|
||||
last validated discards every cached slot outright, no flush attempt, matching
|
||||
`blk_subsys_detach_device()`'s own "device is already gone" reasoning. `blk_vm_evict()`,
|
||||
`blk_vm_load()`'s and `blk_vm_assign()`'s own miss paths, and `block_word_update()` all route
|
||||
through `blk_vm_find()` first, so a single choke point covers every path that could otherwise
|
||||
read or write through a stale slot. A new `blk_vm_epoch` field on `VM` (`vm.h`) tracks the last
|
||||
validated epoch per VM; zero-initialised by `memset(vm, 0, sizeof(*vm))` at VM creation,
|
||||
consistent with `g.epoch` itself starting at 0 — no false invalidation on first use.
|
||||
|
||||
Verified live: attach `disk/usb-thumbdrive-test.img` (blank) at LBN 26074, read block 26074
|
||||
(populates the VM window cache with the blank content — critical, this is what actually exercises
|
||||
the cache-*hit* path rather than a miss), detach, re-attach `disk/usb-thumbdrive-test2.img` (new
|
||||
fixture, distinct repeating `HOTDETACH-REATTACH-FIXTURE-2026-08-25--` content, added this
|
||||
increment — see `disk/README.md`) at the identical LBN range, read block 26074 again. The second
|
||||
read correctly ran a full fresh TUR+READ10 cycle against the device (not a cached-hit shortcut)
|
||||
and `26074 BLOCK 64 TYPE CR` printed `HOTDETACH-REATTACH-FIXTURE-2026-08-25--...` — the new
|
||||
device's real content, not the stale zeros a working pointer-comparison fix would have kept
|
||||
serving. `logs/20260825-134747/amd64/` is that capture (also contains the `blk: detaching disk`
|
||||
line and the two `blk: disk ... LBN 26074..75184` attach lines, byte-identical between the two
|
||||
different backing devices, confirming the LBN-reuse premise itself). `logs/20260825-133413/amd64/`
|
||||
is the earlier run that caught the pointer-comparison bug in the first place — kept as evidence,
|
||||
not deleted, same as this project's established convention for a capture that found a real bug
|
||||
rather than just confirming success. All three architectures re-verified clean, nothing
|
||||
hot-attached: `logs/20260825-135600/amd64/`, `logs/20260825-140038/aarch64/`,
|
||||
`logs/20260825-140626/riscv64/`.
|
||||
|
||||
Milestone 2h is now complete: enumeration, BOT transport, TEST UNIT READY sequencing, READ
|
||||
CAPACITY(10), the `blkio_usb.c` backend, connect-time attach, and hot-detach are all live and
|
||||
verified on all three architectures. WRITE(10) remains unimplemented in the xHCI driver itself
|
||||
(2g's own still-open item) — a future increment, not blocking anything here.
|
||||
|
||||
### Milestone 3 — Block subsystem extensions (Section U items 3-6, Section V area A)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-25T16:48:38Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-25T18:05:55Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
+13
-5
@@ -58,11 +58,19 @@ carrying timestamp noise in git history.
|
||||
|
||||
- `usb-thumbdrive-test.img` — 64MB raw image backing a QEMU `usb-storage`
|
||||
device attached to the xHCI controller's bus (`xhci0.0`) for Milestone 2e/
|
||||
2h hotplug testing, added 2026-08-22. Not yet formatted with any
|
||||
LithosAnanke/Artemis header — at this point in the driver's development
|
||||
it only needs to exist as a backing store for a live Port Status Change
|
||||
event; formatting comes once `blkio_usb.c` and `blk_subsys_attach_device()`
|
||||
wiring exist (Milestone 2h).
|
||||
2h hotplug testing, added 2026-08-22. Blank (all zero) — `blkio_usb.c` +
|
||||
`blk_subsys_attach_device()` wiring (Milestone 2h, done 2026-08-25) attach
|
||||
it as `BLK_FMT_PROVISIONAL` every time, which is the intended, exercised
|
||||
state; not yet `BLK_FMT_FORMATTED` via `BLK-CONFIRM-FORMAT`.
|
||||
- `usb-thumbdrive-test2.img` — 64MB raw image, added 2026-08-25 for
|
||||
Milestone 2h hot-detach/re-attach verification. Filled with a repeating
|
||||
`HOTDETACH-REATTACH-FIXTURE-2026-08-25--` ASCII pattern, deliberately
|
||||
distinguishable from `usb-thumbdrive-test.img`'s all-zero content — the
|
||||
point is proving a block read *after* detaching `usb-thumbdrive-test.img`
|
||||
and re-attaching this one actually returns this pattern rather than
|
||||
silently replaying the old device's cached (all-zero) content, which is
|
||||
exactly the class of bug a same-LBN-range device swap can cause if the
|
||||
VM block window cache (`vm->blk_vm_cbuf[]`) isn't re-validated on a hit.
|
||||
|
||||
**Convention, standing as of 2026-08-22: every virtual disk/thumb-drive image
|
||||
used for testing — Artemis persistence disks above, and USB Mass Storage
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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);
|
||||
|
||||
@@ -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-
|
||||
|
||||
@@ -460,6 +460,8 @@ typedef struct VM
|
||||
uint8_t *blk_vm_cbuf[BLK_VM_SLOTS]; /* C buffer pointer from blk_get_buffer */
|
||||
uint8_t blk_vm_dirty[BLK_VM_SLOTS]; /* 1 = vm->memory modified since last sync */
|
||||
int blk_vm_next; /* Round-robin eviction cursor */
|
||||
uint64_t blk_vm_epoch; /* blk_subsys_epoch() as of last cache validation --
|
||||
* see block_words.c's blk_vm_find() */
|
||||
|
||||
/** @name Physics Hot-Words Cache
|
||||
* @{
|
||||
|
||||
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -152,6 +152,19 @@ static struct {
|
||||
uint64_t total_user_lbn; /* total user-visible LBNs across RAM + all device slots */
|
||||
blk_dev_slot_t *head; /* linked list of device slots */
|
||||
|
||||
/* Bumped on every attach/detach (Milestone 2h). Freeing a slot then
|
||||
* immediately allocating a new one for a same-LBN re-attach can hand
|
||||
* back the *same* heap address (confirmed live: glibc's allocator does
|
||||
* exactly this for a free() followed immediately by a same-size
|
||||
* calloc(), with nothing else allocated in between) -- so a raw
|
||||
* pointer comparison against a cached blk_get_buffer() result cannot
|
||||
* reliably detect "this LBN's device changed underneath a caller
|
||||
* holding a stale cached pointer." A monotonic epoch can't be fooled
|
||||
* by address reuse the way a pointer comparison was found to be (see
|
||||
* block_words.c's blk_vm_find(), the one place outside this file that
|
||||
* caches a blk_get_buffer() result across calls). */
|
||||
uint64_t epoch;
|
||||
|
||||
int initialized;
|
||||
} g = {0};
|
||||
|
||||
@@ -533,6 +546,7 @@ int blk_subsys_add_raw_device(uint8_t *buf, uint32_t nblocks) {
|
||||
|
||||
chain_append(slot);
|
||||
g.total_user_lbn += nblocks;
|
||||
g.epoch++;
|
||||
|
||||
log_message(LOG_INFO, "blk: raw device LBN %u..%u (%u blocks)",
|
||||
slot->start_lbn, slot->start_lbn + nblocks - 1, nblocks);
|
||||
@@ -562,6 +576,7 @@ int blk_subsys_attach_device(struct blkio_dev *dev) {
|
||||
|
||||
chain_append(slot);
|
||||
g.total_user_lbn += slot->user_blocks;
|
||||
g.epoch++;
|
||||
|
||||
log_message(LOG_INFO,
|
||||
"blk: disk '%s' v2 LBN %u..%u (%u user blocks); "
|
||||
@@ -576,6 +591,55 @@ int blk_subsys_attach_device(struct blkio_dev *dev) {
|
||||
return BLK_OK;
|
||||
}
|
||||
|
||||
/* Milestone 2h hot-detach. Deliberately refuses anything but the current
|
||||
* chain tail: block_subsystem.c's own architecture doc (top of this file)
|
||||
* has USB/future devices as the *last* link specifically so a removal
|
||||
* never has to renumber any other slot's start_lbn -- a mid-chain removal
|
||||
* would corrupt every later slot's LBN range, so this is refused outright
|
||||
* rather than attempted.
|
||||
*
|
||||
* Deliberately discards rather than flushes any dirty cache/BAM/vol_meta
|
||||
* state: the device is physically gone by the time this runs (called only
|
||||
* after a real PORTSC disconnect), so a flush attempt cannot succeed --
|
||||
* pretending to try would just call blkio_write() against a vanished
|
||||
* device for no benefit. Revisit if a future graceful-unmount path (as
|
||||
* opposed to a surprise removal) wants a best-effort flush first; today
|
||||
* every removal this driver can observe is a surprise removal.
|
||||
*/
|
||||
int blk_subsys_detach_device(struct blkio_dev *dev) {
|
||||
if (!g.initialized) return BLK_ENODEV;
|
||||
if (!dev) return BLK_EINVAL;
|
||||
|
||||
blk_dev_slot_t *prev = NULL;
|
||||
blk_dev_slot_t *s = g.head;
|
||||
while (s && s->dev != dev) { prev = s; s = s->next; }
|
||||
if (!s) return BLK_ENODEV;
|
||||
|
||||
if (s->next) {
|
||||
log_message(LOG_WARN, "blk: refusing detach of non-tail device (LBN %u..%u)",
|
||||
s->start_lbn, s->start_lbn + s->user_blocks - 1);
|
||||
return BLK_EINVAL;
|
||||
}
|
||||
|
||||
log_message(LOG_INFO, "blk: detaching disk '%s' LBN %u..%u (%u user blocks)",
|
||||
s->vol_meta.label, s->start_lbn, s->start_lbn + s->user_blocks - 1,
|
||||
s->user_blocks);
|
||||
|
||||
if (prev) prev->next = NULL; else g.head = NULL;
|
||||
g.total_user_lbn -= s->user_blocks;
|
||||
g.epoch++;
|
||||
|
||||
blkio_close(dev);
|
||||
if (s->bam) free(s->bam);
|
||||
free(s);
|
||||
|
||||
return BLK_OK;
|
||||
}
|
||||
|
||||
uint64_t blk_subsys_epoch(void) {
|
||||
return g.epoch;
|
||||
}
|
||||
|
||||
int blk_subsys_shutdown(void) {
|
||||
if (!g.initialized) return BLK_OK;
|
||||
|
||||
|
||||
+17
-3
@@ -98,18 +98,32 @@ static void sk_repl_idle(void)
|
||||
* busy-wait -- which blkio_usb_open_msc() uses internally -- must
|
||||
* never run from inside xhci_poll_events()'s own call frame). */
|
||||
xhci_dev_t *xdev = xhci_get_dev();
|
||||
static blkio_dev_t usb_blk_dev; /* single-device scope, matching the xHCI
|
||||
* driver's own; referenced by both the
|
||||
* attach and detach handling below. */
|
||||
if (xdev && xdev->bot_msc_attach_pending) {
|
||||
xdev->bot_msc_attach_pending = 0;
|
||||
uint32_t slot_id = xdev->bot_msc_attach_slot_id;
|
||||
|
||||
static blkio_dev_t usb_blk_dev;
|
||||
int rc = blkio_usb_open_msc(&usb_blk_dev, xdev, slot_id);
|
||||
if (rc == 0) {
|
||||
blk_subsys_attach_device(&usb_blk_dev);
|
||||
if (rc == 0 && blk_subsys_attach_device(&usb_blk_dev) == BLK_OK) {
|
||||
xdev->bot_msc_attached = 1;
|
||||
} else {
|
||||
console_println("xhci: USB MSC block-subsystem attach failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* Milestone 2h hot-detach: the device disconnected (PORTSC, inside the
|
||||
* xhci_poll_events() call above) after having actually attached.
|
||||
* blk_subsys_detach_device() is local block_subsystem.c bookkeeping --
|
||||
* no device round-trip, so it wouldn't strictly need to run outside
|
||||
* xhci_poll_events()'s own call frame -- but handling it here anyway
|
||||
* matches the attach path's shape and keeps xhci.c decoupled from
|
||||
* block_subsystem.c (see bot_msc_detach_pending's own doc comment). */
|
||||
if (xdev && xdev->bot_msc_detach_pending) {
|
||||
xdev->bot_msc_detach_pending = 0;
|
||||
blk_subsys_detach_device(&usb_blk_dev);
|
||||
}
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
|
||||
@@ -327,6 +327,8 @@ int xhci_bringup(xhci_dev_t *dev)
|
||||
dev->bot_cap_block_size = 0;
|
||||
dev->bot_msc_attach_pending = 0;
|
||||
dev->bot_msc_attach_slot_id = 0;
|
||||
dev->bot_msc_attached = 0;
|
||||
dev->bot_msc_detach_pending = 0;
|
||||
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
||||
dev->next_action_slot_id = 0;
|
||||
dev->next_action_length = 0;
|
||||
@@ -1141,6 +1143,15 @@ void xhci_poll_events(void)
|
||||
* the Disable Slot command below can be issued
|
||||
* right now. */
|
||||
dev->port_slot_id[port_id - 1] = 0;
|
||||
/* Milestone 2h hot-detach: fire on the disconnect
|
||||
* itself, independent of whether Disable Slot can
|
||||
* be sent right now -- see bot_msc_detach_pending's
|
||||
* own doc comment for why this point, not Disable
|
||||
* Slot completion. */
|
||||
if (dev->bot_msc_attached) {
|
||||
dev->bot_msc_attached = 0;
|
||||
dev->bot_msc_detach_pending = 1;
|
||||
}
|
||||
if (dev->connect_state == XHCI_CONN_IDLE) {
|
||||
dev->pending_disable_slot_id = disconnecting_slot_id;
|
||||
dev->connect_state = XHCI_CONN_AWAIT_DISABLE_SLOT;
|
||||
|
||||
@@ -155,8 +155,34 @@ void empty_all_buffers(VM *vm) {
|
||||
|
||||
/* --- Block I/O window helpers ----------------------------------------- */
|
||||
|
||||
/* Discard the whole VM block window if the device chain has changed since
|
||||
* it was last validated (Milestone 2h). A device hot-detach followed by a
|
||||
* later re-attach can reuse both the exact same LBN range
|
||||
* (block_subsystem.c's chain always appends at the current tail) *and* the
|
||||
* exact same blk_get_buffer() return address (confirmed live: glibc's
|
||||
* allocator hands the just-freed slot straight back to the very next
|
||||
* same-size calloc()), so neither LBN nor a cached pointer is a reliable
|
||||
* "still the same device" signal on its own. Any epoch change discards
|
||||
* every cached slot outright -- no flush attempt, matching
|
||||
* blk_subsys_detach_device()'s own reasoning: whatever device the stale
|
||||
* content belonged to may already be gone by the time this runs. Called at
|
||||
* the top of every function below that reads vm->blk_vm_lbn[]/
|
||||
* vm->blk_vm_cbuf[] directly, not just blk_vm_find() -- blk_vm_flush_all()
|
||||
* walks the same arrays without going through blk_vm_find() first. */
|
||||
static void blk_vm_check_epoch(VM *vm) {
|
||||
uint64_t epoch = blk_subsys_epoch();
|
||||
if (epoch == vm->blk_vm_epoch) return;
|
||||
for (int i = 0; i < BLK_VM_SLOTS; i++) {
|
||||
vm->blk_vm_lbn[i] = 0;
|
||||
vm->blk_vm_cbuf[i] = NULL;
|
||||
vm->blk_vm_dirty[i] = 0;
|
||||
}
|
||||
vm->blk_vm_epoch = epoch;
|
||||
}
|
||||
|
||||
/* Find the slot holding lbn; return slot index or -1 if not loaded. */
|
||||
static int blk_vm_find(VM *vm, uint32_t lbn) {
|
||||
blk_vm_check_epoch(vm);
|
||||
for (int i = 0; i < BLK_VM_SLOTS; i++) {
|
||||
if (vm->blk_vm_lbn[i] == lbn && vm->blk_vm_cbuf[i] != NULL)
|
||||
return i;
|
||||
@@ -240,8 +266,13 @@ static vaddr_t blk_vm_assign(VM *vm, uint32_t lbn) {
|
||||
|
||||
/* Sync all dirty slots to their C buffers and flush the subsystem.
|
||||
* Re-resolve each buffer pointer by LBN rather than trusting the stored
|
||||
* one -- see blk_vm_evict for why a stored pointer can go stale. */
|
||||
* one -- see blk_vm_evict for why a stored pointer can go stale. Checks
|
||||
* the epoch first (Milestone 2h, see blk_vm_check_epoch()) since this
|
||||
* function walks vm->blk_vm_lbn[]/vm->blk_vm_cbuf[] directly rather than
|
||||
* through blk_vm_find() -- a stale-epoch dirty slot must be discarded, not
|
||||
* flushed onto whatever device now owns that LBN. */
|
||||
static void blk_vm_flush_all(VM *vm) {
|
||||
blk_vm_check_epoch(vm);
|
||||
for (int i = 0; i < BLK_VM_SLOTS; i++) {
|
||||
if (vm->blk_vm_cbuf[i] != NULL && vm->blk_vm_dirty[i]) {
|
||||
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)i * BLOCK_SIZE;
|
||||
|
||||
Reference in New Issue
Block a user