Stage MC-0b2: block subsystem behind g_lock_block (FABRIC-3.10.8 §3.3)

- block_subsystem.c (vendored): every public function's body becomes a
  static *_locked function; bodies call each other only through the
  _locked forms, breaking the internal cycles (relocate, meta relocate,
  migration scan, firsttouch, the meta accessors). Thin public wrappers
  take g_lock_block under __STARKERNEL__; hosted build has no lock and
  still builds clean.
- Lock order: hermes, switch, registry, doe, log, block, console, heap,
  vmm, pmm (Stadium, MC-0d, above block).
- Documented, not coverable by this lock: blk_get_buffer() pointers are
  used after release -- block-buffer ownership across cores is open for
  MC-3.
- Probe (removed): block lock balances on all three ISAs (2,057-2,379
  acquisitions), 0 recursions, 0 held at switch; vblk_io timeouts
  (0/0/4 probe, 0/0/2 acceptance) within the pre-lock riscv64 baseline
  of 0-6.
- Standard sequence on amd64/aarch64/riscv64: hashes and G byte-identical
  to MC-0b, MINT/WIREBIND/USE ok, conservation true, 0 idle growth.
- FABRIC-3.10.8 §3.2 wording fix; lock order noted as checked by
  inspection only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-25 18:57:38 -04:00
