diff --git a/.gitea/workflows/flash-erase.yml b/.gitea/workflows/flash-erase.yml new file mode 100644 index 0000000..998a2c4 --- /dev/null +++ b/.gitea/workflows/flash-erase.yml @@ -0,0 +1,16 @@ +name: Erase Entire 32 MiB SPI Flash + +on: + workflow_dispatch: + +jobs: + flash-erase: + runs-on: serial-runner + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Erase entire flash and verify first 256 bytes + run: | + /usr/bin/python3 scripts/serial_command.py erase diff --git a/firmware/README.md b/firmware/README.md new file mode 100644 index 0000000..f744d6e --- /dev/null +++ b/firmware/README.md @@ -0,0 +1,6 @@ + +The separate `Erase Entire 32 MiB SPI Flash` workflow erases offsets +0x0 through 0x1ffffff, including all firmware and any saved environment. +It requires a successful 32 MiB flash probe, checks the erase/read command +results, and verifies that the first 256 bytes are FF. This sample does +not verify every byte of the flash. No automatic retries are performed. diff --git a/scripts/serial_command.py b/scripts/serial_command.py index c7da679..a6fe407 100644 --- a/scripts/serial_command.py +++ b/scripts/serial_command.py @@ -141,9 +141,31 @@ def transfer(cli, firmware, *, dtb=False, kernel=False, rootfs=False, rootfs_siz print("\nFirmware flash write and full read-back comparison verified.") +def erase_flash(cli): + cli.command("sf probe 0:0", r"^SF: Detected .+total 32 MiB\s*$", timeout=20) + cli.command( + "sf erase 0x0 0x2000000", + r"^SF: 33554432 bytes @ 0x0 Erased: OK\s*$", timeout=600, + ) + cli.command( + "sf read 0x42800000 0x0 0x100", + r"^SF: 256 bytes @ 0x0 Read: OK\s*$", timeout=20, + ) + output = cli.command("md.b 0x42800000 0x100", r"^42800000:") + for offset in range(0, 0x100, 16): + address = f"{0x42800000 + offset:08x}" + rows = re.findall( + r"^" + address + r":((?: [0-9a-fA-F]{2}){16})(?:\s|$)", + output, re.MULTILINE, + ) + if len(rows) != 1 or bytes.fromhex(rows[0]) != b"\xff" * 16: + raise RuntimeError("Erase verification failed at " + address) + print("\nFull-flash erase command succeeded; first 256 bytes verified as FF.") + + def main(): parser = argparse.ArgumentParser() - parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs"), default="check", nargs="?") + parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs", "erase"), default="check", nargs="?") parser.add_argument("--port", default="/dev/ttyUSB0") parser.add_argument("--firmware", type=Path, default=None) parser.add_argument("--rootfs-size", type=lambda value: int(value, 0), help="Actual RootFS partition size in bytes (decimal or 0x-prefixed hex)") @@ -163,6 +185,9 @@ def main(): cli.detect() if args.mode == "check": return + if args.mode == "erase": + erase_flash(cli) + return if args.mode in ("transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs"): transfer(cli, args.firmware, dtb=args.mode == "transfer-dtb", kernel=args.mode == "transfer-kernel", rootfs=args.mode == "transfer-rootfs", rootfs_size=args.rootfs_size) return