add saleae-logic16-extract tool.
authorMarcus Comstedt <marcus@mc.pp.se>
Mon, 5 Aug 2013 11:50:45 +0000 (13:50 +0200)
committerMarcus Comstedt <marcus@mc.pp.se>
Mon, 5 Aug 2013 11:59:54 +0000 (13:59 +0200)
firmware/README.saleae-logic16-extract [new file with mode: 0644]
firmware/parseelf.py [new file with mode: 0644]
firmware/saleae-logic16-extract.py [new file with mode: 0755]

diff --git a/firmware/README.saleae-logic16-extract b/firmware/README.saleae-logic16-extract
new file mode 100644 (file)
index 0000000..2213676
--- /dev/null
@@ -0,0 +1,13 @@
+The saleae-logic16-extract.py tool extracts FPGA bitstreams from the
+vendor software forh the Saleae Logic16 USB logic analyzer. Download
+the Linux version (either 32-bit or 64-bit will do), and unpack it
+to find the main binary called "Logic".
+
+Usage:
+
+$ ./saleae-logic16-extract.py Logic
+saved 149516 bytes to saleae-logic16-fpga-18.bitstream
+saved 149516 bytes to saleae-logic16-fpga-33.bitstream
+
+Copy the resulting files over to the location where sigrok installed its
+firmware files. By default this is /usr/local/share/sigrok-firmware
diff --git a/firmware/parseelf.py b/firmware/parseelf.py
new file mode 100644 (file)
index 0000000..1c6769e
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/python3
+##
+## This file is part of the sigrok-util project.
+##
+## Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se>
+##
+## 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 <http://www.gnu.org/licenses/>.
+##
+
+import struct
+
+class elf:
+
+    def read_struct(this, struct_fmt, struct_fields):
+        fmt = this.elf_endianprefix+str.translate(struct_fmt, this.elf_format);
+        fields = struct.unpack(fmt, this.file.read(struct.calcsize(fmt)))
+        return dict(zip(struct_fields, fields))
+
+    def read_ehdr(this):
+        return this.read_struct('16sHHWNNNWHHHHHH',
+                                ('e_ident', 'e_type', 'e_machine', 'e_version',
+                                 'e_entry', 'e_phoff', 'e_shoff', 'e_flags',
+                                 'e_ehsize', 'e_phentsize', 'e_phnum',
+                                 'e_shentsize', 'e_shnum', 'e_shstrndx'))
+
+    def read_shdr(this):
+        return this.read_struct('WWNNNNWWNN',
+                                ('sh_name', 'sh_type', 'sh_flags', 'sh_addr',
+                                 'sh_offset', 'sh_size', 'sh_link', 'sh_info',
+                                 'sh_addralign', 'sh_entsize'))
+
+    def read_section(this, shdr):
+        this.file.seek(shdr['sh_offset'])
+        return this.file.read(shdr['sh_size'])
+
+    def get_name(this, name, strtab=None):
+        strtab = strtab or this.strtab
+        nul = strtab.find(b'\0', name)
+        if nul < 0:
+            return bytes.decode(strtab[name:])
+        else:
+            return bytes.decode(strtab[name:nul])
+
+    def find_section(this, name):
+        for section in this.shdrs:
+            if this.get_name(section['sh_name']) == name:
+                return section
+        raise KeyError(name)
+
+    def parse_symbol(this):
+        if this.elf_wordsize == 64:
+            return this.read_struct('WBBHNX', 
+                                    ('st_name', 'st_info', 'st_other',
+                                     'st_shndx', 'st_value', 'st_size'))
+        else:
+            return this.read_struct('WNWBBH', 
+                                    ('st_name', 'st_value', 'st_size',
+                                     'st_info', 'st_other', 'st_shndx'))
+
+    def parse_symbols(this, symsecname, strsecname):
+        try:
+            symsechdr = this.find_section(symsecname)
+            strsechdr = this.find_section(strsecname)
+        except KeyError:
+            return {}
+        strsec = this.read_section(strsechdr)
+        this.file.seek(symsechdr['sh_offset'])
+        syms = [this.parse_symbol() for i in
+                range(0, symsechdr['sh_size'] // symsechdr['sh_entsize'])]
+        return {this.get_name(sym['st_name'], strsec): sym for sym in syms}
+
+    def address_to_offset(this, addr):
+        for section in this.shdrs:
+            if (section['sh_addr'] <= addr and
+                section['sh_addr']+section['sh_size'] > addr):
+                return section['sh_offset']+(addr-section['sh_addr'])
+        raise IndexError('address out of range')
+
+    def load_symbol(this, sym):
+        this.file.seek(this.address_to_offset(sym['st_value']))
+        return this.file.read(sym['st_size'])
+
+    def __init__(this, filename):
+        this.file = open(filename, 'rb')
+        magic = this.file.read(16)
+
+        if magic[:4] != b'\x7fELF':
+            raise Exception("ELF signature not found")
+
+        if magic[4] == 1:
+            this.elf_wordsize = 32
+            nativeint = 'Ii'
+        elif magic[4] == 2:
+            this.elf_wordsize = 64
+            nativeint = 'Qq'
+        else:
+            raise Exception("Invalid ELF file class")
+
+        if magic[5] == 1:
+            this.elf_endianprefix = '<'
+        elif magic[5] == 2:
+            this.elf_endianprefix = '>'
+        else:
+            raise Exception("Invalid ELF data encoding")
+
+        this.elf_format = str.maketrans('HWwXxNn', 'HIiQq'+nativeint)
+
+        this.file.seek(0)
+        this.ehdr = this.read_ehdr()
+        this.file.seek(this.ehdr['e_shoff'])
+        this.shdrs = [this.read_shdr() for i in range(this.ehdr['e_shnum'])]
+
+        this.strtab = this.read_section(this.shdrs[this.ehdr['e_shstrndx']])
+
+        this.symtab = this.parse_symbols('.symtab', '.strtab')
+        this.dynsym = this.parse_symbols('.dynsym', '.dynstr')
+
+    def __del__(this):
+        try:
+            this.file.close()
+        except AttributeError:
+            pass
diff --git a/firmware/saleae-logic16-extract.py b/firmware/saleae-logic16-extract.py
new file mode 100755 (executable)
index 0000000..c325174
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/python3
+##
+## This file is part of the sigrok-util project.
+##
+## Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se>
+##
+## 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 <http://www.gnu.org/licenses/>.
+##
+
+import sys
+import parseelf
+
+def extract_symbol(elf, symname, filename):
+    blob = elf.load_symbol(elf.dynsym[symname])
+    f = open(filename, 'wb')
+    f.write(blob)
+    f.close()
+    print("saved %d bytes to %s" % (len(blob), filename))
+
+def extract_bitstream(elf, lv):
+    extract_symbol(elf, 'gLogic16Lv'+lv+'CompressedBitstream',
+                   'saleae-logic16-fpga-'+lv+'.bitstream')
+
+def usage():
+    print("saleae-logic16-extract.py <programfile>")
+    sys.exit()
+
+
+#
+# main
+#
+
+if len(sys.argv) != 2:
+    usage()
+
+try:
+    filename = sys.argv[1]
+    elf = parseelf.elf(filename)
+    extract_bitstream(elf, '18')
+    extract_bitstream(elf, '33')
+except Exception as e:
+    print("Error: %s" % str(e))
+