Files
LithosAnanake/include/block_subsystem.h
T
Robert Allan JamesandClaude Sonnet 5 af267a52a6 Artemis Milestone 2h: hot-detach -- 2h complete
blk_subsys_detach_device() (block_subsystem.c) walks the device chain,
refuses removal of anything but the current tail (a mid-chain removal
would corrupt every later slot's start_lbn -- this architecture's own doc
already argues USB stays last specifically to avoid that), unlinks,
shrinks total_user_lbn, closes and frees the slot. Discards rather than
flushes dirty state -- the device is physically gone by the time this
runs (PORTSC disconnect only). Trigger wiring mirrors the attach path:
bot_msc_attached (set only once attach actually succeeds) gates a new
bot_msc_detach_pending flag set at PORTSC disconnect (not Disable Slot
completion, which is conditionally skipped and would miss concurrent
connect/disconnect pairs), consumed in sk_repl_idle().

Advisor flagged the real hazard ahead of time: block_words.c's VM block
window (blk_vm_lbn[]/blk_vm_cbuf[]) can go stale across a detach then a
same-LBN re-attach, and suggested a pointer-identity re-check in
blk_vm_load() as a minimal fix. That fix was implemented, then directly
falsified by its own designed-for-this test: attach a blank device, read
a block (populating the cache), detach, re-attach a device with distinct
content at the identical LBN, read again -- served stale content from
the first device. Root cause, confirmed live: glibc's allocator hands
free(slot) straight back to the very next same-size calloc(), so the
"fresh" and stale pointers were bitwise identical despite being two
different devices. Fixed properly with a monotonic blk_subsys_epoch()
counter (bumped on every attach/detach) checked by a new
blk_vm_check_epoch() helper at the one choke point (blk_vm_find(), plus
blk_vm_flush_all() which reads the same arrays directly) that covers
every path touching the window cache -- unfooled by address reuse.

Verified live with a new disk/usb-thumbdrive-test2.img fixture (distinct
content from the existing blank test image): attach A, read (cache hit
populated), detach, re-attach B at the same LBN, read again -- correctly
ran a fresh device read and returned B's real content, not A's stale
cached zeros. The failing pointer-comparison attempt's own capture log
kept as evidence, not deleted. All three architectures re-verified clean.
FABRIC-2.md Section X 2h marked complete -- enumeration through
hot-detach all live and verified; only WRITE(10) (2g's own still-open
item) remains unimplemented in the driver, not blocking anything here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
2026-08-25 14:10:05 -04:00

289 lines
11 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
/* =========================
* 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 = bam_start + bam_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;
/* Padding to keep header ≤ 4096 bytes */
uint8_t _pad[4096 - (
4 + 4 + /* magic, version */
4 + 4 + 64 + /* total_volumes, flags, label */
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 */
)];
} blk_volume_meta_t;
/* 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 (glibc's allocator can hand back the
* exact address just free()'d by a detach to the very next attach's
* calloc()) -- 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);
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);
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 */