src/eric7/MicroPython/Tools/uf2conv.py

branch
eric7
changeset 9915
fd5ff16632cb
child 10162
e7040c88b39e
equal deleted inserted replaced
9913:702ad0ef6b3f 9915:fd5ff16632cb
1 #!/usr/bin/env python3
2
3 # Microsoft UF2
4 #
5 # The MIT License (MIT)
6 #
7 # Copyright (c) Microsoft Corporation
8 #
9 # All rights reserved.
10 #
11 # Permission is hereby granted, free of charge, to any person obtaining a copy
12 # of this software and associated documentation files (the "Software"), to deal
13 # in the Software without restriction, including without limitation the rights
14 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 # copies of the Software, and to permit persons to whom the Software is
16 # furnished to do so, subject to the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included in all
19 # copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 # SOFTWARE.
28
29 import sys
30 import struct
31 import subprocess
32 import re
33 import os
34 import os.path
35 import argparse
36 import json
37
38
39 UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
40 UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
41 UF2_MAGIC_END = 0x0AB16F30 # Ditto
42
43 INFO_FILE = "/INFO_UF2.TXT"
44
45 appstartaddr = 0x2000
46 familyid = 0x0
47
48
49 def is_uf2(buf):
50 w = struct.unpack("<II", buf[0:8])
51 return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
52
53
54 def is_hex(buf):
55 try:
56 w = buf[0:30].decode("utf-8")
57 except UnicodeDecodeError:
58 return False
59 if w[0] == ":" and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
60 return True
61 return False
62
63
64 def convert_from_uf2(buf):
65 global appstartaddr
66 global familyid
67 numblocks = len(buf) // 512
68 curraddr = None
69 currfamilyid = None
70 families_found = {}
71 prev_flag = None
72 all_flags_same = True
73 outp = []
74 for blockno in range(numblocks):
75 ptr = blockno * 512
76 block = buf[ptr : ptr + 512]
77 hd = struct.unpack(b"<IIIIIIII", block[0:32])
78 if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
79 print("Skipping block at " + ptr + "; bad magic")
80 continue
81 if hd[2] & 1:
82 # NO-flash flag set; skip block
83 continue
84 datalen = hd[4]
85 if datalen > 476:
86 assert False, "Invalid UF2 data size at " + ptr
87 newaddr = hd[3]
88 if (hd[2] & 0x2000) and (currfamilyid == None):
89 currfamilyid = hd[7]
90 if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid):
91 currfamilyid = hd[7]
92 curraddr = newaddr
93 if familyid == 0x0 or familyid == hd[7]:
94 appstartaddr = newaddr
95 padding = newaddr - curraddr
96 if padding < 0:
97 assert False, "Block out of order at " + ptr
98 if padding > 10 * 1024 * 1024:
99 assert False, "More than 10M of padding needed at " + ptr
100 if padding % 4 != 0:
101 assert False, "Non-word padding size at " + ptr
102 while padding > 0:
103 padding -= 4
104 outp += b"\x00\x00\x00\x00"
105 if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]):
106 outp.append(block[32 : 32 + datalen])
107 curraddr = newaddr + datalen
108 if hd[2] & 0x2000:
109 if hd[7] in families_found.keys():
110 if families_found[hd[7]] > newaddr:
111 families_found[hd[7]] = newaddr
112 else:
113 families_found[hd[7]] = newaddr
114 if prev_flag == None:
115 prev_flag = hd[2]
116 if prev_flag != hd[2]:
117 all_flags_same = False
118 if blockno == (numblocks - 1):
119 print("--- UF2 File Header Info ---")
120 families = load_families()
121 for family_hex in families_found.keys():
122 family_short_name = ""
123 for name, value in families.items():
124 if value == family_hex:
125 family_short_name = name
126 print(
127 "Family ID is {:s}, hex value is 0x{:08x}".format(
128 family_short_name, family_hex
129 )
130 )
131 print("Target Address is 0x{:08x}".format(families_found[family_hex]))
132 if all_flags_same:
133 print("All block flag values consistent, 0x{:04x}".format(hd[2]))
134 else:
135 print("Flags were not all the same")
136 print("----------------------------")
137 if len(families_found) > 1 and familyid == 0x0:
138 outp = []
139 appstartaddr = 0x0
140 return b"".join(outp)
141
142
143 def convert_to_carray(file_content):
144 outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
145 outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
146 for i in range(len(file_content)):
147 if i % 16 == 0:
148 outp += "\n"
149 outp += "0x%02x, " % file_content[i]
150 outp += "\n};\n"
151 return bytes(outp, "utf-8")
152
153
154 def convert_to_uf2(file_content):
155 global familyid
156 datapadding = b""
157 while len(datapadding) < 512 - 256 - 32 - 4:
158 datapadding += b"\x00\x00\x00\x00"
159 numblocks = (len(file_content) + 255) // 256
160 outp = []
161 for blockno in range(numblocks):
162 ptr = 256 * blockno
163 chunk = file_content[ptr : ptr + 256]
164 flags = 0x0
165 if familyid:
166 flags |= 0x2000
167 hd = struct.pack(
168 b"<IIIIIIII",
169 UF2_MAGIC_START0,
170 UF2_MAGIC_START1,
171 flags,
172 ptr + appstartaddr,
173 256,
174 blockno,
175 numblocks,
176 familyid,
177 )
178 while len(chunk) < 256:
179 chunk += b"\x00"
180 block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
181 assert len(block) == 512
182 outp.append(block)
183 return b"".join(outp)
184
185
186 class Block:
187 def __init__(self, addr):
188 self.addr = addr
189 self.bytes = bytearray(256)
190
191 def encode(self, blockno, numblocks):
192 global familyid
193 flags = 0x0
194 if familyid:
195 flags |= 0x2000
196 hd = struct.pack(
197 "<IIIIIIII",
198 UF2_MAGIC_START0,
199 UF2_MAGIC_START1,
200 flags,
201 self.addr,
202 256,
203 blockno,
204 numblocks,
205 familyid,
206 )
207 hd += self.bytes[0:256]
208 while len(hd) < 512 - 4:
209 hd += b"\x00"
210 hd += struct.pack("<I", UF2_MAGIC_END)
211 return hd
212
213
214 def convert_from_hex_to_uf2(buf):
215 global appstartaddr
216 appstartaddr = None
217 upper = 0
218 currblock = None
219 blocks = []
220 for line in buf.split("\n"):
221 if line[0] != ":":
222 continue
223 i = 1
224 rec = []
225 while i < len(line) - 1:
226 rec.append(int(line[i : i + 2], 16))
227 i += 2
228 tp = rec[3]
229 if tp == 4:
230 upper = ((rec[4] << 8) | rec[5]) << 16
231 elif tp == 2:
232 upper = ((rec[4] << 8) | rec[5]) << 4
233 elif tp == 1:
234 break
235 elif tp == 0:
236 addr = upper + ((rec[1] << 8) | rec[2])
237 if appstartaddr == None:
238 appstartaddr = addr
239 i = 4
240 while i < len(rec) - 1:
241 if not currblock or currblock.addr & ~0xFF != addr & ~0xFF:
242 currblock = Block(addr & ~0xFF)
243 blocks.append(currblock)
244 currblock.bytes[addr & 0xFF] = rec[i]
245 addr += 1
246 i += 1
247 numblocks = len(blocks)
248 resfile = b""
249 for i in range(0, numblocks):
250 resfile += blocks[i].encode(i, numblocks)
251 return resfile
252
253
254 def to_str(b):
255 return b.decode("utf-8")
256
257
258 def get_drives():
259 drives = []
260 if sys.platform == "win32":
261 r = subprocess.check_output(
262 [
263 "wmic",
264 "PATH",
265 "Win32_LogicalDisk",
266 "get",
267 "DeviceID,",
268 "VolumeName,",
269 "FileSystem,",
270 "DriveType",
271 ]
272 )
273 for line in to_str(r).split("\n"):
274 words = re.split("\s+", line)
275 if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
276 drives.append(words[0])
277 else:
278 rootpath = "/media"
279 if sys.platform == "darwin":
280 rootpath = "/Volumes"
281 elif sys.platform == "linux":
282 tmp = rootpath + "/" + os.environ["USER"]
283 if os.path.isdir(tmp):
284 rootpath = tmp
285 for d in os.listdir(rootpath):
286 drives.append(os.path.join(rootpath, d))
287
288 def has_info(d):
289 try:
290 return os.path.isfile(d + INFO_FILE)
291 except:
292 return False
293
294 return list(filter(has_info, drives))
295
296
297 def board_id(path):
298 with open(path + INFO_FILE, mode="r") as file:
299 file_content = file.read()
300 return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
301
302
303 def list_drives():
304 for d in get_drives():
305 print(d, board_id(d))
306
307
308 def write_file(name, buf):
309 with open(name, "wb") as f:
310 f.write(buf)
311 print("Wrote %d bytes to %s" % (len(buf), name))
312
313
314 def load_families():
315 # The expectation is that the `uf2families.json` file is in the same
316 # directory as this script. Make a path that works using `__file__`
317 # which contains the full path to this script.
318 filename = "uf2families.json"
319 pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
320 with open(pathname) as f:
321 raw_families = json.load(f)
322
323 families = {}
324 for family in raw_families:
325 families[family["short_name"]] = int(family["id"], 0)
326
327 return families
328
329
330 def main():
331 global appstartaddr, familyid
332
333 def error(msg):
334 print(msg)
335 sys.exit(1)
336
337 parser = argparse.ArgumentParser(description="Convert to UF2 or flash directly.")
338 parser.add_argument(
339 "input", metavar="INPUT", type=str, nargs="?", help="input file (HEX, BIN or UF2)"
340 )
341 parser.add_argument(
342 "-b",
343 "--base",
344 dest="base",
345 type=str,
346 default="0x2000",
347 help="set base address of application for BIN format (default: 0x2000)",
348 )
349 parser.add_argument(
350 "-o",
351 "--output",
352 metavar="FILE",
353 dest="output",
354 type=str,
355 help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible',
356 )
357 parser.add_argument("-d", "--device", dest="device_path", help="select a device path to flash")
358 parser.add_argument("-l", "--list", action="store_true", help="list connected devices")
359 parser.add_argument("-c", "--convert", action="store_true", help="do not flash, just convert")
360 parser.add_argument("-D", "--deploy", action="store_true", help="just flash, do not convert")
361 parser.add_argument(
362 "-f",
363 "--family",
364 dest="family",
365 type=str,
366 default="0x0",
367 help="specify familyID - number or name (default: 0x0)",
368 )
369 parser.add_argument(
370 "-C", "--carray", action="store_true", help="convert binary file to a C array, not UF2"
371 )
372 parser.add_argument(
373 "-i",
374 "--info",
375 action="store_true",
376 help="display header information from UF2, do not convert",
377 )
378 args = parser.parse_args()
379 appstartaddr = int(args.base, 0)
380
381 families = load_families()
382
383 if args.family.upper() in families:
384 familyid = families[args.family.upper()]
385 else:
386 try:
387 familyid = int(args.family, 0)
388 except ValueError:
389 error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
390
391 if args.list:
392 list_drives()
393 else:
394 if not args.input:
395 error("Need input file")
396 with open(args.input, mode="rb") as f:
397 inpbuf = f.read()
398 from_uf2 = is_uf2(inpbuf)
399 ext = "uf2"
400 if args.deploy:
401 outbuf = inpbuf
402 elif from_uf2 and not args.info:
403 outbuf = convert_from_uf2(inpbuf)
404 ext = "bin"
405 elif from_uf2 and args.info:
406 outbuf = ""
407 convert_from_uf2(inpbuf)
408 elif is_hex(inpbuf):
409 outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
410 elif args.carray:
411 outbuf = convert_to_carray(inpbuf)
412 ext = "h"
413 else:
414 outbuf = convert_to_uf2(inpbuf)
415 if not args.deploy and not args.info:
416 print(
417 "Converted to %s, output size: %d, start address: 0x%x"
418 % (ext, len(outbuf), appstartaddr)
419 )
420 if args.convert or ext != "uf2":
421 drives = []
422 if args.output == None:
423 args.output = "flash." + ext
424 else:
425 drives = get_drives()
426
427 if args.output:
428 write_file(args.output, outbuf)
429 else:
430 if len(drives) == 0:
431 error("No drive to deploy.")
432 for d in drives:
433 print("Flashing %s (%s)" % (d, board_id(d)))
434 write_file(d + "/NEW.UF2", outbuf)
435
436
437 if __name__ == "__main__":
438 main()

eric ide

mercurial