Files
LithosAnanake/include/block_subsystem.h
T
Robert Allan JamesandClaude Opus 5 c19cc07ee3 §H.12 step 13: blk_meta_t flags bit constants
BLK_FLAG_CLAIMED/BLK_FLAG_MIGRATING/BLK_FLAG_STALE (bits 0/1/2), matching
the decided §F.4/§H.6 layout. Orthogonal bits, not a mutually-exclusive
enum.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 07:23:27 -04:00

435 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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) 20232025 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-3.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-3.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-3.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-3.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
};
/* 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-2.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);
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);
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 */