/* 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 #include #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 (Forth 1 KiB blocks) 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 at the * TOP of the device's Forth block space, 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. 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 Forth 1 KiB blocks (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"); /* 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 */ 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 (40 bytes) */ uint64_t owner_id; /* User/process ID */ uint64_t permissions; /* rwx-style permissions */ uint64_t acl_block; /* Block number containing ACL (0=none) */ uint64_t signature[2]; /* 128-bit signature */ /* 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 */ /* Padding to reach 341-byte slice */ uint8_t padding[5]; } blk_meta_t; /* 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); /* 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 */