diff options
Diffstat (limited to 'software/pic.bootloader/ihexpic.py')
-rw-r--r-- | software/pic.bootloader/ihexpic.py | 405 |
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' |