310a464acf
Add ability to build a UF2 (https://github.com/Microsoft/uf2) image as an additional output type. This leverages the code partition offset for the UF2 base address, and a configurable UF2 family ID. Includes an unmodified (except for headers for licensing, pylit disabling) version of the uf2conv.py script copied the UF2 format specification repository, used to convert the bin to UF2. Origin: UF2 file format specification reference utilies License: MIT URL: https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py commit: 587abb8b909266e9b468d6284f2fbd425235d1b5 Signed-off-by: Pete Johanson <peter@peterjohanson.com>
324 lines
10 KiB
Python
Executable file
324 lines
10 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Copyright (c) Microsoft Corporation
|
|
# SPDX-License-Identifier: MIT
|
|
# Copied from 7a9e1f4 of https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py
|
|
# pylint: skip-file
|
|
import sys
|
|
import struct
|
|
import subprocess
|
|
import re
|
|
import os
|
|
import os.path
|
|
import argparse
|
|
|
|
|
|
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
|
|
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
|
|
UF2_MAGIC_END = 0x0AB16F30 # Ditto
|
|
|
|
families = {
|
|
'SAMD21': 0x68ed2b88,
|
|
'SAML21': 0x1851780a,
|
|
'SAMD51': 0x55114460,
|
|
'NRF52': 0x1b57745f,
|
|
'STM32F0': 0x647824b6,
|
|
'STM32F1': 0x5ee21072,
|
|
'STM32F2': 0x5d1a0a2e,
|
|
'STM32F3': 0x6b846188,
|
|
'STM32F4': 0x57755a57,
|
|
'STM32F7': 0x53b80f00,
|
|
'STM32G0': 0x300f5633,
|
|
'STM32G4': 0x4c71240a,
|
|
'STM32H7': 0x6db66082,
|
|
'STM32L0': 0x202e3a91,
|
|
'STM32L1': 0x1e1f432d,
|
|
'STM32L4': 0x00ff6919,
|
|
'STM32L5': 0x04240bdf,
|
|
'STM32WB': 0x70d16653,
|
|
'STM32WL': 0x21460ff0,
|
|
'ATMEGA32': 0x16573617,
|
|
'MIMXRT10XX': 0x4FB2D5BD,
|
|
'LPC55': 0x2abc77ec,
|
|
'GD32F350': 0x31D228C6,
|
|
'ESP32S2': 0xbfdd4eee,
|
|
'RP2040': 0xe48bff56
|
|
}
|
|
|
|
INFO_FILE = "/INFO_UF2.TXT"
|
|
|
|
appstartaddr = 0x2000
|
|
familyid = 0x0
|
|
|
|
|
|
def is_uf2(buf):
|
|
w = struct.unpack("<II", buf[0:8])
|
|
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
|
|
|
|
def is_hex(buf):
|
|
try:
|
|
w = buf[0:30].decode("utf-8")
|
|
except UnicodeDecodeError:
|
|
return False
|
|
if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
|
|
return True
|
|
return False
|
|
|
|
def convert_from_uf2(buf):
|
|
global appstartaddr
|
|
numblocks = len(buf) // 512
|
|
curraddr = None
|
|
outp = []
|
|
for blockno in range(numblocks):
|
|
ptr = blockno * 512
|
|
block = buf[ptr:ptr + 512]
|
|
hd = struct.unpack(b"<IIIIIIII", block[0:32])
|
|
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
|
|
print("Skipping block at " + ptr + "; bad magic")
|
|
continue
|
|
if hd[2] & 1:
|
|
# NO-flash flag set; skip block
|
|
continue
|
|
datalen = hd[4]
|
|
if datalen > 476:
|
|
assert False, "Invalid UF2 data size at " + ptr
|
|
newaddr = hd[3]
|
|
if curraddr == None:
|
|
appstartaddr = newaddr
|
|
curraddr = newaddr
|
|
padding = newaddr - curraddr
|
|
if padding < 0:
|
|
assert False, "Block out of order at " + ptr
|
|
if padding > 10*1024*1024:
|
|
assert False, "More than 10M of padding needed at " + ptr
|
|
if padding % 4 != 0:
|
|
assert False, "Non-word padding size at " + ptr
|
|
while padding > 0:
|
|
padding -= 4
|
|
outp += b"\x00\x00\x00\x00"
|
|
outp.append(block[32 : 32 + datalen])
|
|
curraddr = newaddr + datalen
|
|
return b"".join(outp)
|
|
|
|
def convert_to_carray(file_content):
|
|
outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
|
|
outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
|
|
for i in range(len(file_content)):
|
|
if i % 16 == 0:
|
|
outp += "\n"
|
|
outp += "0x%02x, " % file_content[i]
|
|
outp += "\n};\n"
|
|
return bytes(outp, "utf-8")
|
|
|
|
def convert_to_uf2(file_content):
|
|
global familyid
|
|
datapadding = b""
|
|
while len(datapadding) < 512 - 256 - 32 - 4:
|
|
datapadding += b"\x00\x00\x00\x00"
|
|
numblocks = (len(file_content) + 255) // 256
|
|
outp = []
|
|
for blockno in range(numblocks):
|
|
ptr = 256 * blockno
|
|
chunk = file_content[ptr:ptr + 256]
|
|
flags = 0x0
|
|
if familyid:
|
|
flags |= 0x2000
|
|
hd = struct.pack(b"<IIIIIIII",
|
|
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
|
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
|
|
while len(chunk) < 256:
|
|
chunk += b"\x00"
|
|
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
|
|
assert len(block) == 512
|
|
outp.append(block)
|
|
return b"".join(outp)
|
|
|
|
class Block:
|
|
def __init__(self, addr):
|
|
self.addr = addr
|
|
self.bytes = bytearray(256)
|
|
|
|
def encode(self, blockno, numblocks):
|
|
global familyid
|
|
flags = 0x0
|
|
if familyid:
|
|
flags |= 0x2000
|
|
hd = struct.pack("<IIIIIIII",
|
|
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
|
flags, self.addr, 256, blockno, numblocks, familyid)
|
|
hd += self.bytes[0:256]
|
|
while len(hd) < 512 - 4:
|
|
hd += b"\x00"
|
|
hd += struct.pack("<I", UF2_MAGIC_END)
|
|
return hd
|
|
|
|
def convert_from_hex_to_uf2(buf):
|
|
global appstartaddr
|
|
appstartaddr = None
|
|
upper = 0
|
|
currblock = None
|
|
blocks = []
|
|
for line in buf.split('\n'):
|
|
if line[0] != ":":
|
|
continue
|
|
i = 1
|
|
rec = []
|
|
while i < len(line) - 1:
|
|
rec.append(int(line[i:i+2], 16))
|
|
i += 2
|
|
tp = rec[3]
|
|
if tp == 4:
|
|
upper = ((rec[4] << 8) | rec[5]) << 16
|
|
elif tp == 2:
|
|
upper = ((rec[4] << 8) | rec[5]) << 4
|
|
assert (upper & 0xffff) == 0
|
|
elif tp == 1:
|
|
break
|
|
elif tp == 0:
|
|
addr = upper | (rec[1] << 8) | rec[2]
|
|
if appstartaddr == None:
|
|
appstartaddr = addr
|
|
i = 4
|
|
while i < len(rec) - 1:
|
|
if not currblock or currblock.addr & ~0xff != addr & ~0xff:
|
|
currblock = Block(addr & ~0xff)
|
|
blocks.append(currblock)
|
|
currblock.bytes[addr & 0xff] = rec[i]
|
|
addr += 1
|
|
i += 1
|
|
numblocks = len(blocks)
|
|
resfile = b""
|
|
for i in range(0, numblocks):
|
|
resfile += blocks[i].encode(i, numblocks)
|
|
return resfile
|
|
|
|
def to_str(b):
|
|
return b.decode("utf-8")
|
|
|
|
def get_drives():
|
|
drives = []
|
|
if sys.platform == "win32":
|
|
r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
|
|
"get", "DeviceID,", "VolumeName,",
|
|
"FileSystem,", "DriveType"])
|
|
for line in to_str(r).split('\n'):
|
|
words = re.split('\s+', line)
|
|
if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
|
|
drives.append(words[0])
|
|
else:
|
|
rootpath = "/media"
|
|
if sys.platform == "darwin":
|
|
rootpath = "/Volumes"
|
|
elif sys.platform == "linux":
|
|
tmp = rootpath + "/" + os.environ["USER"]
|
|
if os.path.isdir(tmp):
|
|
rootpath = tmp
|
|
for d in os.listdir(rootpath):
|
|
drives.append(os.path.join(rootpath, d))
|
|
|
|
|
|
def has_info(d):
|
|
try:
|
|
return os.path.isfile(d + INFO_FILE)
|
|
except:
|
|
return False
|
|
|
|
return list(filter(has_info, drives))
|
|
|
|
|
|
def board_id(path):
|
|
with open(path + INFO_FILE, mode='r') as file:
|
|
file_content = file.read()
|
|
return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
|
|
|
|
|
|
def list_drives():
|
|
for d in get_drives():
|
|
print(d, board_id(d))
|
|
|
|
|
|
def write_file(name, buf):
|
|
with open(name, "wb") as f:
|
|
f.write(buf)
|
|
print("Wrote %d bytes to %s" % (len(buf), name))
|
|
|
|
|
|
def main():
|
|
global appstartaddr, familyid
|
|
def error(msg):
|
|
print(msg)
|
|
sys.exit(1)
|
|
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
|
|
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
|
|
help='input file (HEX, BIN or UF2)')
|
|
parser.add_argument('-b' , '--base', dest='base', type=str,
|
|
default="0x2000",
|
|
help='set base address of application for BIN format (default: 0x2000)')
|
|
parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str,
|
|
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
|
|
parser.add_argument('-d' , '--device', dest="device_path",
|
|
help='select a device path to flash')
|
|
parser.add_argument('-l' , '--list', action='store_true',
|
|
help='list connected devices')
|
|
parser.add_argument('-c' , '--convert', action='store_true',
|
|
help='do not flash, just convert')
|
|
parser.add_argument('-D' , '--deploy', action='store_true',
|
|
help='just flash, do not convert')
|
|
parser.add_argument('-f' , '--family', dest='family', type=str,
|
|
default="0x0",
|
|
help='specify familyID - number or name (default: 0x0)')
|
|
parser.add_argument('-C' , '--carray', action='store_true',
|
|
help='convert binary file to a C array, not UF2')
|
|
args = parser.parse_args()
|
|
appstartaddr = int(args.base, 0)
|
|
|
|
if args.family.upper() in families:
|
|
familyid = families[args.family.upper()]
|
|
else:
|
|
try:
|
|
familyid = int(args.family, 0)
|
|
except ValueError:
|
|
error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
|
|
|
|
if args.list:
|
|
list_drives()
|
|
else:
|
|
if not args.input:
|
|
error("Need input file")
|
|
with open(args.input, mode='rb') as f:
|
|
inpbuf = f.read()
|
|
from_uf2 = is_uf2(inpbuf)
|
|
ext = "uf2"
|
|
if args.deploy:
|
|
outbuf = inpbuf
|
|
elif from_uf2:
|
|
outbuf = convert_from_uf2(inpbuf)
|
|
ext = "bin"
|
|
elif is_hex(inpbuf):
|
|
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
|
|
elif args.carray:
|
|
outbuf = convert_to_carray(inpbuf)
|
|
ext = "h"
|
|
else:
|
|
outbuf = convert_to_uf2(inpbuf)
|
|
print("Converting to %s, output size: %d, start address: 0x%x" %
|
|
(ext, len(outbuf), appstartaddr))
|
|
if args.convert or ext != "uf2":
|
|
drives = []
|
|
if args.output == None:
|
|
args.output = "flash." + ext
|
|
else:
|
|
drives = get_drives()
|
|
|
|
if args.output:
|
|
write_file(args.output, outbuf)
|
|
else:
|
|
if len(drives) == 0:
|
|
error("No drive to deploy.")
|
|
for d in drives:
|
|
print("Flashing %s (%s)" % (d, board_id(d)))
|
|
write_file(d + "/NEW.UF2", outbuf)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|