]> sigrok.org Git - sigrok-util.git/blame - firmware/kingst-la/sigrok-fwextract-kingst-la2016
sigrok-fwextract-kingst-la2016: concentrate RCC flags in one spot
[sigrok-util.git] / firmware / kingst-la / sigrok-fwextract-kingst-la2016
CommitLineData
1697bec2
FS
1#!/usr/bin/python3
2##
3## This file is part of the sigrok-util project.
4##
5## Copyright (C) 2020 Florian Schmidt <schmidt_florian@gmx.de>
6##
7## This program is free software; you can redistribute it and/or modify
8## it under the terms of the GNU General Public License as published by
9## the Free Software Foundation; either version 3 of the License, or
10## (at your option) any later version.
11##
12## This program is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15## GNU General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with this program; if not, see <http://www.gnu.org/licenses/>.
19##
20
d22be723
GS
21# This utility extracts FX2 MCU firmware and FPGA bitstream images from
22# the "KingstVIS" vendor software. The blobs are kept in Qt resources
23# sections. The script was tested with several v3.5 software versions.
24
76358ac2 25import argparse
1697bec2
FS
26import os
27import sys
28import re
29import struct
30import codecs
31import importlib.util
03a034f4 32import zlib
1697bec2 33
d22be723 34# Reuse the parseelf.py module from saleae-logic16.
1697bec2
FS
35fwdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
36parseelf_py = os.path.join(fwdir, "saleae-logic16", "parseelf.py")
37spec = importlib.util.spec_from_file_location("parseelf", parseelf_py)
38parseelf = importlib.util.module_from_spec(spec)
39spec.loader.exec_module(parseelf)
40
41class qt_resources(object):
249b79d0
GS
42 RCCFileInfo_Compressed = 0x01
43 RCCFileInfo_Directory = 0x02
44
1697bec2
FS
45 def __init__(self, program):
46 self._elf = parseelf.elf(program)
47 self._elf_sections = {} # idx -> data
48 self._read_resources()
49
50 def _get_elf_section(self, idx):
51 s = self._elf_sections.get(idx)
52 if s is None:
53 shdr = self._elf.shdrs[idx]
54 s = self._elf.read_section(shdr), shdr
55 self._elf_sections[idx] = s
56 return s
cce8abc7 57
1697bec2
FS
58 def _get_elf_sym_value(self, sname):
59 sym = self._elf.symtab[sname]
60 section, shdr = self._get_elf_section(sym["st_shndx"])
61 addr = sym["st_value"] - shdr["sh_addr"]
62 value = section[addr:addr + sym["st_size"]]
63 if len(value) != sym["st_size"]:
64 print("warning: symbol %s should be %d bytes, but in section is only %d bytes" % (
65 sname, sym["st_size"], len(value)))
66 return value
cce8abc7 67
d22be723 68 # Qt resource stuff.
1697bec2
FS
69 def _get_resource_name(self, offset):
70 length, i = struct.unpack(">HI", self._res_names[offset:offset + 2 + 4])
71 offset += 2 + 4
72 name = self._res_names[offset:offset + 2 * length].decode("utf-16be")
73 return name
cce8abc7 74
1697bec2
FS
75 def _get_resource_data(self, offset):
76 length = struct.unpack(">I", self._res_datas[offset:offset + 4])[0]
77 offset += 4
78 return self._res_datas[offset:offset + length]
cce8abc7 79
1697bec2 80 def _read_resources(self):
1697bec2
FS
81 def read_table():
82 table = []
83 offset = 0
84 while offset < len(self._res_struct):
85 name_offset, flags = struct.unpack(">IH", self._res_struct[offset:offset+4+2])
86 offset += 6
87 name = self._get_resource_name(name_offset)
249b79d0 88 if flags & self.RCCFileInfo_Directory:
1697bec2
FS
89 child_count, first_child_offset = struct.unpack(">II", self._res_struct[offset:offset + 4 + 4])
90 offset += 4 + 4
91 table.append((name, flags, child_count, first_child_offset))
92 else:
93 country, language, data_offset = struct.unpack(">HHI", self._res_struct[offset:offset + 2 + 2 + 4])
94 offset += 2 + 2 + 4
95 table.append((name, flags, country, language, data_offset))
96 return table
97 def read_dir_entries(table, which, parents=[]):
98 name, flags = which[:2]
249b79d0 99 if not flags & self.RCCFileInfo_Directory:
1697bec2
FS
100 raise Exception("not a directory!")
101 child_count, first_child = which[2:]
102 for i in range(child_count):
103 child = table[first_child + i]
64859bad 104 flags = child[1]
249b79d0 105 if flags & self.RCCFileInfo_Directory:
1697bec2
FS
106 read_dir_entries(table, child, parents + [child[0]])
107 else:
108 country, language, data_offset = child[2:]
109 full_name = "/".join(parents + [child[0]])
110 self._resources[full_name] = data_offset
64859bad 111 self._resource_flags[full_name] = flags
cce8abc7 112
1697bec2
FS
113 self._res_datas = self._get_elf_sym_value("_ZL16qt_resource_data")
114 self._res_names = self._get_elf_sym_value("_ZL16qt_resource_name")
115 self._res_struct = self._get_elf_sym_value("_ZL18qt_resource_struct")
cce8abc7 116
1697bec2 117 self._resources = {} # res_fn -> res_offset
64859bad 118 self._resource_flags = {} # res_fn -> RCC_flags
1697bec2
FS
119 table = read_table()
120 read_dir_entries(table, table[0])
121
122 def get_resource(self, res_fn):
123 offset = self._resources[res_fn]
64859bad 124 flags = self._resource_flags[res_fn]
1697bec2 125 data = self._get_resource_data(offset)
249b79d0 126 if flags & self.RCCFileInfo_Compressed:
64859bad 127 data = zlib.decompress(data[4:])
1697bec2
FS
128 return data
129
130 def find_resource_names(self, res_fn_re):
131 for key in self._resources.keys():
132 m = re.match(res_fn_re, key)
133 if m is not None:
134 yield key
cce8abc7 135
1697bec2
FS
136class res_writer(object):
137 def __init__(self, res):
138 self.res = res
cce8abc7 139
896d66ee 140 def _decode_crc(self, data, decoder=None):
1697bec2
FS
141 if decoder is not None:
142 data = decoder(data)
896d66ee
GS
143 data = bytearray(data)
144 crc = zlib.crc32(data) & 0xffffffff
145 return data, crc
146
147 def _write_file(self, fn, data):
1697bec2
FS
148 with open(fn, "wb") as fp:
149 fp.write(data)
cce8abc7 150
896d66ee
GS
151 def extract_re(self, resource_pattern, fname_pattern, decoder=None):
152 resources = sorted(res.find_resource_names(resource_pattern))
153 for resource in resources:
154 fname = re.sub(resource_pattern, fname_pattern, resource)
155 fname = fname.lower()
156 data = self.res.get_resource(resource)
157 data, crc = self._decode_crc(data, decoder=decoder)
158 self._write_file(fname, data)
159 print("resource {rsc}, file {fname}, size {size}, checksum {crc:08x}".format(
160 rsc = resource, fname = fname, size = len(data), crc = crc,
161 ))
1697bec2
FS
162
163def decode_intel_hex(hexdata):
164 """ return list of (address, data)
165 """
166 datas = []
d22be723 167 # Assume LF-only or CR-LF style end-of-line.
1697bec2
FS
168 for line in hexdata.split(b"\n"):
169 line = line.strip()
170 if chr(line[0]) != ":": raise Exception("invalid line: %r" % line)
171 offset = 1
172 record = codecs.decode(line[offset:], "hex")
173 byte_count, address, record_type = struct.unpack(">BHB", record[:1 + 2 + 1])
174 offset = 1 + 2 + 1
175 if byte_count > 0:
176 data = record[offset:offset + byte_count]
177 offset += byte_count
178 checksum = record[offset]
179 ex_checksum = (~sum(record[:offset]) + 1) & 0xff
180 if ex_checksum != checksum: raise Exception("invalid checksum %#x in %r" % (checksum, line))
181 if record_type == 0:
182 datas.append((address, data))
183 elif record_type == 1:
184 break
185 return datas
186
187def intel_hex_as_blob(hexdata):
188 """ return continuous bytes sequence including all data
189 (loosing start address here)
190 """
191 data = decode_intel_hex(hexdata)
192 data.sort()
193 last = data[-1]
194 length = last[0] + len(last[1])
195 img = bytearray(length)
196 for off, part in data:
197 img[off:off + len(part)] = part
198 return img
199
200def maybe_intel_hex_as_blob(data):
201 if data[0] == ord(":") and max(data) < 127:
202 return intel_hex_as_blob(data)
d22be723 203 return data # Keep binary data.
1697bec2
FS
204
205if __name__ == "__main__":
76358ac2
GS
206 parser = argparse.ArgumentParser(description = "KingstVIS firmware extraction")
207 parser.add_argument('executable', help = "KingstVIS executable file")
208 options = parser.parse_args()
209 exe_fn = options.executable
cce8abc7 210
76358ac2 211 res = qt_resources(exe_fn)
1697bec2 212 writer = res_writer(res)
03a034f4 213
9814f29f
GS
214 # Extract all MCU firmware and FPGA bitstream images. The sigrok
215 # project may not cover all KingstVIS supported devices. Users can
216 # either just copy those files which are strictly required for their
217 # specific device (diagnostics will identify those). Or just copy a
218 # few more files while some of them remain unused later (their size
219 # is small). Seeing which files would be contained is considered
220 # valuable, to identify device variants or candidate models.
221 writer.extract_re(r"fwusb/fw(.*)", r"kingst-la-\1.fw", decoder=maybe_intel_hex_as_blob)
222 writer.extract_re(r"fwfpga/(.*)", r"kingst-\1-fpga.bitstream")