|
|
|
@@ -82,14 +82,19 @@ class UBoot:
|
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def transfer(cli, firmware):
|
|
|
|
|
def transfer(cli, firmware, *, dtb=False, kernel=False):
|
|
|
|
|
address = 0x41000000 if dtb else 0x42000000
|
|
|
|
|
offset = 0x310000 if kernel else (0x200000 if dtb else 0)
|
|
|
|
|
limit = 0x500000 if kernel else (0x110000 if dtb else 0x200000)
|
|
|
|
|
dump_size = 64 if kernel else 32
|
|
|
|
|
filename = "zImage" if kernel else ("sun8i-v3s-licheepi-zero-dock.dtb" if dtb else "u-boot-sunxi-with-spl.bin")
|
|
|
|
|
image = firmware.read_bytes()
|
|
|
|
|
if not 32 <= len(image) <= 0x200000:
|
|
|
|
|
raise RuntimeError("Firmware size must be between 32 and 0x200000 bytes")
|
|
|
|
|
if not dump_size <= len(image) <= limit:
|
|
|
|
|
raise RuntimeError(f"Firmware size must be between {dump_size} and {limit:#x} bytes")
|
|
|
|
|
# Invalidate any previous transfer length before attempting a new transfer.
|
|
|
|
|
cli.command("setenv filesize")
|
|
|
|
|
output = cli.command(
|
|
|
|
|
"tftp 0x42000000 u-boot-sunxi-with-spl.bin",
|
|
|
|
|
f"tftp {address:#x} {filename}",
|
|
|
|
|
r"^Bytes transferred = \d+", timeout=120,
|
|
|
|
|
)
|
|
|
|
|
transferred = re.findall(r"^Bytes transferred = (\d+)", output, re.MULTILINE)
|
|
|
|
@@ -99,42 +104,46 @@ def transfer(cli, firmware):
|
|
|
|
|
sizes = re.findall(r"^([0-9a-fA-F]+)$", output, re.MULTILINE)
|
|
|
|
|
if len(sizes) != 1 or int(sizes[0], 16) != len(image):
|
|
|
|
|
raise RuntimeError("U-Boot filesize differs from the local firmware file")
|
|
|
|
|
output = cli.command("md.b 0x42000000 0x20", r"^42000000:")
|
|
|
|
|
output = cli.command(f"md.b {address:#x} {dump_size:#x}", rf"^{address:08x}:")
|
|
|
|
|
dumped = bytearray()
|
|
|
|
|
for address in ("42000000", "42000010"):
|
|
|
|
|
rows = re.findall(r"^" + address + r":((?: [0-9a-fA-F]{2}){16})(?:\s|$)", output, re.MULTILINE)
|
|
|
|
|
for row_offset in range(0, dump_size, 16):
|
|
|
|
|
row_address = f"{address + row_offset:08x}"
|
|
|
|
|
rows = re.findall(r"^" + row_address + r":((?: [0-9a-fA-F]{2}){16})(?:\s|$)", output, re.MULTILINE)
|
|
|
|
|
if len(rows) != 1:
|
|
|
|
|
raise RuntimeError("Missing or invalid memory dump at " + address)
|
|
|
|
|
raise RuntimeError("Missing or invalid memory dump at " + row_address)
|
|
|
|
|
dumped.extend(bytes.fromhex(rows[0]))
|
|
|
|
|
if dumped != image[:32]:
|
|
|
|
|
raise RuntimeError("First 32 bytes in RAM differ from the local firmware file")
|
|
|
|
|
print("\nTransfer verified: size and first 32 bytes match.")
|
|
|
|
|
if dumped != image[:dump_size]:
|
|
|
|
|
raise RuntimeError(f"First {dump_size} bytes in RAM differ from the local firmware file")
|
|
|
|
|
print(f"\nTransfer verified: size and first {dump_size} bytes match.")
|
|
|
|
|
cli.command("sf probe 0:0", r"^SF: Detected .+", timeout=20)
|
|
|
|
|
cli.command(
|
|
|
|
|
"sf write 0x42000000 0x0 ${filesize}",
|
|
|
|
|
rf"^SF: {len(image)} bytes @ 0x0 Written: OK\s*$", timeout=120,
|
|
|
|
|
f"sf write {address:#x} {offset:#x} ${{filesize}}",
|
|
|
|
|
rf"^SF: {len(image)} bytes @ {offset:#x} Written: OK\s*$", timeout=120,
|
|
|
|
|
)
|
|
|
|
|
cli.command(
|
|
|
|
|
"sf read 0x42800000 0x0 ${filesize}",
|
|
|
|
|
rf"^SF: {len(image)} bytes @ 0x0 Read: OK\s*$", timeout=120,
|
|
|
|
|
f"sf read 0x42800000 {offset:#x} ${{filesize}}",
|
|
|
|
|
rf"^SF: {len(image)} bytes @ {offset:#x} Read: OK\s*$", timeout=120,
|
|
|
|
|
)
|
|
|
|
|
output = cli.command(
|
|
|
|
|
"cmp.b 0x42000000 0x42800000 ${filesize}",
|
|
|
|
|
f"cmp.b {address:#x} 0x42800000 ${{filesize}}",
|
|
|
|
|
r"^Total of \d+ byte\(s\) were the same", timeout=60,
|
|
|
|
|
)
|
|
|
|
|
compared = re.findall(r"^Total of (\d+) byte\(s\) were the same", output, re.MULTILINE)
|
|
|
|
|
if compared != [str(len(image))]:
|
|
|
|
|
raise RuntimeError("Flash comparison byte count differs from firmware size")
|
|
|
|
|
print("\nU-Boot flash write and full read-back comparison verified.")
|
|
|
|
|
print("\nFirmware flash write and full read-back comparison verified.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
parser.add_argument("mode", choices=("check", "configure", "transfer"), default="check", nargs="?")
|
|
|
|
|
parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel"), default="check", nargs="?")
|
|
|
|
|
parser.add_argument("--port", default="/dev/ttyUSB0")
|
|
|
|
|
parser.add_argument("--firmware", type=Path, default=Path(__file__).resolve().parent.parent / "firmware" / "u-boot-sunxi-with-spl.bin")
|
|
|
|
|
parser.add_argument("--firmware", type=Path, default=None)
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
if args.mode == "transfer" and not args.firmware.is_file():
|
|
|
|
|
if args.firmware is None:
|
|
|
|
|
filename = "zImage" if args.mode == "transfer-kernel" else ("sun8i-v3s-licheepi-zero-dock.dtb" if args.mode == "transfer-dtb" else "u-boot-sunxi-with-spl.bin")
|
|
|
|
|
args.firmware = Path(__file__).resolve().parent.parent / "firmware" / filename
|
|
|
|
|
if args.mode in ("transfer", "transfer-dtb", "transfer-kernel") and not args.firmware.is_file():
|
|
|
|
|
raise RuntimeError("Firmware file not found: " + str(args.firmware))
|
|
|
|
|
# Shared by both workflows: fail rather than interleave serial commands.
|
|
|
|
|
with open("/tmp/uboot-serial.lock", "w") as lock:
|
|
|
|
@@ -144,8 +153,8 @@ def main():
|
|
|
|
|
cli.detect()
|
|
|
|
|
if args.mode == "check":
|
|
|
|
|
return
|
|
|
|
|
if args.mode == "transfer":
|
|
|
|
|
transfer(cli, args.firmware)
|
|
|
|
|
if args.mode in ("transfer", "transfer-dtb", "transfer-kernel"):
|
|
|
|
|
transfer(cli, args.firmware, dtb=args.mode == "transfer-dtb", kernel=args.mode == "transfer-kernel")
|
|
|
|
|
return
|
|
|
|
|
cli.command("version", r"^U-Boot \d")
|
|
|
|
|
cli.command(
|
|
|
|
|