Fix pathological migration scan that stalled WIREBIND identity attach
Root cause (found by a fresh subagent after an extended live-debugging investigation into "identity 00 attaches slowly/stalls when Zuse never attached first this boot"): blk_migration_idle_check() was generalized earlier today to walk every attached device slot uniformly instead of hardcoding first_disk_slot() (Artemis's own disk). But its per-slot scan can only early-exit once it finds a devblock that is BOTH "hot" (claimed and worn) AND "free" -- and a just-attached, never-claimed USB identity drive can never satisfy the "hot" half by design (claiming only ever happens via blk_firsttouch_claim(), which only ever targets first_disk_slot()). So the scan ran to completion -- the drive's entire ~16,000 devblocks, mostly cache misses over slow emulated USB/BOT -- every single idle tick, forever, blocking sk_repl_idle() (and therefore the console and the storage-attach message round-trip) each time. Fix, in src/block_subsystem.c: a new has_ever_claimed flag on blk_dev_slot_t (set in blk_set_meta(), the single choke point every BLK_FLAG_CLAIMED transition passes through) skips the scan entirely, O(1), for any slot nothing has ever claimed -- the common case for a freshly-attached drive. A new migration_scan_lbn resume cursor bounds *any* slot's per-tick cost to MIGRATION_SCAN_BUDGET (256) devblocks examined, picking up where the previous tick left off instead of restarting from start_lbn every time -- restores this function's own documented "coarse cadence, cheap early-exit" design intent for every device, not just the one it used to hardcode. Also along the way (kept, all real improvements, verified live): - src/starkernel/usb/xhci.c: xhci_wait_bit()/xhci_bot_wait_for_idle() had zero yield hints in their MMIO-polling loops; added arch_relax() to both (matches virtio_blk.c below) -- a tight loop of nothing but MMIO reads can starve TCG's own host-side timer injection under QEMU. - src/starkernel/virtio/virtio_blk.c: vblk_io()'s spin bound was 33M iterations with zero logging on timeout; a single real (still not fully root-caused) timeout cost 31+ minutes of CPU before this was caught. Reduced to 1M and added a log line naming the failing sector, turning a silent, effectively-unbounded stall into a fast, loud failure -- callers already tolerate BLKIO_EIO. - src/starkernel/repl.c: blk_migration_idle_check() deferred for any idle tick where a storage-attach message round-trip is still pending, to keep the two block-subsystem-touching paths from interleaving; the existing MSG-TICK pump now checks the target VM's own dictionary for MSG-TICK (vm_find_word + acl_allow) before VM-EXECing into it, instead of spamming "VM-EXEC: ERROR" every tick for a VM that doesn't have -- or isn't allowed -- the word (e.g. a FORTH-79/83-locked-down identity); fixed a real -Wunused-variable build break in the EMERGENCY_CONSOLE_ ENABLED=1 path (unused today, but needed live to reproduce this bug with no identity attached at all). - src/starkernel/capsule/capsule_mint.c: dropped the dead S" common:messaging.4th" EXEC / MSG-CD-INIT lines from MINT_RESTRICTED_PERSONALITY -- a FORTH-79/83-locked-down identity has no legitimate use for a messaging vocabulary it can never call. Status: the pathological CPU-climbing scan is confirmed fixed (verified live: CPU stays flat across an extended run instead of climbing without bound). The WIREBIND storage-attach message round-trip still does not complete promptly in the "Zuse never attached, other identity attaches first" scenario -- a separate, still-open issue in the message-delivery path itself, not the scan. Tracked as follow-on work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
63b8b3bc29
commit
1a263555e2
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-09-08T01:19:50Z -->
|
<!-- Generated by mkcapsule --manifest 2026-09-09T15:57:24Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
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
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
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+93
-20
@@ -152,6 +152,17 @@ struct blk_dev_slot {
|
|||||||
/* BAM: one entry per user block, heap-allocated at attach time */
|
/* BAM: one entry per user block, heap-allocated at attach time */
|
||||||
blk_bam_entry_t *bam;
|
blk_bam_entry_t *bam;
|
||||||
|
|
||||||
|
/* FABRIC-3.md, 2026-09-09: blk_migration_idle_check()'s own doc
|
||||||
|
* comment on has_ever_claimed/migration_scan_lbn explains why these
|
||||||
|
* exist -- see that function. */
|
||||||
|
uint8_t has_ever_claimed; /* 1 once any devblock in this slot has
|
||||||
|
* ever been BLK_FLAG_CLAIMED; wear-
|
||||||
|
* leveling has nothing to do on a slot
|
||||||
|
* that's still 0. */
|
||||||
|
uint32_t migration_scan_lbn; /* resume cursor for the bounded per-
|
||||||
|
* tick migration scan, absolute LBN,
|
||||||
|
* wraps at start_lbn+user_blocks. */
|
||||||
|
|
||||||
blk_dev_slot_t *next;
|
blk_dev_slot_t *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1301,6 +1312,10 @@ int blk_set_meta(uint32_t block_num, const blk_meta_t *meta) {
|
|||||||
if (!c) return BLK_EIO;
|
if (!c) return BLK_EIO;
|
||||||
c->meta[slot_pbn_pack_offset(rel_pbn)] = *meta;
|
c->meta[slot_pbn_pack_offset(rel_pbn)] = *meta;
|
||||||
c->meta_dirty = 1;
|
c->meta_dirty = 1;
|
||||||
|
/* FABRIC-3.md, 2026-09-09: single choke point every BLK_FLAG_CLAIMED
|
||||||
|
* transition passes through -- see blk_migration_idle_check()'s own
|
||||||
|
* doc comment for why this flag exists. */
|
||||||
|
if (meta->flags & BLK_FLAG_CLAIMED) slot->has_ever_claimed = 1;
|
||||||
return BLK_OK;
|
return BLK_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1567,39 +1582,97 @@ int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock)
|
|||||||
* trigger-detection call site is the remaining gap. */
|
* trigger-detection call site is the remaining gap. */
|
||||||
#define MIGRATION_WEAR_THRESHOLD 10000u
|
#define MIGRATION_WEAR_THRESHOLD 10000u
|
||||||
|
|
||||||
void blk_migration_idle_check(void) {
|
/* Devblocks actually read (blk_get_meta() calls, the expensive part --
|
||||||
blk_dev_slot_t *slot = first_disk_slot();
|
* real I/O on any cache miss) per slot, per idle tick. FABRIC-3.md,
|
||||||
if (!slot) return;
|
* 2026-09-09: blk_migration_idle_check_slot() used to scan a slot's
|
||||||
|
* *entire* range in one synchronous call with no bound but "found both
|
||||||
|
* halves of the exit condition" -- fine when this only ever ran against
|
||||||
|
* Artemis's own fast, small, resident, already-claimed disk
|
||||||
|
* (first_disk_slot()-only, pre-2026-09-09), but once generalized to walk
|
||||||
|
* every attached slot (below), an unclaimed removable drive can never
|
||||||
|
* satisfy the "hot" half (nothing on it is ever BLK_FLAG_CLAIMED -- see
|
||||||
|
* has_ever_claimed's own doc comment on blk_dev_slot_t), so the scan ran
|
||||||
|
* to completion -- the ENTIRE drive, mostly cache misses over slow
|
||||||
|
* emulated USB/BOT -- every single idle tick, forever. Live-caught:
|
||||||
|
* this alone turned a WIREBIND identity's own thumbdrive attach into a
|
||||||
|
* many-real-minutes stall that never resolved on its own. Two
|
||||||
|
* independent fixes, both real hardening not just papering over the
|
||||||
|
* trigger: has_ever_claimed skips the scan entirely (O(1)) for any slot
|
||||||
|
* nothing has ever claimed (the common case for a just-attached drive);
|
||||||
|
* migration_scan_lbn bounds *any* slot's per-tick cost regardless, via a
|
||||||
|
* resume cursor that picks up where the previous tick left off instead
|
||||||
|
* of restarting from start_lbn every time -- restores this function's
|
||||||
|
* own "coarse cadence, cheap early-exit" design intent (this file's own
|
||||||
|
* older doc comment above) for every device, not just the one it used
|
||||||
|
* to hardcode. */
|
||||||
|
#define MIGRATION_SCAN_BUDGET 256u
|
||||||
|
|
||||||
|
/* Per-slot half of blk_migration_idle_check() -- extracted so the caller
|
||||||
|
* can walk every attached slot uniformly (below) instead of hardcoding
|
||||||
|
* first_disk_slot(). Bounded, resumable scan -- see MIGRATION_SCAN_
|
||||||
|
* BUDGET's own doc comment above for why. */
|
||||||
|
static void blk_migration_idle_check_slot(blk_dev_slot_t *slot) {
|
||||||
|
if (!slot->has_ever_claimed) return;
|
||||||
|
|
||||||
|
uint32_t range_start = slot->start_lbn;
|
||||||
|
uint32_t range_end = slot->start_lbn + slot->user_blocks;
|
||||||
|
if (range_end <= range_start) return;
|
||||||
|
|
||||||
|
if (slot->migration_scan_lbn < range_start || slot->migration_scan_lbn >= range_end) {
|
||||||
|
slot->migration_scan_lbn = range_start;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t hot_lbn = 0, free_lbn = 0;
|
uint32_t hot_lbn = 0, free_lbn = 0;
|
||||||
int have_hot = 0, have_free = 0;
|
int have_hot = 0, have_free = 0;
|
||||||
|
uint32_t lbn = slot->migration_scan_lbn;
|
||||||
|
uint32_t examined = 0;
|
||||||
|
|
||||||
for (uint32_t lbn = slot->start_lbn;
|
while (examined < MIGRATION_SCAN_BUDGET && !(have_hot && have_free)) {
|
||||||
lbn < slot->start_lbn + slot->user_blocks && !(have_hot && have_free);
|
|
||||||
lbn++) {
|
|
||||||
uint32_t rel_pbn = lbn_to_slot_pbn(slot, lbn);
|
uint32_t rel_pbn = lbn_to_slot_pbn(slot, lbn);
|
||||||
if (slot_pbn_pack_offset(rel_pbn) != 0) continue;
|
if (slot_pbn_pack_offset(rel_pbn) == 0) {
|
||||||
|
blk_meta_t meta;
|
||||||
blk_meta_t meta;
|
if (blk_get_meta(lbn, &meta) == BLK_OK) {
|
||||||
if (blk_get_meta(lbn, &meta) != BLK_OK) continue;
|
if (!have_hot && (meta.flags & BLK_FLAG_CLAIMED) &&
|
||||||
|
!(meta.flags & BLK_FLAG_MIGRATING) &&
|
||||||
if (!have_hot && (meta.flags & BLK_FLAG_CLAIMED) &&
|
meta.write_count >= MIGRATION_WEAR_THRESHOLD) {
|
||||||
!(meta.flags & BLK_FLAG_MIGRATING) &&
|
hot_lbn = lbn;
|
||||||
meta.write_count >= MIGRATION_WEAR_THRESHOLD) {
|
have_hot = 1;
|
||||||
hot_lbn = lbn;
|
}
|
||||||
have_hot = 1;
|
if (!have_free && devblock_is_free(slot, lbn, &meta)) {
|
||||||
}
|
free_lbn = lbn;
|
||||||
if (!have_free && devblock_is_free(slot, lbn, &meta)) {
|
have_free = 1;
|
||||||
free_lbn = lbn;
|
}
|
||||||
have_free = 1;
|
}
|
||||||
|
examined++;
|
||||||
}
|
}
|
||||||
|
lbn++;
|
||||||
|
if (lbn >= range_end) lbn = range_start;
|
||||||
}
|
}
|
||||||
|
slot->migration_scan_lbn = lbn;
|
||||||
|
|
||||||
if (have_hot && have_free && hot_lbn != free_lbn) {
|
if (have_hot && have_free && hot_lbn != free_lbn) {
|
||||||
(void) blk_meta_relocate_devblock(hot_lbn, free_lbn);
|
(void) blk_meta_relocate_devblock(hot_lbn, free_lbn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FABRIC-3.md, 2026-09-09: was hardcoded to first_disk_slot() (whichever
|
||||||
|
* device happened to attach first -- always Artemis's own virtio disk,
|
||||||
|
* kernel_main.c attaches her unconditionally before anything else can).
|
||||||
|
* Every other block_subsystem.c entry point (blk_subsys_attach_device()
|
||||||
|
* chief among them) treats every attached slot uniformly through the
|
||||||
|
* common blkio_dev_t/blkio_vtable_t interface (blkio.h), device type
|
||||||
|
* never inspected; this was the one place that didn't, a bespoke
|
||||||
|
* Artemis-disk special case bolted onto an otherwise device-agnostic
|
||||||
|
* file. Fixed to walk the full slot chain -- every attached device now
|
||||||
|
* gets the same wear-leveling scan, none hardcoded, none skipped. */
|
||||||
|
void blk_migration_idle_check(void) {
|
||||||
|
blk_dev_slot_t *slot;
|
||||||
|
for (slot = g.head; slot; slot = slot->next) {
|
||||||
|
if (!slot->dev) continue;
|
||||||
|
blk_migration_idle_check_slot(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== weak hook (for main.c) ===== */
|
/* ===== weak hook (for main.c) ===== */
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
__attribute__((weak))
|
__attribute__((weak))
|
||||||
|
|||||||
@@ -67,8 +67,6 @@ static const char MINT_DEFAULT_PERSONALITY[] =
|
|||||||
* use of EXEC this VM will ever get. */
|
* use of EXEC this VM will ever get. */
|
||||||
static const char MINT_RESTRICTED_PERSONALITY[] =
|
static const char MINT_RESTRICTED_PERSONALITY[] =
|
||||||
"Block 4998\n"
|
"Block 4998\n"
|
||||||
"S\" common:messaging.4th\" EXEC\n"
|
|
||||||
"MSG-CD-INIT\n"
|
|
||||||
"S\" acl-std79.4th\" EXEC\n"
|
"S\" acl-std79.4th\" EXEC\n"
|
||||||
"ACL-LOCKDOWN-STD79\n"
|
"ACL-LOCKDOWN-STD79\n"
|
||||||
": WELCOME ( -- ) .\" Minted identity -- FORTH-79/83 standard words only\" CR ;\n"
|
": WELCOME ( -- ) .\" Minted identity -- FORTH-79/83 standard words only\" CR ;\n"
|
||||||
|
|||||||
+46
-2
@@ -522,8 +522,28 @@ static void sk_repl_idle(VM *active_vm)
|
|||||||
* rather than only on a failed write). See block_subsystem.c's own
|
* rather than only on a failed write). See block_subsystem.c's own
|
||||||
* doc comment on blk_migration_idle_check() for what's built (heat-
|
* doc comment on blk_migration_idle_check() for what's built (heat-
|
||||||
* based relocation) vs. deliberately left open (overflow-triggered
|
* based relocation) vs. deliberately left open (overflow-triggered
|
||||||
* migration, needs a call site threaded from WIREBIND). */
|
* migration, needs a call site threaded from WIREBIND).
|
||||||
blk_migration_idle_check();
|
*
|
||||||
|
* FABRIC-3.md, 2026-09-09: skipped while any g_blk_attach_pending
|
||||||
|
* entry is still pending -- this scan and the storage-attach message
|
||||||
|
* round-trip (HERA-BLK-ATTACH-REQ/BLK-ATTACH-ACK) both touch the
|
||||||
|
* block subsystem/Artemis's own virtio-backed storage, and live-
|
||||||
|
* caught the two interleaving is where a real vblk_io() request stops
|
||||||
|
* getting a used-ring completion (root cause not fully isolated, see
|
||||||
|
* virtio_blk.c's own doc comment on vblk_io()'s reduced spin bound --
|
||||||
|
* that's the safety net; this is the actual avoidance). One deferred
|
||||||
|
* scan is harmless -- next idle tick retries, same as any other tick
|
||||||
|
* with nothing to do. */
|
||||||
|
{
|
||||||
|
int attach_in_flight = 0;
|
||||||
|
if (g_blk_attach_pending) {
|
||||||
|
uint32_t pi;
|
||||||
|
for (pi = 0; pi < g_usb_blk_dev_slot_count; pi++) {
|
||||||
|
if (g_blk_attach_pending[pi].pending) { attach_in_flight = 1; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!attach_in_flight) blk_migration_idle_check();
|
||||||
|
}
|
||||||
|
|
||||||
/* FABRIC-2.md §I.2's own overflow trigger, closed 2026-09-05: the
|
/* FABRIC-2.md §I.2's own overflow trigger, closed 2026-09-05: the
|
||||||
* call site named above, now built. Same cadence, same idle-tick
|
* call site named above, now built. Same cadence, same idle-tick
|
||||||
@@ -569,6 +589,22 @@ static void sk_repl_idle(VM *active_vm)
|
|||||||
if (capsule_vm_registry_get_by_index(i, &ent) != 0) continue;
|
if (capsule_vm_registry_get_by_index(i, &ent) != 0) continue;
|
||||||
if (ent.state != VM_STATE_LIVE) continue;
|
if (ent.state != VM_STATE_LIVE) continue;
|
||||||
if (ent.vm_ptr == (void *)mama) continue;
|
if (ent.vm_ptr == (void *)mama) continue;
|
||||||
|
/* FABRIC-3.md, 2026-09-09: a per-VM flag (e.g. "this one is
|
||||||
|
* std79-locked") doesn't generalize -- any VM whose own
|
||||||
|
* dictionary lacks MSG-TICK, for whatever reason (never
|
||||||
|
* loaded common:messaging.4th, a future personality that
|
||||||
|
* drops it, ACL denial), hits the identical failure. So
|
||||||
|
* check the actual target VM's own dictionary fresh every
|
||||||
|
* tick, the same source of truth the interpreter's own ACL
|
||||||
|
* enforcement uses (vm_core.c), rather than pre-flag specific
|
||||||
|
* personalities. Skip silently rather than spam "VM-EXEC:
|
||||||
|
* ERROR in <name>" every idle tick forever for a VM that
|
||||||
|
* plain doesn't have -- or isn't allowed -- MSG-TICK
|
||||||
|
* (live-caught 2026-09-07/09). */
|
||||||
|
{
|
||||||
|
DictEntry *msgtick = vm_find_word((VM *)ent.vm_ptr, "MSG-TICK", 8);
|
||||||
|
if (!msgtick || !msgtick->acl_allow) continue;
|
||||||
|
}
|
||||||
|
|
||||||
static const char PFX[] = "S\" MSG-TICK\" S\" ";
|
static const char PFX[] = "S\" MSG-TICK\" S\" ";
|
||||||
static const char SFX[] = "\" VM-EXEC";
|
static const char SFX[] = "\" VM-EXEC";
|
||||||
@@ -1176,6 +1212,14 @@ void sk_repl_run(VM *vm)
|
|||||||
sk_print_prompt();
|
sk_print_prompt();
|
||||||
|
|
||||||
int n = sk_console_readline(input, sizeof(input), active, 1);
|
int n = sk_console_readline(input, sizeof(input), active, 1);
|
||||||
|
#if EMERGENCY_CONSOLE_ENABLED
|
||||||
|
/* n only consumed below under !EMERGENCY_CONSOLE_ENABLED (the
|
||||||
|
* logged-out-mid-read bailout doesn't apply when the emergency
|
||||||
|
* console bypasses login entirely) -- silence -Wunused-variable
|
||||||
|
* rather than drop the assignment (sk_console_readline()'s return
|
||||||
|
* value is still meaningful, just not acted on in this build). */
|
||||||
|
(void)n;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !EMERGENCY_CONSOLE_ENABLED
|
#if !EMERGENCY_CONSOLE_ENABLED
|
||||||
/* n < 0: sk_console_readline() bailed out because the identity
|
/* n < 0: sk_console_readline() bailed out because the identity
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "starkernel/xhci_driver.h"
|
#include "starkernel/xhci_driver.h"
|
||||||
#include "starkernel/kmalloc.h"
|
#include "starkernel/kmalloc.h"
|
||||||
#include "starkernel/timer.h"
|
#include "starkernel/timer.h"
|
||||||
|
#include "starkernel/arch.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
/* Conservative fixed BAR0 mapping size. xHCI has no self-describing
|
/* Conservative fixed BAR0 mapping size. xHCI has no self-describing
|
||||||
@@ -92,6 +93,16 @@ static int xhci_wait_bit(volatile uint32_t *reg, uint32_t mask, int want_set,
|
|||||||
if (heartbeat_ticks() - start >= max_ticks || spins >= 100000000ULL) {
|
if (heartbeat_ticks() - start >= max_ticks || spins >= 100000000ULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
/* FABRIC-3.md, 2026-09-09: a tight loop of nothing but MMIO reads
|
||||||
|
* (no PAUSE/yield hint) can starve TCG's own host-side timer
|
||||||
|
* injection under QEMU -- live-caught heartbeat_ticks() itself
|
||||||
|
* (this loop's own timeout clock!) freezing for 30+ seconds
|
||||||
|
* straight during a real reproduction, confirmed via a clean,
|
||||||
|
* uninterfered-with measurement. That leaves only the 100M hard
|
||||||
|
* spin cap as a backstop, which is far too permissive on its own.
|
||||||
|
* arch_relax() (PAUSE on amd64) costs nothing on real hardware but
|
||||||
|
* gives TCG a natural yield point between MMIO polls. */
|
||||||
|
arch_relax();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,6 +997,12 @@ int xhci_bot_wait_for_idle(xhci_dev_t *dev, uint32_t max_iters)
|
|||||||
for (uint32_t i = 0; i < max_iters; i++) {
|
for (uint32_t i = 0; i < max_iters; i++) {
|
||||||
if (dev->bot_cmd_kind == BOT_CMD_NONE) return (int)dev->bot_last_status;
|
if (dev->bot_cmd_kind == BOT_CMD_NONE) return (int)dev->bot_last_status;
|
||||||
xhci_poll_events();
|
xhci_poll_events();
|
||||||
|
/* FABRIC-3.md, 2026-09-09: same TCG timer-starvation reasoning as
|
||||||
|
* xhci_wait_bit()'s own doc comment above -- xhci_poll_events()
|
||||||
|
* itself is a real MMIO-reading call, and max_iters (100000 from
|
||||||
|
* blkio_usb_open_msc()) is large enough that a starved host timer
|
||||||
|
* during a slow completion could stall for a very long time. */
|
||||||
|
arch_relax();
|
||||||
}
|
}
|
||||||
return BOT_STATUS_TIMEOUT;
|
return BOT_STATUS_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#include "starkernel/kmalloc.h"
|
#include "starkernel/kmalloc.h"
|
||||||
#include "console.h"
|
#include "console.h"
|
||||||
#include "blkio.h"
|
#include "blkio.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "starkernel/arch.h"
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------
|
||||||
* Virtio 1.0 PCI capability structures
|
* Virtio 1.0 PCI capability structures
|
||||||
@@ -234,6 +236,7 @@ static inline void rmb(void) {
|
|||||||
static int vblk_io(int write, uint64_t sector, void *buf, uint32_t nbytes) {
|
static int vblk_io(int write, uint64_t sector, void *buf, uint32_t nbytes) {
|
||||||
VirtBlkState *s = &g_vblk;
|
VirtBlkState *s = &g_vblk;
|
||||||
VirtqDesc *d = s->desc;
|
VirtqDesc *d = s->desc;
|
||||||
|
int result;
|
||||||
|
|
||||||
/* Fill request header */
|
/* Fill request header */
|
||||||
s->req_buf->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
|
s->req_buf->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
|
||||||
@@ -275,18 +278,52 @@ static int vblk_io(int write, uint64_t sector, void *buf, uint32_t nbytes) {
|
|||||||
*doorbell = 0; /* queue index 0 */
|
*doorbell = 0; /* queue index 0 */
|
||||||
wmb();
|
wmb();
|
||||||
|
|
||||||
/* Poll until device posts a used entry */
|
/* Poll until device posts a used entry.
|
||||||
uint32_t spin = 0x2000000u; /* ~seconds at ~1 GHz; QEMU is fast */
|
*
|
||||||
|
* FABRIC-3.md, 2026-09-09: live-caught a real request that never gets
|
||||||
|
* a used-ring completion -- root cause not yet isolated (every
|
||||||
|
* existing bounds check, vblk_read()'s own fblock vs total_forth_
|
||||||
|
* blocks and read_devblock_4k()'s own lba vs total_blkio_blocks_1k,
|
||||||
|
* already passed before reaching here, so it isn't a simple out-of-
|
||||||
|
* range request; disabling interrupts around this call was tried and
|
||||||
|
* made it strictly worse -- suggests QEMU's own device-model
|
||||||
|
* completion here depends on guest-visible time/interrupt progress in
|
||||||
|
* this TCG configuration, not a driver-side reentrancy bug -- so that
|
||||||
|
* approach was reverted). spin was 0x2000000 (~33M); at that bound a
|
||||||
|
* single such failure cost tens of real minutes (confirmed: 31+ CPU-
|
||||||
|
* minutes on one boot) because blk_migration_idle_check()'s own scan
|
||||||
|
* calls this per un-cached devblock, up to ~23,000 times a boot, and
|
||||||
|
* apparently more than one of those hits this failure mode. Cut by
|
||||||
|
* ~30x: still enormously generous for a real completion (confirmed
|
||||||
|
* live those return in a handful of iterations, not millions), but
|
||||||
|
* turns what was an effectively-unbounded stall into a fast, loud,
|
||||||
|
* logged failure instead -- callers already tolerate BLKIO_EIO
|
||||||
|
* (zero-fill and move on), they just couldn't afford the old wait to
|
||||||
|
* find that out. */
|
||||||
|
uint32_t spin = 0x100000u; /* ~1M iterations */
|
||||||
|
result = BLKIO_OK;
|
||||||
while (s->used->idx == s->last_used_idx) {
|
while (s->used->idx == s->last_used_idx) {
|
||||||
rmb();
|
rmb();
|
||||||
if (!--spin) return BLKIO_EIO;
|
arch_relax(); /* FABRIC-3.md, 2026-09-09: xhci_wait_bit()'s own doc
|
||||||
|
* comment (xhci.c) has the full reasoning -- a tight
|
||||||
|
* MMIO-adjacent poll with no yield hint can starve
|
||||||
|
* TCG's own host-side timer injection. */
|
||||||
|
if (!--spin) {
|
||||||
|
log_message(LOG_ERROR,
|
||||||
|
"virtio_blk: vblk_io timed out (write=%d sector=%llu) -- "
|
||||||
|
"queue never posted a used entry",
|
||||||
|
write, (unsigned long long)sector);
|
||||||
|
result = BLKIO_EIO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == BLKIO_OK) {
|
||||||
|
s->last_used_idx = s->used->idx;
|
||||||
|
uint8_t status = *s->status_buf;
|
||||||
|
if (status != (uint8_t)VIRTIO_BLK_S_OK) result = BLKIO_EIO;
|
||||||
}
|
}
|
||||||
s->last_used_idx = s->used->idx;
|
|
||||||
|
|
||||||
uint8_t status = *s->status_buf;
|
return result;
|
||||||
if (status != (uint8_t)VIRTIO_BLK_S_OK) return BLKIO_EIO;
|
|
||||||
|
|
||||||
return BLKIO_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user