blob: e92c2ec3ab88f1d73f8213464b839c1e4ac5cae0 [file] [log] [blame]
Pete Johanson310a4642020-12-31 16:51:52 -05001#!/usr/bin/env python3
2# Copyright (c) Microsoft Corporation
3# SPDX-License-Identifier: MIT
4# Copied from 7a9e1f4 of https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py
5# pylint: skip-file
6import sys
7import struct
8import subprocess
9import re
10import os
11import os.path
12import argparse
13
14
15UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
16UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
17UF2_MAGIC_END = 0x0AB16F30 # Ditto
18
19families = {
20 'SAMD21': 0x68ed2b88,
21 'SAML21': 0x1851780a,
22 'SAMD51': 0x55114460,
23 'NRF52': 0x1b57745f,
24 'STM32F0': 0x647824b6,
25 'STM32F1': 0x5ee21072,
26 'STM32F2': 0x5d1a0a2e,
27 'STM32F3': 0x6b846188,
28 'STM32F4': 0x57755a57,
29 'STM32F7': 0x53b80f00,
30 'STM32G0': 0x300f5633,
31 'STM32G4': 0x4c71240a,
32 'STM32H7': 0x6db66082,
33 'STM32L0': 0x202e3a91,
34 'STM32L1': 0x1e1f432d,
35 'STM32L4': 0x00ff6919,
36 'STM32L5': 0x04240bdf,
37 'STM32WB': 0x70d16653,
38 'STM32WL': 0x21460ff0,
39 'ATMEGA32': 0x16573617,
40 'MIMXRT10XX': 0x4FB2D5BD,
41 'LPC55': 0x2abc77ec,
42 'GD32F350': 0x31D228C6,
43 'ESP32S2': 0xbfdd4eee,
44 'RP2040': 0xe48bff56
45}
46
47INFO_FILE = "/INFO_UF2.TXT"
48
49appstartaddr = 0x2000
50familyid = 0x0
51
52
53def is_uf2(buf):
54 w = struct.unpack("<II", buf[0:8])
55 return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
56
57def is_hex(buf):
58 try:
59 w = buf[0:30].decode("utf-8")
60 except UnicodeDecodeError:
61 return False
62 if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
63 return True
64 return False
65
66def convert_from_uf2(buf):
67 global appstartaddr
68 numblocks = len(buf) // 512
69 curraddr = None
70 outp = []
71 for blockno in range(numblocks):
72 ptr = blockno * 512
73 block = buf[ptr:ptr + 512]
74 hd = struct.unpack(b"<IIIIIIII", block[0:32])
75 if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
76 print("Skipping block at " + ptr + "; bad magic")
77 continue
78 if hd[2] & 1:
79 # NO-flash flag set; skip block
80 continue
81 datalen = hd[4]
82 if datalen > 476:
83 assert False, "Invalid UF2 data size at " + ptr
84 newaddr = hd[3]
85 if curraddr == None:
86 appstartaddr = newaddr
87 curraddr = newaddr
88 padding = newaddr - curraddr
89 if padding < 0:
90 assert False, "Block out of order at " + ptr
91 if padding > 10*1024*1024:
92 assert False, "More than 10M of padding needed at " + ptr
93 if padding % 4 != 0:
94 assert False, "Non-word padding size at " + ptr
95 while padding > 0:
96 padding -= 4
97 outp += b"\x00\x00\x00\x00"
98 outp.append(block[32 : 32 + datalen])
99 curraddr = newaddr + datalen
100 return b"".join(outp)
101
102def convert_to_carray(file_content):
103 outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
104 outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
105 for i in range(len(file_content)):
106 if i % 16 == 0:
107 outp += "\n"
108 outp += "0x%02x, " % file_content[i]
109 outp += "\n};\n"
110 return bytes(outp, "utf-8")
111
112def convert_to_uf2(file_content):
113 global familyid
114 datapadding = b""
115 while len(datapadding) < 512 - 256 - 32 - 4:
116 datapadding += b"\x00\x00\x00\x00"
117 numblocks = (len(file_content) + 255) // 256
118 outp = []
119 for blockno in range(numblocks):
120 ptr = 256 * blockno
121 chunk = file_content[ptr:ptr + 256]
122 flags = 0x0
123 if familyid:
124 flags |= 0x2000
125 hd = struct.pack(b"<IIIIIIII",
126 UF2_MAGIC_START0, UF2_MAGIC_START1,
127 flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
128 while len(chunk) < 256:
129 chunk += b"\x00"
130 block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
131 assert len(block) == 512
132 outp.append(block)
133 return b"".join(outp)
134
135class Block:
136 def __init__(self, addr):
137 self.addr = addr
138 self.bytes = bytearray(256)
139
140 def encode(self, blockno, numblocks):
141 global familyid
142 flags = 0x0
143 if familyid:
144 flags |= 0x2000
145 hd = struct.pack("<IIIIIIII",
146 UF2_MAGIC_START0, UF2_MAGIC_START1,
147 flags, self.addr, 256, blockno, numblocks, familyid)
148 hd += self.bytes[0:256]
149 while len(hd) < 512 - 4:
150 hd += b"\x00"
151 hd += struct.pack("<I", UF2_MAGIC_END)
152 return hd
153
154def convert_from_hex_to_uf2(buf):
155 global appstartaddr
156 appstartaddr = None
157 upper = 0
158 currblock = None
159 blocks = []
160 for line in buf.split('\n'):
161 if line[0] != ":":
162 continue
163 i = 1
164 rec = []
165 while i < len(line) - 1:
166 rec.append(int(line[i:i+2], 16))
167 i += 2
168 tp = rec[3]
169 if tp == 4:
170 upper = ((rec[4] << 8) | rec[5]) << 16
171 elif tp == 2:
172 upper = ((rec[4] << 8) | rec[5]) << 4
173 assert (upper & 0xffff) == 0
174 elif tp == 1:
175 break
176 elif tp == 0:
177 addr = upper | (rec[1] << 8) | rec[2]
178 if appstartaddr == None:
179 appstartaddr = addr
180 i = 4
181 while i < len(rec) - 1:
182 if not currblock or currblock.addr & ~0xff != addr & ~0xff:
183 currblock = Block(addr & ~0xff)
184 blocks.append(currblock)
185 currblock.bytes[addr & 0xff] = rec[i]
186 addr += 1
187 i += 1
188 numblocks = len(blocks)
189 resfile = b""
190 for i in range(0, numblocks):
191 resfile += blocks[i].encode(i, numblocks)
192 return resfile
193
194def to_str(b):
195 return b.decode("utf-8")
196
197def get_drives():
198 drives = []
199 if sys.platform == "win32":
200 r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
201 "get", "DeviceID,", "VolumeName,",
202 "FileSystem,", "DriveType"])
203 for line in to_str(r).split('\n'):
204 words = re.split('\s+', line)
205 if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
206 drives.append(words[0])
207 else:
208 rootpath = "/media"
209 if sys.platform == "darwin":
210 rootpath = "/Volumes"
211 elif sys.platform == "linux":
212 tmp = rootpath + "/" + os.environ["USER"]
213 if os.path.isdir(tmp):
214 rootpath = tmp
215 for d in os.listdir(rootpath):
216 drives.append(os.path.join(rootpath, d))
217
218
219 def has_info(d):
220 try:
221 return os.path.isfile(d + INFO_FILE)
222 except:
223 return False
224
225 return list(filter(has_info, drives))
226
227
228def board_id(path):
229 with open(path + INFO_FILE, mode='r') as file:
230 file_content = file.read()
231 return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
232
233
234def list_drives():
235 for d in get_drives():
236 print(d, board_id(d))
237
238
239def write_file(name, buf):
240 with open(name, "wb") as f:
241 f.write(buf)
242 print("Wrote %d bytes to %s" % (len(buf), name))
243
244
245def main():
246 global appstartaddr, familyid
247 def error(msg):
248 print(msg)
249 sys.exit(1)
250 parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
251 parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
252 help='input file (HEX, BIN or UF2)')
253 parser.add_argument('-b' , '--base', dest='base', type=str,
254 default="0x2000",
255 help='set base address of application for BIN format (default: 0x2000)')
256 parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str,
257 help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
258 parser.add_argument('-d' , '--device', dest="device_path",
259 help='select a device path to flash')
260 parser.add_argument('-l' , '--list', action='store_true',
261 help='list connected devices')
262 parser.add_argument('-c' , '--convert', action='store_true',
263 help='do not flash, just convert')
264 parser.add_argument('-D' , '--deploy', action='store_true',
265 help='just flash, do not convert')
266 parser.add_argument('-f' , '--family', dest='family', type=str,
267 default="0x0",
268 help='specify familyID - number or name (default: 0x0)')
269 parser.add_argument('-C' , '--carray', action='store_true',
270 help='convert binary file to a C array, not UF2')
271 args = parser.parse_args()
272 appstartaddr = int(args.base, 0)
273
274 if args.family.upper() in families:
275 familyid = families[args.family.upper()]
276 else:
277 try:
278 familyid = int(args.family, 0)
279 except ValueError:
280 error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
281
282 if args.list:
283 list_drives()
284 else:
285 if not args.input:
286 error("Need input file")
287 with open(args.input, mode='rb') as f:
288 inpbuf = f.read()
289 from_uf2 = is_uf2(inpbuf)
290 ext = "uf2"
291 if args.deploy:
292 outbuf = inpbuf
293 elif from_uf2:
294 outbuf = convert_from_uf2(inpbuf)
295 ext = "bin"
296 elif is_hex(inpbuf):
297 outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
298 elif args.carray:
299 outbuf = convert_to_carray(inpbuf)
300 ext = "h"
301 else:
302 outbuf = convert_to_uf2(inpbuf)
303 print("Converting to %s, output size: %d, start address: 0x%x" %
304 (ext, len(outbuf), appstartaddr))
305 if args.convert or ext != "uf2":
306 drives = []
307 if args.output == None:
308 args.output = "flash." + ext
309 else:
310 drives = get_drives()
311
312 if args.output:
313 write_file(args.output, outbuf)
314 else:
315 if len(drives) == 0:
316 error("No drive to deploy.")
317 for d in drives:
318 print("Flashing %s (%s)" % (d, board_id(d)))
319 write_file(d + "/NEW.UF2", outbuf)
320
321
322if __name__ == "__main__":
323 main()