diff --git a/docs/v4.0.0/plans/2026-10-29-stick-5-proxy-zuse-and-real-zuse.md b/docs/v4.0.0/plans/2026-10-29-stick-5-proxy-zuse-and-real-zuse.md index 7954a53b..a1523686 100644 --- a/docs/v4.0.0/plans/2026-10-29-stick-5-proxy-zuse-and-real-zuse.md +++ b/docs/v4.0.0/plans/2026-10-29-stick-5-proxy-zuse-and-real-zuse.md @@ -353,7 +353,7 @@ Tasks 1-3 accepted and merged (#159, master 594d2f93); #155 merged with them. **Task 4, `MINT-ZUSE`** (Captain Bob, 2026-11-08: "work in small increments and memorize frequently"): - [x] 4.1 the first failing test: `qemu_session.py` mode `mint_zuse` and `add_stick(n, img, port)` - [x] 4.2 run `mint_zuse` on amd64 before any kernel change: FAIL, "UNKNOWN WORD: 'MINT-ZUSE'" (logs/20261108-175928); the prompt check tightened to need the minted line first -- [ ] 4.3 `gpt_write_one()` in `kernel/src/gpt.c` (one partition, protective MBR, both headers) +- [x] 4.3 `gpt_write_one()` in `kernel/src/gpt.c` (one partition, protective MBR, both headers): builds clean on three ISAs; checked on the host (a throwaway harness, not in the tree) -- 600 MiB and 32 MiB blank images and the pair's used Zuse stick each give one identity partition, `gpt_read` reads it back, `sfdisk --verify` clean. Not yet called. - [ ] 4.4 `sk_v4_identity_offer_wait()` / `sk_v4_identity_recheck_offered()`: a stick offered, not attached, while a mint waits - [ ] 4.5 `sk_v4_mint.c`: the flow -- plug-in line, size line, `YES`, GPT, zeroed, minted, read back, marker - [ ] 4.6 `MINT-ZUSE` in `sk_v4_words[]` (request 2): word_count 317 -> 318 diff --git a/kernel/include/starkernel/gpt.h b/kernel/include/starkernel/gpt.h index 05b413ed..fb7cf029 100644 --- a/kernel/include/starkernel/gpt.h +++ b/kernel/include/starkernel/gpt.h @@ -49,6 +49,16 @@ uint64_t gpt_alternate_lba(void); * sector); GPT_BAD or GPT_EIO otherwise. */ int gpt_grow_last(blkio_dev_t *disk, const uint8_t type[16], uint64_t max_part_sectors, uint64_t *new_last_lba); +/* Write a new GPT on `disk` with one partition of `type` (plan + * 2026-10-29-stick-5, Task 4: the real Zuse's stick): at LBA 2048, at most + * `part_sectors` long (on a whole MiB, no further than the disk allows), a + * fresh DiskGUID (into `disk_guid`) and partition GUID from the RNG, 128 + * entries, the backup header and entries at the disk's end, the protective + * MBR written last (the old one zeroed first, so a write torn anywhere leaves + * no table). Nothing inside the partition is written. GPT_OK; GPT_BAD for a + * disk whose sectors are not 512 bytes or that is too small; GPT_EIO. */ +int gpt_write_one(blkio_dev_t *disk, const uint8_t type[16], uint64_t part_sectors, uint8_t disk_guid[16]); + /* The index of the first partition of `type`, or -1. */ int gpt_find(const gpt_part_t *parts, unsigned count, const uint8_t type[16]); diff --git a/kernel/src/gpt.c b/kernel/src/gpt.c index 88700e5c..6c83273a 100644 --- a/kernel/src/gpt.c +++ b/kernel/src/gpt.c @@ -13,9 +13,12 @@ * The writer (plan 2026-10-29-stick-5, Task 2; STICK.md 5a): the stick's last * partition grown to the end of a disk larger than the image written to it -- * the backup header and entry array moved to the disk's end, both headers - * rewritten with their CRCs, the backup before the primary. + * rewritten with their CRCs, the backup before the primary. And a new disk + * of one partition (gpt_write_one, plan 2026-10-29-stick-5, Task 4): the + * real Zuse's stick, written by MINT-ZUSE. */ #include "starkernel/gpt.h" +#include "starkernel/rng.h" #include "console.h" #include #include @@ -263,6 +266,100 @@ int gpt_grow_last(blkio_dev_t *disk, const uint8_t type[16], uint64_t max_part_s return GPT_OK; } +/* ---- the writer: a new disk with one partition (MINT-ZUSE) -------------- */ + +#define GPT_NENT 128u +#define GPT_ESIZE 128u +#define GPT_ENT_SECTORS ((GPT_NENT * GPT_ESIZE) / GPT_SECTOR) /* 32 */ + +static uint8_t g_gpt_new_entries[GPT_NENT * GPT_ESIZE]; + +/* A random GUID, version 4, as on disk (the RNG; the caller has entropy). */ +static int gpt_new_guid(uint8_t g[16]) +{ + if (rng_get_bytes(g, 16) != 0) return -1; + g[7] = (uint8_t)((g[7] & 0x0Fu) | 0x40u); /* version 4, in the third field's high byte */ + g[8] = (uint8_t)((g[8] & 0x3Fu) | 0x80u); /* the RFC 4122 variant */ + return 0; +} + +int gpt_write_one(blkio_dev_t *disk, const uint8_t type[16], uint64_t part_sectors, uint8_t disk_guid[16]) +{ + static uint8_t hdr[GPT_SECTOR], bak[GPT_SECTOR], mbr[GPT_SECTOR]; + static const char name[] = "StarshipOS identity"; + blkio_info_t info; + uint64_t disk_sectors, alt, last_usable, first = 2048u, last, i; + uint8_t *e = g_gpt_new_entries, part_guid[16]; + + if (!disk || disk->forth_block_size != BLKIO_FORTH_BLOCK_SIZE) return GPT_BAD; + if (disk->vt->info(disk, &info) == BLKIO_OK && info.phys_sector_size != 0u && + info.phys_sector_size != GPT_SECTOR) + return GPT_BAD; /* sectors that are not 512 bytes */ + disk_sectors = (uint64_t)disk->total_blocks * (BLKIO_FORTH_BLOCK_SIZE / GPT_SECTOR); + alt = disk_sectors - 1u; + last_usable = alt - GPT_ENT_SECTORS - 1u; + if (disk_sectors < first + 2048u + GPT_ENT_SECTORS + 2u) return GPT_BAD; + if (part_sectors > last_usable - first + 1u) part_sectors = last_usable - first + 1u; + part_sectors -= part_sectors % 2048u; /* whole MiB, so whole 1 KiB blocks */ + if (part_sectors == 0u) return GPT_BAD; + last = first + part_sectors - 1u; + if (gpt_new_guid(disk_guid) != 0 || gpt_new_guid(part_guid) != 0) return GPT_EIO; + + /* the one entry: type, its own GUID, first and last LBA, its name in UTF-16LE */ + memset(g_gpt_new_entries, 0, sizeof(g_gpt_new_entries)); + memcpy(e, type, 16); + memcpy(e + 16, part_guid, 16); + put_le64(e + 32, first); + put_le64(e + 40, last); + for (i = 0; name[i]; i++) e[56u + 2u * i] = (uint8_t)name[i]; + + /* the primary header; the backup is it with its LBAs swapped */ + memset(hdr, 0, sizeof(hdr)); + memcpy(hdr, "EFI PART", 8); + put_le32(hdr + 8, 0x00010000u); + put_le32(hdr + 12, 92u); + put_le64(hdr + 24, 1u); + put_le64(hdr + 32, alt); + put_le64(hdr + 40, 2u + GPT_ENT_SECTORS); + put_le64(hdr + 48, last_usable); + memcpy(hdr + 56, disk_guid, 16); + put_le64(hdr + 72, 2u); + put_le32(hdr + 80, GPT_NENT); + put_le32(hdr + 84, GPT_ESIZE); + put_le32(hdr + 88, gpt_crc32(g_gpt_new_entries, sizeof(g_gpt_new_entries))); + memcpy(bak, hdr, sizeof(bak)); + put_le64(bak + 24, alt); + put_le64(bak + 32, 1u); + put_le64(bak + 72, alt - GPT_ENT_SECTORS); + put_le32(hdr + 16, gpt_crc32(hdr, 92u)); + put_le32(bak + 16, gpt_crc32(bak, 92u)); + + /* the protective MBR: one partition of type 0xEE over the disk */ + memset(mbr, 0, sizeof(mbr)); + mbr[446u + 1u] = 0x00u; mbr[446u + 2u] = 0x02u; mbr[446u + 3u] = 0x00u; /* CHS start 0/0/2 */ + mbr[446u + 4u] = 0xEEu; + mbr[446u + 5u] = 0xFFu; mbr[446u + 6u] = 0xFFu; mbr[446u + 7u] = 0xFFu; /* CHS end: past it */ + put_le32(mbr + 446u + 8u, 1u); + put_le32(mbr + 446u + 12u, alt > 0xFFFFFFFFu ? 0xFFFFFFFFu : (uint32_t)alt); + mbr[510] = 0x55u; mbr[511] = 0xAAu; + + /* the old MBR zeroed first, then the backup, the primary, and the new MBR + * last: until it is written the disk is not a GPT disk at all (gpt_read: + * no protective MBR), so a write torn anywhere leaves no table naming a + * partition not yet written -- a used stick's old table included */ + { + static const uint8_t zero[GPT_SECTOR]; + if (put_sectors(disk, 0u, zero, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK) return GPT_EIO; + } + if (put_sectors(disk, alt - GPT_ENT_SECTORS, g_gpt_new_entries, GPT_ENT_SECTORS) != 0 || + put_sectors(disk, alt, bak, 1u) != 0 || + put_sectors(disk, 2u, g_gpt_new_entries, GPT_ENT_SECTORS) != 0 || + put_sectors(disk, 1u, hdr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK || + put_sectors(disk, 0u, mbr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK) + return GPT_EIO; + return GPT_OK; +} + /* ---- a partition as a blkio device ------------------------------------- */ static int part_open(blkio_dev_t *dev, const blkio_params_t *p)