FABRIC-3.md §I.2: FIRSTTOUCH + migration state machine (blk_meta_relocate_devblock)

Closes the block-subsystem punch-list item -- built exactly to §F.11's
already-decided algorithm after re-verifying it against current
blk_meta_t (a 2026-09-03 re-scoping note had wrongly claimed the chain
fields no longer existed; they do, untouched by BMAPFMT).

blk_firsttouch_claim(): one linear scan of Artemis's own device
(new blk_get_first_disk_range(), correctly bounding the scan instead of
the global multi-device LBN space), scattered-chain claim via
prev_block/next_block/chain_length, owner_fp stamped on every member
devblock, fails outright with no partial claim.

blk_meta_relocate_devblock(): the real migration primitive -- bridges
the existing FORTH-block-granularity blk_subsys_relocate_block() up to
devblock granularity (BLK_PACK_RATIO=3, corrected mid-design), running
it 3x and transferring blk_meta_t ownership fields. The "migration
state machine" turned out to be just the 2 states BLK_FLAG_MIGRATING
already reserved; the real design work was the trigger. Two were
scoped in conversation (overflow onto Artemis; heat-based wear
leveling); heat/wear-leveling is built and wired into sk_repl_idle()
via blk_meta_t.write_count. Overflow is deliberately left open,
precisely scoped (needs a slot-lookup-by-device-pointer call site
threaded from WIREBIND) rather than guessed at.

