/* sk_v4_grow.c -- Artemis grows to fill the stick at its first boot * (docs/v4.0.0/STICK.md 5a; plan 2026-10-29-stick-5, Task 2). * * Metadata only: the volume's header, and its fence -- the top-of-volume * system metadata, which holds the Artemis signature and Zuse's genesis * marker (artemis_sig.c, blk_meta_zone_read/write) -- moved to the new top. * The volume's data blocks are never written, and its BAM is not resized: the * image is built with a BAM sized for the cap (mkartemis --bam-for), so the * new blocks are tracked already and read as free. * * The block subsystem places a volume's fence by its device's size, not by * its header (blk_format_or_load_disk), so a volume always fills its * partition: the partition is grown no further than the BAM can track * (sk_v4_grow_cap_sectors), then the volume to the partition. */ #include "starkernel/v4/sk_v4_grow.h" #include "block_subsystem.h" #include static union { uint8_t bytes[4096]; blk_volume_meta_t meta; } g_hdr; static uint8_t g_move[4096]; static int dev_rw4k(blkio_dev_t *dev, uint64_t devblock, uint8_t *buf, int write) { uint32_t base = (uint32_t)devblock * 4u, k; for (k = 0; k < 4u; k++) { int rc = write ? blkio_write(dev, base + k, buf + k * 1024u) : blkio_read(dev, base + k, buf + k * 1024u); if (rc != BLKIO_OK) return -1; } return 0; } static int read_header(blkio_dev_t *art) { if (dev_rw4k(art, 0u, g_hdr.bytes, 0) != 0) return -1; if (g_hdr.meta.magic != 0x53544652u || g_hdr.meta.version != 2u || g_hdr.meta.bam_devblocks == 0u || g_hdr.meta.meta_fence_blocks == 0u) return -1; return 0; } /* The most devblocks a volume with this header's BAM can be: no more Forth * blocks in its payload than the BAM tracks (compute_totals_from_B). */ static uint64_t bam_cover_devblocks(const blk_volume_meta_t *m) { uint64_t tracked = 32768ULL * m->bam_devblocks; return tracked / 3u + 1u + m->bam_devblocks + m->reloc_devblocks + m->meta_fence_blocks; } uint64_t sk_v4_grow_cap_sectors(blkio_dev_t *art, uint64_t cap_sectors) { uint64_t cover; if (read_header(art) != 0) return 0; cover = bam_cover_devblocks(&g_hdr.meta) * 8u; /* 4 KiB devblocks, in 512-byte sectors */ return cover < cap_sectors ? cover : cap_sectors; } int sk_v4_grow_artemis(blkio_dev_t *art) { blkio_info_t info; uint64_t old_t, new_t, f, k, old_total; if (read_header(art) != 0) return -1; if (blkio_info(art, &info) != BLKIO_OK) return -1; old_t = g_hdr.meta.total_devblocks; new_t = info.total_blocks / 4u; if (new_t <= old_t) return 0; /* not grown: it fills its partition */ if (new_t > bam_cover_devblocks(&g_hdr.meta)) return -1; f = g_hdr.meta.meta_fence_blocks; /* the old and new fences never overlap (a partition grows by whole MiB, * 256 devblocks, past a 128-devblock fence), so a copy torn by a power * loss is simply done again from the old top at the next boot */ if (f >= old_t || new_t - old_t < f) return -1; /* the fence to the new top, devblock by devblock */ for (k = 0; k < f; k++) { if (dev_rw4k(art, old_t - 1u - k, g_move, 0) != 0) return -1; if (dev_rw4k(art, new_t - 1u - k, g_move, 1) != 0) return -1; } if (blkio_flush(art) != BLKIO_OK) return -1; /* the header last: until it is written, the old header names the old top */ old_total = g_hdr.meta.total_blocks; g_hdr.meta.total_devblocks = new_t; blk_volume_compute_totals(&g_hdr.meta); g_hdr.meta.free_blocks += g_hdr.meta.total_blocks - old_total; if (dev_rw4k(art, 0u, g_hdr.bytes, 1) != 0) return -1; if (blkio_flush(art) != BLKIO_OK) return -1; return 1; }