]> sigrok.org Git - sigrok-util.git/blame - firmware/kingst-la/sigrok-fwextract-kingst-la2016
sigrok-fwextract-kingst-la2016: rephrase resource file writer
[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):
42 def __init__(self, program):
43 self._elf = parseelf.elf(program)
44 self._elf_sections = {} # idx -> data
45 self._read_resources()
46
47 def _get_elf_section(self, idx):
48 s = self._elf_sections.get(idx)
49 if s is None:
50 shdr = self._elf.shdrs[idx]
51 s = self._elf.read_section(shdr), shdr
52 self._elf_sections[idx] = s
53 return s
cce8abc7 54
1697bec2
FS
55 def _get_elf_sym_value(self, sname):
56 sym = self._elf.symtab[sname]
57 section, shdr = self._get_elf_section(sym["st_shndx"])
58 addr = sym["st_value"] - shdr["sh_addr"]
59 value = section[addr:addr + sym["st_size"]]
60 if len(value) != sym["st_size"]:
61 print("warning: symbol %s should be %d bytes, but in section is only %d bytes" % (
62 sname, sym["st_size"], len(value)))
63 return value
cce8abc7 64
d22be723 65 # Qt resource stuff.
1697bec2
FS
66 def _get_resource_name(self, offset):
67 length, i = struct.unpack(">HI", self._res_names[offset:offset + 2 + 4])
68 offset += 2 + 4
69 name = self._res_names[offset:offset + 2 * length].decode("utf-16be")
70 return name
cce8abc7 71
1697bec2
FS
72 def _get_resource_data(self, offset):
73 length = struct.unpack(">I", self._res_datas[offset:offset + 4])[0]
74 offset += 4
75 return self._res_datas[offset:offset + length]
cce8abc7 76
1697bec2
FS
77 def _read_resources(self):
78 RCCFileInfo_Directory = 0x02
79 def read_table():
80 table = []
81 offset = 0
82 while offset < len(self._res_struct):
83 name_offset, flags = struct.unpack(">IH", self._res_struct[offset:offset+4+2])
84 offset += 6
85 name = self._get_resource_name(name_offset)
86 if flags & RCCFileInfo_Directory:
87 child_count, first_child_offset = struct.unpack(">II", self._res_struct[offset:offset + 4 + 4])
88 offset += 4 + 4
89 table.append((name, flags, child_count, first_child_offset))
90 else:
91 country, language, data_offset = struct.unpack(">HHI", self._res_struct[offset:offset + 2 + 2 + 4])
92 offset += 2 + 2 + 4
93 table.append((name, flags, country, language, data_offset))
94 return table
95 def read_dir_entries(table, which, parents=[]):
96 name, flags = which[:2]
97 if not flags & RCCFileInfo_Directory:
98 raise Exception("not a directory!")
99 child_count, first_child = which[2:]
100 for i in range(child_count):
101 child = table[first_child + i]
cce8abc7 102 if child[1] & RCCFileInfo_Directory:
1697bec2
FS
103 read_dir_entries(table, child, parents + [child[0]])
104 else:
105 country, language, data_offset = child[2:]
106 full_name = "/".join(parents + [child[0]])
107 self._resources[full_name] = data_offset
cce8abc7 108
1697bec2
FS
109 self._res_datas = self._get_elf_sym_value("_ZL16qt_resource_data")
110 self._res_names = self._get_elf_sym_value("_ZL16qt_resource_name")
111 self._res_struct = self._get_elf_sym_value("_ZL18qt_resource_struct")
cce8abc7 112
1697bec2
FS
113 self._resources = {} # res_fn -> res_offset
114 table = read_table()
115 read_dir_entries(table, table[0])
116
117 def get_resource(self, res_fn):
118 offset = self._resources[res_fn]
119 data = self._get_resource_data(offset)
120 return data
121
122 def find_resource_names(self, res_fn_re):
123 for key in self._resources.keys():
124 m = re.match(res_fn_re, key)
125 if m is not None:
126 yield key
cce8abc7 127
1697bec2
FS
128class res_writer(object):
129 def __init__(self, res):
130 self.res = res
cce8abc7 131
896d66ee 132 def _decode_crc(self, data, decoder=None):
1697bec2
FS
133 if decoder is not None:
134 data = decoder(data)
896d66ee
GS
135 data = bytearray(data)
136 crc = zlib.crc32(data) & 0xffffffff
137 return data, crc
138
139 def _write_file(self, fn, data):
1697bec2
FS
140 with open(fn, "wb") as fp:
141 fp.write(data)
cce8abc7 142
896d66ee
GS
143 def extract_re(self, resource_pattern, fname_pattern, decoder=None):
144 resources = sorted(res.find_resource_names(resource_pattern))
145 for resource in resources:
146 fname = re.sub(resource_pattern, fname_pattern, resource)
147 fname = fname.lower()
148 data = self.res.get_resource(resource)
149 data, crc = self._decode_crc(data, decoder=decoder)
150 self._write_file(fname, data)
151 print("resource {rsc}, file {fname}, size {size}, checksum {crc:08x}".format(
152 rsc = resource, fname = fname, size = len(data), crc = crc,
153 ))
1697bec2
FS
154
155def decode_intel_hex(hexdata):
156 """ return list of (address, data)
157 """
158 datas = []
d22be723 159 # Assume LF-only or CR-LF style end-of-line.
1697bec2
FS
160 for line in hexdata.split(b"\n"):
161 line = line.strip()
162 if chr(line[0]) != ":": raise Exception("invalid line: %r" % line)
163 offset = 1
164 record = codecs.decode(line[offset:], "hex")
165 byte_count, address, record_type = struct.unpack(">BHB", record[:1 + 2 + 1])
166 offset = 1 + 2 + 1
167 if byte_count > 0:
168 data = record[offset:offset + byte_count]
169 offset += byte_count
170 checksum = record[offset]
171 ex_checksum = (~sum(record[:offset]) + 1) & 0xff
172 if ex_checksum != checksum: raise Exception("invalid checksum %#x in %r" % (checksum, line))
173 if record_type == 0:
174 datas.append((address, data))
175 elif record_type == 1:
176 break
177 return datas
178
179def intel_hex_as_blob(hexdata):
180 """ return continuous bytes sequence including all data
181 (loosing start address here)
182 """
183 data = decode_intel_hex(hexdata)
184 data.sort()
185 last = data[-1]
186 length = last[0] + len(last[1])
187 img = bytearray(length)
188 for off, part in data:
189 img[off:off + len(part)] = part
190 return img
191
192def maybe_intel_hex_as_blob(data):
193 if data[0] == ord(":") and max(data) < 127:
194 return intel_hex_as_blob(data)
d22be723 195 return data # Keep binary data.
1697bec2
FS
196
197if __name__ == "__main__":
76358ac2
GS
198 parser = argparse.ArgumentParser(description = "KingstVIS firmware extraction")
199 parser.add_argument('executable', help = "KingstVIS executable file")
200 options = parser.parse_args()
201 exe_fn = options.executable
cce8abc7 202
76358ac2 203 res = qt_resources(exe_fn)
1697bec2 204 writer = res_writer(res)
03a034f4 205
9814f29f
GS
206 # Extract all MCU firmware and FPGA bitstream images. The sigrok
207 # project may not cover all KingstVIS supported devices. Users can
208 # either just copy those files which are strictly required for their
209 # specific device (diagnostics will identify those). Or just copy a
210 # few more files while some of them remain unused later (their size
211 # is small). Seeing which files would be contained is considered
212 # valuable, to identify device variants or candidate models.
213 writer.extract_re(r"fwusb/fw(.*)", r"kingst-la-\1.fw", decoder=maybe_intel_hex_as_blob)
214 writer.extract_re(r"fwfpga/(.*)", r"kingst-\1-fpga.bitstream")