Also flagged, not fixed: BMAPFMT's owner_fp/CLAIMED and the pre-existing
BAM allocator are two parallel, unreconciled accounting systems --
FIRSTTOUCH/relocate only touch the former.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground); logs and DoE CSVs from this session's verification runs
included per this repo's own audit-artifact convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
Robert Allan James
2026-09-04 10:33:47 -04:00
co-authored by Claude Sonnet 5
parent 1d468a65b1
commit 4018fe8b04
13 changed files with 27992 additions and 17 deletions
+112 -1
View File
@@ -306,7 +306,8 @@ enum {
BLK_ENODEV = -4,
BLK_ERESERVED = -5,
BLK_EDIRTY = -6,
BLK_ENOMEM = -7
BLK_ENOMEM = -7,
BLK_ENOSPC = -8 /* not enough free devblocks -- blk_firsttouch_claim() */
};
/* Per-block BAM entry — one per user block in each device slot */
@@ -374,6 +375,116 @@ uint64_t blk_subsys_epoch(void);
*/
int blk_subsys_relocate_block(uint32_t home_lbn, uint32_t target_lbn);
/* blk_get_device_range - the LBN range (start_lbn, count of 1 KiB FORTH
* blocks) a specific already-attached device occupies (FABRIC-3.md §I.2,
* 2026-09-04). Mirrors blk_subsys_detach_device()'s own dev-pointer slot
* lookup, exposed publicly for the first time -- needed so a caller can
* scope a scan/query to exactly one attached device rather than the
* global, multi-device LBN space blk_get_total_blocks()/blk_get_meta()
* otherwise address. Returns BLK_ENODEV if dev isn't attached.
*/
int blk_get_device_range(struct blkio_dev *dev, uint32_t *out_start_lbn, uint32_t *out_count);
/* blk_get_device_free_blocks - free vs. total 1 KiB FORTH-block count for
* a specific already-attached device (same slot lookup as
* blk_get_device_range()). RAM-backed slots (no on-disk vol_meta) report
* out_total == out_free == 0, matching blk_mark_allocated()/_free()'s own
* "!slot->raw_base" gate on touching free_blocks bookkeeping at all.
* Returns BLK_ENODEV if dev isn't attached.
*/
int blk_get_device_free_blocks(struct blkio_dev *dev, uint64_t *out_free, uint64_t *out_total);
/* blk_get_first_disk_range - the LBN range of first_disk_slot(), i.e.
* whichever disk-backed device blk_get_volume_meta()/blk_meta_zone_read()
* already implicitly target -- Artemis's own disk.img in practice, since
* it attaches at boot before any USB hotplug (the same boot-order
* convention those two functions already rely on, not a new assumption).
* FIRSTTOUCH (below) scans exactly this range, not the global LBN space.
* Returns BLK_ENODEV if no disk-backed device is attached at all.
*/
int blk_get_first_disk_range(uint32_t *out_start_lbn, uint32_t *out_count);
/* blk_firsttouch_claim - FABRIC-3.md §F.11, decisions 1-3, built exactly
* as scoped 2026-08-27 (re-verified against current blk_meta_t 2026-09-04
* before building -- the chain fields below are real and untouched by
* BMAPFMT's unrelated owner_fp/acl_allow/acl_ttl/flags field addition).
*
* One linear scan over blk_get_first_disk_range()'s devblocks (4 KiB
* granularity -- one blk_meta_t per devblock, not per 1 KiB FORTH block),
* via blk_get_meta(). Answers both questions the scan needs in the same
* pass: does owner_fp already own a claim (not reused by this call --
* every call makes a *new* claim of exactly `count` additional devblocks,
* per §F.11's own text; an existing claim is a separate, not-yet-decided
* question), and which devblocks are free (owner_fp all-zero, BLK_FLAG_
* CLAIMED clear). A claim is a scattered chain, not a required contiguous
* run -- links devblocks via blk_meta_t's prev_block/next_block/
* chain_length fields; owner_fp is stamped onto *every* member devblock,
* not just the head, so ownership reads locally from any member without
* walking the chain. Fails outright (BLK_ENOSPC), no partial claim, if
* fewer than `count` free devblocks exist.
*
* @param owner_fp 8-byte fingerprint (this session's convention: the
* first 8 bytes of the claiming identity's 32-byte
* VMIdentity.owner_pubkey -- owner_fp had no real
* producer anywhere in this codebase before this,
* only accessors, so this call site sets the
* convention rather than following one).
* @param count Devblocks to claim (whole devblocks, §F.4 decision
* 4, already inherited, not revisited here).
* @param out_chain_head The claim's identity to the caller: its
* chain-head devblock number (a 1 KiB FORTH-block
* LBN, the representative/first sub-block of that
* devblock -- not a separate devblock-index
* namespace; blk_get_meta()/blk_set_meta() already
* resolve any LBN within a devblock to the same
* shared blk_meta_t, so this is a valid handle for
* blk_meta_relocate_devblock() below).
* @return BLK_OK, BLK_ENODEV (no disk-backed device), BLK_EINVAL (NULL
* arg or count == 0), or BLK_ENOSPC (not enough free devblocks).
*/
int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *out_chain_head);
/* blk_meta_relocate_devblock - the real migration primitive (FABRIC-3.md
* §I.2, migration state machine, 2026-09-04): moves one devblock's full
* content and blk_meta_t ownership from home_devblock to target_devblock.
* BLK_FLAG_MIGRATING is set on home_devblock for the duration -- the
* state machine genuinely is just this one flag's two states (clear =
* STABLE, set = MIGRATING), matching what BLK_FLAG_MIGRATING was already
* reserved for.
*
* Devblock granularity (4 KiB) holds BLK_PACK_RATIO (3, not 4 -- the 4th
* 1 KiB slice is the packed blk_meta_t metadata region) FORTH blocks
* (1 KiB each); the actual content copy runs blk_subsys_relocate_block()
* BLK_PACK_RATIO times, once per FORTH-block offset within the devblock
* -- that function is the only existing content-relocation primitive,
* and it operates at FORTH-block granularity, not devblock granularity,
* so this function is the bridge between the two. On success, transfers
* owner_fp/acl_allow/acl_ttl from home's blk_meta_t to target's, clears
* home's (freed, BLK_FLAG_CLAIMED unset), and sets BLK_FLAG_CLAIMED on
* target.
*
* NOT atomic across all BLK_PACK_RATIO FORTH-block copies -- a mid-loop
* failure (e.g. blk_subsys_relocate_block()'s own reloc-table
* exhaustion) leaves however many already succeeded in place, redirected, with
* BLK_FLAG_MIGRATING still set on home_devblock and blk_meta_t ownership
* NOT yet transferred. This is a known, documented limitation, not
* silently accepted: true all-or-nothing atomicity across BLK_PACK_RATIO
* separate relocation-table entries would need its own transaction log, out of
* scope for this pass. A caller that hits a non-BLK_OK return should
* treat home_devblock as still MIGRATING and not yet safe to reclaim.
*
* @return BLK_OK, BLK_EINVAL (home == target, or invalid devblock), or
* whichever blk_subsys_relocate_block() error stopped the loop.
*/
int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock);
/* blk_migration_idle_check - heat/wear-leveling migration trigger, meant
* to be called once per idle tick (FABRIC-3.md §I.2, 2026-09-04). See
* block_subsystem.c's own doc comment for the full scan/threshold
* description and what's deliberately left open (overflow-triggered
* migration). */
void blk_migration_idle_check(void);
int blk_subsys_shutdown(void);
uint8_t *blk_get_buffer(uint32_t block_num, int writable);