1#!/usr/bin/python 2 3# Written by Antonio Galea - 2010/11/18 4# Distributed under Gnu LGPL 3.0 5# see http://www.gnu.org/licenses/lgpl-3.0.txt 6 7import sys, struct, zlib, os 8from optparse import OptionParser 9 10DEFAULT_DEVICE = "0x0483:0xdf11" 11 12 13def named(tuple, names): 14 return dict(zip(names.split(), tuple)) 15 16 17def consume(fmt, data, names): 18 n = struct.calcsize(fmt) 19 return named(struct.unpack(fmt, data[:n]), names), data[n:] 20 21 22def cstring(string): 23 return string.split("\0", 1)[0] 24 25 26def compute_crc(data): 27 return 0xFFFFFFFF & -zlib.crc32(data) - 1 28 29 30def parse(file, dump_images=False): 31 print('File: "%s"' % file) 32 data = open(file, "rb").read() 33 crc = compute_crc(data[:-4]) 34 prefix, data = consume("<5sBIB", data, "signature version size targets") 35 print("%(signature)s v%(version)d, image size: %(size)d, targets: %(targets)d" % prefix) 36 for t in range(prefix["targets"]): 37 tprefix, data = consume( 38 "<6sBI255s2I", data, "signature altsetting named name size elements" 39 ) 40 tprefix["num"] = t 41 if tprefix["named"]: 42 tprefix["name"] = cstring(tprefix["name"]) 43 else: 44 tprefix["name"] = "" 45 print( 46 '%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d' 47 % tprefix 48 ) 49 tsize = tprefix["size"] 50 target, data = data[:tsize], data[tsize:] 51 for e in range(tprefix["elements"]): 52 eprefix, target = consume("<2I", target, "address size") 53 eprefix["num"] = e 54 print(" %(num)d, address: 0x%(address)08x, size: %(size)d" % eprefix) 55 esize = eprefix["size"] 56 image, target = target[:esize], target[esize:] 57 if dump_images: 58 out = "%s.target%d.image%d.bin" % (file, t, e) 59 open(out, "wb").write(image) 60 print(' DUMPED IMAGE TO "%s"' % out) 61 if len(target): 62 print("target %d: PARSE ERROR" % t) 63 suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc") 64 print( 65 "usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" 66 % suffix 67 ) 68 if crc != suffix["crc"]: 69 print("CRC ERROR: computed crc32 is 0x%08x" % crc) 70 data = data[16:] 71 if data: 72 print("PARSE ERROR") 73 74 75def build(file, targets, device=DEFAULT_DEVICE): 76 data = b"" 77 for t, target in enumerate(targets): 78 tdata = b"" 79 for image in target: 80 # pad image to 8 bytes (needed at least for L476) 81 pad = (8 - len(image["data"]) % 8) % 8 82 image["data"] = image["data"] + bytes(bytearray(8)[0:pad]) 83 # 84 tdata += struct.pack("<2I", image["address"], len(image["data"])) + image["data"] 85 tdata = ( 86 struct.pack("<6sBI255s2I", b"Target", 0, 1, b"ST...", len(tdata), len(target)) + tdata 87 ) 88 data += tdata 89 data = struct.pack("<5sBIB", b"DfuSe", 1, len(data) + 11, len(targets)) + data 90 v, d = map(lambda x: int(x, 0) & 0xFFFF, device.split(":", 1)) 91 data += struct.pack("<4H3sB", 0, d, v, 0x011A, b"UFD", 16) 92 crc = compute_crc(data) 93 data += struct.pack("<I", crc) 94 open(file, "wb").write(data) 95 96 97if __name__ == "__main__": 98 usage = """ 99%prog [-d|--dump] infile.dfu 100%prog {-b|--build} address:file.bin [-b address:file.bin ...] [{-D|--device}=vendor:device] outfile.dfu""" 101 parser = OptionParser(usage=usage) 102 parser.add_option( 103 "-b", 104 "--build", 105 action="append", 106 dest="binfiles", 107 help="build a DFU file from given BINFILES", 108 metavar="BINFILES", 109 ) 110 parser.add_option( 111 "-D", 112 "--device", 113 action="store", 114 dest="device", 115 help="build for DEVICE, defaults to %s" % DEFAULT_DEVICE, 116 metavar="DEVICE", 117 ) 118 parser.add_option( 119 "-d", 120 "--dump", 121 action="store_true", 122 dest="dump_images", 123 default=False, 124 help="dump contained images to current directory", 125 ) 126 (options, args) = parser.parse_args() 127 128 if options.binfiles and len(args) == 1: 129 target = [] 130 for arg in options.binfiles: 131 try: 132 address, binfile = arg.split(":", 1) 133 except ValueError: 134 print("Address:file couple '%s' invalid." % arg) 135 sys.exit(1) 136 try: 137 address = int(address, 0) & 0xFFFFFFFF 138 except ValueError: 139 print("Address %s invalid." % address) 140 sys.exit(1) 141 if not os.path.isfile(binfile): 142 print("Unreadable file '%s'." % binfile) 143 sys.exit(1) 144 target.append({"address": address, "data": open(binfile, "rb").read()}) 145 outfile = args[0] 146 device = DEFAULT_DEVICE 147 if options.device: 148 device = options.device 149 try: 150 v, d = map(lambda x: int(x, 0) & 0xFFFF, device.split(":", 1)) 151 except: 152 print("Invalid device '%s'." % device) 153 sys.exit(1) 154 build(outfile, [target], device) 155 elif len(args) == 1: 156 infile = args[0] 157 if not os.path.isfile(infile): 158 print("Unreadable file '%s'." % infile) 159 sys.exit(1) 160 parse(infile, dump_images=options.dump_images) 161 else: 162 parser.print_help() 163 sys.exit(1) 164