aboutsummaryrefslogtreecommitdiff
path: root/tools/dfu.py
blob: 6591436e9e211e3a7c3af07a5b62df9bfdb2c40f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/python

# Written by Antonio Galea - 2010/11/18
# Distributed under Gnu LGPL 3.0
# see http://www.gnu.org/licenses/lgpl-3.0.txt

import sys, struct, zlib, os
from optparse import OptionParser

DEFAULT_DEVICE = "0x0483:0xdf11"


def named(tuple, names):
    return dict(zip(names.split(), tuple))


def consume(fmt, data, names):
    n = struct.calcsize(fmt)
    return named(struct.unpack(fmt, data[:n]), names), data[n:]


def cstring(string):
    return string.split("\0", 1)[0]


def compute_crc(data):
    return 0xFFFFFFFF & -zlib.crc32(data) - 1


def parse(file, dump_images=False):
    print('File: "%s"' % file)
    data = open(file, "rb").read()
    crc = compute_crc(data[:-4])
    prefix, data = consume("<5sBIB", data, "signature version size targets")
    print("%(signature)s v%(version)d, image size: %(size)d, targets: %(targets)d" % prefix)
    for t in range(prefix["targets"]):
        tprefix, data = consume(
            "<6sBI255s2I", data, "signature altsetting named name size elements"
        )
        tprefix["num"] = t
        if tprefix["named"]:
            tprefix["name"] = cstring(tprefix["name"])
        else:
            tprefix["name"] = ""
        print(
            '%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d'
            % tprefix
        )
        tsize = tprefix["size"]
        target, data = data[:tsize], data[tsize:]
        for e in range(tprefix["elements"]):
            eprefix, target = consume("<2I", target, "address size")
            eprefix["num"] = e
            print("  %(num)d, address: 0x%(address)08x, size: %(size)d" % eprefix)
            esize = eprefix["size"]
            image, target = target[:esize], target[esize:]
            if dump_images:
                out = "%s.target%d.image%d.bin" % (file, t, e)
                open(out, "wb").write(image)
                print('    DUMPED IMAGE TO "%s"' % out)
        if len(target):
            print("target %d: PARSE ERROR" % t)
    suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")
    print(
        "usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x"
        % suffix
    )
    if crc != suffix["crc"]:
        print("CRC ERROR: computed crc32 is 0x%08x" % crc)
    data = data[16:]
    if data:
        print("PARSE ERROR")


def build(file, targets, device=DEFAULT_DEVICE):
    data = b""
    for t, target in enumerate(targets):
        tdata = b""
        for image in target:
            # pad image to 8 bytes (needed at least for L476)
            pad = (8 - len(image["data"]) % 8) % 8
            image["data"] = image["data"] + bytes(bytearray(8)[0:pad])
            #
            tdata += struct.pack("<2I", image["address"], len(image["data"])) + image["data"]
        tdata = (
            struct.pack("<6sBI255s2I", b"Target", 0, 1, b"ST...", len(tdata), len(target)) + tdata
        )
        data += tdata
    data = struct.pack("<5sBIB", b"DfuSe", 1, len(data) + 11, len(targets)) + data
    v, d = map(lambda x: int(x, 0) & 0xFFFF, device.split(":", 1))
    data += struct.pack("<4H3sB", 0, d, v, 0x011A, b"UFD", 16)
    crc = compute_crc(data)
    data += struct.pack("<I", crc)
    open(file, "wb").write(data)


if __name__ == "__main__":
    usage = """
%prog [-d|--dump] infile.dfu
%prog {-b|--build} address:file.bin [-b address:file.bin ...] [{-D|--device}=vendor:device] outfile.dfu"""
    parser = OptionParser(usage=usage)
    parser.add_option(
        "-b",
        "--build",
        action="append",
        dest="binfiles",
        help="build a DFU file from given BINFILES",
        metavar="BINFILES",
    )
    parser.add_option(
        "-D",
        "--device",
        action="store",
        dest="device",
        help="build for DEVICE, defaults to %s" % DEFAULT_DEVICE,
        metavar="DEVICE",
    )
    parser.add_option(
        "-d",
        "--dump",
        action="store_true",
        dest="dump_images",
        default=False,
        help="dump contained images to current directory",
    )
    (options, args) = parser.parse_args()

    if options.binfiles and len(args) == 1:
        target = []
        for arg in options.binfiles:
            try:
                address, binfile = arg.split(":", 1)
            except ValueError:
                print("Address:file couple '%s' invalid." % arg)
                sys.exit(1)
            try:
                address = int(address, 0) & 0xFFFFFFFF
            except ValueError:
                print("Address %s invalid." % address)
                sys.exit(1)
            if not os.path.isfile(binfile):
                print("Unreadable file '%s'." % binfile)
                sys.exit(1)
            target.append({"address": address, "data": open(binfile, "rb").read()})
        outfile = args[0]
        device = DEFAULT_DEVICE
        if options.device:
            device = options.device
        try:
            v, d = map(lambda x: int(x, 0) & 0xFFFF, device.split(":", 1))
        except:
            print("Invalid device '%s'." % device)
            sys.exit(1)
        build(outfile, [target], device)
    elif len(args) == 1:
        infile = args[0]
        if not os.path.isfile(infile):
            print("Unreadable file '%s'." % infile)
            sys.exit(1)
        parse(infile, dump_images=options.dump_images)
    else:
        parser.print_help()
        sys.exit(1)