Skip to content

Instantly share code, notes, and snippets.

@eliotb
Last active October 26, 2022 16:35
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save eliotb/1073231 to your computer and use it in GitHub Desktop.
Save eliotb/1073231 to your computer and use it in GitHub Desktop.
Tools for working with Texas Instruments COFF and AIS files
#!/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' <eblennerhassett@audioscience.com>
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,
print
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" <eblennerhassett@audioscience.com>
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):
print
if (i + 1) % 8:
print
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],
print
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. <support@audioscience.com>
( 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" <eblennerhassett@audioscience.com>
'''
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:
print
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:
print
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" <eblennerhassett@audioscience.com>
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)
@altendky
Copy link

altendky commented Nov 3, 2016

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

@altendky
Copy link

altendky commented Nov 3, 2016

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.

@altendky
Copy link

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.

@LRGH
Copy link

LRGH commented Jan 20, 2017

(...) 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment