diff --git a/pm3py/core/flash.py b/pm3py/core/flash.py index 3f284a6..5f8c384 100644 --- a/pm3py/core/flash.py +++ b/pm3py/core/flash.py @@ -98,19 +98,40 @@ def build_blocks(segments: list[tuple[int, bytes]], block_size: int = FLASH_BLOCK_SIZE) -> list[tuple[int, bytes]]: """Turn segments into a list of (address, block) writes. - Each segment is padded out to ``block_size`` boundaries with 0xFF (erased - flash), then split into block-sized chunks. Addresses are block-aligned so - each chunk maps onto whole flash pages. + Segments that fall in the same or overlapping flash blocks are merged into a + single buffer first, so a page shared by two adjacent segments is written + once with both segments' real data. Writing such a page twice would be a bug: + the SAM7 erase-programs each page atomically, so a second write of the shared + page (with 0xFF padding where the other segment's data lives) would wipe the + first segment's bytes. Gaps within a merged run and the aligned outer edges + are filled with 0xFF (erased flash). """ + if not segments: + return [] + segs = sorted(segments) + + # Group consecutive segments whose block-aligned spans touch or overlap. + groups: list[list[tuple[int, bytes]]] = [[segs[0]]] + for paddr, data in segs[1:]: + g = groups[-1] + g_end = max(a + len(d) for a, d in g) + if paddr < _align_up(g_end, block_size): + g.append((paddr, data)) + else: + groups.append([(paddr, data)]) + blocks: list[tuple[int, bytes]] = [] - for paddr, data in segments: - start = _align_down(paddr, block_size) - pre = paddr - start - end = _align_up(paddr + len(data), block_size) - post = end - (paddr + len(data)) - blob = b"\xFF" * pre + data + b"\xFF" * post + for g in groups: + g_start = g[0][0] + g_end = max(a + len(d) for a, d in g) + astart = _align_down(g_start, block_size) + aend = _align_up(g_end, block_size) + blob = bytearray(b"\xFF" * (aend - astart)) + for paddr, data in g: + off = paddr - astart + blob[off:off + len(data)] = data for i in range(0, len(blob), block_size): - blocks.append((start + i, blob[i:i + block_size])) + blocks.append((astart + i, bytes(blob[i:i + block_size]))) return blocks diff --git a/tests/test_flash.py b/tests/test_flash.py index cf1b9e5..57a4605 100644 --- a/tests/test_flash.py +++ b/tests/test_flash.py @@ -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)])