The OS Card

The internal "OS" SD card — partition layout, what the FAT boot partition holds, how a slot is selected, and the two update tiers. This is the concrete form of the design in cm45 §7, §9, §10 and §11, sized for a 32 GB card and corrected against current Raspberry Pi firmware behaviour. The corrections themselves, and why they matter, are in bringup.

Status: design. Nothing here is built. This page fixes the layout so mkcard can be written against a settled shape.

Back to bringup · the design notes: cm45 · the update flow: firmware


The shape, in one paragraph

The card is never written except during a firmware update. It carries a FAT boot partition holding the Pi firmware and every AntOS binary, two rootfs slots plus a never-updated recovery slot, a write-once factory restore image, and drive F: — the built-in read-only asset library. Slot selection is os_prefix inside the one boot partition; A/B trial and rollback is the bootloader's own tryboot; which AntOS binary runs is antboot reading the MCP79410's battery-backed SRAM.


Partition map

Sized for a 32 GB card as the floor, designed against 29.0 GiB usable — cards sold as 32 GB vary between vendors, and everything except the tail is fixed-size so a slightly smaller card still takes the image.

32 GB is also a better stopping point than it looks: it is the top of SDHC. At 64 GB you are into SDXC, which is exFAT-by-default and marginally fussier to format FAT32 across tools. Staying at 32 keeps the boot medium in better-trodden territory.

Part FS Size Label Contents
p1 FAT32 3 GiB ANTBOOT Firmware, three slot prefixes, and antos/ — which is what actually wants the room
p2 squashfs 4 GiB Rootfs slot A — raw partition, no filesystem wrapper
p3 squashfs 4 GiB Rootfs slot B — must match A exactly; an image built for one gets written to the other
p4 extended to end Container, spanning all remaining space so logical partitions can be added later without moving anything
p5 squashfs 2 GiB Recovery — written once at manufacture. Need not match the rootfs slots
p6 FAT32 2 GiB ANTREST Factory restore image — the golden rootfs plus boot payload, written once
p7 squashfs 11 GiB ANTASSET Drive F: — the built-in read-only asset library
free ~3.0 GiB Unallocated inside p4 — headroom for a partition that does not exist yet

Every partition is read-only in normal operation. There is no data partition and no writable region of any kind — see below.

Why the slack goes inside the partitions, not between them

A file on p1 can grow into that partition's free space at any time. A partition cannot grow into free space on the card without repartitioning — and repartitioning a fielded machine is exactly the operation the never-written design exists to avoid. So every partition size here is a one-way door: whatever ships is what those machines have for the life of the product. That argues for generosity now rather than tidiness.

  • Rootfs slots at 4 GiB, far more than today needs — an rpi-image-gen Debian base with the Pi kernel, mesa, libdrm and the vc4 KMS stack is 800 MB – 1.2 GB uncompressed, so a realistic squashfs is 300–450 MB. The margin is for self-hosted development, which cm45 §18 lists as one of the main things Linux newly makes possible. A rootfs that eventually carries GCC, binutils and headers grows by most of a gigabyte, and that is precisely the change a fixed partition size would veto years after it was set.
  • Both rootfs slots identical, recovery free to differ. A and B must match because an image built for one gets written to the other — that is the A/B mechanism. Recovery is never a rootfs update target, so it can be sized for what it is: a stripped rootfs that still carries a DRM stack, because it has to drive the keyboard panel to show recovery prompts on the Ant64.
  • FAT at 3 GiB, because §10's "rollback stops being a recovery procedure and becomes picking a different one" needs the space to be true. Boot files barely register: start4.elf + fixup4.dat are ~3.5 MB, and each slot prefix holds two kernels, three DTBs, overlays and two initramfs images for roughly 50 MB — about 160 MB for all three. Everything else, near enough 2.8 GiB, is antos/. At ~60 MB for an A72+A76 pair that is forty-plus retained builds, well past the point where more helps.

Three details that bite when mkcard writes the image

  • Align every partition to 4 MiB. Misalignment against the card's erase block costs read-modify-write cycles on the one operation you care about — writing a slot during an update.
  • Let p4 run to the end of the card, not to a fixed sector. Logical partitions can then be appended inside it later without touching p1p3, which is the only kind of layout change a fielded machine could survive.
  • Pick the squashfs compressor by measurement. zstd compresses better than lz4 but decompresses slower — and on a slow SD card, where I/O dominates, better compression usually wins on boot time. Usually is not always; it is a two-minute A/B.

Specify the card, not just the size

