wolfBoot/tools/scripts/c2000_flashimg.py

117 lines
4.6 KiB
Python

#!/usr/bin/env python3
# c2000_flashimg.py
#
# Host-side converter for the wolfBoot TI C2000 C28x (TMS320F28P550SJ) port.
#
# The C28x is word-addressed with CHAR_BIT==16. wolfBoot stores a signed image
# in the BOOT partition as a SPLIT layout:
#
# [ header : one octet per 16-bit flash word ][ firmware : native 16-bit words ]
#
# The host `sign` tool operates on an octet stream in which each firmware
# program word is serialized low-octet-then-high-octet. This script bridges
# the host octet world and the C28x word world with that exact convention, so
# the digest the target computes (see image_sha256() in src/image.c) matches
# the digest `sign` computed.
#
# Two steps, matching the build/sign/flash flow:
#
# 1) fw2oct - extract the application firmware, as it lives in flash (a raw
# little-endian 16-bit-word image, e.g. produced from the linked
# .out with the TI hex2000 utility), into the octet stream that
# is handed to `sign` as the firmware payload. Each 16-bit word
# W becomes two octets: (W & 0xFF) then (W >> 8).
#
# 2) hdr2cells - from the signed image (`sign` output = header octets +
# firmware octets), take the fixed-size header and expand each
# header octet into its own 16-bit flash cell (value = octet,
# high byte 0). This "header blob" is flashed at the BOOT
# partition base; the application .out is flashed natively (its
# codestart is linked at BOOT_ADDRESS + IMAGE_HEADER_SIZE), so no
# firmware repack is needed.
#
# Copyright (C) 2026 wolfSSL Inc. GPLv3 - see project headers.
import argparse
import struct
import sys
def read_file(path):
with open(path, "rb") as f:
return f.read()
def write_file(path, data):
with open(path, "wb") as f:
f.write(data)
def fw2oct(args):
"""Firmware native-word image -> host octet stream (low, high per word)."""
words = read_file(args.infile)
if len(words) % 2 != 0:
sys.stderr.write("error: input length %d is not a whole number of "
"16-bit words\n" % len(words))
return 1
out = bytearray()
for i in range(0, len(words), 2):
# Input is a little-endian 16-bit-word image (2 bytes/word).
w = struct.unpack_from("<H", words, i)[0]
out.append(w & 0xFF) # low octet first
out.append((w >> 8) & 0xFF) # high octet
write_file(args.outfile, out)
sys.stderr.write("fw2oct: %d words -> %d octets -> %s\n"
% (len(words) // 2, len(out), args.outfile))
return 0
def hdr2cells(args):
"""Signed image header octets -> C28x flash cells (one octet per word)."""
signed = read_file(args.signed)
hdr_sz = args.header_size
if len(signed) < hdr_sz:
sys.stderr.write("error: signed image (%d) shorter than header size "
"(%d)\n" % (len(signed), hdr_sz))
return 1
header = signed[:hdr_sz]
out = bytearray()
for octet in header:
# Each header octet occupies its own 16-bit flash cell (high byte 0),
# so wolfBoot's octet parser reads it back byte-identically.
out += struct.pack("<H", octet & 0xFF)
write_file(args.outfile, out)
sys.stderr.write("hdr2cells: %d header octets -> %d-word blob for load "
"address 0x%X -> %s\n"
% (hdr_sz, hdr_sz, args.addr, args.outfile))
return 0
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
sub = ap.add_subparsers(dest="cmd", required=True)
p1 = sub.add_parser("fw2oct",
help="firmware word-image -> octet stream for sign")
p1.add_argument("infile", help="firmware native-word image (LE 16-bit words)")
p1.add_argument("outfile", help="output octet stream to feed to sign")
p1.set_defaults(func=fw2oct)
p2 = sub.add_parser("hdr2cells",
help="signed image -> header blob (octet-per-cell)")
p2.add_argument("signed", help="signed image (sign output)")
p2.add_argument("outfile", help="output header cell blob (LE 16-bit words)")
p2.add_argument("--header-size", type=lambda x: int(x, 0), default=256,
help="IMAGE_HEADER_SIZE in octets (default 256 for ECC256)")
p2.add_argument("--addr", type=lambda x: int(x, 0), default=0xA0000,
help="BOOT partition base word address (default 0xA0000)")
p2.set_defaults(func=hdr2cells)
args = ap.parse_args()
return args.func(args)
if __name__ == "__main__":
sys.exit(main())