logo

qmk_firmware

custom branch of QMK firmware git clone https://anongit.hacktivis.me/git/qmk_firmware.git

uf2conv.py (13014B)


  1. #!/usr/bin/env python3
  2. # yapf: disable
  3. import sys
  4. import struct
  5. import subprocess
  6. import re
  7. import os
  8. import os.path
  9. import argparse
  10. import json
  11. from time import sleep
  12. UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
  13. UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
  14. UF2_MAGIC_END = 0x0AB16F30 # Ditto
  15. INFO_FILE = "/INFO_UF2.TXT"
  16. appstartaddr = 0x2000
  17. familyid = 0x0
  18. def is_uf2(buf):
  19. w = struct.unpack("<II", buf[0:8])
  20. return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
  21. def is_hex(buf):
  22. try:
  23. w = buf[0:30].decode("utf-8")
  24. except UnicodeDecodeError:
  25. return False
  26. if w[0] == ':' and re.match(rb"^[:0-9a-fA-F\r\n]+$", buf):
  27. return True
  28. return False
  29. def convert_from_uf2(buf):
  30. global appstartaddr
  31. global familyid
  32. numblocks = len(buf) // 512
  33. curraddr = None
  34. currfamilyid = None
  35. families_found = {}
  36. prev_flag = None
  37. all_flags_same = True
  38. outp = []
  39. for blockno in range(numblocks):
  40. ptr = blockno * 512
  41. block = buf[ptr:ptr + 512]
  42. hd = struct.unpack(b"<IIIIIIII", block[0:32])
  43. if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
  44. print("Skipping block at " + ptr + "; bad magic")
  45. continue
  46. if hd[2] & 1:
  47. # NO-flash flag set; skip block
  48. continue
  49. datalen = hd[4]
  50. if datalen > 476:
  51. assert False, "Invalid UF2 data size at " + ptr
  52. newaddr = hd[3]
  53. if (hd[2] & 0x2000) and (currfamilyid == None):
  54. currfamilyid = hd[7]
  55. if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid):
  56. currfamilyid = hd[7]
  57. curraddr = newaddr
  58. if familyid == 0x0 or familyid == hd[7]:
  59. appstartaddr = newaddr
  60. padding = newaddr - curraddr
  61. if padding < 0:
  62. assert False, "Block out of order at " + ptr
  63. if padding > 10*1024*1024:
  64. assert False, "More than 10M of padding needed at " + ptr
  65. if padding % 4 != 0:
  66. assert False, "Non-word padding size at " + ptr
  67. while padding > 0:
  68. padding -= 4
  69. outp.append(b"\x00\x00\x00\x00")
  70. if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]):
  71. outp.append(block[32 : 32 + datalen])
  72. curraddr = newaddr + datalen
  73. if hd[2] & 0x2000:
  74. if hd[7] in families_found.keys():
  75. if families_found[hd[7]] > newaddr:
  76. families_found[hd[7]] = newaddr
  77. else:
  78. families_found[hd[7]] = newaddr
  79. if prev_flag == None:
  80. prev_flag = hd[2]
  81. if prev_flag != hd[2]:
  82. all_flags_same = False
  83. if blockno == (numblocks - 1):
  84. print("--- UF2 File Header Info ---")
  85. families = load_families()
  86. for family_hex in families_found.keys():
  87. family_short_name = ""
  88. for name, value in families.items():
  89. if value == family_hex:
  90. family_short_name = name
  91. print("Family ID is {:s}, hex value is 0x{:08x}".format(family_short_name,family_hex))
  92. print("Target Address is 0x{:08x}".format(families_found[family_hex]))
  93. if all_flags_same:
  94. print("All block flag values consistent, 0x{:04x}".format(hd[2]))
  95. else:
  96. print("Flags were not all the same")
  97. print("----------------------------")
  98. if len(families_found) > 1 and familyid == 0x0:
  99. outp = []
  100. appstartaddr = 0x0
  101. return b"".join(outp)
  102. def 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. def convert_to_uf2(file_content):
  112. global familyid
  113. datapadding = b""
  114. while len(datapadding) < 512 - 256 - 32 - 4:
  115. datapadding += b"\x00\x00\x00\x00"
  116. numblocks = (len(file_content) + 255) // 256
  117. outp = []
  118. for blockno in range(numblocks):
  119. ptr = 256 * blockno
  120. chunk = file_content[ptr:ptr + 256]
  121. flags = 0x0
  122. if familyid:
  123. flags |= 0x2000
  124. hd = struct.pack(b"<IIIIIIII",
  125. UF2_MAGIC_START0, UF2_MAGIC_START1,
  126. flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
  127. while len(chunk) < 256:
  128. chunk += b"\x00"
  129. block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
  130. assert len(block) == 512
  131. outp.append(block)
  132. return b"".join(outp)
  133. class Block:
  134. def __init__(self, addr):
  135. self.addr = addr
  136. self.bytes = bytearray(256)
  137. def encode(self, blockno, numblocks):
  138. global familyid
  139. flags = 0x0
  140. if familyid:
  141. flags |= 0x2000
  142. if devicetype:
  143. flags |= 0x8000
  144. hd = struct.pack("<IIIIIIII",
  145. UF2_MAGIC_START0, UF2_MAGIC_START1,
  146. flags, self.addr, 256, blockno, numblocks, familyid)
  147. hd += self.bytes[0:256]
  148. if devicetype:
  149. hd += bytearray(b'\x08\x29\xa7\xc8')
  150. hd += bytearray(devicetype.to_bytes(4, 'little'))
  151. while len(hd) < 512 - 4:
  152. hd += b"\x00"
  153. hd += struct.pack("<I", UF2_MAGIC_END)
  154. return hd
  155. def convert_from_hex_to_uf2(buf):
  156. global appstartaddr
  157. appstartaddr = None
  158. upper = 0
  159. currblock = None
  160. blocks = []
  161. for line in buf.split('\n'):
  162. if line[0] != ":":
  163. continue
  164. i = 1
  165. rec = []
  166. while i < len(line) - 1:
  167. rec.append(int(line[i:i+2], 16))
  168. i += 2
  169. tp = rec[3]
  170. if tp == 4:
  171. upper = ((rec[4] << 8) | rec[5]) << 16
  172. elif tp == 2:
  173. upper = ((rec[4] << 8) | rec[5]) << 4
  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. def to_str(b):
  194. return b.decode("utf-8")
  195. def get_drives():
  196. drives = []
  197. if sys.platform == "win32":
  198. r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
  199. "get", "DeviceID,", "VolumeName,",
  200. "FileSystem,", "DriveType"])
  201. for line in to_str(r).split('\n'):
  202. words = re.split(r'\s+', line)
  203. if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
  204. drives.append(words[0])
  205. else:
  206. searchpaths = ["/media"]
  207. if sys.platform == "darwin":
  208. searchpaths = ["/Volumes"]
  209. elif sys.platform == "linux":
  210. searchpaths += ["/media/" + os.environ["USER"], '/run/media/' + os.environ["USER"]]
  211. for rootpath in searchpaths:
  212. if os.path.isdir(rootpath):
  213. for d in os.listdir(rootpath):
  214. if os.path.isdir(rootpath):
  215. drives.append(os.path.join(rootpath, d))
  216. def has_info(d):
  217. try:
  218. return os.path.isfile(d + INFO_FILE)
  219. except:
  220. return False
  221. return list(filter(has_info, drives))
  222. def board_id(path):
  223. with open(path + INFO_FILE, mode='r') as file:
  224. file_content = file.read()
  225. return re.search(r"Board-ID: ([^\r\n]*)", file_content).group(1)
  226. def list_drives():
  227. for d in get_drives():
  228. print(d, board_id(d))
  229. def write_file(name, buf):
  230. with open(name, "wb") as f:
  231. f.write(buf)
  232. print("Wrote %d bytes to %s" % (len(buf), name))
  233. def load_families():
  234. # The expectation is that the `uf2families.json` file is in the same
  235. # directory as this script. Make a path that works using `__file__`
  236. # which contains the full path to this script.
  237. filename = "uf2families.json"
  238. pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
  239. with open(pathname) as f:
  240. raw_families = json.load(f)
  241. families = {}
  242. for family in raw_families:
  243. families[family["short_name"]] = int(family["id"], 0)
  244. return families
  245. def main():
  246. global appstartaddr, familyid
  247. def error(msg):
  248. print(msg, file=sys.stderr)
  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('-f', '--family', dest='family', type=str,
  257. default="0x0",
  258. help='specify familyID - number or name (default: 0x0)')
  259. parser.add_argument('-t' , '--device-type', dest='devicetype', type=str,
  260. help='specify deviceTypeID extension tag - number')
  261. parser.add_argument('-o', '--output', metavar="FILE", dest='output', type=str,
  262. help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
  263. parser.add_argument('-d', '--device', dest="device_path",
  264. help='select a device path to flash')
  265. parser.add_argument('-l', '--list', action='store_true',
  266. help='list connected devices')
  267. parser.add_argument('-c', '--convert', action='store_true',
  268. help='do not flash, just convert')
  269. parser.add_argument('-D', '--deploy', action='store_true',
  270. help='just flash, do not convert')
  271. parser.add_argument('-w', '--wait', action='store_true',
  272. help='wait for device to flash')
  273. parser.add_argument('-C', '--carray', action='store_true',
  274. help='convert binary file to a C array, not UF2')
  275. parser.add_argument('-i', '--info', action='store_true',
  276. help='display header information from UF2, do not convert')
  277. args = parser.parse_args()
  278. appstartaddr = int(args.base, 0)
  279. families = load_families()
  280. if args.family.upper() in families:
  281. familyid = families[args.family.upper()]
  282. else:
  283. try:
  284. familyid = int(args.family, 0)
  285. except ValueError:
  286. error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
  287. global devicetype
  288. devicetype = int(args.devicetype, 0) if args.devicetype else None
  289. if args.list:
  290. list_drives()
  291. else:
  292. if not args.input:
  293. error("Need input file")
  294. with open(args.input, mode='rb') as f:
  295. inpbuf = f.read()
  296. from_uf2 = is_uf2(inpbuf)
  297. ext = "uf2"
  298. if args.deploy:
  299. outbuf = inpbuf
  300. elif from_uf2 and not args.info:
  301. outbuf = convert_from_uf2(inpbuf)
  302. ext = "bin"
  303. elif from_uf2 and args.info:
  304. outbuf = ""
  305. convert_from_uf2(inpbuf)
  306. elif is_hex(inpbuf):
  307. outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
  308. elif args.carray:
  309. outbuf = convert_to_carray(inpbuf)
  310. ext = "h"
  311. else:
  312. outbuf = convert_to_uf2(inpbuf)
  313. if not args.deploy and not args.info:
  314. print("Converted to %s, output size: %d, start address: 0x%x" %
  315. (ext, len(outbuf), appstartaddr))
  316. if args.convert or ext != "uf2":
  317. if args.output == None:
  318. args.output = "flash." + ext
  319. if args.output:
  320. write_file(args.output, outbuf)
  321. if ext == "uf2" and not args.convert and not args.info:
  322. drives = get_drives()
  323. if len(drives) == 0:
  324. if args.wait:
  325. print("Waiting for drive to deploy...")
  326. while len(drives) == 0:
  327. sleep(0.1)
  328. drives = get_drives()
  329. elif not args.output:
  330. error("No drive to deploy.")
  331. for d in drives:
  332. print("Flashing %s (%s)" % (d, board_id(d)))
  333. write_file(d + "/NEW.UF2", outbuf)
  334. if __name__ == "__main__":
  335. main()