FABRIC.md -> FABRIC-0.md FABRIC-2.md -> FABRIC-1.md FABRIC-3.md -> FABRIC-2.md (the current/living document) FABRIC-4.md unchanged (new #3 to follow separately) Every cross-reference repo-wide updated to match, including doc-comment citations inside kernel source (.c/.h) files -- done via an ordered placeholder substitution (FABRIC-3.md->placeholder2, FABRIC-2.md-> placeholder1, FABRIC.md->placeholder0, then placeholders resolved to final names) in a single pass per file to avoid double-shifting already-renamed references. One line in capsules/font.4th grew past the 64-char block-format limit as a side effect of the longer filename; shortened it and reverified with mkcapsule --lint (34/34 pass) before rebuilding. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground) after the fix; 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
564 lines
28 KiB
C
564 lines
28 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
This file is part of the StarForth project.
|
||
|
||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
|
||
You may obtain a copy of the License at:
|
||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||
|
||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
express or implied, including but not limited to the warranties of
|
||
merchantability, fitness for a particular purpose, and noninfringement.
|
||
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
This file is part of the StarForth project.
|
||
|
||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
|
||
You may obtain a copy of the License at:
|
||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||
|
||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
express or implied, including but not limited to the warranties of
|
||
merchantability, fitness for a particular purpose, and noninfringement.
|
||
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
|
||
*/
|
||
|
||
/*
|
||
*** StarForth ***
|
||
Block Subsystem - Layer 2: Mapping & Business Logic (v2)
|
||
---------------------------------------------------------
|
||
Architecture (unified block address space):
|
||
- LBN 0..2047: FAST RAM (volatile, g.ram_base)
|
||
- LBN 2048..x: RAMDRIVE (raw RAM buffer, volatile; first attached device)
|
||
- LBN x..y: DISK IMG (virtio-blk, persistent; second attached device)
|
||
- LBN y+: USB / future devices (chained)
|
||
|
||
Each non-RAM device is attached via blk_subsys_attach_device() (formatted disk)
|
||
or blk_subsys_add_raw_device() (volatile RAM buffer). Devices are appended to
|
||
a chain; each gets a contiguous slot in the unified logical BAM (g.bam[]).
|
||
Physical BAMs (on-disk) are synced to/from their logical BAM slot at attach/flush.
|
||
|
||
blkio NOTE:
|
||
- blkio backends operate on 1 KiB units.
|
||
- One 4 KiB “devblock” == 4 consecutive 1 KiB blkio blocks.
|
||
|
||
License: See LICENSE file. No warranty.
|
||
*/
|
||
#ifndef STARFORTH_BLOCK_SUBSYSTEM_H
|
||
#define STARFORTH_BLOCK_SUBSYSTEM_H
|
||
|
||
#include <stdint.h>
|
||
#include <stddef.h>
|
||
#include "vm.h"
|
||
#include "blkio.h" /* ensure struct blkio_dev is fully visible */
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* Core configuration constants */
|
||
#define BLK_FORTH_SIZE 1024u /* Forth block size */
|
||
#define BLK_RAM_BLOCKS 2080u /* Physical RAM blocks (user-visible LBN 0..2047) */
|
||
#define BLK_DISK_START 2080u /* Device-backed Forth blocks start at LBN 2080 */
|
||
#define BLK_DEVICE_SECTOR 4096u /* Physical “devblock” size (4×1 KiB blkio units) */
|
||
#define BLK_PACK_RATIO 3u /* 3× 1 KiB data per 4 KiB devblock (plus 1 KiB metadata) */
|
||
#define BLK_META_TOTAL 1024u /* Last 1 KiB in a 4 KiB devblock is metadata */
|
||
#define BLK_META_PER_BLOCK 341u /* 341×3 ~= 1023, padded to 1024 */
|
||
|
||
/* Forth-friendly reserved ranges */
|
||
#ifndef BLK_FORTH_SYS_RESERVED
|
||
#define BLK_FORTH_SYS_RESERVED 32u /* RAM physical blocks 0..31 hidden; user LBN 0 = PBN 32 */
|
||
#endif
|
||
#ifndef BLK_DISK_SYS_RESERVED
|
||
#define BLK_DISK_SYS_RESERVED 32u /* First 32 blocks of each disk reserved (byte-aligned BAM offset) */
|
||
#endif
|
||
#ifndef BLK_META_FENCE_INIT
|
||
#define BLK_META_FENCE_INIT 128u /* Starting size (4 KiB devblocks -- 512 KiB) of the
|
||
* top-of-device system-metadata fence, grows downward
|
||
* from here. */
|
||
#endif
|
||
|
||
/* =========================
|
||
* On-disk volume header v2
|
||
* =========================
|
||
* Serialized into device devblock 0 (4 KiB = 4×1 KiB blkio blocks 0..3).
|
||
* BAM lives in external devblocks [bam_start .. bam_start+bam_devblocks-1] (each 4 KiB).
|
||
* All *_devblocks indices are in 4 KiB units.
|
||
*/
|
||
typedef struct {
|
||
/* Identification & versioning */
|
||
uint32_t magic; /* 0x53544652 "STFR" */
|
||
uint32_t version; /* 2 */
|
||
|
||
/* Administrative info */
|
||
uint32_t total_volumes;
|
||
uint32_t flags;
|
||
char label[64];
|
||
|
||
/* Physical device geometry (4 KiB devblocks) */
|
||
uint64_t total_devblocks; /* count of 4 KiB devblocks (blkio_info.total_blocks / 4) */
|
||
|
||
/* BAM placement (external 1-bit bitmap region, stored in 4 KiB pages) */
|
||
uint32_t bam_start; /* usually 1 */
|
||
uint32_t bam_devblocks; /* number of 4 KiB pages used by BAM */
|
||
uint32_t devblock_base; /* first payload devblock = reloc_start + reloc_devblocks */
|
||
|
||
/* Capacity modeling (Forth 1 KiB blocks tracked/usable) */
|
||
uint64_t tracked_blocks; /* 32768 * bam_devblocks (bits per 4 KiB page) */
|
||
uint64_t total_blocks; /* min(tracked, 3 * (total_devblocks - 1 - bam_devblocks)) */
|
||
uint64_t free_blocks;
|
||
|
||
/* Allocation hints */
|
||
uint64_t first_free; /* next free Forth block (>= 1024) */
|
||
uint64_t last_allocated;
|
||
|
||
/* Reserved low ranges */
|
||
uint32_t reserved_disk_lo; /* e.g., 32 blocks reserved at 1024.. */
|
||
uint32_t reserved_ram_lo; /* e.g., 33 blocks reserved at 0..32 */
|
||
|
||
/* Timestamps (optional) */
|
||
uint64_t created_time;
|
||
uint64_t mounted_time;
|
||
|
||
/* Optional integrity (unused yet) */
|
||
uint64_t hdr_crc;
|
||
|
||
/* Relocation-exception table placement (Milestone 2h+ single-block relocation).
|
||
* Appended after hdr_crc, carved out of what was previously _pad[] -- appending
|
||
* (not inserting) preserves every earlier field's byte offset, so a pre-existing
|
||
* formatted volume's zeroed padding reads back here as reloc_devblocks=0 ("no
|
||
* reloc capacity"), gracefully, not a format-breaking change. See
|
||
* block_subsystem.c's reloc_flush_to_disk()/reloc_load_from_disk(). */
|
||
uint32_t reloc_start; /* usually bam_start + bam_devblocks */
|
||
uint32_t reloc_devblocks; /* number of 4 KiB pages used by the reloc table (0 = none) */
|
||
|
||
/* System-metadata fence (Phase 8, 2026-08-26): a reserved zone of
|
||
* whole, RAW (unpacked -- same shape as the header/BAM/reloc-table
|
||
* devblocks, not the 3-Forth-blocks-per-devblock user payload
|
||
* packing) devblocks at the TOP of the device's payload region,
|
||
* opposite end from reserved_disk_lo's bottom BAM reservation,
|
||
* growing DOWNWARD as system metadata (starting with Zuse's cert)
|
||
* needs more room. Never RAM-backed -- this field only exists on
|
||
* real disk-backed slots. Units are 4 KiB devblocks, same as
|
||
* bam_devblocks/reloc_devblocks (NOT Forth 1 KiB blocks -- corrected
|
||
* 2026-08-26, before anything depended on the original wrong unit,
|
||
* so this reads as a clean field, not a migration). Folded directly
|
||
* into compute_totals_from_B()'s payload4k calculation, so
|
||
* total_blocks/user_blocks/free_blocks all shrink correctly with no
|
||
* separate subtraction needed elsewhere. Appended after
|
||
* reloc_devblocks, carved out of what was previously _pad[] -- same
|
||
* graceful-default technique as reloc_devblocks itself: a
|
||
* pre-existing formatted volume's zeroed padding reads back here as
|
||
* meta_fence_blocks=0 ("no fence yet"), not a format-breaking
|
||
* change. See FABRIC-2.md's Phase 8 §C writeup for the full design. */
|
||
uint32_t meta_fence_blocks; /* current fence size, in 4 KiB devblocks (0 = none yet) */
|
||
|
||
/* Padding to keep header ≤ 4096 bytes */
|
||
uint8_t _pad[4096 - (
|
||
4 + 4 + /* magic, version */
|
||
4 + 4 + 64 + /* total_volumes, flags, label */
|
||
4 + /* compiler alignment gap before total_devblocks'
|
||
* uint64_t sibling tracked_blocks -- verified via
|
||
* offsetof(), not hand-derived (2026-08-26: this
|
||
* exact formula was off by 4 bytes from trusting
|
||
* arithmetic alone before this fix) */
|
||
8 + /* total_devblocks */
|
||
4 + 4 + 4 + /* bam_start, bam_devblocks, devblock_base */
|
||
8 + 8 + 8 + /* tracked_blocks, total_blocks, free_blocks */
|
||
8 + 8 + /* first_free, last_allocated */
|
||
4 + 4 + /* reserved ranges */
|
||
8 + 8 + /* timestamps */
|
||
8 + /* hdr_crc */
|
||
4 + 4 + /* reloc_start, reloc_devblocks */
|
||
4 /* meta_fence_blocks */
|
||
)];
|
||
} blk_volume_meta_t;
|
||
|
||
/* Same discipline homeblocks_sig.h's own header-size check uses: the pad
|
||
* math above is meant to keep this struct at exactly one 4 KiB devblock,
|
||
* verified at compile time rather than trusted by inspection -- adding
|
||
* meta_fence_blocks is exactly the kind of edit that could silently
|
||
* throw this off by a few bytes. */
|
||
_Static_assert(sizeof(blk_volume_meta_t) == 4096,
|
||
"blk_volume_meta_t must be exactly one 4 KiB devblock");
|
||
|
||
/* blk_meta_t.flags bit values -- FABRIC-2.md §F.4/§H.6/§H.12 step 13,
|
||
* decided 2026-09-02/03. Orthogonal bits, not a mutually-exclusive enum:
|
||
* a block can be both CLAIMED and MIGRATING at once. Grounded in the only
|
||
* states §F.4 actually motivated by a real need (MIGSM/UNCLEAN, two
|
||
* then-currently-blocked graph nodes) plus CLAIMED/STALE, the names
|
||
* already used loosely in that pass's own prose. 61 bits remain reserved. */
|
||
#define BLK_FLAG_CLAIMED (1ull << 0) /* owned, per BMAPFMT's owner_fp */
|
||
#define BLK_FLAG_MIGRATING (1ull << 1) /* mid-migration; serves MIGSM */
|
||
#define BLK_FLAG_STALE (1ull << 2) /* interrupted flush; serves UNCLEAN */
|
||
|
||
/* Per-1 KiB block metadata (packed into top 1 KiB region of each 4 KiB sector). */
|
||
typedef struct {
|
||
/* Core integrity (16 bytes) */
|
||
uint64_t checksum; /* CRC64 of block data */
|
||
uint64_t magic; /* 0x424C4B5F5354524BULL "BLK_STRK" */
|
||
|
||
/* Timestamps (16 bytes) */
|
||
uint64_t created_time; /* Unix timestamp (creation) */
|
||
uint64_t modified_time; /* Unix timestamp (last write) */
|
||
|
||
/* Block status (16 bytes) */
|
||
uint64_t flags; /* Status flags -- BLK_FLAG_* bits above */
|
||
uint64_t write_count; /* Number of writes (wear leveling) */
|
||
|
||
/* Content identification (32 bytes) */
|
||
uint64_t content_type; /* 0=empty, 1=source, 2=data, ... */
|
||
uint64_t encoding; /* 0=ASCII, 1=UTF-8, 2=binary, ... */
|
||
uint64_t content_length; /* Actual data length (≤ 1024) */
|
||
uint64_t reserved1; /* Alignment/future use */
|
||
|
||
/* Cryptographic (64 bytes) */
|
||
uint64_t entropy[4]; /* 256-bit entropy/random seed */
|
||
uint64_t hash[4]; /* SHA-256 (optional) */
|
||
|
||
/* Security & ownership -- FABRIC-2.md §F.4/§H.6/§H.12 step 12, decided
|
||
* 2026-08-27/2026-09-03: BMAPFMT repurposes this slot rather than
|
||
* building a separate on-drive block-map table (distributed
|
||
* ownership/ACL, travels with the block itself). Replaces the old
|
||
* owner_id/permissions/acl_block/signature[2] fields, which predated
|
||
* and directly conflicted with both the anti-POSIX principle and
|
||
* VMIdentity's pubkey-based model. Not the same 40-byte budget the
|
||
* old fields occupied -- natural alignment padding (uint32_t acl_ttl
|
||
* and uint64_t reserved_future each force a few bytes of compiler-
|
||
* inserted padding after the preceding uint8_t fields) makes this
|
||
* section's real footprint smaller; verified below via
|
||
* _Static_assert on the whole struct's actual sizeof(), not trusted
|
||
* by hand (see the blk_volume_meta_t padding-bug lesson this project
|
||
* already learned once). */
|
||
uint8_t owner_fp[8]; /* truncated fingerprint of owner's VMIdentity
|
||
* pubkey -- cheap per-block; full pubkey
|
||
* resolves via the drive's own identity
|
||
* record. */
|
||
uint8_t acl_allow; /* cached fast-deny bit, checked first --
|
||
* vm.c:611-624's exact pattern, applied to a
|
||
* block instead of a word. */
|
||
uint32_t acl_ttl; /* countdown, same shape as DictEntry's
|
||
* acl_ttl -- blocks support temporary
|
||
* elevation too, same ACL-TTL-reuse
|
||
* mechanism and Zuse-eligibility-list gating
|
||
* as the word card (§H.5). */
|
||
uint8_t acl_reserved[3]; /* still genuinely undecided -- deliberate
|
||
* slack per "flexibility until we understand
|
||
* the recipe," not a placeholder to fill
|
||
* reflexively. */
|
||
uint64_t reserved_future; /* untouched budget, same reasoning. */
|
||
|
||
/* Link/chain support (32 bytes) */
|
||
uint64_t prev_block; /* Previous in chain (0=none) */
|
||
uint64_t next_block; /* Next in chain (0=none) */
|
||
uint64_t parent_block; /* Parent/index (0=none) */
|
||
uint64_t chain_length; /* Total blocks in chain */
|
||
|
||
/* Application-specific (120 bytes) */
|
||
uint64_t app_data[15]; /* 15×64-bit app-defined fields */
|
||
|
||
uint8_t padding[5]; /* trailing slack, unrelated to any exact size target --
|
||
* the old "341-byte slice" comment here was already
|
||
* inaccurate before FABRIC-2.md §H.12 step 12's edit
|
||
* (sizeof(blk_meta_t) was 344, not 341, due to
|
||
* ordinary trailing struct-alignment padding after
|
||
* this array -- harmless since BLK_META_PER_BLOCK,
|
||
* the only thing that constant would matter to, has
|
||
* zero callers anywhere in this codebase). Verify
|
||
* this struct's real size with the _Static_assert
|
||
* below, not by re-deriving it from this comment. */
|
||
} blk_meta_t;
|
||
|
||
/* Verified via offsetof()/sizeof(), not trusted by hand -- see the
|
||
* blk_volume_meta_t padding-bug lesson this project already learned once
|
||
* (a hand-summed struct padding formula hid a real 4-byte alignment gap).
|
||
* 336, not the BLK_META_PER_BLOCK/"341-byte slice" figure this struct's
|
||
* own comments have long claimed -- that mismatch predates this assert and
|
||
* is harmless today (see padding[5]'s own comment above), but this assert
|
||
* now makes any future drift in either direction fail the build instead of
|
||
* silently mismatching a constant nothing currently checks against it. */
|
||
_Static_assert(sizeof(blk_meta_t) == 336,
|
||
"blk_meta_t size changed -- update this assert and check BLK_META_PER_BLOCK");
|
||
|
||
/* Error codes */
|
||
enum {
|
||
BLK_OK = 0,
|
||
BLK_EINVAL = -1,
|
||
BLK_ERANGE = -2,
|
||
BLK_EIO = -3,
|
||
BLK_ENODEV = -4,
|
||
BLK_ERESERVED = -5,
|
||
BLK_EDIRTY = -6,
|
||
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 */
|
||
typedef struct {
|
||
uint8_t allocated; /* 0=free, 1=in use */
|
||
uint8_t dirty; /* 1=UPDATE called; content needs flush */
|
||
} blk_bam_entry_t;
|
||
|
||
/* Low-level disk container format state. This is distinct from — and known
|
||
* nothing about — any higher-level content classification (e.g. Artemis's
|
||
* own BLANK/LithosAnanke/Unrecognized marker check). A slot stays
|
||
* PROVISIONAL, and all writes to it are refused, until its owner explicitly
|
||
* calls blk_subsys_confirm_format() after deciding the disk is safe to
|
||
* touch. This is what makes "halt, disk preserved" actually true instead
|
||
* of being overwritten by this layer's own opportunistic reformat before
|
||
* the owner ever gets a look. */
|
||
enum { BLK_FMT_FORMATTED = 0, BLK_FMT_PROVISIONAL = 1 };
|
||
|
||
/* ===== Public API ===== */
|
||
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 (this kernel's own first-fit kmalloc,
|
||
* src/starkernel/memory/kmalloc.c, can hand back the exact address just
|
||
* free()'d by a detach to the very next attach's calloc() -- confirmed
|
||
* live) -- 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);
|
||
|
||
/* Single-block relocation. Copies home_lbn's current content to target_lbn
|
||
* (both must already be valid LBNs -- target_lbn is expected to be a block
|
||
* the caller's own identity already owns, on whichever device it's being
|
||
* relocated to; this function does not itself validate ownership, that's
|
||
* policy, left to the caller -- see this session's ACL-owns-policy
|
||
* direction in FABRIC-1.md), frees home_lbn's original BAM entry, records
|
||
* an LBN->LBN redirect so every future access to home_lbn transparently
|
||
* resolves to target_lbn instead, and bumps blk_subsys_epoch() so any VM's
|
||
* cached block window correctly invalidates. Persisted immediately to the
|
||
* relocation-owner device's on-disk table (see block_subsystem.c's
|
||
* reloc_flush_to_disk()) if one exists.
|
||
*
|
||
* Returns BLK_OK on success.
|
||
* Returns BLK_EINVAL if home_lbn == target_lbn, or home_lbn is already
|
||
* relocated (call again with a different target to re-relocate --
|
||
* not supported by simply calling this twice on the same home_lbn).
|
||
* Returns BLK_ERANGE if either LBN doesn't resolve to a valid device.
|
||
* Returns BLK_EIO if the content copy fails (e.g. target device refuses
|
||
* the write -- a read-only backend like blkio_usb.c today).
|
||
* Returns BLK_ENOMEM if the in-memory relocation table is full.
|
||
*/
|
||
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-2.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-2.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-2.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-2.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);
|
||
|
||
uint8_t *blk_get_empty_buffer(uint32_t block_num);
|
||
|
||
int blk_flush(uint32_t block_num);
|
||
|
||
int blk_update(uint32_t block_num);
|
||
|
||
int blk_get_volume_meta(blk_volume_meta_t *meta);
|
||
|
||
int blk_set_volume_meta(const blk_volume_meta_t *meta);
|
||
|
||
/* Top-of-device system-metadata fence I/O (Phase 8, 2026-08-26) -- raw,
|
||
* unpacked 4 KiB devblocks, no FORTH word wraps either. See
|
||
* block_subsystem.c's own doc comment on these two functions for the
|
||
* full addressing/refusal rules. buf must point to exactly 4096 bytes.
|
||
* Returns BLK_OK, BLK_ENODEV (no disk-backed device attached),
|
||
* BLK_EINVAL (devblock_from_top >= the on-disk fence size, or NULL buf),
|
||
* or BLK_EIO (underlying blkio read/write failed). */
|
||
int blk_meta_zone_read(uint32_t devblock_from_top, uint8_t buf[4096]);
|
||
|
||
int blk_meta_zone_write(uint32_t devblock_from_top, const uint8_t buf[4096]);
|
||
|
||
/* CRC-64/ISO (poly 0x42F0E1EBA9EA3693), reflected, init/final all-ones --
|
||
* exposed for homeblocks_sig.c's drive-signature integrity check, which
|
||
* needs the exact same algorithm this file already uses for per-block
|
||
* checksums rather than a second, duplicate CRC implementation. */
|
||
uint64_t compute_crc64(const uint8_t *data, size_t len);
|
||
|
||
int blk_is_valid(uint32_t block_num);
|
||
|
||
uint32_t blk_get_total_blocks(void);
|
||
|
||
int blk_get_meta(uint32_t block_num, blk_meta_t *meta);
|
||
|
||
int blk_set_meta(uint32_t block_num, const blk_meta_t *meta);
|
||
|
||
/* BMAPFMT field accessors -- FABRIC-2.md §F.4/§H.6/§H.12 step 14. Thin
|
||
* read-modify-write wrappers over blk_get_meta()/blk_set_meta() (which
|
||
* already own the caching/dirty-tracking), one per new blk_meta_t field.
|
||
* FORTH wrappers (BLK-ACL-ALLOW@/! etc., §H.12 step 15) call these, not
|
||
* blk_get_meta()/blk_set_meta() directly -- same C-primitive/FORTH-policy
|
||
* split as the existing word-level ACL system. */
|
||
int blk_owner_fp_get(uint32_t block_num, uint8_t out_fp[8]);
|
||
int blk_owner_fp_set(uint32_t block_num, const uint8_t fp[8]);
|
||
|
||
int blk_acl_allow_get(uint32_t block_num, uint8_t *out_allow);
|
||
int blk_acl_allow_set(uint32_t block_num, uint8_t allow);
|
||
|
||
int blk_acl_ttl_get(uint32_t block_num, uint32_t *out_ttl);
|
||
int blk_acl_ttl_set(uint32_t block_num, uint32_t ttl);
|
||
|
||
int blk_flags_get(uint32_t block_num, uint64_t *out_flags);
|
||
int blk_flags_set(uint32_t block_num, uint64_t flags);
|
||
|
||
int blk_is_allocated(uint32_t block_num);
|
||
|
||
int blk_mark_allocated(uint32_t block_num);
|
||
|
||
int blk_mark_free(uint32_t block_num);
|
||
|
||
int blk_allocate(uint32_t * block_num);
|
||
|
||
int blk_subsys_add_raw_device(uint8_t *buf, uint32_t nblocks);
|
||
|
||
/* Commit the low-level format (write header + BAM) for the disk slot that
|
||
* owns lbn. No-op (returns BLK_OK) if already FORMATTED. Must be called
|
||
* by the disk's owner before any write to that slot will succeed. */
|
||
int blk_subsys_confirm_format(uint32_t lbn);
|
||
|
||
#ifdef __cplusplus
|
||
} /* extern "C" */
|
||
#endif
|
||
|
||
#endif /* STARFORTH_BLOCK_SUBSYSTEM_H */ |