From 1697bec274489f9a4921d707b067f34d489eb0dd Mon Sep 17 00:00:00 2001 From: Florian Schmidt Date: Sat, 21 Mar 2020 11:20:01 +0100 Subject: [PATCH] sigrok-fwextract-kingst-la2016: fpga bitstream and uC fw from vendor --- .gitignore | 2 + .../kingst-la/sigrok-fwextract-kingst-la2016 | 192 ++++++++++++++++++ .../sigrok-fwextract-kingst-la2016.1 | 64 ++++++ 3 files changed, 258 insertions(+) create mode 100755 firmware/kingst-la/sigrok-fwextract-kingst-la2016 create mode 100644 firmware/kingst-la/sigrok-fwextract-kingst-la2016.1 diff --git a/.gitignore b/.gitignore index 567609b..155abe0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ build/ +firmware/kingst-la/*.bitstream +firmware/kingst-la/*.fw diff --git a/firmware/kingst-la/sigrok-fwextract-kingst-la2016 b/firmware/kingst-la/sigrok-fwextract-kingst-la2016 new file mode 100755 index 0000000..8d9e1b4 --- /dev/null +++ b/firmware/kingst-la/sigrok-fwextract-kingst-la2016 @@ -0,0 +1,192 @@ +#!/usr/bin/python3 +## +## This file is part of the sigrok-util project. +## +## Copyright (C) 2020 Florian Schmidt +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import os +import sys +import re +import struct +import codecs +import importlib.util + +# reuse parseelf.py module from saleae-logic16: +fwdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +parseelf_py = os.path.join(fwdir, "saleae-logic16", "parseelf.py") +spec = importlib.util.spec_from_file_location("parseelf", parseelf_py) +parseelf = importlib.util.module_from_spec(spec) +spec.loader.exec_module(parseelf) + +class qt_resources(object): + def __init__(self, program): + self._elf = parseelf.elf(program) + self._elf_sections = {} # idx -> data + self._read_resources() + + def _get_elf_section(self, idx): + s = self._elf_sections.get(idx) + if s is None: + shdr = self._elf.shdrs[idx] + s = self._elf.read_section(shdr), shdr + self._elf_sections[idx] = s + return s + + def _get_elf_sym_value(self, sname): + sym = self._elf.symtab[sname] + section, shdr = self._get_elf_section(sym["st_shndx"]) + addr = sym["st_value"] - shdr["sh_addr"] + value = section[addr:addr + sym["st_size"]] + if len(value) != sym["st_size"]: + print("warning: symbol %s should be %d bytes, but in section is only %d bytes" % ( + sname, sym["st_size"], len(value))) + return value + + # qt resource stuff: + def _get_resource_name(self, offset): + length, i = struct.unpack(">HI", self._res_names[offset:offset + 2 + 4]) + offset += 2 + 4 + name = self._res_names[offset:offset + 2 * length].decode("utf-16be") + return name + + def _get_resource_data(self, offset): + length = struct.unpack(">I", self._res_datas[offset:offset + 4])[0] + offset += 4 + return self._res_datas[offset:offset + length] + + def _read_resources(self): + RCCFileInfo_Directory = 0x02 + def read_table(): + table = [] + offset = 0 + while offset < len(self._res_struct): + name_offset, flags = struct.unpack(">IH", self._res_struct[offset:offset+4+2]) + offset += 6 + name = self._get_resource_name(name_offset) + if flags & RCCFileInfo_Directory: + child_count, first_child_offset = struct.unpack(">II", self._res_struct[offset:offset + 4 + 4]) + offset += 4 + 4 + table.append((name, flags, child_count, first_child_offset)) + else: + country, language, data_offset = struct.unpack(">HHI", self._res_struct[offset:offset + 2 + 2 + 4]) + offset += 2 + 2 + 4 + table.append((name, flags, country, language, data_offset)) + return table + def read_dir_entries(table, which, parents=[]): + name, flags = which[:2] + if not flags & RCCFileInfo_Directory: + raise Exception("not a directory!") + child_count, first_child = which[2:] + for i in range(child_count): + child = table[first_child + i] + if child[1] & RCCFileInfo_Directory: + read_dir_entries(table, child, parents + [child[0]]) + else: + country, language, data_offset = child[2:] + full_name = "/".join(parents + [child[0]]) + self._resources[full_name] = data_offset + + self._res_datas = self._get_elf_sym_value("_ZL16qt_resource_data") + self._res_names = self._get_elf_sym_value("_ZL16qt_resource_name") + self._res_struct = self._get_elf_sym_value("_ZL18qt_resource_struct") + + self._resources = {} # res_fn -> res_offset + table = read_table() + read_dir_entries(table, table[0]) + + def get_resource(self, res_fn): + offset = self._resources[res_fn] + data = self._get_resource_data(offset) + return data + + def find_resource_names(self, res_fn_re): + for key in self._resources.keys(): + m = re.match(res_fn_re, key) + if m is not None: + yield key + +class res_writer(object): + def __init__(self, res): + self.res = res + + def _write_file(self, fn, data, decoder=None): + if decoder is not None: + data = decoder(data) + with open(fn, "wb") as fp: + fp.write(data) + print("saved %d bytes to %s" % (len(data), fn)) + + def extract(self, res_fn, out_fn, decoder=None): + self._write_file(out_fn, self.res.get_resource(res_fn), decoder=decoder) + + def extract_re(self, res_fn_re, out_fn, decoder=None): + for res_fn in res.find_resource_names(res_fn_re): + fn = re.sub(res_fn_re, out_fn, res_fn).lower() + self._write_file(fn, self.res.get_resource(res_fn), decoder=decoder) + +def decode_intel_hex(hexdata): + """ return list of (address, data) + """ + datas = [] + # assume \n or \r\n* + for line in hexdata.split(b"\n"): + line = line.strip() + if chr(line[0]) != ":": raise Exception("invalid line: %r" % line) + offset = 1 + record = codecs.decode(line[offset:], "hex") + byte_count, address, record_type = struct.unpack(">BHB", record[:1 + 2 + 1]) + offset = 1 + 2 + 1 + if byte_count > 0: + data = record[offset:offset + byte_count] + offset += byte_count + checksum = record[offset] + ex_checksum = (~sum(record[:offset]) + 1) & 0xff + if ex_checksum != checksum: raise Exception("invalid checksum %#x in %r" % (checksum, line)) + if record_type == 0: + datas.append((address, data)) + elif record_type == 1: + break + return datas + +def intel_hex_as_blob(hexdata): + """ return continuous bytes sequence including all data + (loosing start address here) + """ + data = decode_intel_hex(hexdata) + data.sort() + last = data[-1] + length = last[0] + len(last[1]) + img = bytearray(length) + for off, part in data: + img[off:off + len(part)] = part + return img + +def maybe_intel_hex_as_blob(data): + if data[0] == ord(":") and max(data) < 127: + return intel_hex_as_blob(data) + return data # keep binary data + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("sigrok-fwextract-kingst-la2016 ") + sys.exit() + + res = qt_resources(sys.argv[1]) + + writer = res_writer(res) + writer.extract("fwfpga/LA2016A", "kingst-la2016a-fpga.bitstream") + writer.extract_re(r"fwusb/fw(.*)", r"kingst-la-\1.fw", decoder=maybe_intel_hex_as_blob) diff --git a/firmware/kingst-la/sigrok-fwextract-kingst-la2016.1 b/firmware/kingst-la/sigrok-fwextract-kingst-la2016.1 new file mode 100644 index 0000000..6dbeed8 --- /dev/null +++ b/firmware/kingst-la/sigrok-fwextract-kingst-la2016.1 @@ -0,0 +1,64 @@ +.TH SIGROK\-FWEXTRACT\-KINGST\-LA2016 1 "Mar 21, 2020" +.SH "NAME" +sigrok\-fwextract\-kingst\-la2016 \- Extract Kingst LA2016 firmware +.SH "SYNOPSIS" +.B sigrok\-fwextract\-kingst\-la2016 [FILE] +.SH "DESCRIPTION" +This tool extracts FX2 firmware and FPGA bitstreams from the vendor +software for the Kingst LA2016 USB logic analyzer. Download the Linux +version from[1], and unpack it to find the main binary called "KingstVIS". +.PP +In order to extract the firmware/bitstreams, run the following command: +.PP +.B " $ tar -xzf KingstVIS_v3.4.0.tar.gz KingstVIS/KingstVIS" +.PP +.B " $ sigrok-fwextract-kingst-la2016 KingstVIS/KingstVIS" +.br +.RB " saved 177666 bytes to kingst-la2016a-fpga.bitstream +.br +.RB " saved 5350 bytes to kingst-la-01a1.fw" +.br +.RB " saved 5430 bytes to kingst-la-01a2.fw" +.br +.RB " saved 5718 bytes to kingst-la-01a3.fw" +.br +.RB " saved 142412 bytes to kingst-la-01a4.fw" +.br +.RB " saved 5452 bytes to kingst-la-03a1.fw" +.PP +Copy the resulting files over to the location where libsigrok expects +to find its firmware files. By default this is +.BR /usr/local/share/sigrok-firmware . +.SH OPTIONS +None. +.SH "EXIT STATUS" +Exits with 0 on success, 1 on most failures. +.SH "SEE ALSO" +\fBsigrok\-fwextract\-saleae\-logic16\fP(1) +.br +\fBsigrok\-fwextract\-dreamsourcelab\-dslogic\fP(1) +.br +\fBsigrok\-fwextract\-hantek\-dso\fP(1) +.br +\fBsigrok\-fwextract\-lecroy\-logicstudio\fP(1) +.br +\fBsigrok\-fwextract\-sysclk\-lwla1016\fP(1) +.br +\fBsigrok\-fwextract\-sysclk\-lwla1034\fP(1) +.SH "BUGS" +Please report any bugs via Bugzilla +.RB "(" http://sigrok.org/bugzilla ")" +or on the sigrok\-devel mailing list +.RB "(" sigrok\-devel@lists.souceforge.net ")." +.SH "LICENSE" +This program is covered by the GNU General Public License (GPL), +version 3 or later. +.SH "AUTHORS" +Please see the individual source code files. +.SH "NOTES" +.IP " 1." 4 +Vendor website +.RS 4 +.RB http://www.qdkingst.com/download +.br +\%MD5 of v3.4.0: ca407133cb83b700983d2b704a4255c2 -- 2.30.2