Skip to content

feat(firmware): image identity block and bootloader-ready app layout #362

Closed

Description

@mottihoresh

Goal

Every STM32 app image carries a small identity block and is laid out to sit behind the bootloader, so the bootloader, the Gateway uploader and the client can all check an image before and after flashing.

Depends on #361 (spike — done).

Plan (signed off 2026-10-25)

  • Scope: production firmware only — Templates/ (PanelBridge, PanelGroup), Panels/ (Center_Armament, PDU_Left/Right/Center), Examples/ (E2E_DCS_Test, and SoakRig once test(firmware): add SoakRig node sketches for the #291 soak rig #339 lands — the test(firmware): v1.0 stability gate — multi-node live-DCS soak + API/wire freeze review #290 soak runs on the bootloader layout). Tests/ stay plain ST-Link builds at 0x08000000: they only prove the code compiles and are loaded by hand; library-test bench boards carry no bootloader.
  • Bootloader offset: 4 KB (0x1000) — the smallest that works: the F103 write-protects in 4 KB blocks, and the bootloader protects its own block, so the app must start at the next one. Today's bootloader is 2.6 KB (test(firmware): CAN bootloader spike — minimal own bootloader, two STM32s then the RP2040 #361).
  • The offset flips in feat(firmware): CAN bootloader — core #363, with the bootloader and the bootstrap image. feat(firmware): image identity block and bootloader-ready app layout #362 ships the identity block and a shared layout switch left at offset 0, so no board is ever left with nothing at 0x08000000.
  • Identity block at a fixed app start + 0x200 (0x08000200 now, 0x08001200 after the flip) — the same address on every chip; costs ~244 B of padding after the F103's vector table.
  • Shared files in Firmware/shared/: stm32.ini (common settings: the linker script, the stamp step, later the offset — one line), stm32.ld (the STM32 core's linker script + an .osh_identity section at +0x200), osh_image.py (post-build stamp: flat binary → length + CRC-32/MPEG-2 skipping the CRC word → objcopy --update-section into the .elf; the .bin is made from the stamped .elf). Each in-scope platformio.ini gets two lines pointing at them.
  • FirmwareImage library — FirmwareImage.h: the layout, constants and CRC test vectors (synced to the client for SkyHawkClient#177). The instance is defined in each role library with its kind — PanelGroup, PduNode, PanelBridge — plus a weak fallback in STM32Board (kind UNKNOWN).
  • Verify first on one project, before the sweep: the custom linker script still gets the core's offset / size settings, the stamp runs before the .bin, the stamped .elf flashes over ST-Link, the PDU's size limit still holds.

Identity block (32 bytes, little-endian, fixed offset after the vector table, e.g. app start + 0x200)

Offset Size Field Source
0 4 magic = "OSKY" constant
4 2 layoutVersion constant, starts at 1
6 2 blockSize (= 32) constant — readers parse the prefix they know (#295 growth rule)
8 4 imageLength post-build stamp
12 4 imageCrc — CRC-32/MPEG-2 over the image, this field excluded post-build stamp
16 1 nodeId the same NODE_ID macro the code uses (D10)
17 1 nodeKind CANProtocol's kind constant (#344)
18 3 fwMajor / fwMinor / fwPatch FirmwareVersion.h (#295 A11) — the same value READY reports
21 11 reserved, 0 —

No commit id and no "dev build" flag: release images are built from the release tag, so the version identifies the commit; dev builds keep the last release's version (accepted).

Scope

  • The block as a const struct in a shared header, placed by a linker section at the fixed offset (project copy of the core's linker script, or a core option — to verify).
  • App start at the bootloader offset in a shared PlatformIO config: board_upload.offset_address + -DVECT_TAB_OFFSET.
  • Post-build step fills imageLength + imageCrc in both .bin and .elf (objcopy --update-section) — ST-Link uploads flash the .elf, and an unstamped app would fail the bootloader's boot check.
  • CRC-32/MPEG-2 = the F103 hardware CRC unit's algorithm (≠ zip CRC-32 / Node zlib.crc32). Export test vectors for the client via npm run sync.
  • Spec + docs in the same PR (FirmwarePlan, TechSpec, docs/firmware/).

From the spike (#361)

  • App offset — settled. board_build.flash_offset = 0x2000 + board_upload.offset_address = 0x08002000. The STM32 core turns flash_offset into VECT_TAB_OFFSET (its SystemInit sets VTOR) and the linker's LD_FLASH_OFFSET, so no project copy of the linker script is needed for the offset. Verified on the bench: the app reports VTOR = 0x08002000. (The identity block's fixed spot may still need one linker section.)
  • Offset = 8 KB for now — the bootloader is 2.2–2.6 KB, so 4 KB fits too; pick once feat(firmware): CAN bootloader — safety features #364's features are measured.
  • board_upload.maximum_size = 64 KB − the bootloader, so an app that outgrows its space fails the build.
  • CRC-32/MPEG-2 (the F103's hardware unit, 32-bit little-endian words) matched a host implementation on every bench run — use that algorithm and those test vectors.
  • Spike code: local branch spike/361-bootloader (370fcf12, 24c949ec — not pushed), Firmware/Spike/Bootloader361/app/.

Done when

Every STM32 project builds with the block; a host test checks the stamped CRC against the test vectors; ST-Link flashing of a stamped .elf still boots.

Client side

OpenSkyHawk/SkyHawkClient#177 (From file… reads the identity block) — design: https://claude.ai/artifact/R8DXWWC4uyAby54tBQLo8v (Cockpit board).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfirmwareFirmware implementation workpriority: mediumWanted and scoped; not blocking today

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions