288 lines
9.7 KiB
Python
Executable File
288 lines
9.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
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
|
|
|
|
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(filename):
|
|
with open(filename, mode='r') as file:
|
|
try:
|
|
for line in file:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
if line[0] == ':':
|
|
continue
|
|
return False
|
|
return True
|
|
except:
|
|
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 " + str(ptr))
|
|
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 " + str(ptr)
|
|
newaddr = hd[3]
|
|
if curraddr == None:
|
|
appstartaddr = newaddr
|
|
curraddr = newaddr
|
|
padding = newaddr - curraddr
|
|
if padding < 0:
|
|
assert False, "Block out of order at " + str(ptr)
|
|
if padding > 10*1024*1024:
|
|
assert False, "More than 10M of padding needed at " + str(ptr)
|
|
if padding % 4 != 0:
|
|
assert False, "Non-word padding size at " + str(ptr)
|
|
while padding > 0:
|
|
padding -= 4
|
|
outp.append(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 char 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)
|
|
datapadding = b"\x00" * (512 - 256 - 32 - 4)
|
|
block = hd + self.bytes + datapadding + struct.pack("<I", UF2_MAGIC_END)
|
|
return block
|
|
|
|
def convert_from_hex_to_uf2(records):
|
|
global appstartaddr
|
|
appstartaddr = None
|
|
upper = 0
|
|
blocks = {}
|
|
for line in records:
|
|
if line[0] != ':':
|
|
continue
|
|
(lenstr, addrstr, typestr, data, chkstr) = (line[1:3], line[3:7], line[7:9], line[9:-2], line[-2:])
|
|
if int(chkstr, 16) != (-(sum(int(data[i:i+2], 16) for i in range(0, len(data), 2)) + int(typestr, 16) + int(addrstr, 16) + int(lenstr, 16)) & 0xff):
|
|
assert False, "Invalid hex checksum for line: " + line
|
|
tp = int(typestr, 16)
|
|
if tp == 4:
|
|
upper = int(data, 16) << 16
|
|
elif tp == 2:
|
|
upper = int(data, 16) << 4
|
|
elif tp == 1:
|
|
break
|
|
elif tp == 0:
|
|
addr = upper + int(addrstr, 16)
|
|
if appstartaddr == None:
|
|
appstartaddr = addr
|
|
i = 0
|
|
while i < len(data):
|
|
if addr in blocks:
|
|
block = blocks[addr]
|
|
else:
|
|
block = Block(addr & ~0xff)
|
|
blocks[addr & ~0xff] = block
|
|
block.bytes[addr & 0xff] = int(data[i:i+2], 16)
|
|
addr += 1
|
|
i += 2
|
|
|
|
blocks = sorted(blocks.values(), key=lambda x: x.addr)
|
|
return b"".join(block.encode(i, len(blocks)) for i, block in enumerate(blocks))
|
|
|
|
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 ["RP2040"]:
|
|
familyid = 0xe48bff56
|
|
else:
|
|
try:
|
|
familyid = int(args.family, 0)
|
|
except ValueError:
|
|
error("Family ID needs to be a number or one of: RP2040")
|
|
|
|
if args.list:
|
|
drives = get_drives()
|
|
if len(drives) == 0:
|
|
error("No drives found.")
|
|
for d in drives:
|
|
print(d, info_uf2(d))
|
|
return
|
|
|
|
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 = os.path.splitext(args.input)[1].lower()
|
|
if from_uf2:
|
|
outbuf = convert_from_uf2(inpbuf)
|
|
elif is_hex(args.input):
|
|
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8").split('\n'))
|
|
elif ext == ".bin":
|
|
if args.carray:
|
|
outbuf = convert_to_carray(inpbuf)
|
|
else:
|
|
outbuf = convert_to_uf2(inpbuf)
|
|
else:
|
|
error("Extension %s not supported." % ext)
|
|
|
|
if args.deploy:
|
|
drives = get_drives()
|
|
if len(drives) == 0:
|
|
error("No drives to deploy.")
|
|
for d in drives:
|
|
print("Flashing %s (%s)" % (d, info_uf2(d)))
|
|
with open(d + "NEW.UF2", "wb") as f:
|
|
f.write(outbuf)
|
|
elif args.output == None:
|
|
if args.carray:
|
|
print(outbuf.decode("utf-8"))
|
|
else:
|
|
drives = get_drives()
|
|
if len(drives) == 1:
|
|
args.output = drives[0] + "NEW.UF2"
|
|
else:
|
|
if from_uf2:
|
|
args.output = "flash.bin"
|
|
else:
|
|
args.output = "flash.uf2"
|
|
if args.output:
|
|
with open(args.output, mode='wb') as f:
|
|
f.write(outbuf)
|
|
print("Wrote %d bytes to %s." % (len(outbuf), args.output))
|
|
|
|
def get_drives():
|
|
def check_errors(r):
|
|
if r.returncode != 0:
|
|
return []
|
|
return r.stdout.split('\n')
|
|
|
|
if sys.platform == "win32":
|
|
return [r + "\\" for r in check_errors(subprocess.run(
|
|
['wmic', 'logicaldisk', 'get', 'size,freespace,caption'],
|
|
capture_output=True, text=True)) if r and not r.startswith("Caption")]
|
|
elif sys.platform == "darwin":
|
|
def parse_os_x_mount_output(mount_output):
|
|
drives = []
|
|
for line in mount_output:
|
|
m = re.match(r'^/dev/disk.*? on (.*?) \([^/]*\)$', line)
|
|
if m:
|
|
drives.append(m.group(1) + "/")
|
|
return drives
|
|
return parse_os_x_mount_output(check_errors(subprocess.run(['mount'], capture_output=True, text=True)))
|
|
else:
|
|
def parse_linux_mount_output(mount_output):
|
|
drives = []
|
|
for line in mount_output:
|
|
words = line.split()
|
|
if len(words) >= 3:
|
|
drives.append(words[2] + "/")
|
|
return drives
|
|
return parse_linux_mount_output(check_errors(subprocess.run(['mount'], capture_output=True, text=True)))
|
|
|
|
def info_uf2(d):
|
|
try:
|
|
with open(d + INFO_FILE, mode='r') as f:
|
|
return f.read()
|
|
except:
|
|
return "UF2 Bootloader"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |