#!/usr/bin/env python3 """Dirty-page-buffer acceptance test: the loader carries no buffer discard, so a page filled over words an earlier writer left behind programs those instead. This asserts the whole contract — the corruption is real and a bare verify sees it, the repairing verify fixes it in one rewrite (the write that took the stale words auto-erased the buffer), and it stays fixed. The state is reached the way the loader cannot prevent: an application dirties the buffer and jumps in with no reset between. Real boot-sectioned megas forbid that outright — SPM executes only from the boot section (Atmel-8271 §26.2) — but simavr dispatches SPM from anywhere, which is what makes the path constructible at all. Usage: pbdirty.py """ import os import sys def fail(message): print(f"FAIL: {message}") sys.exit(1) def main(): device_bin, elf, mcu, hz, base_hex, page, baud, app_bin, tool, workdir = sys.argv[1:] page, baud = int(page), int(baud) sys.path.insert(0, os.path.dirname(os.path.abspath(tool))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import pbsim import pureboot as pb os.makedirs(workdir, exist_ok=True) dump = os.path.join(workdir, "dump.bin") # Reset boots the application on a BOOTRST-unprogrammed mega; its 'L' is # the loader entry this test needs, reached without a reset. device = pbsim.Device(device_bin, elf, mcu, hz, base_hex, page, baud, dump, reset_hex="0") try: port = pb.Port(device.pty, baud) loader = pb.Loader(port) loader.connect(25) # Install the application and hand over to it. pb.op_flash(loader, app_bin, erase=False, verify=True) loader.run_application() if port.read_exact(3, 5.0) != b"APP": fail("the application did not start") port.write(b"D") if port.read_exact(1, 5.0) != b"D": fail("the application did not acknowledge dirtying the page buffer") port.write(b"L") loader = pb.Loader(port) loader.connect(25) # Program by hand, so the corruption is observable before anything # repairs it. pages = pb.plan_flash(open(app_bin, "rb").read(), loader.info) for address in sorted(pages): loader.write_page(address, pages[address]) try: pb.verify_pages(loader, pages) except pb.Error as error: if "verify failed" not in str(error): fail(f"the read-back failed, but not at verify: {error}") else: # Either the fixture no longer dirties the buffer, or the loader # clears it again — in which case this test's premise is gone. fail("programming over a dirty page buffer came back clean") # What the programming path uses: one rewrite settles it, and it stays # settled. pb.verify_pages(loader, pages, repair=True) pb.verify_pages(loader, pages) # Ground truth beyond the loader's own read-back. loader.run_application() if port.read_exact(3, 5.0) != b"APP": fail("the application did not start after the recovered write") port.close() finally: device.stop() print("pbdirty: a dirty page buffer is caught by verify and cleared by the retry") if __name__ == "__main__": main()