summaryrefslogtreecommitdiff
path: root/software/pic.bootloader/ihexpic.py
diff options
context:
space:
mode:
Diffstat (limited to 'software/pic.bootloader/ihexpic.py')
-rw-r--r--software/pic.bootloader/ihexpic.py405
1 files changed, 405 insertions, 0 deletions
diff --git a/software/pic.bootloader/ihexpic.py b/software/pic.bootloader/ihexpic.py
new file mode 100644
index 0000000..e30204c
--- /dev/null
+++ b/software/pic.bootloader/ihexpic.py
@@ -0,0 +1,405 @@
+# spreadspace pic utils
+#
+#
+# Copyright (C) 2011 Christian Pointner <equinox@spreadspace.org>
+#
+# This file is part of spreadspace pic utils.
+#
+# spreadspace pic utils 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
+# any later version.
+#
+# spreadspace pic utils 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 spreadspace pic utils. If not, see <http://www.gnu.org/licenses/>.
+#
+#*********************************************************************
+# This is based on IntelHex 1.4
+# which can be found at: http://www.bialix.com/intelhex/
+#
+#
+# Copyright (c) 2005-2012, 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 author 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 writer for PIC Microcontroller.'''
+
+
+from array import array
+from binascii import hexlify, unhexlify
+from bisect import bisect_right
+import sys
+
+
+class IHexPic(object):
+ ''' Intel HEX file reader for PIC Microcontroller. '''
+
+ def __init__(self):
+ # private members
+ self._buf = {}
+ self._offset = 0
+
+ def _decode_record(self, s, line=0):
+ '''Decode one record of HEX file.
+
+ @param s line with HEX record.
+ @param line line number (for error messages).
+
+ @raise EndOfFile if EOF record encountered.
+ '''
+ s = s.rstrip('\r\n')
+ if not s:
+ return # empty line
+
+ if s[0] == ':':
+ try:
+ bin = array('B', unhexlify(str(s[1:])))
+ except (TypeError, ValueError):
+ # this might be raised by unhexlify when odd hexascii digits
+ raise HexRecordError(line=line)
+ length = len(bin)
+ if length < 5:
+ raise HexRecordError(line=line)
+ else:
+ raise HexRecordError(line=line)
+
+ record_length = bin[0]
+ if length != (5 + record_length):
+ raise RecordLengthError(line=line)
+ if record_length%2 != 0:
+ raise RecordLengthError(line=line)
+
+ addr = bin[1]*256 + bin[2]
+ if addr%2 != 0:
+ raise RecordAlignmentError(line=line)
+
+ record_type = bin[3]
+ if not (0 <= record_type <= 5):
+ raise RecordTypeError(line=line)
+
+ crc = sum(bin)
+ crc &= 0x0FF
+ if crc != 0:
+ raise RecordChecksumError(line=line)
+
+ if record_type == 0:
+ # data record
+ addr += self._offset
+ for i in xrange(4, 4+record_length, 2):
+ addr16 = addr/2
+ if not self._buf.get(addr16, None) is None:
+ raise AddressOverlapError(address=addr16, line=line)
+ self._buf[addr16] = bin[i] + (bin[i+1]<<8)
+ addr += 2 # FIXME: addr should be wrapped
+ # BUT after 02 record (at 64K boundary)
+ # and after 04 record (at 4G boundary)
+
+ elif record_type == 1:
+ # end of file record
+ if record_length != 0:
+ raise EOFRecordError(line=line)
+ raise _EndOfFile
+
+ elif record_type == 2:
+ # Extended 8086 Segment Record
+ if record_length != 2 or addr != 0:
+ raise ExtendedSegmentAddressRecordError(line=line)
+ self._offset = (bin[4]*256 + bin[5]) * 16
+
+ elif record_type == 4:
+ # Extended Linear Address Record
+ if record_length != 2 or addr != 0:
+ raise ExtendedLinearAddressRecordError(line=line)
+ self._offset = (bin[4]*256 + bin[5]) * 65536
+
+ else:
+ raise UnsupportedRecordTypeError(type=record_type, line=line)
+
+
+ def load_from_file(self, fobj):
+ """Load hex file into internal buffer.
+
+ @param fobj file name or file-like object
+ """
+ if getattr(fobj, "read", None) is None:
+ fobj = open(fobj, "r")
+ fclose = fobj.close
+ else:
+ fclose = None
+
+ line = 0
+
+ try:
+ decode = self._decode_record
+ try:
+ for s in fobj:
+ line += 1
+ decode(s, line)
+ except _EndOfFile:
+ pass
+ finally:
+ if fclose:
+ fclose()
+
+ def load_from_dict(self, dikt):
+ """Load data from dictionary. Dictionary should contain int keys
+ representing addresses. Values should be the data to be stored in
+ those addresses in unsigned char form (i.e. not strings).
+
+ The contents of the dict will be merged with this object and will
+ overwrite any conflicts.
+ """
+ s = dikt.copy()
+ for k in s.keys():
+ if type(k) not in (int, long) or k < 0:
+ raise ValueError('Source dictionary should have only int keys')
+ self._buf.update(s)
+
+ def write_to_file(self, f):
+ """Write data to file f in HEX format.
+
+ @param f filename or file-like object for writing
+ """
+ fwrite = getattr(f, "write", None)
+ if fwrite:
+ fobj = f
+ fclose = None
+ else:
+ fobj = file(f, 'w')
+ fwrite = fobj.write
+ fclose = fobj.close
+
+ # Translation table for uppercasing hex ascii string.
+ # timeit shows that using hexstr.translate(table)
+ # is faster than hexstr.upper():
+ # 0.452ms vs. 0.652ms (translate vs. upper)
+ if sys.version_info[0] >= 3:
+ table = bytes(range(256)).upper()
+ else:
+ table = ''.join(chr(i).upper() for i in range(256))
+
+
+ # data
+ addresses = self._buf.keys()
+ addresses.sort()
+ addr_len = len(addresses)
+ if addr_len:
+ minaddr = addresses[0]
+ maxaddr = addresses[-1]
+
+ if maxaddr > 65535/2:
+ need_offset_record = True
+ else:
+ need_offset_record = False
+ high_ofs = 0
+
+ cur_addr = minaddr
+ cur_ix = 0
+
+ while cur_addr <= maxaddr:
+ if need_offset_record:
+ bin = array('B', str('\0'*7))
+ bin[0] = 2 # reclen
+ bin[1] = 0 # offset msb
+ bin[2] = 0 # offset lsb
+ bin[3] = 4 # rectype
+ high_ofs = int(cur_addr>>16)
+ b = divmod(high_ofs*2, 256)
+ bin[4] = b[0] # msb of high_ofs
+ bin[5] = b[1] # lsb of high_ofs
+ bin[6] = (-sum(bin)) & 0x0FF # chksum
+ fwrite(':' +
+ str(hexlify(bin.tostring()).translate(table)) +
+ '\n')
+
+ while True:
+ # produce one record
+ low_addr = cur_addr & 0x0FFFF
+ # chain_len off by 1
+ chain_len = min(7, 65535-low_addr, maxaddr-cur_addr)
+
+ # search continuous chain
+ stop_addr = cur_addr + chain_len
+ if chain_len:
+ ix = bisect_right(addresses, stop_addr,
+ cur_ix,
+ min(cur_ix+chain_len+1, addr_len))
+ chain_len = ix - cur_ix # real chain_len
+ # there could be small holes in the chain
+ # but we will catch them by try-except later
+ # so for big continuous files we will work
+ # at maximum possible speed
+ else:
+ chain_len = 1 # real chain_len
+
+ bin = array('B', str('\0'*(5+(chain_len*2))))
+ b = divmod(low_addr*2, 256)
+ bin[1] = b[0] # msb of low_addr
+ bin[2] = b[1] # lsb of low_addr
+ bin[3] = 0 # rectype
+ try: # if there is small holes we'll catch them
+ for i in range(chain_len):
+ bin[4+(i*2)] = self._buf[cur_addr+i] & 0x00FF
+ bin[4+(i*2)+1] = self._buf[cur_addr+i] >> 8
+ except KeyError:
+ # we catch a hole so we should shrink the chain
+ chain_len = i
+ bin = bin[:5+(i*2)]
+ bin[0] = chain_len*2
+ bin[4+(chain_len*2)] = (-sum(bin)) & 0x0FF # chksum
+ fwrite(':' +
+ str(hexlify(bin.tostring()).translate(table)) +
+ '\n')
+
+ # adjust cur_addr/cur_ix
+ cur_ix += chain_len
+ if cur_ix < addr_len:
+ cur_addr = addresses[cur_ix]
+ else:
+ cur_addr = maxaddr + 1
+ break
+ high_addr = int(cur_addr>>16)
+ if high_addr > high_ofs:
+ break
+
+ # end-of-file record
+ fwrite(":00000001FF\n")
+ if fclose:
+ fclose()
+
+ def get_lowest_addr(self):
+ return sorted(self._buf.keys())[0]
+
+ def get_highest_addr(self):
+ return sorted(self._buf.keys())[-1]
+
+ def items(self):
+ return self._buf.items()
+
+ def __getitem__(self, addr):
+ t = type(addr)
+ if t in (int, long):
+ if addr < 0:
+ raise TypeError('Address should be >= 0.')
+ return self._buf(addr, 0xFFFF)
+ else:
+ raise TypeError('Address has unsupported type: %s' % t)
+
+
+##
+# IHexPic Errors Hierarchy:
+#
+# IHexPicError - basic error
+# HexReaderError - general hex reader error
+# AddressOverlapError - data for the same address overlap
+# HexRecordError - hex record decoder base error
+# RecordLengthError - record has invalid length
+# RecordTypeError - record has invalid type (RECTYP)
+# RecordChecksumError - record checksum mismatch
+# EOFRecordError - invalid EOF record (type 01)
+# ExtendedAddressRecordError - extended address record base error
+# ExtendedSegmentAddressRecordError - invalid extended segment address record (type 02)
+# ExtendedLinearAddressRecordError - invalid extended linear address record (type 04)
+# UnsupportedRecordError - invalid/unsupported record type
+# _EndOfFile - it's not real error, used internally by hex reader as signal that EOF record found
+
+class IHexPicError(Exception):
+ '''Base Exception class for IHexPic module'''
+
+ _fmt = 'IHexPic base error' #: format string
+
+ def __init__(self, msg=None, **kw):
+ """Initialize the Exception with the given message.
+ """
+ self.msg = msg
+ for key, value in kw.items():
+ setattr(self, key, value)
+
+ def __str__(self):
+ """Return the message in this Exception."""
+ if self.msg:
+ return self.msg
+ try:
+ return self._fmt % self.__dict__
+ except (NameError, ValueError, KeyError), e:
+ return 'Unprintable exception %s: %s' \
+ % (repr(e), str(e))
+
+class _EndOfFile(IHexPicError):
+ """Used for internal needs only."""
+ _fmt = 'EOF record reached -- signal to stop read file'
+
+
+class HexReaderError(IHexPicError):
+ _fmt = 'Hex reader base error'
+
+class AddressOverlapError(HexReaderError):
+ _fmt = 'Hex file has data overlap at address 0x%(address)X on line %(line)d'
+
+
+class HexRecordError(HexReaderError):
+ _fmt = 'Hex file contains invalid record at line %(line)d'
+
+
+class RecordLengthError(HexRecordError):
+ _fmt = 'Record at line %(line)d has invalid length'
+
+class RecordAlignmentError(HexRecordError):
+ _fmt = 'Record at line %(line)d has invalid adress (not 16bit aligned)'
+
+class RecordTypeError(HexRecordError):
+ _fmt = 'Record at line %(line)d has invalid record type'
+
+class RecordChecksumError(HexRecordError):
+ _fmt = 'Record at line %(line)d has invalid checksum'
+
+class EOFRecordError(HexRecordError):
+ _fmt = 'File has invalid End-of-File record'
+
+class UnsupportedRecordError(HexRecordError):
+ _fmt = 'File has unsupported record type %(type)d at line %(line)d'
+
+
+class ExtendedAddressRecordError(HexRecordError):
+ _fmt = 'Base class for extended address exceptions'
+
+class ExtendedSegmentAddressRecordError(ExtendedAddressRecordError):
+ _fmt = 'Invalid Extended Segment Address Record at line %(line)d'
+
+class ExtendedLinearAddressRecordError(ExtendedAddressRecordError):
+ _fmt = 'Invalid Extended Linear Address Record at line %(line)d'