For a boot card endurance is nearly irrelevant — it is barely written. What matters is retention: a machine that sits powered off for months on a shelf, where cheap TLC consumer cards are the weak part. At 32 GB pSLC gets expensive enough to be a real BOM line, so the sensible component is an industrial-grade card chosen for retention rather than endurance; the usual pSLC argument is about write cycles, which this card barely uses.

Fix the part number in the BOM rather than leaving it as "any 32 GB card". A support queue full of machines that stopped booting after a winter in a cupboard is a miserable failure mode to diagnose, and it arrives years after the decision that caused it.


What a boot slot contains

p1 — FAT32, label ANTBOOT

config.txt            ← os_prefix=a/  · the committed slot
tryboot.txt           ← os_prefix=b/  · loaded INSTEAD of config.txt after
                        reboot "0 tryboot". Committing = swapping these two lines.
start4.elf            ← CM4 only, and must match fixup4.dat exactly.
fixup4.dat              The CM5 has no start*.elf and ignores both.

a/                    ← slot A. NOTHING lives at the partition root except
  kernel8.img           the files above — see the os_prefix trap below.
  kernel_2712.img
  bcm2711-rpi-cm4.dtb
  bcm2712-rpi-cm5l-cm5io.dtb   ← CM5 Lite = board type 0x1a
  bcm2712-rpi-cm5-cm5io.dtb    ← board type 0x18
  overlays/
    README            ← REQUIRED, or this slot silently shares another's overlays
  initramfs8          ← CM4  \  auto_initramfs=1 derives both names
  initramfs_2712      ← CM5  /  from the kernel filename
  cmdline.txt         ← root=/dev/mmcblk0p2 ro init=/sbin/antboot
b/                    ← identical shape, root=…p3
rec/                  ← identical shape, root=…p5
                        ~50 MB per prefix, ~160 MB for all three

antos/                ← Tier 1 lives here, outside the slots, because
  manifest.inf          it updates on a completely different cadence.
  manifest.bak          Two copies, written alternately, each checksummed.
  antos-a72-0.1.0       ~2.8 GiB of the partition ends up here —
  antos-a76-0.1.0       forty-plus retained builds.
  init.zip

The os_prefix trap, and the one line of policy that fixes it

The firmware tests a prefix for viability: if the expected kernel and .dtb are not found at the prefixed location, the prefix is discarded and set to empty. A corrupt or half-written slot directory therefore does not fail — it quietly boots whatever is at the partition root.

So: leave no kernel and no DTB at the root of the boot partition. A discarded prefix then has nothing to fall back onto, and turns into a clean, visible boot failure instead of a wrong-slot boot.

Second trap in the same mechanism: overlays only follow the prefix if ${os_prefix}${overlay_prefix}README exists — otherwise they are treated as shared. Overlays are kernel-coupled, so each slot wants its own. Put a README in every slot's overlays/ or slot B will silently load slot A's.

Why tryboot.txt rather than autoboot.txt

autoboot.txt is read before config.txt, which is what stops GPIO conditionals from choosing a partition — the reason cm45 §9 put mode selection in os_prefix in the first place. Keeping one boot partition and letting tryboot.txt carry a different os_prefix preserves that property, keeps the GPIO conditional able to reach recovery, and makes committing an update a ~40-byte write to a FAT file rather than a partition-table concern.

autoboot.txt remains available if a partition-level split is ever wanted: 512 bytes maximum, and only [all], [none], [tryboot] are legal section headers.


config.txt

[all]
auto_initramfs=1
camera_auto_detect=0
display_auto_detect=0
force_eeprom_read=0        # skips all HAT I2C probing
disable_poe_fan=1
dtparam=audio=off
dtoverlay=vc4-kms-v3d

[cm4]                      # eZX. Note [cm4] also inherits anything under [pi4].
kernel=kernel8.img
device_tree=bcm2711-rpi-cm4.dtb
dtoverlay=dwc2,dr_mode=host    # without this the IO board's hub reports
                               # "hub doesn't have any ports" — USB2 defaults to OTG
dtoverlay=sdio                 # drive A:, GPIO22-27
disable_splash=1               # genuinely works here

[cm5]                      # Ant64. Inherits [pi5].
kernel=kernel_2712.img
dtoverlay=sdio-pi5             # same pins, rp1_mmc0 controller
dtparam=pciex1_gen=3           # design for Gen2, test Gen3, verify it trained
# disable_splash does NOTHING here — see bringup, correction 4.

[cm4] and [cm4s] also see [pi4]; [cm5] also sees [pi5]. To target one model only, revert the setting under the narrower filter, or use [board-type=0x14] (CM4) / 0x18 (CM5) / 0x1a (CM5 Lite).

Lines deliberately not here

A boot file whose every line is load-bearing is one you can reason about at 2 a.m.; one padded with settings that quietly do nothing is not. Two that look like obvious wins and are not:

