-
-
Save eliotb/1073231 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python | |
'''Read fully linked TI COFF file, and generate AIS format file | |
Commandline parameters | |
enable sequential read, | |
pll and emifb configuration | |
pinmux configuration | |
enable checksums | |
'Eliot Blennerhassett' <[email protected]> | |
AudioScience Inc. 2011 | |
''' | |
from struct import pack, unpack | |
from collections import namedtuple | |
from ticoff import Coff | |
#from binascii import crc32 | |
#from numpy import array, fromstring, uint32 | |
class aisgen(object): | |
def __init__(self, aisgen_compatible=False): | |
self.funcs = {} | |
self.crc_enabled = False | |
self.aisgen_compatible = aisgen_compatible | |
self.verbosity = 0 | |
def magic(self): | |
self.f.write(pack('<I', 0x41504954)) | |
def enable_crc(self): | |
if self.verbosity > 1: | |
print 'Enable CRC' | |
self.crc_enabled = True | |
self.f.write(pack('<I', 0x58535903)) | |
def disable_crc(self): | |
if self.verbosity > 1: | |
print 'Disable CRC' | |
self.crc_enabled = False | |
self.f.write(pack('<I', 0x58535904)) | |
def validate_crc(self, crc, seek): | |
if self.verbosity > 1: | |
print 'Validate CRC 0x%08X, seek %d' % (crc, seek) | |
self.f.write(pack('<III', 0x58535902, crc, 0x100000000 + seek)) | |
def section_load(self, s): | |
wlen = len(s.data) | |
odd = wlen % 4 | |
write_data = s.data | |
npad = 0 | |
spad = '' | |
if odd: | |
npad = (4 - odd) | |
write_data += ('\0' * npad) | |
#write_data += '\x48\x00\x00' # Test to match particular example from AISgen :( | |
spad = ' + %d bytes padding' % npad | |
command = pack('<I', 0x58535901) | |
if self.aisgen_compatible: | |
# Generate matching TI AISgen, size in header includes padding | |
if self.verbosity > 1: | |
print 'Load section 0x%X[0x%06X] %s' % (s.virt_addr, len(write_data), s.name) | |
params = pack('<II', s.virt_addr, len(write_data)) | |
else: | |
# Generate according to spec, size in header excludes padding | |
if self.verbosity > 1: | |
print 'Load section 0x%X[0x%06X]%s %s' % (s.virt_addr, wlen, spad, s.name) | |
params = pack('<II', s.virt_addr, wlen) | |
crc_data = params + write_data | |
self.f.write(command + crc_data) | |
if self.crc_enabled and self.crc_calc: | |
crc = self.crc_calc(crc_data) | |
# Seek backwards, over section data + 2 headers | |
seek = -(len(crc_data) + 16) | |
self.validate_crc(crc, seek) | |
# These values obtained here: | |
# http://e2e.ti.com/support/dsp/tms320c6000_high_performance_dsps/f/115/p/118006/423501.aspx | |
fill_types = {'8bit' : 0, '16bit' : 1, '32bit' : 2} | |
def section_fill(self, addr, nbytes, fill): | |
remainder_to_fill_type = [2, 0, 1, 0] | |
if self.verbosity > 1: | |
print 'Fill section 0x%X[0x%06X] = 0x%X' % (addr, nbytes, fill) | |
odd = nbytes % 4 | |
fill_type = remainder_to_fill_type[odd] | |
command = pack('<I', 0x5853590a) | |
params = pack('<IIII', addr, nbytes, fill_type, fill) | |
self.f.write(command + params) | |
if self.crc_enabled: | |
npad = 0 | |
if odd: | |
npad = (4 - odd) | |
crc_data = params + chr(fill & 0xFF) * (nbytes + npad) | |
crc = self.crc_calc(crc_data) | |
seek = -(4 * (5 + 3)) # back over section fill + validate crc commands | |
self.validate_crc(crc, seek) | |
def section(self, s): | |
f = s.fill_value() | |
if f is not None: | |
self.section_fill(s.virt_addr, len(s.data), f) | |
else: | |
self.section_load(s) | |
def execute_function(self, fname, params): | |
if self.verbosity > 1: | |
print 'Execute function %s' % fname, | |
for p in params: | |
print '0x%X' % p, | |
fnum, nparams = self.funcs[fname] | |
if len(params) != nparams: | |
raise ValueError('Expected %d params for %s', nparams, fname) | |
fh = (len(params) << 16) + fnum | |
self.f.write(pack('<II', 0x5853590D, fh)) | |
self.f.write(pack('<%dI' % len(params), *params)) | |
PllAndClockParams = namedtuple('PllAndClockParams', | |
'pllm postdiv plldiv3 plldiv5 plldiv7 ' + | |
'clkmode pll_lock_cnt spi_prescale') | |
def pll_and_spi_clock_config(self, c): | |
'''expects parameters to be instance of PllAndClockParams''' | |
#print c | |
param1 = (c.pllm << 24) + (c.postdiv << 16) + (c.plldiv3 << 8) + c.plldiv5 | |
param2 = (c.clkmode << 24) + (c.plldiv7 << 16) + c.pll_lock_cnt | |
self.execute_function('PLL and Clock Configuration', (param1, param2, c.spi_prescale)) | |
EmifbParams = namedtuple('EmifbParams', 'sdcfg sdtim1 sdtim2 sdrfc') | |
def emifb_config(self, c): | |
'''expects parameters to be instance of EmifbParams''' | |
#print c | |
self.execute_function('EMIFB SDRAM Configuration', c) | |
def pinmux_config(self, c): | |
'''expects parameters to be 2 or 3 element tuple | |
(index, value [, mask]) | |
''' | |
c = c + (0xFFFFFFFF, ) # default mask if not provided | |
self.execute_function('Pinmux Configuration', (c[0], c[2], c[1])) | |
def sequential_read(self): | |
if self.verbosity > 1: | |
print 'Sequential read enable' | |
self.f.write(pack('<I', 0x58535963)) | |
def jump_and_close(self, addr): | |
if self.verbosity > 1: | |
print 'Jump and close 0x%X' % addr | |
self.f.write(pack('<II', 0x58535906, addr)) | |
def generate(self, filename, coff, options): | |
if self.verbosity > 0: | |
print 'Generating for %s...' % self.rom, | |
load_sections = coff.loadable_sections | |
entry_point = coff.entry_point | |
with open(filename, 'wb') as self.f: | |
self.magic() | |
if options.sequential_read: | |
self.sequential_read() | |
if options.pll_config: | |
pll_params = eval('self.PllAndClockParams(%s)' % options.pll_config) | |
self.pll_and_spi_clock_config(pll_params) | |
if options.emifb_config: | |
emifb_params = eval('self.EmifbParams(%s)' % options.emifb_config) | |
self.emifb_config(emifb_params) | |
for c in options.pinmux_config: | |
params = eval('(%s)' % c) | |
self.pinmux_config(params) | |
if options.enable_crc: | |
self.enable_crc() | |
for s in load_sections: | |
self.section(s) | |
self.jump_and_close(entry_point) | |
class aisgen_d800k001(aisgen): | |
'''Instance of aisgen that implements ROM d800k001 | |
CRC calculation and special functions''' | |
def __init__(self): | |
aisgen.__init__(self) | |
self.rom = 'd800k001' | |
self.funcs = { | |
# func name : (id, param count) | |
'PLL0 Configuration' : (0, 2), | |
'Peripheral Clock Configuration' : (1, 1), | |
'EMIFB SDRAM Configuration' : (2, 4), | |
'EMIFA SDRAM Configuration' : (3, 4), | |
'EMIFA CE Space Configuration' : (4, 4), | |
'PLL and Clock Configuration' : (5, 3), | |
} | |
def _crc_calc_direct(self, data, crc=0): | |
'''Reference implementation of CRC | |
Process 32 bit words MSB first | |
Data is little endian. Very SLOW!''' | |
words = fromstring(data, dtype=uint32) | |
for word in words: | |
bit_no = 31; | |
while bit_no >= 0: | |
msb_bit = crc & 0x80000000 | |
crc = ((word >> bit_no) & 0x1) | (crc << 1) | |
if msb_bit: | |
crc ^= 0x04C11DB7 | |
bit_no -= 1 | |
return crc & 0xFFFFFFFF | |
def crc_calc(self, in_str, crc=0): | |
'''Generate CRC to match TI AISgen output, from data zero padded to N*4 bytes. | |
This is not the same as standard CRC calculation, lacking the augmentation of | |
the input data with 32 zero bits. | |
Applies to C6747/5/3 devices ROM d800k003, but not C6748/6/2, see | |
http://e2e.ti.com/support/dsp/tms320c6000_high_performance_dsps/f/115/p/119062/426491.aspx | |
''' | |
nbytes = len(in_str) | |
for w in range(0, nbytes, 4): | |
# in-place endian reversal to | |
# process little endian words MSbyte first | |
for b in range(3, -1, -1): | |
ofs = w + b | |
c = in_str[ofs] | |
octet = ord(c) | |
for i in range(8): | |
topbit = crc & 0x80000000 | |
crc = ((octet >> (7 - i)) & 0x01) | ((crc << 1) & 0xFFFFFFFF) | |
if topbit: | |
crc ^= 0x04C11DB7 | |
return crc | |
class aisgen_d800k003(aisgen_d800k001): | |
'''Instance of aisgen that implements ROM d800k003 | |
CRC calculation and special functions''' | |
def __init__(self): | |
aisgen_d800k001.__init__(self) | |
self.rom = 'd800k003' | |
self.funcs['Power and Sleep Controller Configuration'] = (6, 1) | |
self.funcs['Pinmux Configuration'] = (7, 3) | |
roms = { | |
'd800k001' : aisgen_d800k001, | |
'd800k003' : aisgen_d800k003 | |
} | |
if __name__ == '__main__': | |
from optparse import OptionParser | |
from sys import exit | |
parser = OptionParser(usage='%prog [options] input.coff output.ais') | |
parser.add_option('-c', '--enable-crc', action='store_true', | |
default=False, help='enable per-section CRCs') | |
parser.add_option('-r', '--rom', action='store', choices=roms.keys(), metavar='ROM_VERSION', | |
default='d800k003', help='one of %s' % roms.keys()) | |
parser.add_option('-s', '--sequential-read', action='store_true', | |
default=False, help='use SPI sequential read') | |
parser.add_option('-v', '--verbosity', action='store', type='int', | |
default=1, help='verbosity of output') | |
parser.add_option('-b', '--emifb-config', action='store', type='string', | |
help='E.g. --emifb-config="sdcfg=0x10620, sdtim1=0x01912a08, sdtim2=0x70080005, sdrfc=0x8000079e"') | |
parser.add_option('-p', '--pll-config', action='store', type='string', | |
help='E.g. --pll-config="pllm=29, postdiv=1, plldiv3=3, plldiv5=2, plldiv7=5, clkmode=1, pll_lock_cnt=402, spi_prescale=6"') | |
parser.add_option('-m', '--pinmux-config', action='append', type='string', | |
default=[], help='PINMUX_CONFIG=regnum,value[,optional mask]. May be used more than once\n'+ | |
'E.g. --pinmux-config=0,0x10011101 -m1,0x1001,0xFFFF') | |
options, args = parser.parse_args() | |
# | |
if args[0][0] == '@': | |
fa = [] | |
with open(args[0][1:]) as f: | |
for l in f: | |
fa.append(l.strip()) | |
fa = fa + args[1:] | |
options, args = parser.parse_args(args=fa, values=options) | |
if len(args) < 2: | |
parser.print_help() | |
exit() | |
coff = Coff(args[0]) | |
gen = roms[options.rom]() | |
gen.verbosity = options.verbosity | |
gen.generate(args[1], coff, options) | |
if options.verbosity: | |
print 'Finished' | |
#!/usr/bin/env python | |
""" | |
Parse AIS file, and optionally generate C array | |
"Eliot Blennerhassett" <[email protected]> | |
AudioScience Inc. 2011 | |
""" | |
import os | |
import struct | |
import sys | |
from optparse import OptionParser | |
def read(format): | |
datalen = struct.calcsize(format) | |
data = Input.read(datalen) | |
if len(data) != datalen: | |
raise EOFError | |
ret = struct.unpack(format, data) | |
if len(ret) < 2: | |
ret = ret[0] | |
return ret | |
def section_load(): | |
address = read('I') | |
size = read('I') | |
# data length is rounded up from size to multiple o4 | |
data = read('%dI' % ((size + 3) / 4)) | |
try: | |
len(data) | |
except TypeError: | |
data = [data] | |
print '// Section load %#x[%#x]' % (address, size) | |
if all([d == data[0] for d in data]): | |
print '// ^ could use Section fill with %#x command' % data[0] | |
if gen_h: | |
print '%#x, %#x,' % (address, size) | |
for i in xrange(len(data)): | |
print '0x%08x,' % data[i], | |
if not((i + 1) % 8): | |
if (i + 1) % 8: | |
def section_fill(): | |
address = read('I') | |
size = read('I') | |
type = read('I') | |
pattern = read('I') | |
print '// Section Fill type %d %#x[%#x] = %#x' % (type, address, size, pattern) | |
if gen_h: | |
print '%#x, %#x, %#x, %#x' % (address, size, type, pattern) | |
def boot_table(): | |
type = read('I') | |
address = read('I') | |
data = read('I') | |
sleep = read('I') | |
print '// Boot table type %d *%#x = %#x, sleep %d' % (type, address, data, sleep ) | |
if gen_h: | |
print '%#x, %#x, %#x, %#x' % (type, address, data, sleep) | |
# This function list comes from sprab1b.pdf 'Using the TMS320C6747/45/43 Bootloader' | |
funcs = { | |
0 : ('PLL0 Configuration', 2), | |
1 : ('Peripheral Clock Configuration', 1), | |
2 : ('EMIFB SDRAM Configuration', 4), | |
3 : ('EMIFA SDRAM Configuration', 4), | |
4 : ('EMIFA CE Space Configuration', 4), | |
5 : ('PLL and Clock Configuration', 3), | |
6 : ('Power and Sleep Controller Configuration', 1), | |
7 : ('Pinmux Configuration', 3), | |
} | |
def function_execute(): | |
arg = read('I') | |
func = arg & 0xFFFF | |
ac = (arg & 0xFFFF0000) >> 16 | |
try: | |
fn, ac1 = funcs[func] | |
except KeyError: | |
fn = 'Unkown %#x' % func | |
ac1 = 0 | |
assert ac == ac1, 'Arg count mismatch %d %d' % (ac, ac1) | |
print '// Function %d : %s(' % (func, fn), | |
args = [] | |
for i in range(ac): | |
args.append(read('I')) | |
print '%#x, ' % args[i], | |
print ')' | |
if gen_h: | |
print '%#x,' % arg, | |
for i in range(ac): | |
print '%#x, ' % args[i], | |
def jump_and_close(): | |
address = read('I') | |
print '// Close and jump to %#x' % address | |
if gen_h: | |
print '%#x,' % address | |
def jump(op): | |
address = read('I') | |
print '// Jump to %#x' % address | |
if gen_h: | |
print '%#x,' % address | |
def validate_crc(): | |
crc = read('I') | |
seek = read('I') | |
print '// Validate CRC %#x, seek %x' % (crc, seek) | |
if gen_h: | |
print '%#x, %#x,' % (crc, seek) | |
def enable_crc(): | |
print '// Enable CRC' | |
def disable_crc(): | |
print '// Enable CRC' | |
def sequential_read(): | |
print '// Sequential read enable' | |
opcodes = { | |
0x58535901 : section_load, | |
0x58535902 : validate_crc, | |
0x58535903 : enable_crc, | |
0x58535904 : disable_crc, | |
0x58535905 : jump, | |
0x58535906 : jump_and_close, | |
0x58535907 : boot_table, | |
0x5853590a : section_fill, | |
0x5853590D : function_execute, | |
0x58535963 : sequential_read, | |
} | |
parser = OptionParser() | |
parser.add_option("-g", "--generate-header", | |
action="store_true", dest="gen_h", default=False, | |
help="generate full header file on stdout") | |
(options, args) = parser.parse_args() | |
fname = args[0] | |
fsize = os.path.getsize(fname) | |
gen_h = options.gen_h | |
if gen_h: | |
print '''// Generated from %s | |
#include <stdint.h> | |
uint32_t ais_array[] = {''' | |
with open (fname, 'rb') as Input: | |
magic = read('I') | |
if not (magic == 0x41504954): | |
sys.exit('Incorrect magic, not an AIS file?') | |
print '// AIS magic ID' | |
if gen_h: | |
print '%#x,' % magic | |
while True: | |
try: | |
opcode = read('I') | |
except EOFError: | |
break | |
try: | |
command = opcodes[opcode] | |
except KeyError: | |
print 'unknown opcode %#x' % opcode | |
if gen_h: | |
print '%#x,' % opcode, | |
command() | |
if gen_h: | |
print '};' | |
assert Input.tell() == fsize, 'file not consumed?' |
Copyright (C) 1997-2016 AudioScience, Inc. | |
This software is provided 'as-is', without any express or implied warranty. | |
In no event will AudioScience Inc. be held liable for any damages arising | |
from the use of this software. | |
Permission is granted to anyone to use this software for any purpose, | |
including commercial applications, and to alter it and redistribute it | |
freely, subject to the following restrictions: | |
1. The origin of this software must not be misrepresented; you must not | |
claim that you wrote the original software. If you use this software | |
in a product, an acknowledgment in the product documentation would be | |
appreciated but is not required. | |
2. Altered source versions must be plainly marked as such, and must not be | |
misrepresented as being the original software. | |
3. This copyright notice and list of conditions may not be altered or removed | |
from any source distribution. | |
AudioScience, Inc. <[email protected]> | |
( This license is GPL compatible see http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses ) |
#!/usr/bin/env python | |
'''Read TI pinmux data files, and pinmux config file | |
and generate pinmux register values, and comments. | |
Requires mux[1-5].txt from TI pinsetup utility OMAP-L1x7_C6747-5-3_pinsetup | |
There are 20 pinmux registers, each with 8 4-bit fields | |
"Eliot Blennnerhassett" <[email protected]> | |
''' | |
from sys import exit | |
def read_pin_data(): | |
'''Read pin data into 2 dictionaries. | |
pd key is pin function name, value is (reg_idx, field_idx, field value, ?) | |
pf key (reg_idx, field_idx), value is [list of pin names] | |
pin data files contain lines like | |
AXR0[0],10,1,1,830 | |
pin_func_name, reg_idx, field_idx, field_value, ?? | |
not sure what the last value represents? | |
''' | |
pd = {} # setups per pin function | |
pf = {} # pin functions per register field | |
for fi in range(1, 6): | |
fn = 'mux%d.txt' % fi | |
with open(fn) as f: | |
for l in f: | |
t = l.strip().split(',') | |
if len(l) < 5: | |
continue | |
pin_name = t[0] | |
p = tuple([int(s) for s in t[1:]]) | |
pi = p[0:2] | |
# ((reg_idx, field_idx) : ['pin_name', ...] | |
if pf.has_key(pi): | |
pf[pi].append(pin_name) | |
else: | |
pf[pi] = [pin_name] | |
# 'pin_name' : (reg_idx, field_idx, field value, ?) | |
pd[pin_name] = p | |
return pd, pf | |
def read_pin_config(fn, pd): | |
'''read pin config from file named fn, and add pinmux setup data from pd | |
pin config is a list of pin function names, one per line | |
''' | |
pins = [] | |
with open(fn) as f: | |
for l in f: | |
n = l.strip() | |
if not len(n): | |
continue | |
ps = pd[n] | |
pins.append((n, ps)) | |
return pins | |
def print_alpha_pinlist(pins): | |
'''print list of pin names in alphabetical order''' | |
pins.sort() # pin name order | |
print '/*\nDefined pins:', | |
wrap = 0 | |
for pin in pins: | |
if wrap > 70: | |
wrap = 0 | |
print pin[0], | |
wrap += len(pin[0]) + 1 | |
print '\n*/' | |
def print_muxfield_pinlist(pins): | |
'''print list of pin names grouped by mux field value''' | |
pins.sort(key=lambda p : (p[1][2], p[1][0], p[1][1])) | |
prev_mux = None | |
print '/*\nPins by mux field setting:', | |
for pin in pins: | |
mux = pin[1][2] | |
if mux != prev_mux: | |
print '\n\nMUX%d Pins:' % mux | |
prev_mux = mux | |
wrap = 0 | |
if wrap > 70: | |
wrap = 0 | |
print pin[0], | |
wrap += len(pin[0]) + 1 | |
print '\n*/' | |
def print_pinmux_pinlist(pins): | |
'''print list of pin names grouped by pinmux register''' | |
pins.sort(key=lambda p : (p[1][0], p[1][1])) | |
print '/* Pins by pinmux register' | |
prev_regidx = None | |
for pin in pins: | |
regidx = pin[1][0] | |
if regidx != prev_regidx: | |
print '\npinmux[%d] pins' % regidx | |
prev_regidx = regidx | |
print pin[0], | |
print '\n*/' | |
def calculate_pinmux_values(pins, pd, pf): | |
'''convert the selected pin data into appropriate bitfields | |
in registers | |
''' | |
regs = [0] * 20 | |
error = False | |
for pin in pins: | |
ps = pin[1] | |
reg = ps[0] | |
field_ofs = ps[1] * 4 | |
field_mask = 0xF << field_ofs | |
field_val = ps[2] << field_ofs | |
if (regs[reg] & field_mask): | |
fv = (regs[reg] & field_mask) >> field_ofs | |
# find which pin already assigned to the field | |
current = (reg, ps[1], fv) | |
shared = pf[ps[0:2]] | |
for name in shared: | |
id = pd[name][0:3] | |
if current == id: | |
break; | |
print 'Conflict setting %s : pinmux[%d][%d] already set to %s' % (pin[0], reg, ps[1], name) | |
error = True | |
else: | |
regs[reg] = regs[reg] | field_val; | |
if error: | |
exit(1) | |
return regs | |
formats = { | |
'aisgen' : '--pinmux-config=%d,0x%08X', | |
'array' : '\t/* %d */ 0x%08X,', | |
'define' : '#define PINMUX%d_VALUE 0x%08X', | |
} | |
def print_pinmux_values(format, regs): | |
'''print the pinmux register value list | |
formatted for various uses | |
''' | |
fs = formats[format] | |
if format == 'array': | |
print 'unsigned int pinmux_values = {' | |
for i in range(len(regs)): | |
print fs % (i, regs[i]) | |
if format == 'array': | |
print '};' | |
def print_known_pins(pf): | |
pl = list( pf.iteritems()) | |
pl.sort(key=lambda p : (p[0][0], p[0][1])) | |
for p in pl: | |
print 'pinmux %s selects %s' % p | |
if __name__ == '__main__': | |
from optparse import OptionParser | |
parser = OptionParser(usage='%prog [options] input.pin') | |
parser.add_option('-a', '--print-alpha', action='store_true', | |
default=False, help='Print alphabetical pinlist comment') | |
parser.add_option('-m', '--print-muxfield', action='store_true', | |
default=False, help='Print pinlist by mux value comment') | |
parser.add_option('-r', '--print-register', action='store_true', | |
default=False, help='Print pinlist by register comment') | |
parser.add_option('-f', '--format', action='store', choices=formats.keys(), | |
default='define', help='one of %s' % formats.keys()) | |
parser.add_option('-p', '--print-known', action='store_true', | |
default=False, help='print known pins') | |
options, args = parser.parse_args() | |
pd, pf = read_pin_data() | |
if options.print_known: | |
print_known_pins(pf) | |
exit(0) | |
pins = read_pin_config(args[0], pd) | |
regs = calculate_pinmux_values(pins, pd, pf) | |
if options.print_alpha: | |
print_alpha_pinlist(pins) | |
if options.print_muxfield: | |
print_muxfield_pinlist(pins) | |
if options.print_register: | |
print_pinmux_pinlist(pins) | |
print_pinmux_values(options.format, regs) |
#!/usr/bin/env python | |
""" | |
Read TI COFF file into a python object | |
"Eliot Blennerhassett" <[email protected]> | |
AudioScience Inc. 2011 | |
""" | |
from array import array | |
from collections import namedtuple | |
from optparse import OptionParser | |
from struct import unpack, calcsize | |
def read_struct(file, format): | |
'''read struct data formatted according to format''' | |
datalen = calcsize(format) | |
data = file.read(datalen) | |
if len(data) != datalen: | |
raise EOFError | |
ret = unpack(format, data) | |
if len(ret) < 2: | |
ret = ret[0] | |
return ret | |
def read_cstr(file): | |
'''read zero terminated c string from file''' | |
output = "" | |
while True: | |
char = file.read(1) | |
if len(char) == 0: | |
raise RuntimeError ("EOF while reading cstr") | |
if char == '\0': | |
break | |
output += char | |
return output | |
# From spraao8.pdf table 7 | |
class Section(namedtuple('Section', | |
'name, virt_size, virt_addr, raw_data_size, raw_data_ptr, relocation_ptr,' + | |
'linenum_ptr, linenum_count, reloc_count, flags, reserved, mem_page, data')): | |
__slots__ = () # prevent creation of instance dictionaries | |
section_fmt = '<8s9L2H' | |
section_flags = [ | |
(0x00000001, 'STYP_DSECT'), (0x00000002, 'STYP_NOLOAD'), | |
(0x00000004, 'STYP_GROUPED'), (0x00000008, 'STYP_PAD'), | |
(0x00000010, 'STYP_COPY'), (0x00000020, 'STYP_TEXT'), | |
(0x00000040, 'STYP_DATA'), (0x00000080, 'STYP_BSS'), | |
(0x00000100, 'STYP_100'), (0x00000200, 'STYP_200'), | |
(0x00000200, 'STYP_400'), (0x00000800, 'STYP_800'), | |
(0x00001000, 'STYP_BLOCK'), (0x00002000, 'STYP_PASS'), | |
(0x00004000, 'STYP_CLINK'),(0x00008000, 'STYP_VECTOR'), | |
(0x00010000, 'STYP_PADDED'), | |
] | |
@property | |
def is_zero_filled(self): | |
return (self.data is not None) and not any([d != '\0' for d in self.data]) | |
def fill_value(self): | |
if self.data is None: | |
return None | |
if len(self.data) % 4: | |
return None # only handle 32 bit fill | |
a = array('I', self.data) | |
if all([d == a[0] for d in a]): | |
return a[0] | |
else: | |
return None | |
@property | |
def is_loadable(self): | |
return not(self.flags & 0x00000010 or self.data is None) | |
def __str__(self): | |
s = 'Section %s' % self.name | |
if self.flags: | |
s += ' Flags =' | |
for f in Section.section_flags: | |
if self.flags & f[0]: | |
s = s + ' ' + f[1] | |
for fv in self._asdict().iteritems(): | |
if fv[0] == 'data' or fv[0] == 'name': continue | |
s = s + '\n\t%s = 0x%X' % fv | |
return s | |
class Coff(object): | |
Header = namedtuple('Header', | |
'machine_type, section_count, time_date, symbol_table_ptr, ' + | |
'symbol_count, optional_header_size, flags, target_id') | |
header_fmt = '<2H3L3H' | |
OptionalHeader = namedtuple('OptionalHeader', | |
'magic, version, exe_size, init_data_size, uninit_data_size, entry_point, exe_start, init_start') | |
optheader_fmt = '<2H6L' | |
def __init__(self, filename=None): | |
self.header = None | |
self.optheader = None | |
self.sections = [] | |
if filename is not None: | |
self.from_file(filename) | |
def from_file(self, name): | |
with open(name, 'rb') as f: | |
self.from_stream(f) | |
def from_stream(self, f): | |
self.header = self.Header(*read_struct(f, self.header_fmt)) | |
self.optheader = self.OptionalHeader(*read_struct(f, self.optheader_fmt)) | |
self.sections = [] | |
for i in range(self.header.section_count): | |
section = Section(*read_struct(f, Section.section_fmt), data=None) | |
section = section._replace(name=self.symname(f, section.name)) | |
if section.raw_data_ptr and section.raw_data_size: | |
here = f.tell() | |
f.seek(section.raw_data_ptr) | |
data = f.read(section.raw_data_size) | |
f.seek(here) | |
section = section._replace(data=data) | |
self.sections.append(section) | |
self.entry_point = self.optheader.entry_point | |
self.sections.sort(key=lambda s: s.virt_addr) | |
@property | |
def loadable_sections(self): | |
return [s for s in self.sections if s.is_loadable] | |
def string_table_entry (self, f, offset): | |
here = f.tell() | |
seek = self.header.symbol_table_ptr + self.header.symbol_count * 18 + offset | |
f.seek(seek) | |
s = read_cstr(f) | |
f.seek(here) | |
return s | |
def symname(self, f, value): | |
parts = unpack("<2L", value) | |
if parts[0] == 0: | |
return self.string_table_entry(f, parts[1]) | |
else: | |
return value.rstrip('\0') | |
def __str__(self): | |
s = 'TI coff file' | |
if self.header is not None: | |
for fv in self.header._asdict().iteritems(): | |
s = s + '\n\t%s = 0x%X' % fv | |
if self.optheader is not None: | |
s += '\nOptional Header' | |
for fv in self.optheader._asdict().iteritems(): | |
s = s + '\n\t%s = 0x%X' % fv | |
return s | |
if __name__ == '__main__': | |
parser = OptionParser() | |
parser.add_option("-d", "--dump_data", | |
action="store_true", dest="dump", default=False, | |
help="dump section data") | |
options, args = parser.parse_args() | |
c = Coff(args[0]) | |
print c | |
for section in c.sections: | |
print section | |
if section.data is not None and options.dump: | |
print repr(section) | |
Over in https://gist.github.com/altendky/28a99c9cc6d221264820ed56f90dc62d I adapted ticoff.py to run in both Python 2 and 3. I did only a very basic test as below with no differences shown.
export file=/epc/t/409/afe.out; echo 3; python3 ticoff.py -d ${file} > afe.3; echo 2; python2 ticoff.py -d ${file} > afe.2; diff afe.2 afe.3
I'm sure it could be done better, but this seems to work.
I was comparing my ticoff.py
to some parsing I did of another program flashing my device and noticed that ticoff.py
seemed to provide half the data I expected. I looked at the read and noticed that it does not account for each memory location containing 2-bytes. I can see this is the case for my device based on the flashing protocol and the device responding with an address incremented by only three when six bytes had been written. This is also observed in the embedded software.
# TODO: `2 *` is hard coded to handle the 2-bytes per
# address scenario. This should obviously be
# detected somehow, unless it is always correct.
data = bytes(f.read(2 * section.raw_data_size))
I have not looked yet at how consistent this is across products or if it is detectable from something in the COFF file.
(...) I looked at the read and noticed that it does not account for each memory location containing 2-bytes. (...)
I have not looked yet at how consistent this is across products or if it is detectable from something in the COFF file.
It may depend on the target CPU, which is mentioned in the COFF file, in the field 'target_id' in the header (NB: this field seems to be an addition made by TI to the normal COFF header).
I have noticed this issue in a COFF with Target ID 0x9D, aka. TMS320C2800.
Hi Eliot,
I am developing a Python service tool for some TI based embedded hardware. I am trying to add support for reflashing the DSP with files generated from the C2000 toolchain. I ran across this code of yours for processing the TI COFF formatted files and was interested in using it but they were not published with a license.
My application is licensed GPL v2+ and is available at: https://github.com/altendky/st
Note that most of the code is now in the
lib
branch. At some point I will get it separated to it's own repository, but not yet.Would you be willing to share the code with some GPL compatible license?
Regardless, thanks for sharing the script.
Cheers,
-kyle