gameduino/j1/firmware/intelhex.py

644 lines
20 KiB
Python

#!/usr/bin/python
# Copyright (c) 2005-2007, Alexander Belchenko
# All rights reserved.
#
# Redistribution and use in source and binary forms,
# with or without modification, are permitted provided
# that the following conditions are met:
#
# * Redistributions of source code must retain
# the above copyright notice, this list of conditions
# and the following disclaimer.
# * Redistributions in binary form must reproduce
# the above copyright notice, this list of conditions
# and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the <Alexander Belchenko>
# nor the names of its contributors may be used to endorse
# or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''Intel HEX file format reader and converter.
This script also may be used as hex2bin convertor utility.
@author Alexander Belchenko (bialix AT ukr net)
@version 0.8.6
@date 2007/04/26
'''
__docformat__ = "javadoc"
from array import array
from binascii import hexlify, unhexlify
class IntelHex:
''' Intel HEX file reader. '''
def __init__(self, fname):
''' Constructor.
@param fname file name of HEX file or file object.
'''
#public members
self.Error = None
self.AddrOverlap = None
self.padding = 0x0FF
# Start Address
self.start_addr = None
# private members
self._fname = fname
self._buf = {}
self._readed = False
self._eof = False
self._offset = 0
def readfile(self):
''' Read file into internal buffer.
@return True if successful.
'''
if self._readed:
return True
if not hasattr(self._fname, "read"):
f = file(self._fname, "rU")
fclose = f.close
else:
f = self._fname
fclose = None
self._offset = 0
self._eof = False
result = True
for s in f:
if not self.decode_record(s):
result = False
break
if self._eof:
break
if fclose:
fclose()
self._readed = result
return result
def decode_record(self, s):
''' Decode one record of HEX file.
@param s line with HEX record.
@return True if line decode OK, or this is not HEX line.
False if this is invalid HEX line or checksum error.
'''
s = s.rstrip('\r\n')
if not s:
return True # empty line
if s[0] == ':':
try:
bin = array('B', unhexlify(s[1:]))
except TypeError:
# this might be raised by unhexlify when odd hexascii digits
self.Error = "Odd hexascii digits"
return False
length = len(bin)
if length < 5:
self.Error = "Too short line"
return False
else:
return True # first char must be ':'
record_length = bin[0]
if length != (5 + record_length):
self.Error = "Invalid line length"
return False
addr = bin[1]*256 + bin[2]
record_type = bin[3]
if not (0 <= record_type <= 5):
self.Error = "Invalid type of record: %d" % record_type
return False
crc = sum(bin)
crc &= 0x0FF
if crc != 0:
self.Error = "Invalid crc"
return False
if record_type == 0:
# data record
addr += self._offset
for i in xrange(4, 4+record_length):
if not self._buf.get(addr, None) is None:
self.AddrOverlap = addr
self._buf[addr] = bin[i]
addr += 1 # FIXME: addr should be wrapped on 64K boundary
elif record_type == 1:
# end of file record
if record_length != 0:
self.Error = "Bad End-of-File Record"
return False
self._eof = True
elif record_type == 2:
# Extended 8086 Segment Record
if record_length != 2 or addr != 0:
self.Error = "Bad Extended 8086 Segment Record"
return False
self._offset = (bin[4]*256 + bin[5]) * 16
elif record_type == 4:
# Extended Linear Address Record
if record_length != 2 or addr != 0:
self.Error = "Bad Extended Linear Address Record"
return False
self._offset = (bin[4]*256 + bin[5]) * 65536
elif record_type == 3:
# Start Segment Address Record
if record_length != 4 or addr != 0:
self.Error = "Bad Start Segment Address Record"
return False
if self.start_addr:
self.Error = "Start Address Record appears twice"
return False
self.start_addr = {'CS': bin[4]*256 + bin[5],
'IP': bin[6]*256 + bin[7],
}
elif record_type == 5:
# Start Linear Address Record
if record_length != 4 or addr != 0:
self.Error = "Bad Start Linear Address Record"
return False
if self.start_addr:
self.Error = "Start Address Record appears twice"
return False
self.start_addr = {'EIP': (bin[4]*16777216 +
bin[5]*65536 +
bin[6]*256 +
bin[7]),
}
return True
def _get_start_end(self, start=None, end=None):
"""Return default values for start and end if they are None
"""
if start is None:
start = min(self._buf.keys())
if end is None:
end = max(self._buf.keys())
if start > end:
start, end = end, start
return start, end
def tobinarray(self, start=None, end=None, pad=None):
''' Convert to binary form.
@param start start address of output bytes.
@param end end address of output bytes.
@param pad fill empty spaces with this value
(if None used self.padding).
@return array of unsigned char data.
'''
if pad is None:
pad = self.padding
bin = array('B')
if self._buf == {}:
return bin
start, end = self._get_start_end(start, end)
for i in xrange(start, end+1):
bin.append(self._buf.get(i, pad))
return bin
def tobinstr(self, start=None, end=None, pad=0xFF):
''' Convert to binary form.
@param start start address of output bytes.
@param end end address of output bytes.
@param pad fill empty spaces with this value
(if None used self.padding).
@return string of binary data.
'''
return self.tobinarray(start, end, pad).tostring()
def tobinfile(self, fobj, start=None, end=None, pad=0xFF):
'''Convert to binary and write to file.
@param fobj file name or file object for writing output bytes.
@param start start address of output bytes.
@param end end address of output bytes.
@param pad fill empty spaces with this value
(if None used self.padding).
'''
if not hasattr(fobj, "write"):
fobj = file(fobj, "wb")
fclose = fobj.close
else:
fclose = None
fobj.write(self.tobinstr(start, end, pad))
if fclose:
fclose()
def minaddr(self):
''' Get minimal address of HEX content. '''
aa = self._buf.keys()
if aa == []:
return 0
else:
return min(aa)
def maxaddr(self):
''' Get maximal address of HEX content. '''
aa = self._buf.keys()
if aa == []:
return 0
else:
return max(aa)
def __getitem__(self, addr):
''' Get byte from address.
@param addr address of byte.
@return byte if address exists in HEX file, or self.padding
if no data found.
'''
return self._buf.get(addr, self.padding)
def __setitem__(self, addr, byte):
self._buf[addr] = byte
def writefile(self, f, write_start_addr=True):
"""Write data to file f in HEX format.
@param f filename or file-like object for writing
@param write_start_addr enable or disable writing start address
record to file (enabled by default).
If there is no start address nothing
will be written.
@return True if successful.
"""
fwrite = getattr(f, "write", None)
if fwrite:
fobj = f
fclose = None
else:
fobj = file(f, 'w')
fwrite = fobj.write
fclose = fobj.close
# start address record if any
if self.start_addr and write_start_addr:
keys = self.start_addr.keys()
keys.sort()
bin = array('B', '\0'*9)
if keys == ['CS','IP']:
# Start Segment Address Record
bin[0] = 4 # reclen
bin[1] = 0 # offset msb
bin[2] = 0 # offset lsb
bin[3] = 3 # rectyp
cs = self.start_addr['CS']
bin[4] = (cs >> 8) & 0x0FF
bin[5] = cs & 0x0FF
ip = self.start_addr['IP']
bin[6] = (ip >> 8) & 0x0FF
bin[7] = ip & 0x0FF
bin[8] = (-sum(bin)) & 0x0FF # chksum
fwrite(':')
fwrite(hexlify(bin.tostring()).upper())
fwrite('\n')
elif keys == ['EIP']:
# Start Linear Address Record
bin[0] = 4 # reclen
bin[1] = 0 # offset msb
bin[2] = 0 # offset lsb
bin[3] = 5 # rectyp
eip = self.start_addr['EIP']
bin[4] = (eip >> 24) & 0x0FF
bin[5] = (eip >> 16) & 0x0FF
bin[6] = (eip >> 8) & 0x0FF
bin[7] = eip & 0x0FF
bin[8] = (-sum(bin)) & 0x0FF # chksum
fwrite(':')
fwrite(hexlify(bin.tostring()).upper())
fwrite('\n')
else:
self.Error = ('Invalid start address value: %r'
% self.start_addr)
return False
# data
minaddr = IntelHex.minaddr(self)
maxaddr = IntelHex.maxaddr(self)
if maxaddr > 65535:
offset = (minaddr/65536)*65536
else:
offset = None
while True:
if offset != None:
# emit 32-bit offset record
high_ofs = offset / 65536
offset_record = ":02000004%04X" % high_ofs
bytes = divmod(high_ofs, 256)
csum = 2 + 4 + bytes[0] + bytes[1]
csum = (-csum) & 0x0FF
offset_record += "%02X\n" % csum
ofs = offset
if (ofs + 65536) > maxaddr:
rng = xrange(maxaddr - ofs + 1)
else:
rng = xrange(65536)
else:
ofs = 0
offset_record = ''
rng = xrange(maxaddr + 1)
csum = 0
k = 0
record = ""
for addr in rng:
byte = self._buf.get(ofs+addr, None)
if byte != None:
if k == 0:
# optionally offset record
fobj.write(offset_record)
offset_record = ''
# start data record
record += "%04X00" % addr
bytes = divmod(addr, 256)
csum = bytes[0] + bytes[1]
k += 1
# continue data in record
record += "%02X" % byte
csum += byte
# check for length of record
if k < 16:
continue
if k != 0:
# close record
csum += k
csum = (-csum) & 0x0FF
record += "%02X" % csum
fobj.write(":%02X%s\n" % (k, record))
# cleanup
csum = 0
k = 0
record = ""
else:
if k != 0:
# close record
csum += k
csum = (-csum) & 0x0FF
record += "%02X" % csum
fobj.write(":%02X%s\n" % (k, record))
# advance offset
if offset is None:
break
offset += 65536
if offset > maxaddr:
break
# end-of-file record
fobj.write(":00000001FF\n")
if fclose:
fclose()
return True
#/IntelHex
class IntelHex16bit(IntelHex):
"""Access to data as 16-bit words."""
def __init__(self, source):
"""Construct class from HEX file
or from instance of ordinary IntelHex class.
@param source file name of HEX file or file object
or instance of ordinary IntelHex class
"""
if isinstance(source, IntelHex):
# from ihex8
self.Error = source.Error
self.AddrOverlap = source.AddrOverlap
self.padding = source.padding
# private members
self._fname = source._fname
self._buf = source._buf
self._readed = source._readed
self._eof = source._eof
self._offset = source._offset
else:
IntelHex.__init__(self, source)
if self.padding == 0x0FF:
self.padding = 0x0FFFF
def __getitem__(self, addr16):
"""Get 16-bit word from address.
Raise error if found only one byte from pair.
@param addr16 address of word (addr8 = 2 * addr16).
@return word if bytes exists in HEX file, or self.padding
if no data found.
"""
addr1 = addr16 * 2
addr2 = addr1 + 1
byte1 = self._buf.get(addr1, None)
byte2 = self._buf.get(addr2, None)
if byte1 != None and byte2 != None:
return byte1 | (byte2 << 8) # low endian
if byte1 == None and byte2 == None:
return self.padding
raise Exception, 'Bad access in 16-bit mode (not enough data)'
def __setitem__(self, addr16, word):
addr_byte = addr16 * 2
bytes = divmod(word, 256)
self._buf[addr_byte] = bytes[1]
self._buf[addr_byte+1] = bytes[0]
def minaddr(self):
'''Get minimal address of HEX content in 16-bit mode.'''
aa = self._buf.keys()
if aa == []:
return 0
else:
return min(aa)/2
def maxaddr(self):
'''Get maximal address of HEX content in 16-bit mode.'''
aa = self._buf.keys()
if aa == []:
return 0
else:
return max(aa)/2
#/class IntelHex16bit
def hex2bin(fin, fout, start=None, end=None, size=None, pad=0xFF):
"""Hex-to-Bin convertor engine.
@return 0 if all OK
@param fin input hex file (filename or file-like object)
@param fout output bin file (filename or file-like object)
@param start start of address range (optional)
@param end end of address range (optional)
@param size size of resulting file (in bytes) (optional)
@param pad padding byte (optional)
"""
h = IntelHex(fin)
if not h.readfile():
print "Bad HEX file"
return 1
# start, end, size
if size != None and size != 0:
if end == None:
if start == None:
start = h.minaddr()
end = start + size - 1
else:
if (end+1) >= size:
start = end + 1 - size
else:
start = 0
try:
h.tobinfile(fout, start, end, pad)
except IOError:
print "Could not write to file: %s" % fout
return 1
return 0
#/def hex2bin
if __name__ == '__main__':
import getopt
import os
import sys
usage = '''Hex2Bin python converting utility.
Usage:
python intelhex.py [options] file.hex [out.bin]
Arguments:
file.hex name of hex file to processing.
out.bin name of output file.
If omitted then output write to file.bin.
Options:
-h, --help this help message.
-p, --pad=FF pad byte for empty spaces (ascii hex value).
-r, --range=START:END specify address range for writing output
(ascii hex value).
Range can be in form 'START:' or ':END'.
-l, --length=NNNN,
-s, --size=NNNN size of output (decimal value).
'''
pad = 0xFF
start = None
end = None
size = None
try:
opts, args = getopt.getopt(sys.argv[1:], "hp:r:l:s:",
["help", "pad=", "range=",
"length=", "size="])
for o, a in opts:
if o in ("-h", "--help"):
print usage
sys.exit(0)
elif o in ("-p", "--pad"):
try:
pad = int(a, 16) & 0x0FF
except:
raise getopt.GetoptError, 'Bad pad value'
elif o in ("-r", "--range"):
try:
l = a.split(":")
if l[0] != '':
start = int(l[0], 16)
if l[1] != '':
end = int(l[1], 16)
except:
raise getopt.GetoptError, 'Bad range value(s)'
elif o in ("-l", "--lenght", "-s", "--size"):
try:
size = int(a, 10)
except:
raise getopt.GetoptError, 'Bad size value'
if start != None and end != None and size != None:
raise getopt.GetoptError, 'Cannot specify START:END and SIZE simultaneously'
if not args:
raise getopt.GetoptError, 'Hex file is not specified'
if len(args) > 2:
raise getopt.GetoptError, 'Too many arguments'
except getopt.GetoptError, msg:
print msg
print usage
sys.exit(2)
fin = args[0]
if len(args) == 1:
import os.path
name, ext = os.path.splitext(fin)
fout = name + ".bin"
else:
fout = args[1]
if not os.path.isfile(fin):
print "File not found"
sys.exit(1)
sys.exit(hex2bin(fin, fout, start, end, size, pad))