Line Why it is absent
arm_64bit=1 A no-op on both machines. The firmware "defaults to 1 on Raspberry Pi 4, 400, and Compute Module 4 and 4S", and models that support only a 64-bit kernel — "Compute Modules since CM5" — ignore the flag entirely. It also interacts with an explicit kernel=, which we set: "if the name given in an explicit kernel option matches one of the known kernels then arm_64bit is set accordingly." So it is inferred correctly on both, twice over.
arm_boost=1 No effect on the Ant64, and out of spec on the eZX. It applies "only to later Raspberry Pi 4B revisions, which include two-phase power delivery, and all revisions of Raspberry Pi 400" — the CM4 is not on that list, and the BCM2712 does not use it at all. Raspberry Pi's position on the CM4 is explicit: "CM4 remains at 1.5 GHz by default. With Compute Module we are much more focused on providing a stable, consistent platform for our industrial customers than on pushing the performance envelope."

arm_boost is still worth using — as an instrument, not a setting. The premise test in dev_step2 asks whether the CM4 holds frame time on GLES. If it fails at 1.5 GHz and passes at 1.8, that is a headroom measurement worth having even though we would not ship it, because it says the eZX is marginal rather than comfortable. Shipping it would be a separate decision requiring our carrier's power delivery and the fanless case's sustained thermals validated at 1.8 GHz — and for a games machine, a part that boosts and then throttles mid-play is worse than one that runs flat.

Bootloader EEPROM

BOOT_ORDER=0xe1              # SD, then stop with an error — §7's honest option
PARTITION_WALK=0             # do not let the firmware overrule the slot choice
ENABLE_SELF_UPDATE=0         # no invisible second firmware path
NET_INSTALL_ENABLED=0        # ~1s of boot
DISABLE_HDMI=1               # the CM5's real "splash"
HDMI_DELAY=0
# BOOT_UART=1 only while measuring. CM4: GPIO14/15 (header pins 8/10).
# CM5: module TP35/TP36 only — the CM5IO has no debug UART connector.

PARTITION_WALK defaults to 1 since the August 2025 bootloader: if the requested partition fails, the firmware scans partitions 0–8 for another bootable one. On a card whose whole point is a deliberate A/B/recovery choice that is a silent override, so it must be turned off. Deterministic failure is what the recovery ladder in cm45 §12 is built on.

Read the shipped values with rpi-eeprom-config before optimising anything — §7's note that the CM4 default is BOOT_ORDER=0xf25641 describes a shipped config, not the firmware default, which is 0xf41 when the field is empty. Also clear NET_INSTALL_AT_POWER_ON, which shipped configs do set.


The two update tiers

Tier What moves Mechanism Rollback
1 — routine The AntOS binary only Write into antos/ on the live slot under a temp name, fsync, verify the hash, rename, update manifest.inf. antboot restarts its child. Previous binary is still present; selection is one byte in the RTC SRAM
2 — rare Kernel, DTBs, rootfs, config Write the whole inactive slot, then reboot "0 tryboot" Automatic — the one-shot flag clears before the firmware runs, so a failed boot lands back on the old slot with no counter logic of your own

The MCP79410's battery-backed SRAM holds the AntOS binary selection and the commit/health flag that says whether the last tryboot succeeded. The firmware's own flag survives one reboot; the RTC is what turns that into a policy.

Two things forty retained builds require

  • Something has to prune. Without a policy the partition fills over years and a Tier 1 update fails at the worst possible moment — no room to write the new binary before switching to it. Keep the current build, the last-known-good, and the N most recent; prune oldest first; and refuse to start an update unless there is room for the new binary plus headroom.
  • The manifest becomes the single point of failure. One byte in SRAM indexes into it, so losing manifest.inf to a torn FAT write loses the selection for all of them. Write two copies alternately, each checksummed, and have antboot prefer the newer valid one — the same reasoning §9 already applies to the RTC boot block, applied to the thing the RTC points at.

Drive F: belongs on the card

cm45 §19 describes F: as living in spare space on a USB stick, but its own requirements argue against that: "treat F: as part of the system image, not user data." On a small card there was nowhere else to put it. On this one there is, and putting it on the card satisfies four of §19's rules by construction rather than by policy:

  • Read-only is structural. The card is never written, so F: cannot be modified, deleted or half-overwritten by anything — including a user reformatting a stick.
  • "Always there" becomes literally true. On D: it depended on the right stick being fitted; on the card it ships with the machine and survives replacing every USB device in it.
  • Growth is forced onto a versioned cadence. §19 worries that F: "just increasing" fills the stick over years and that the updater must refuse to grow it past a set fraction. On the card, growing F: is a Tier 2 update — signed, versioned, verified — so the size budget is enforced by the release process instead of by a rule someone has to remember.
  • It needs no A/B pair. §19 already requires F: to be append-only and immutable, with a new version at a new path. Versions therefore coexist inside one partition, and a rootfs slot simply declares which F: version it needs — so rolling back B to A cannot strand you on the wrong assets.

