#!/usr/bin/env python3 """Build Efinity raw-HEX images for Ti375C529 persistent Rocket boot.""" import argparse import hashlib import json import re from pathlib import Path HEX_BYTE = re.compile(r"[0-9a-fA-F]{2}") def parse_int(value): return int(value, 0) def read_raw_hex(path): data = bytearray() with path.open() as source: for line_number, line in enumerate(source, 1): value = line.strip() if not HEX_BYTE.fullmatch(value): raise SystemExit( f"{path}:{line_number}: expected one hexadecimal byte per line" ) data.append(int(value, 16)) return bytes(data) def write_raw_hex(path, chunks): path.parent.mkdir(parents=True, exist_ok=True) with path.open("w") as output: for chunk in chunks: for value in chunk: output.write(f"{value:02X}\n") def sha256(data): return hashlib.sha256(data).hexdigest() def main(): parser = argparse.ArgumentParser( description="Combine an Efinity configuration HEX and Rocket app image" ) parser.add_argument("--config-hex", type=Path, required=True) parser.add_argument("--app-bin", type=Path, required=True) parser.add_argument("--app-offset", type=parse_int, required=True) parser.add_argument("--flash-size", type=parse_int, default=0x04000000) parser.add_argument("--nav-context-offset", type=parse_int, required=True) parser.add_argument("--ephemeris-offset", type=parse_int, required=True) parser.add_argument("--combined-hex", type=Path, required=True) parser.add_argument("--app-hex", type=Path, required=True) parser.add_argument("--manifest", type=Path, required=True) args = parser.parse_args() config = read_raw_hex(args.config_hex) app = args.app_bin.read_bytes() app_end = args.app_offset + len(app) if args.app_offset & 0xFFFF: raise SystemExit("application offset must be aligned to a 64-KiB sector") if len(config) > args.app_offset: raise SystemExit( f"configuration image (0x{len(config):x} bytes) overlaps application " f"at 0x{args.app_offset:x}" ) if app_end > args.flash_size: raise SystemExit( f"application ends at 0x{app_end:x}, beyond flash size 0x{args.flash_size:x}" ) reserved_size = 2 * 0x10000 if args.nav_context_offset + reserved_size > args.ephemeris_offset: raise SystemExit("navigation context overlaps ephemeris flash region") if args.ephemeris_offset + reserved_size > args.flash_size: raise SystemExit("ephemeris flash region exceeds device capacity") if app_end > args.nav_context_offset: raise SystemExit( f"application ends at 0x{app_end:x}, overlapping persistent data at " f"0x{args.nav_context_offset:x}" ) padding_size = args.app_offset - len(config) write_raw_hex(args.app_hex, (app,)) write_raw_hex(args.combined_hex, (config, bytes([0xFF]) * padding_size, app)) manifest = { "format": "Efinity raw HEX (one byte per line)", "flash": "Ti375C529 lower SPI NOR (U47)", "flash_size": f"0x{args.flash_size:08x}", "configuration": { "source": str(args.config_hex), "offset": "0x00000000", "size": len(config), "sha256": sha256(config), }, "application": { "source": str(args.app_bin), "offset": f"0x{args.app_offset:08x}", "size": len(app), "sha256": sha256(app), }, "navigation_context": { "offset": f"0x{args.nav_context_offset:08x}", "size": reserved_size, }, "ephemeris_cache": { "offset": f"0x{args.ephemeris_offset:08x}", "size": reserved_size, }, "combined": { "image": str(args.combined_hex), "programmed_length": app_end, }, } args.manifest.parent.mkdir(parents=True, exist_ok=True) args.manifest.write_text(json.dumps(manifest, indent=2) + "\n") print(f"configuration: 0x00000000..0x{len(config) - 1:08x}") print(f"application: 0x{args.app_offset:08x}..0x{app_end - 1:08x}") print(f"combined HEX: {args.combined_hex}") if __name__ == "__main__": main()