co-authored by Claude Opus 5.5
parent 5bc7b4dee7
commit 33f4090d6d
16 changed files with 55305 additions and 82 deletions
+244 -80
View File
@@ -58,6 +58,54 @@
#include <stdlib.h>
#include <stdint.h>
/* ===== locking (FABRIC-3.10.8 Stage MC-0b2) =====
*
* The kernel build serializes this whole subsystem -- the device chain,
* every slot's cache and BAM, the relocation table, and the device I/O
* issued on their behalf -- behind one spinlock, g_lock_block
* (starkernel/spinlock.h; below the log and DoE regions, above the console
* and heap in the lock order). The hosted build has no second thread and
* no lock.
*
* Every public function is a thin wrapper at the bottom of this file that
* takes the lock around its *_locked body. Bodies call each other only
* through the *_locked forms (this file's public functions call one
* another in cycles: set/get meta, the migration scan, relocation), so the
* lock is never taken twice.
*
* Not covered, and cannot be by a lock here: blk_get_buffer() returns a
* pointer into a RAM block, a ramdrive, or a device cache slot, which the
* caller reads and writes after the lock is released. On one core nothing
* can evict that slot in between; with worker cores (MC-3) it can. */
#ifdef __STARKERNEL__
#include "starkernel/spinlock.h"
#define BLK_LOCK() sk_spin_lock(&g_lock_block)
#define BLK_UNLOCK() sk_spin_unlock(&g_lock_block)
#else
#define BLK_LOCK() ((void) 0)
#define BLK_UNLOCK() ((void) 0)
#endif
struct blkio_dev;
static int blk_subsys_init_locked(VM *vm, uint8_t *ram_base, size_t ram_size);
static int blk_subsys_add_raw_device_locked(uint8_t *buf, uint32_t nblocks);
static int blk_subsys_attach_device_locked(struct blkio_dev *dev);
static int blk_subsys_detach_device_locked(struct blkio_dev *dev);
static int blk_subsys_relocate_block_locked(uint32_t home_lbn, uint32_t target_lbn);
static int blk_subsys_shutdown_locked(void);
static int blk_subsys_confirm_format_locked(uint32_t lbn);
static uint8_t *blk_get_buffer_locked(uint32_t block_num, int writable);
static int blk_update_locked(uint32_t block_num);
static int blk_flush_locked(uint32_t block_num);
static int blk_is_allocated_locked(uint32_t block_num);
static int blk_mark_allocated_locked(uint32_t block_num);
static int blk_mark_free_locked(uint32_t block_num);
static int blk_allocate_locked(uint32_t *block_num);
static int blk_is_valid_locked(uint32_t block_num);
static int blk_get_meta_locked(uint32_t block_num, blk_meta_t *meta);
static int blk_set_meta_locked(uint32_t block_num, const blk_meta_t *meta);
static int blk_meta_relocate_devblock_locked(uint32_t home_devblock, uint32_t target_devblock);
/* ===== compile-time config ===== */
#ifndef BLK_FORTH_SYS_RESERVED
# define BLK_FORTH_SYS_RESERVED 32u
@@ -644,7 +692,7 @@ static inline uint64_t blk_get_timestamp(void) {
/* ===== public API ===== */
int blk_subsys_init(VM *vm, uint8_t *ram_base, size_t ram_size) {
static int blk_subsys_init_locked(VM *vm, uint8_t *ram_base, size_t ram_size) {
if (!vm || !ram_base || ram_size < ((size_t) BLK_RAM_BLOCKS * BLK_FORTH_SIZE))
return BLK_EINVAL;
memset(&g, 0, sizeof(g));
@@ -659,7 +707,7 @@ int blk_subsys_init(VM *vm, uint8_t *ram_base, size_t ram_size) {
return BLK_OK;
}
int blk_subsys_add_raw_device(uint8_t *buf, uint32_t nblocks) {
static int blk_subsys_add_raw_device_locked(uint8_t *buf, uint32_t nblocks) {
if (!g.initialized) return BLK_ENODEV;
if (!buf || nblocks == 0) return BLK_EINVAL;
@@ -685,7 +733,7 @@ int blk_subsys_add_raw_device(uint8_t *buf, uint32_t nblocks) {
static blk_dev_slot_t *first_disk_slot(void); /* defined below; used here to identify the
* relocation-table owner right after attach */
int blk_subsys_attach_device(struct blkio_dev *dev) {
static int blk_subsys_attach_device_locked(struct blkio_dev *dev) {
if (!g.initialized) return BLK_ENODEV;
if (!dev) return BLK_EINVAL;
@@ -752,7 +800,7 @@ int blk_subsys_attach_device(struct blkio_dev *dev) {
* opposed to a surprise removal) wants a best-effort flush first; today
* every removal this driver can observe is a surprise removal.
*/
int blk_subsys_detach_device(struct blkio_dev *dev) {
static int blk_subsys_detach_device_locked(struct blkio_dev *dev) {
if (!g.initialized) return BLK_ENODEV;
if (!dev) return BLK_EINVAL;
@@ -788,7 +836,7 @@ int blk_subsys_detach_device(struct blkio_dev *dev) {
* job) or validate that target_lbn is genuinely owned by whoever is
* asking (also ACL's job) -- it just executes one, correctly, once told
* to. */
int blk_subsys_relocate_block(uint32_t home_lbn, uint32_t target_lbn) {
static int blk_subsys_relocate_block_locked(uint32_t home_lbn, uint32_t target_lbn) {
if (!g.initialized) return BLK_ENODEV;
if (home_lbn == target_lbn) return BLK_EINVAL;
@@ -803,7 +851,7 @@ int blk_subsys_relocate_block(uint32_t home_lbn, uint32_t target_lbn) {
}
if (g.reloc_count >= BLK_RELOC_MAX_ENTRIES) return BLK_ENOMEM;
if (!blk_is_valid(home_lbn) || !blk_is_valid(target_lbn)) return BLK_ERANGE;
if (!blk_is_valid_locked(home_lbn) || !blk_is_valid_locked(target_lbn)) return BLK_ERANGE;
/* Same-device only (Phase 8 v3, 2026-09-23): this function's own purpose
* is relocating a block to a new LBN on the device it's being relocated
@@ -824,21 +872,21 @@ int blk_subsys_relocate_block(uint32_t home_lbn, uint32_t target_lbn) {
* device's cache array, if home_lbn and target_lbn happen to share a
* device. Not a hypothetical: this is exactly the class of stale
* pointer this file's own blk_vm_evict() comment already warns about. */
uint8_t *src = blk_get_buffer(home_lbn, 0);
uint8_t *src = blk_get_buffer_locked(home_lbn, 0);
if (!src) return BLK_EIO;
uint8_t staged[BLK_FORTH_SIZE];
memcpy(staged, src, BLK_FORTH_SIZE);
uint8_t *dst = blk_get_buffer(target_lbn, 1);
uint8_t *dst = blk_get_buffer_locked(target_lbn, 1);
if (!dst) return BLK_EIO;
memcpy(dst, staged, BLK_FORTH_SIZE);
if (blk_update(target_lbn) != BLK_OK) return BLK_EIO;
if (blk_update_locked(target_lbn) != BLK_OK) return BLK_EIO;
/* Free home_lbn's original backing block -- must happen before the
* redirect is inserted below, while resolve_lbn(home_lbn) still
* resolves to itself; inserting the redirect first would make this
* call free target_lbn instead. */
(void) blk_mark_free(home_lbn);
(void) blk_mark_free_locked(home_lbn);
g.reloc[g.reloc_count].home_lbn = home_lbn;
g.reloc[g.reloc_count].actual_lbn = target_lbn;
@@ -851,14 +899,10 @@ int blk_subsys_relocate_block(uint32_t home_lbn, uint32_t target_lbn) {
return BLK_OK;
}
uint64_t blk_subsys_epoch(void) {
return g.epoch;
}
int blk_subsys_shutdown(void) {
static int blk_subsys_shutdown_locked(void) {
if (!g.initialized) return BLK_OK;
blk_flush(0);
blk_flush_locked(0);
blk_dev_slot_t *s = g.head;
while (s) {
@@ -889,7 +933,7 @@ int blk_subsys_shutdown(void) {
* decided it is safe to touch (e.g. Artemis's ART-BOOT-DETECT resolving
* to BLANK or a recognized marker). Must NOT be called on the path that
* halts for unrecognized content — that is what keeps the disk untouched. */
int blk_subsys_confirm_format(uint32_t lbn) {
static int blk_subsys_confirm_format_locked(uint32_t lbn) {
if (!g.initialized) return BLK_ENODEV;
blk_dev_slot_t *slot = lbn_to_slot(lbn);
if (!slot || !slot->dev) return BLK_ERANGE;
@@ -898,7 +942,7 @@ int blk_subsys_confirm_format(uint32_t lbn) {
/* ===== block buffer access ===== */
uint8_t *blk_get_buffer(uint32_t block_num, int writable) {
static uint8_t *blk_get_buffer_locked(uint32_t block_num, int writable) {
if (!g.initialized) return NULL;
block_num = resolve_lbn(block_num);
@@ -935,13 +979,13 @@ uint8_t *blk_get_buffer(uint32_t block_num, int writable) {
return c->data + pack * BLK_FORTH_SIZE;
}
uint8_t *blk_get_empty_buffer(uint32_t block_num) {
uint8_t *p = blk_get_buffer(block_num, 1);
static uint8_t *blk_get_empty_buffer_locked(uint32_t block_num) {
uint8_t *p = blk_get_buffer_locked(block_num, 1);
if (p) memset(p, 0, BLK_FORTH_SIZE);
return p;
}
int blk_update(uint32_t block_num) {
static int blk_update_locked(uint32_t block_num) {
if (!g.initialized) return BLK_ENODEV;
block_num = resolve_lbn(block_num);
@@ -997,7 +1041,7 @@ int blk_update(uint32_t block_num) {
return BLK_OK;
}
int blk_flush(uint32_t block_num) {
static int blk_flush_locked(uint32_t block_num) {
if (!g.initialized) return BLK_ENODEV;
if (block_num > 0) block_num = resolve_lbn(block_num); /* 0 is the "flush all" sentinel */
@@ -1052,7 +1096,7 @@ int blk_flush(uint32_t block_num) {
/* ===== BAM wrappers ===== */
int blk_is_allocated(uint32_t block_num) {
static int blk_is_allocated_locked(uint32_t block_num) {
if (!g.initialized) return BLK_ENODEV;
block_num = resolve_lbn(block_num);
if (block_num < g.ram_user) return BLK_EINVAL;
@@ -1061,7 +1105,7 @@ int blk_is_allocated(uint32_t block_num) {
return slot->bam[block_num - slot->start_lbn].allocated ? 1 : 0;
}
int blk_mark_allocated(uint32_t block_num) {
static int blk_mark_allocated_locked(uint32_t block_num) {
if (!g.initialized) return BLK_ENODEV;
block_num = resolve_lbn(block_num);
if (block_num < g.ram_user) return BLK_EINVAL;
@@ -1080,7 +1124,7 @@ int blk_mark_allocated(uint32_t block_num) {
return BLK_OK;
}
int blk_mark_free(uint32_t block_num) {
static int blk_mark_free_locked(uint32_t block_num) {
if (!g.initialized) return BLK_ENODEV;
block_num = resolve_lbn(block_num);
if (block_num < g.ram_user) return BLK_EINVAL;
@@ -1125,11 +1169,11 @@ static int devblock_claimed_by_lbn(blk_dev_slot_t *slot, uint32_t lbn) {
uint32_t offset = lbn - slot->start_lbn;
if (offset < pack) return 0;
blk_meta_t meta;
if (blk_get_meta(lbn - pack, &meta) != BLK_OK) return 0;
if (blk_get_meta_locked(lbn - pack, &meta) != BLK_OK) return 0;
return (meta.flags & BLK_FLAG_CLAIMED) != 0;
}
int blk_allocate(uint32_t *block_num) {
static int blk_allocate_locked(uint32_t *block_num) {
if (!block_num) return BLK_EINVAL;
if (!g.initialized) return BLK_ENODEV;
@@ -1170,7 +1214,7 @@ static blk_dev_slot_t *first_disk_slot(void) {
return NULL;
}
int blk_get_volume_meta(blk_volume_meta_t *meta) {
static int blk_get_volume_meta_locked(blk_volume_meta_t *meta) {
if (!meta) return BLK_EINVAL;
blk_dev_slot_t *slot = first_disk_slot();
if (!slot) return BLK_ENODEV;
@@ -1178,7 +1222,7 @@ int blk_get_volume_meta(blk_volume_meta_t *meta) {
return BLK_OK;
}
int blk_set_volume_meta(const blk_volume_meta_t *meta) {
static int blk_set_volume_meta_locked(const blk_volume_meta_t *meta) {
if (!meta) return BLK_EINVAL;
blk_dev_slot_t *slot = first_disk_slot();
if (!slot) return BLK_ENODEV;
@@ -1197,7 +1241,7 @@ static blk_dev_slot_t *slot_by_dev(struct blkio_dev *dev) {
return NULL;
}
int blk_get_device_range(struct blkio_dev *dev, uint32_t *out_start_lbn, uint32_t *out_count) {
static int blk_get_device_range_locked(struct blkio_dev *dev, uint32_t *out_start_lbn, uint32_t *out_count) {
if (!dev || !out_start_lbn || !out_count) return BLK_EINVAL;
blk_dev_slot_t *slot = slot_by_dev(dev);
if (!slot) return BLK_ENODEV;
@@ -1206,11 +1250,11 @@ int blk_get_device_range(struct blkio_dev *dev, uint32_t *out_start_lbn, uint32_
return BLK_OK;
}
const void *blk_lbn_device_handle(uint32_t lbn) {
static const void *blk_lbn_device_handle_locked(uint32_t lbn) {
return (const void *) lbn_to_slot(resolve_lbn(lbn));
}
int blk_get_device_free_blocks(struct blkio_dev *dev, uint64_t *out_free, uint64_t *out_total) {
static int blk_get_device_free_blocks_locked(struct blkio_dev *dev, uint64_t *out_free, uint64_t *out_total) {
if (!dev || !out_free || !out_total) return BLK_EINVAL;
blk_dev_slot_t *slot = slot_by_dev(dev);
if (!slot) return BLK_ENODEV;
@@ -1220,7 +1264,7 @@ int blk_get_device_free_blocks(struct blkio_dev *dev, uint64_t *out_free, uint64
return BLK_OK;
}
int blk_get_first_disk_range(uint32_t *out_start_lbn, uint32_t *out_count) {
static int blk_get_first_disk_range_locked(uint32_t *out_start_lbn, uint32_t *out_count) {
if (!out_start_lbn || !out_count) return BLK_EINVAL;
blk_dev_slot_t *slot = first_disk_slot();
if (!slot) return BLK_ENODEV;
@@ -1241,7 +1285,7 @@ int blk_get_first_disk_range(uint32_t *out_start_lbn, uint32_t *out_count) {
* No FORTH word wraps this -- C-only, same discipline as
* vm_zuse_cert_install() itself; Zuse's cert is this zone's first tenant.
*/
int blk_meta_zone_read(uint32_t devblock_from_top, uint8_t buf[4096]) {
static int blk_meta_zone_read_locked(uint32_t devblock_from_top, uint8_t buf[4096]) {
if (!buf) return BLK_EINVAL;
blk_dev_slot_t *slot = first_disk_slot();
if (!slot || !slot->dev) return BLK_ENODEV;
@@ -1261,7 +1305,7 @@ int blk_meta_zone_read(uint32_t devblock_from_top, uint8_t buf[4096]) {
return BLK_OK;
}
int blk_meta_zone_write(uint32_t devblock_from_top, const uint8_t buf[4096]) {
static int blk_meta_zone_write_locked(uint32_t devblock_from_top, const uint8_t buf[4096]) {
if (!buf) return BLK_EINVAL;
blk_dev_slot_t *slot = first_disk_slot();
if (!slot || !slot->dev) return BLK_ENODEV;
@@ -1280,19 +1324,14 @@ int blk_meta_zone_write(uint32_t devblock_from_top, const uint8_t buf[4096]) {
return BLK_OK;
}
int blk_is_valid(uint32_t block_num) {
static int blk_is_valid_locked(uint32_t block_num) {
if (!g.initialized) return 0;
block_num = resolve_lbn(block_num);
if (block_num < g.ram_user) return 1;
return lbn_to_slot(block_num) ? 1 : 0;
}
uint32_t blk_get_total_blocks(void) {
uint64_t n = g.total_user_lbn;
return (n > 0xFFFFFFFFu) ? 0xFFFFFFFFu : (uint32_t) n;
}
int blk_get_meta(uint32_t block_num, blk_meta_t *meta) {
static int blk_get_meta_locked(uint32_t block_num, blk_meta_t *meta) {
if (!meta) return BLK_EINVAL;
if (!g.initialized) return BLK_ENODEV;
block_num = resolve_lbn(block_num);
@@ -1315,7 +1354,7 @@ int blk_get_meta(uint32_t block_num, blk_meta_t *meta) {
return BLK_OK;
}
int blk_set_meta(uint32_t block_num, const blk_meta_t *meta) {
static int blk_set_meta_locked(uint32_t block_num, const blk_meta_t *meta) {
if (!meta) return BLK_EINVAL;
if (!g.initialized) return BLK_ENODEV;
block_num = resolve_lbn(block_num);
@@ -1340,78 +1379,78 @@ int blk_set_meta(uint32_t block_num, const blk_meta_t *meta) {
* which already own caching/dirty-tracking -- these add no state of
* their own. */
int blk_owner_fp_get(uint32_t block_num, uint8_t out_fp[8]) {
static int blk_owner_fp_get_locked(uint32_t block_num, uint8_t out_fp[8]) {
blk_meta_t meta;
int rc;
if (!out_fp) return BLK_EINVAL;
rc = blk_get_meta(block_num, &meta);
rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
memcpy(out_fp, meta.owner_fp, sizeof(meta.owner_fp));
return BLK_OK;
}
int blk_owner_fp_set(uint32_t block_num, const uint8_t fp[8]) {
static int blk_owner_fp_set_locked(uint32_t block_num, const uint8_t fp[8]) {
blk_meta_t meta;
int rc;
if (!fp) return BLK_EINVAL;
rc = blk_get_meta(block_num, &meta);
rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
memcpy(meta.owner_fp, fp, sizeof(meta.owner_fp));
return blk_set_meta(block_num, &meta);
return blk_set_meta_locked(block_num, &meta);
}
int blk_acl_allow_get(uint32_t block_num, uint8_t *out_allow) {
static int blk_acl_allow_get_locked(uint32_t block_num, uint8_t *out_allow) {
blk_meta_t meta;
int rc;
if (!out_allow) return BLK_EINVAL;
rc = blk_get_meta(block_num, &meta);
rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
*out_allow = meta.acl_allow;
return BLK_OK;
}
int blk_acl_allow_set(uint32_t block_num, uint8_t allow) {
static int blk_acl_allow_set_locked(uint32_t block_num, uint8_t allow) {
blk_meta_t meta;
int rc = blk_get_meta(block_num, &meta);
int rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
meta.acl_allow = allow;
return blk_set_meta(block_num, &meta);
return blk_set_meta_locked(block_num, &meta);
}
int blk_acl_ttl_get(uint32_t block_num, uint32_t *out_ttl) {
static int blk_acl_ttl_get_locked(uint32_t block_num, uint32_t *out_ttl) {
blk_meta_t meta;
int rc;
if (!out_ttl) return BLK_EINVAL;
rc = blk_get_meta(block_num, &meta);
rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
*out_ttl = meta.acl_ttl;
return BLK_OK;
}
int blk_acl_ttl_set(uint32_t block_num, uint32_t ttl) {
static int blk_acl_ttl_set_locked(uint32_t block_num, uint32_t ttl) {
blk_meta_t meta;
int rc = blk_get_meta(block_num, &meta);
int rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
meta.acl_ttl = ttl;
return blk_set_meta(block_num, &meta);
return blk_set_meta_locked(block_num, &meta);
}
int blk_flags_get(uint32_t block_num, uint64_t *out_flags) {
static int blk_flags_get_locked(uint32_t block_num, uint64_t *out_flags) {
blk_meta_t meta;
int rc;
if (!out_flags) return BLK_EINVAL;
rc = blk_get_meta(block_num, &meta);
rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
*out_flags = meta.flags;
return BLK_OK;
}
int blk_flags_set(uint32_t block_num, uint64_t flags) {
static int blk_flags_set_locked(uint32_t block_num, uint64_t flags) {
blk_meta_t meta;
int rc = blk_get_meta(block_num, &meta);
int rc = blk_get_meta_locked(block_num, &meta);
if (rc != BLK_OK) return rc;
meta.flags = flags;
return blk_set_meta(block_num, &meta);
return blk_set_meta_locked(block_num, &meta);
}
/* FABRIC-2.md §F.11/§I.2, built 2026-09-04. is_lbn_zero() -- BLK_FLAG_
@@ -1454,7 +1493,7 @@ static int devblock_is_free(blk_dev_slot_t *slot, uint32_t rep_lbn, const blk_me
* devblocks in one claim. */
#define BLK_FIRSTTOUCH_MAX_CLAIM 256u
int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *out_chain_head) {
static int blk_firsttouch_claim_locked(const uint8_t owner_fp[8], uint32_t count, uint32_t *out_chain_head) {
if (!owner_fp || !out_chain_head || count == 0) return BLK_EINVAL;
blk_dev_slot_t *slot = first_disk_slot();
@@ -1477,7 +1516,7 @@ int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *ou
if (slot_pbn_pack_offset(rel_pbn) != 0) continue; /* not this devblock's representative LBN */
blk_meta_t meta;
if (blk_get_meta(lbn, &meta) != BLK_OK) continue;
if (blk_get_meta_locked(lbn, &meta) != BLK_OK) continue;
if (devblock_is_free(slot, lbn, &meta)) found[nfound++] = lbn;
}
@@ -1488,13 +1527,13 @@ int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *ou
* locally from any member without walking the chain. */
for (uint32_t i = 0; i < nfound; i++) {
blk_meta_t meta;
if (blk_get_meta(found[i], &meta) != BLK_OK) return BLK_EIO;
if (blk_get_meta_locked(found[i], &meta) != BLK_OK) return BLK_EIO;
memcpy(meta.owner_fp, owner_fp, 8);
meta.flags |= BLK_FLAG_CLAIMED;
meta.prev_block = (i == 0) ? 0 : found[i - 1];
meta.next_block = (i + 1 == nfound) ? 0 : found[i + 1];
meta.chain_length = nfound;
if (blk_set_meta(found[i], &meta) != BLK_OK) return BLK_EIO;
if (blk_set_meta_locked(found[i], &meta) != BLK_OK) return BLK_EIO;
/* blk_meta_t/BAM reconciliation, 2026-09-05: blk_set_meta() above
* only touches blk_meta_t -- mark the BAM allocated too, for all
@@ -1504,7 +1543,7 @@ int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *ou
* (blk_mark_allocated()'s own bookkeeping) correctly reflects the
* claim instead of silently overstating free space. */
for (uint32_t j = 0; j < BLK_PACK_RATIO; j++) {
(void) blk_mark_allocated(found[i] + j);
(void) blk_mark_allocated_locked(found[i] + j);
}
}
@@ -1515,7 +1554,7 @@ int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *ou
/* blk_owner_has_claim - FABRIC-2.md §I.2, 2026-09-05. Disk-scan answer to
* "does owner_fp already own a claim," per the decision recorded in
* block_subsystem.h: matching BLK_FLAG_CLAIMED devblock, not a RAM flag. */
int blk_owner_has_claim(const uint8_t owner_fp[8]) {
static int blk_owner_has_claim_locked(const uint8_t owner_fp[8]) {
if (!owner_fp) return 0;
blk_dev_slot_t *slot = first_disk_slot();
@@ -1528,7 +1567,7 @@ int blk_owner_has_claim(const uint8_t owner_fp[8]) {
if (slot_pbn_pack_offset(rel_pbn) != 0) continue;
blk_meta_t meta;
if (blk_get_meta(lbn, &meta) != BLK_OK) continue;
if (blk_get_meta_locked(lbn, &meta) != BLK_OK) continue;
if ((meta.flags & BLK_FLAG_CLAIMED) &&
memcmp(meta.owner_fp, owner_fp, 8) == 0) {
return 1;
@@ -1537,15 +1576,15 @@ int blk_owner_has_claim(const uint8_t owner_fp[8]) {
return 0;
}
int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock) {
static int blk_meta_relocate_devblock_locked(uint32_t home_devblock, uint32_t target_devblock) {
if (home_devblock == target_devblock) return BLK_EINVAL;
blk_meta_t home_meta;
int rc = blk_get_meta(home_devblock, &home_meta);
int rc = blk_get_meta_locked(home_devblock, &home_meta);
if (rc != BLK_OK) return rc;
home_meta.flags |= BLK_FLAG_MIGRATING;
if (blk_set_meta(home_devblock, &home_meta) != BLK_OK) return BLK_EIO;
if (blk_set_meta_locked(home_devblock, &home_meta) != BLK_OK) return BLK_EIO;
/* Devblock granularity is BLK_PACK_RATIO FORTH blocks -- move each
* one via the existing, only, FORTH-block-granularity relocation
@@ -1555,24 +1594,24 @@ int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock)
* transferred -- a real, documented limitation, not silently
* dropped). */
for (uint32_t i = 0; i < BLK_PACK_RATIO; i++) {
rc = blk_subsys_relocate_block(home_devblock + i, target_devblock + i);
rc = blk_subsys_relocate_block_locked(home_devblock + i, target_devblock + i);
if (rc != BLK_OK) return rc;
}
blk_meta_t target_meta;
rc = blk_get_meta(target_devblock, &target_meta);
rc = blk_get_meta_locked(target_devblock, &target_meta);
if (rc != BLK_OK) return rc;
memcpy(target_meta.owner_fp, home_meta.owner_fp, sizeof(target_meta.owner_fp));
target_meta.acl_allow = home_meta.acl_allow;
target_meta.acl_ttl = home_meta.acl_ttl;
target_meta.flags |= BLK_FLAG_CLAIMED;
if (blk_set_meta(target_devblock, &target_meta) != BLK_OK) return BLK_EIO;
if (blk_set_meta_locked(target_devblock, &target_meta) != BLK_OK) return BLK_EIO;
memset(home_meta.owner_fp, 0, sizeof(home_meta.owner_fp));
home_meta.acl_allow = 0;
home_meta.acl_ttl = 0;
home_meta.flags &= ~(BLK_FLAG_CLAIMED | BLK_FLAG_MIGRATING);
return blk_set_meta(home_devblock, &home_meta);
return blk_set_meta_locked(home_devblock, &home_meta);
}
/* FABRIC-2.md §I.2, 2026-09-04: heat/wear-leveling migration trigger.
@@ -1647,7 +1686,7 @@ static void blk_migration_idle_check_slot(blk_dev_slot_t *slot) {
uint32_t rel_pbn = lbn_to_slot_pbn(slot, lbn);
if (slot_pbn_pack_offset(rel_pbn) == 0) {
blk_meta_t meta;
if (blk_get_meta(lbn, &meta) == BLK_OK) {
if (blk_get_meta_locked(lbn, &meta) == BLK_OK) {
if (!have_hot && (meta.flags & BLK_FLAG_CLAIMED) &&
!(meta.flags & BLK_FLAG_MIGRATING) &&
meta.write_count >= MIGRATION_WEAR_THRESHOLD) {
@@ -1667,7 +1706,7 @@ static void blk_migration_idle_check_slot(blk_dev_slot_t *slot) {
slot->migration_scan_lbn = lbn;
if (have_hot && have_free && hot_lbn != free_lbn) {
(void) blk_meta_relocate_devblock(hot_lbn, free_lbn);
(void) blk_meta_relocate_devblock_locked(hot_lbn, free_lbn);
}
}
@@ -1681,7 +1720,7 @@ static void blk_migration_idle_check_slot(blk_dev_slot_t *slot) {
* Artemis-disk special case bolted onto an otherwise device-agnostic
* file. Fixed to walk the full slot chain -- every attached device now
* gets the same wear-leveling scan, none hardcoded, none skipped. */
void blk_migration_idle_check(void) {
static void blk_migration_idle_check_locked(void) {
blk_dev_slot_t *slot;
for (slot = g.head; slot; slot = slot->next) {
if (!slot->dev) continue;
@@ -1689,6 +1728,131 @@ void blk_migration_idle_check(void) {
}
}
/* ===== public API: each takes g_lock_block around its *_locked body =====
* (MC-0b2 -- see "locking" at the top of this file). */
int blk_subsys_init(VM *vm, uint8_t *ram_base, size_t ram_size) {
int rc; BLK_LOCK(); rc = blk_subsys_init_locked(vm, ram_base, ram_size); BLK_UNLOCK(); return rc;
}
int blk_subsys_add_raw_device(uint8_t *buf, uint32_t nblocks) {
int rc; BLK_LOCK(); rc = blk_subsys_add_raw_device_locked(buf, nblocks); BLK_UNLOCK(); return rc;
}
int blk_subsys_attach_device(struct blkio_dev *dev) {
int rc; BLK_LOCK(); rc = blk_subsys_attach_device_locked(dev); BLK_UNLOCK(); return rc;
}
int blk_subsys_detach_device(struct blkio_dev *dev) {
int rc; BLK_LOCK(); rc = blk_subsys_detach_device_locked(dev); BLK_UNLOCK(); return rc;
}
int blk_subsys_relocate_block(uint32_t home_lbn, uint32_t target_lbn) {
int rc; BLK_LOCK(); rc = blk_subsys_relocate_block_locked(home_lbn, target_lbn); BLK_UNLOCK(); return rc;
}
uint64_t blk_subsys_epoch(void) {
uint64_t e; BLK_LOCK(); e = g.epoch; BLK_UNLOCK(); return e;
}
int blk_subsys_shutdown(void) {
int rc; BLK_LOCK(); rc = blk_subsys_shutdown_locked(); BLK_UNLOCK(); return rc;
}
int blk_subsys_confirm_format(uint32_t lbn) {
int rc; BLK_LOCK(); rc = blk_subsys_confirm_format_locked(lbn); BLK_UNLOCK(); return rc;
}
uint8_t *blk_get_buffer(uint32_t block_num, int writable) {
uint8_t *p; BLK_LOCK(); p = blk_get_buffer_locked(block_num, writable); BLK_UNLOCK(); return p;
}
uint8_t *blk_get_empty_buffer(uint32_t block_num) {
uint8_t *p; BLK_LOCK(); p = blk_get_empty_buffer_locked(block_num); BLK_UNLOCK(); return p;
}
int blk_update(uint32_t block_num) {
int rc; BLK_LOCK(); rc = blk_update_locked(block_num); BLK_UNLOCK(); return rc;
}
int blk_flush(uint32_t block_num) {
int rc; BLK_LOCK(); rc = blk_flush_locked(block_num); BLK_UNLOCK(); return rc;
}
int blk_is_allocated(uint32_t block_num) {
int rc; BLK_LOCK(); rc = blk_is_allocated_locked(block_num); BLK_UNLOCK(); return rc;
}
int blk_mark_allocated(uint32_t block_num) {
int rc; BLK_LOCK(); rc = blk_mark_allocated_locked(block_num); BLK_UNLOCK(); return rc;
}
int blk_mark_free(uint32_t block_num) {
int rc; BLK_LOCK(); rc = blk_mark_free_locked(block_num); BLK_UNLOCK(); return rc;
}
int blk_allocate(uint32_t *block_num) {
int rc; BLK_LOCK(); rc = blk_allocate_locked(block_num); BLK_UNLOCK(); return rc;
}
int blk_get_volume_meta(blk_volume_meta_t *meta) {
int rc; BLK_LOCK(); rc = blk_get_volume_meta_locked(meta); BLK_UNLOCK(); return rc;
}
int blk_set_volume_meta(const blk_volume_meta_t *meta) {
int rc; BLK_LOCK(); rc = blk_set_volume_meta_locked(meta); BLK_UNLOCK(); return rc;
}
int blk_get_device_range(struct blkio_dev *dev, uint32_t *out_start_lbn, uint32_t *out_count) {
int rc; BLK_LOCK(); rc = blk_get_device_range_locked(dev, out_start_lbn, out_count); BLK_UNLOCK(); return rc;
}
const void *blk_lbn_device_handle(uint32_t lbn) {
const void *h; BLK_LOCK(); h = blk_lbn_device_handle_locked(lbn); BLK_UNLOCK(); return h;
}
int blk_get_device_free_blocks(struct blkio_dev *dev, uint64_t *out_free, uint64_t *out_total) {
int rc; BLK_LOCK(); rc = blk_get_device_free_blocks_locked(dev, out_free, out_total); BLK_UNLOCK(); return rc;
}
int blk_get_first_disk_range(uint32_t *out_start_lbn, uint32_t *out_count) {
int rc; BLK_LOCK(); rc = blk_get_first_disk_range_locked(out_start_lbn, out_count); BLK_UNLOCK(); return rc;
}
int blk_meta_zone_read(uint32_t devblock_from_top, uint8_t buf[4096]) {
int rc; BLK_LOCK(); rc = blk_meta_zone_read_locked(devblock_from_top, buf); BLK_UNLOCK(); return rc;
}
int blk_meta_zone_write(uint32_t devblock_from_top, const uint8_t buf[4096]) {
int rc; BLK_LOCK(); rc = blk_meta_zone_write_locked(devblock_from_top, buf); BLK_UNLOCK(); return rc;
}
int blk_is_valid(uint32_t block_num) {
int rc; BLK_LOCK(); rc = blk_is_valid_locked(block_num); BLK_UNLOCK(); return rc;
}
uint32_t blk_get_total_blocks(void) {
uint64_t n; BLK_LOCK(); n = g.total_user_lbn; BLK_UNLOCK();
return (n > 0xFFFFFFFFu) ? 0xFFFFFFFFu : (uint32_t) n;
}
int blk_get_meta(uint32_t block_num, blk_meta_t *meta) {
int rc; BLK_LOCK(); rc = blk_get_meta_locked(block_num, meta); BLK_UNLOCK(); return rc;
}
int blk_set_meta(uint32_t block_num, const blk_meta_t *meta) {
int rc; BLK_LOCK(); rc = blk_set_meta_locked(block_num, meta); BLK_UNLOCK(); return rc;
}
int blk_owner_fp_get(uint32_t block_num, uint8_t out_fp[8]) {
int rc; BLK_LOCK(); rc = blk_owner_fp_get_locked(block_num, out_fp); BLK_UNLOCK(); return rc;
}
int blk_owner_fp_set(uint32_t block_num, const uint8_t fp[8]) {
int rc; BLK_LOCK(); rc = blk_owner_fp_set_locked(block_num, fp); BLK_UNLOCK(); return rc;
}
int blk_acl_allow_get(uint32_t block_num, uint8_t *out_allow) {
int rc; BLK_LOCK(); rc = blk_acl_allow_get_locked(block_num, out_allow); BLK_UNLOCK(); return rc;
}
int blk_acl_allow_set(uint32_t block_num, uint8_t allow) {
int rc; BLK_LOCK(); rc = blk_acl_allow_set_locked(block_num, allow); BLK_UNLOCK(); return rc;
}
int blk_acl_ttl_get(uint32_t block_num, uint32_t *out_ttl) {
int rc; BLK_LOCK(); rc = blk_acl_ttl_get_locked(block_num, out_ttl); BLK_UNLOCK(); return rc;
}
int blk_acl_ttl_set(uint32_t block_num, uint32_t ttl) {
int rc; BLK_LOCK(); rc = blk_acl_ttl_set_locked(block_num, ttl); BLK_UNLOCK(); return rc;
}
int blk_flags_get(uint32_t block_num, uint64_t *out_flags) {
int rc; BLK_LOCK(); rc = blk_flags_get_locked(block_num, out_flags); BLK_UNLOCK(); return rc;
}
int blk_flags_set(uint32_t block_num, uint64_t flags) {
int rc; BLK_LOCK(); rc = blk_flags_set_locked(block_num, flags); BLK_UNLOCK(); return rc;
}
int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *out_chain_head) {
int rc; BLK_LOCK(); rc = blk_firsttouch_claim_locked(owner_fp, count, out_chain_head); BLK_UNLOCK(); return rc;
}
int blk_owner_has_claim(const uint8_t owner_fp[8]) {
int rc; BLK_LOCK(); rc = blk_owner_has_claim_locked(owner_fp); BLK_UNLOCK(); return rc;
}
int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock) {
int rc; BLK_LOCK(); rc = blk_meta_relocate_devblock_locked(home_devblock, target_devblock); BLK_UNLOCK(); return rc;
}
void blk_migration_idle_check(void) {
BLK_LOCK(); blk_migration_idle_check_locked(); BLK_UNLOCK();
}
/* ===== weak hook (for main.c) ===== */
#if defined(__GNUC__) || defined(__clang__)
__attribute__((weak))
+2
View File
@@ -35,6 +35,7 @@ sk_spinlock_t g_lock_switch = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_hermes = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_doe = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_log = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_block = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_console = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_heap = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_pmm = SK_SPINLOCK_INIT;
@@ -183,6 +184,7 @@ static const char *lock_name(const volatile uint32_t *w)
if (w == &g_lock_hermes.owner) return "hermes";
if (w == &g_lock_doe.owner) return "doe";
if (w == &g_lock_log.owner) return "log";
if (w == &g_lock_block.owner) return "block";
if (w == &g_lock_console.owner) return "console";
if (w == &g_lock_heap.owner) return "heap";
if (w == &g_lock_pmm.owner) return "pmm";