fix(flash): merge segments sharing a flash page

Found on the first real-hardware flash (PM3 Easy). The fullimage ELF has two
adjacent PT_LOAD segments (seg1 ends at 0x15E5AC, seg2 starts there) that share
the 0x15E400 flash page. build_blocks aligned each segment independently, so the
shared page was written twice — seg2's 0xFF-padded head, written last,
erase-programmed the page and wiped seg1's real bytes there (the SAM7 programs a
whole page atomically). That region holds version_information, so the flashed OS
booted but reported 'Missing/Invalid version information'.

Fix: merge segments whose block-aligned spans touch into one buffer, so every
shared page is written once with both segments' real data; 0xFF only fills gaps
and aligned outer edges. Re-flash verified on hardware: 3df2ed2e8 -> 303bd5873,
valid version, matches_pin true. Bootrom untouched throughout (fullimage-only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-05 21:59:31 -07:00
parent b0b46b3919
commit 9f926a176c
2 changed files with 52 additions and 10 deletions

View File

@@ -105,6 +105,27 @@ def test_build_blocks_aligns_and_pads():
assert blocks[1][1][-100:] == b"\xFF" * 100
def test_build_blocks_merges_shared_page():
# Two adjacent segments sharing the flash page at 0x102200: seg1 ends at
# 0x1022AC, seg2 starts there. The shared page must keep seg1's real tail —
# writing it twice (seg2's 0xFF head last) would wipe it (the on-hardware bug).
seg1 = (0x102000, b"\x11" * 0x2AC) # ends 0x1022AC (unaligned)
seg2 = (0x1022AC, b"\x22" * 0x100) # ends 0x1023AC
blocks = dict(build_blocks([seg1, seg2]))
shared = blocks[0x102200] # 0x102200..0x102400
assert shared[0x00:0xAC] == b"\x11" * 0xAC # seg1 tail preserved, NOT 0xFF
assert shared[0xAC:0x100] == b"\x22" * 0x54 # seg2 head
# no duplicate block addresses (would mean a double-write of a page)
addrs = [a for a, _ in build_blocks([seg1, seg2])]
assert len(addrs) == len(set(addrs))
def test_build_blocks_separate_when_far_apart():
# A real gap between segments -> separate groups, no giant fill between them.
blocks = build_blocks([(0x102000, b"\x11" * 16), (0x120000, b"\x22" * 16)])
assert {a for a, _ in blocks} == {0x102000, 0x120000}
def test_build_blocks_pads_low_side():
# unaligned start -> leading 0xFF padding down to the block boundary
blocks = build_blocks([(0x102010, b"\x22" * 16)])