At 11 GiB the core/extended split stops being necessary

On a smaller card F: would have had to split — fonts and sprites on the card, sample libraries and SoundFonts on D: — because a decent GM SoundFont is 500 MB and a quantised local model is GB-scale. With 11 GiB reserved, the heavy content fits alongside the light, including AMY patch data, DX7 .syx banks, GM patch sets and Needle's model file. Everything on F: is then guaranteed present, which is a much simpler promise than a two-tier one.

Keep the mechanism anyway — per-asset tier in the manifest, games declaring their F: dependencies, verify F:. §19 wants the dependency declaration for version contracts regardless, and it is what would let assets live on D: if a variant ever shipped a smaller card. Build the machinery, ship one tier.

Reserving is free, which is the point: a squashfs image only occupies what its content needs, so an 11 GiB partition holding 1 GiB of assets costs nothing until it does not. What you cannot do is enlarge it later.

F: on the card is also the natural home for the assets that must exist before any storage is enumerated — ImGui's fonts, the CRT preset LUTs, the custom terminfo entry. Those are needed early enough that depending on a USB stick having spun up was always going to be awkward.

Shipping F: in the image raises the licensing stakes

§19's whitelist and provenance rules were written when F: was optional content on a stick. Baking it into the system image means every unit distributes every asset, unambiguously and commercially — so the CC-BY attribution obligation now attaches to the product itself, and a laundered "free sprite pack" becomes a defect in the shipped image rather than in a download.

Nothing in the policy changes; the enforcement does. Record source URL, licence text as published, and date per asset before anything goes in, because removing an asset from F: later contradicts the append-only rule — and the auto-generated credits list, which §19 already identifies as the killer feature, is what discharges the obligation at scale.


The factory restore image (p6)

firmware already says the recovery slot re-flashes the OS slots "from a firmware image on the card or attached media" — but on a small card there was no room for that image, so the on-card half of that sentence was aspirational. Here it fits.

That adds a rung to cm45 §12's ladder where it was thinnest. Today, a machine whose both OS slots are bad needs either a network, a USB stick, or the case opened and the card read in a PC. With p6 present, recovery boots and restores from the same card — no network, no stick, no service visit — and because it is written once at manufacture and never again, it is as trustworthy as the recovery slot itself.

What it is not: it does not protect against a dead card, and it must never become a Tier 2 update target, or it stops being a known-good reference and becomes just a third slot. Write it at provisioning and never again — the same discipline that makes the recovery slot worth having.


There is no data partition

An earlier draft of firmware listed a data area alongside the slots. It has been removed, deliberately. The card is never written except during a firmware update, so a writable partition on it would be a hole in that property rather than an exception to it — and the moment one exists, things start living there. The guarantee only holds if there is nowhere on the card that a write is permitted.

Everything that might have gone there already has a better home:

Would-be content Actual home Why
Logs tmpfs, or USB Ephemeral by nature; §7 already says never the card
User data, scripts, saves DBFS on D: The whole point of DBFS
Build scratch D:/temp, outside DBFS Real paths for Linux tools, and it stays out of the D!: shadow
ImGui layout memory — no imgui.ini Already decided in cm45 §18
Boot state, slot selection MCP79410 battery-backed SRAM Survives power loss; keeps the card genuinely never-written
Machine identity ATECC608C lockable slots Must survive a compute-module swap
Board revision, calibration ATECC608C / MCP79410 EEPROM See below

The last row carries an argument worth stating rather than assuming. Provisioning data must be off the card entirely, not merely write-once on it, because the documented recovery floor is re-imaging the card from a PC — so anything identifying the machine that lived on the card would be destroyed by its own recovery path. Identity belongs to the carrier, which is the same reason it does not live on the swappable compute module either.

If something later appears that genuinely must be written during normal operation, it belongs on D:. Adding a partition here should be treated as a design failure to be argued for, not a convenience.


Related

  • bringup — the firmware corrections behind this layout, and the order to build it in
  • cm45 — §7 boot media, §9 boot mode selection, §10 antboot, §11 updates, §12 recovery
  • firmware — the A/B update flow and the recovery story
  • filesystem — drive letters and the DBFS scheme

Important: The Ant64 family of home computers are at early design/prototype stage, everything you see here is subject to change.