changes to communication and sweep methods
[goodfet] / client / intelhex.py
1 # Copyright (c) 2005-2009, Alexander Belchenko
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms,
5 # with or without modification, are permitted provided
6 # that the following conditions are met:
7 #
8 # * Redistributions of source code must retain
9 #   the above copyright notice, this list of conditions
10 #   and the following disclaimer.
11 # * Redistributions in binary form must reproduce
12 #   the above copyright notice, this list of conditions
13 #   and the following disclaimer in the documentation
14 #   and/or other materials provided with the distribution.
15 # * Neither the name of the author nor the names
16 #   of its contributors may be used to endorse
17 #   or promote products derived from this software
18 #   without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
22 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
23 # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 # IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
25 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
26 # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
28 # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32 # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34 '''Intel HEX file format reader and converter.
35
36 @author     Alexander Belchenko (bialix AT ukr net)
37 @version    1.1
38 '''
39
40
41 __docformat__ = "javadoc"
42
43
44 from array import array
45 from binascii import hexlify, unhexlify
46 from bisect import bisect_right
47 import os
48
49
50 class IntelHex(object):
51     ''' Intel HEX file reader. '''
52
53     def __init__(self, source=None):
54         ''' Constructor. If source specified, object will be initialized
55         with the contents of source. Otherwise the object will be empty.
56
57         @param  source      source for initialization
58                             (file name of HEX file, file object, addr dict or
59                              other IntelHex object)
60         '''
61         #public members
62         self.padding = 0x0FF
63         # Start Address
64         self.start_addr = None
65
66         # private members
67         self._buf = {}
68         self._offset = 0
69
70         if source is not None:
71             if isinstance(source, basestring) or getattr(source, "read", None):
72                 # load hex file
73                 self.loadhex(source)
74             elif isinstance(source, dict):
75                 self.fromdict(source)
76             elif isinstance(source, IntelHex):
77                 self.padding = source.padding
78                 if source.start_addr:
79                     self.start_addr = source.start_addr.copy()
80                 self._buf = source._buf.copy()
81             else:
82                 raise ValueError("source: bad initializer type")
83
84     def _decode_record(self, s, line=0):
85         '''Decode one record of HEX file.
86
87         @param  s       line with HEX record.
88         @param  line    line number (for error messages).
89
90         @raise  EndOfFile   if EOF record encountered.
91         '''
92         s = s.rstrip('\r\n')
93         if not s:
94             return          # empty line
95
96         if s[0] == ':':
97             try:
98                 bin = array('B', unhexlify(s[1:]))
99             except TypeError:
100                 # this might be raised by unhexlify when odd hexascii digits
101                 raise HexRecordError(line=line)
102             length = len(bin)
103             if length < 5:
104                 raise HexRecordError(line=line)
105         else:
106             raise HexRecordError(line=line)
107
108         record_length = bin[0]
109         if length != (5 + record_length):
110             raise RecordLengthError(line=line)
111
112         addr = bin[1]*256 + bin[2]
113
114         record_type = bin[3]
115         if not (0 <= record_type <= 5):
116             raise RecordTypeError(line=line)
117
118         crc = sum(bin)
119         crc &= 0x0FF
120         if crc != 0:
121             raise RecordChecksumError(line=line)
122
123         if record_type == 0:
124             # data record
125             addr += self._offset
126             for i in xrange(4, 4+record_length):
127                 if not self._buf.get(addr, None) is None:
128                     raise AddressOverlapError(address=addr, line=line)
129                 self._buf[addr] = bin[i]
130                 addr += 1   # FIXME: addr should be wrapped 
131                             # BUT after 02 record (at 64K boundary)
132                             # and after 04 record (at 4G boundary)
133
134         elif record_type == 1:
135             # end of file record
136             if record_length != 0:
137                 raise EOFRecordError(line=line)
138             raise _EndOfFile
139
140         elif record_type == 2:
141             # Extended 8086 Segment Record
142             if record_length != 2 or addr != 0:
143                 raise ExtendedSegmentAddressRecordError(line=line)
144             self._offset = (bin[4]*256 + bin[5]) * 16
145
146         elif record_type == 4:
147             # Extended Linear Address Record
148             if record_length != 2 or addr != 0:
149                 raise ExtendedLinearAddressRecordError(line=line)
150             self._offset = (bin[4]*256 + bin[5]) * 65536
151
152         elif record_type == 3:
153             # Start Segment Address Record
154             if record_length != 4 or addr != 0:
155                 raise StartSegmentAddressRecordError(line=line)
156             if self.start_addr:
157                 raise DuplicateStartAddressRecordError(line=line)
158             self.start_addr = {'CS': bin[4]*256 + bin[5],
159                                'IP': bin[6]*256 + bin[7],
160                               }
161
162         elif record_type == 5:
163             # Start Linear Address Record
164             if record_length != 4 or addr != 0:
165                 raise StartLinearAddressRecordError(line=line)
166             if self.start_addr:
167                 raise DuplicateStartAddressRecordError(line=line)
168             self.start_addr = {'EIP': (bin[4]*16777216 +
169                                        bin[5]*65536 +
170                                        bin[6]*256 +
171                                        bin[7]),
172                               }
173
174     def loadhex(self, fobj):
175         """Load hex file into internal buffer. This is not necessary
176         if object was initialized with source set. This will overwrite
177         addresses if object was already initialized.
178
179         @param  fobj        file name or file-like object
180         """
181         if getattr(fobj, "read", None) is None:
182             fobj = file(fobj, "r")
183             fclose = fobj.close
184         else:
185             fclose = None
186
187         self._offset = 0
188         line = 0
189
190         try:
191             decode = self._decode_record
192             try:
193                 for s in fobj:
194                     line += 1
195                     decode(s, line)
196             except _EndOfFile:
197                 pass
198         finally:
199             if fclose:
200                 fclose()
201
202     def loadbin(self, fobj, offset=0):
203         """Load bin file into internal buffer. Not needed if source set in
204         constructor. This will overwrite addresses without warning
205         if object was already initialized.
206
207         @param  fobj        file name or file-like object
208         @param  offset      starting address offset
209         """
210         fread = getattr(fobj, "read", None)
211         if fread is None:
212             f = file(fobj, "rb")
213             fread = f.read
214             fclose = f.close
215         else:
216             fclose = None
217
218         try:
219             for b in array('B', fread()):
220                 self._buf[offset] = b
221                 offset += 1
222         finally:
223             if fclose:
224                 fclose()
225
226     def loadfile(self, fobj, format):
227         """Load data file into internal buffer. Preferred wrapper over
228         loadbin or loadhex.
229
230         @param  fobj        file name or file-like object
231         @param  format      file format ("hex" or "bin")
232         """
233         if format == "hex":
234             self.loadhex(fobj)
235         elif format == "bin":
236             self.loadbin(fobj)
237         else:
238             raise ValueError('format should be either "hex" or "bin";'
239                 ' got %r instead' % format)
240
241     # alias (to be consistent with method tofile)
242     fromfile = loadfile
243
244     def fromdict(self, dikt):
245         """Load data from dictionary. Dictionary should contain int keys
246         representing addresses. Values should be the data to be stored in
247         those addresses in unsigned char form (i.e. not strings).
248         The dictionary may contain the key, ``start_addr``
249         to indicate the starting address of the data as described in README.
250
251         The contents of the dict will be merged with this object and will
252         overwrite any conflicts. This function is not necessary if the
253         object was initialized with source specified.
254         """
255         s = dikt.copy()
256         start_addr = s.get('start_addr')
257         if s.has_key('start_addr'):
258             del s['start_addr']
259         for k in s.keys():
260             if type(k) not in (int, long) or k < 0:
261                 raise ValueError('Source dictionary should have only int keys')
262         self._buf.update(s)
263         if start_addr is not None:
264             self.start_addr = start_addr
265
266     def _get_start_end(self, start=None, end=None):
267         """Return default values for start and end if they are None
268         """
269         if start is None:
270             start = min(self._buf.keys())
271         if end is None:
272             end = max(self._buf.keys())
273         if start > end:
274             start, end = end, start
275         return start, end
276
277     def tobinarray(self, start=None, end=None, pad=None):
278         ''' Convert this object to binary form as array. If start and end 
279         unspecified, they will be inferred from the data.
280         @param  start   start address of output bytes.
281         @param  end     end address of output bytes.
282         @param  pad     fill empty spaces with this value
283                         (if None used self.padding).
284         @return         array of unsigned char data.
285         '''
286         if pad is None:
287             pad = self.padding
288
289         bin = array('B')
290
291         if self._buf == {}:
292             return bin
293
294         start, end = self._get_start_end(start, end)
295
296         for i in xrange(start, end+1):
297             bin.append(self._buf.get(i, pad))
298
299         return bin
300
301     def tobinstr(self, start=None, end=None, pad=0xFF):
302         ''' Convert to binary form and return as a string.
303         @param  start   start address of output bytes.
304         @param  end     end address of output bytes.
305         @param  pad     fill empty spaces with this value
306                         (if None used self.padding).
307         @return         string of binary data.
308         '''
309         return self.tobinarray(start, end, pad).tostring()
310
311     def tobinfile(self, fobj, start=None, end=None, pad=0xFF):
312         '''Convert to binary and write to file.
313
314         @param  fobj    file name or file object for writing output bytes.
315         @param  start   start address of output bytes.
316         @param  end     end address of output bytes.
317         @param  pad     fill empty spaces with this value
318                         (if None used self.padding).
319         '''
320         if getattr(fobj, "write", None) is None:
321             fobj = file(fobj, "wb")
322             close_fd = True
323         else:
324             close_fd = False
325
326         fobj.write(self.tobinstr(start, end, pad))
327
328         if close_fd:
329             fobj.close()
330
331     def todict(self):
332         '''Convert to python dictionary.
333
334         @return         dict suitable for initializing another IntelHex object.
335         '''
336         r = {}
337         r.update(self._buf)
338         if self.start_addr:
339             r['start_addr'] = self.start_addr
340         return r
341
342     def addresses(self):
343         '''Returns all used addresses in sorted order.
344         @return         list of occupied data addresses in sorted order. 
345         '''
346         aa = self._buf.keys()
347         aa.sort()
348         return aa
349
350     def minaddr(self):
351         '''Get minimal address of HEX content.
352         @return         minimal address or None if no data
353         '''
354         aa = self._buf.keys()
355         if aa == []:
356             return None
357         else:
358             return min(aa)
359
360     def maxaddr(self):
361         '''Get maximal address of HEX content.
362         @return         maximal address or None if no data
363         '''
364         aa = self._buf.keys()
365         if aa == []:
366             return None
367         else:
368             return max(aa)
369
370     def __getitem__(self, addr):
371         ''' Get requested byte from address.
372         @param  addr    address of byte.
373         @return         byte if address exists in HEX file, or self.padding
374                         if no data found.
375         '''
376         t = type(addr)
377         if t in (int, long):
378             if addr < 0:
379                 raise TypeError('Address should be >= 0.')
380             return self._buf.get(addr, self.padding)
381         elif t == slice:
382             addresses = self._buf.keys()
383             ih = IntelHex()
384             if addresses:
385                 addresses.sort()
386                 start = addr.start or addresses[0]
387                 stop = addr.stop or (addresses[-1]+1)
388                 step = addr.step or 1
389                 for i in xrange(start, stop, step):
390                     x = self._buf.get(i)
391                     if x is not None:
392                         ih[i] = x
393             return ih
394         else:
395             raise TypeError('Address has unsupported type: %s' % t)
396
397     def __setitem__(self, addr, byte):
398         """Set byte at address."""
399         t = type(addr)
400         if t in (int, long):
401             if addr < 0:
402                 raise TypeError('Address should be >= 0.')
403             self._buf[addr] = byte
404         elif t == slice:
405             addresses = self._buf.keys()
406             if not isinstance(byte, (list, tuple)):
407                 raise ValueError('Slice operation expects sequence of bytes')
408             start = addr.start
409             stop = addr.stop
410             step = addr.step or 1
411             if None not in (start, stop):
412                 ra = range(start, stop, step)
413                 if len(ra) != len(byte):
414                     raise ValueError('Length of bytes sequence does not match '
415                         'address range')
416             elif (start, stop) == (None, None):
417                 raise TypeError('Unsupported address range')
418             elif start is None:
419                 start = stop - len(byte)
420             elif stop is None:
421                 stop = start + len(byte)
422             if start < 0:
423                 raise TypeError('start address cannot be negative')
424             if stop < 0:
425                 raise TypeError('stop address cannot be negative')
426             j = 0
427             for i in xrange(start, stop, step):
428                 self._buf[i] = byte[j]
429                 j += 1
430         else:
431             raise TypeError('Address has unsupported type: %s' % t)
432
433     def __delitem__(self, addr):
434         """Delete byte at address."""
435         t = type(addr)
436         if t in (int, long):
437             if addr < 0:
438                 raise TypeError('Address should be >= 0.')
439             del self._buf[addr]
440         elif t == slice:
441             addresses = self._buf.keys()
442             if addresses:
443                 addresses.sort()
444                 start = addr.start or addresses[0]
445                 stop = addr.stop or (addresses[-1]+1)
446                 step = addr.step or 1
447                 for i in xrange(start, stop, step):
448                     x = self._buf.get(i)
449                     if x is not None:
450                         del self._buf[i]
451         else:
452             raise TypeError('Address has unsupported type: %s' % t)
453
454     def __len__(self):
455         """Return count of bytes with real values."""
456         return len(self._buf.keys())
457
458     def write_hex_file(self, f, write_start_addr=True):
459         """Write data to file f in HEX format.
460
461         @param  f                   filename or file-like object for writing
462         @param  write_start_addr    enable or disable writing start address
463                                     record to file (enabled by default).
464                                     If there is no start address in obj, nothing
465                                     will be written regardless of this setting.
466         """
467         fwrite = getattr(f, "write", None)
468         if fwrite:
469             fobj = f
470             fclose = None
471         else:
472             fobj = file(f, 'w')
473             fwrite = fobj.write
474             fclose = fobj.close
475
476         # Translation table for uppercasing hex ascii string.
477         # timeit shows that using hexstr.translate(table)
478         # is faster than hexstr.upper():
479         # 0.452ms vs. 0.652ms (translate vs. upper)
480         table = ''.join(chr(i).upper() for  i in range(256))
481
482         # start address record if any
483         if self.start_addr and write_start_addr:
484             keys = self.start_addr.keys()
485             keys.sort()
486             bin = array('B', '\0'*9)
487             if keys == ['CS','IP']:
488                 # Start Segment Address Record
489                 bin[0] = 4      # reclen
490                 bin[1] = 0      # offset msb
491                 bin[2] = 0      # offset lsb
492                 bin[3] = 3      # rectyp
493                 cs = self.start_addr['CS']
494                 bin[4] = (cs >> 8) & 0x0FF
495                 bin[5] = cs & 0x0FF
496                 ip = self.start_addr['IP']
497                 bin[6] = (ip >> 8) & 0x0FF
498                 bin[7] = ip & 0x0FF
499                 bin[8] = (-sum(bin)) & 0x0FF    # chksum
500                 fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n')
501             elif keys == ['EIP']:
502                 # Start Linear Address Record
503                 bin[0] = 4      # reclen
504                 bin[1] = 0      # offset msb
505                 bin[2] = 0      # offset lsb
506                 bin[3] = 5      # rectyp
507                 eip = self.start_addr['EIP']
508                 bin[4] = (eip >> 24) & 0x0FF
509                 bin[5] = (eip >> 16) & 0x0FF
510                 bin[6] = (eip >> 8) & 0x0FF
511                 bin[7] = eip & 0x0FF
512                 bin[8] = (-sum(bin)) & 0x0FF    # chksum
513                 fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n')
514             else:
515                 if fclose:
516                     fclose()
517                 raise InvalidStartAddressValueError(start_addr=self.start_addr)
518
519         # data
520         addresses = self._buf.keys()
521         addresses.sort()
522         addr_len = len(addresses)
523         if addr_len:
524             minaddr = addresses[0]
525             maxaddr = addresses[-1]
526     
527             if maxaddr > 65535:
528                 need_offset_record = True
529             else:
530                 need_offset_record = False
531             high_ofs = 0
532
533             cur_addr = minaddr
534             cur_ix = 0
535
536             while cur_addr <= maxaddr:
537                 if need_offset_record:
538                     bin = array('B', '\0'*7)
539                     bin[0] = 2      # reclen
540                     bin[1] = 0      # offset msb
541                     bin[2] = 0      # offset lsb
542                     bin[3] = 4      # rectyp
543                     high_ofs = int(cur_addr/65536)
544                     bytes = divmod(high_ofs, 256)
545                     bin[4] = bytes[0]   # msb of high_ofs
546                     bin[5] = bytes[1]   # lsb of high_ofs
547                     bin[6] = (-sum(bin)) & 0x0FF    # chksum
548                     fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n')
549
550                 while True:
551                     # produce one record
552                     low_addr = cur_addr & 0x0FFFF
553                     # chain_len off by 1
554                     chain_len = min(15, 65535-low_addr, maxaddr-cur_addr)
555
556                     # search continuous chain
557                     stop_addr = cur_addr + chain_len
558                     if chain_len:
559                         ix = bisect_right(addresses, stop_addr,
560                                           cur_ix,
561                                           min(cur_ix+chain_len+1, addr_len))
562                         chain_len = ix - cur_ix     # real chain_len
563                         # there could be small holes in the chain
564                         # but we will catch them by try-except later
565                         # so for big continuous files we will work
566                         # at maximum possible speed
567                     else:
568                         chain_len = 1               # real chain_len
569
570                     bin = array('B', '\0'*(5+chain_len))
571                     bytes = divmod(low_addr, 256)
572                     bin[1] = bytes[0]   # msb of low_addr
573                     bin[2] = bytes[1]   # lsb of low_addr
574                     bin[3] = 0          # rectype
575                     try:    # if there is small holes we'll catch them
576                         for i in range(chain_len):
577                             bin[4+i] = self._buf[cur_addr+i]
578                     except KeyError:
579                         # we catch a hole so we should shrink the chain
580                         chain_len = i
581                         bin = bin[:5+i]
582                     bin[0] = chain_len
583                     bin[4+chain_len] = (-sum(bin)) & 0x0FF    # chksum
584                     fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n')
585
586                     # adjust cur_addr/cur_ix
587                     cur_ix += chain_len
588                     if cur_ix < addr_len:
589                         cur_addr = addresses[cur_ix]
590                     else:
591                         cur_addr = maxaddr + 1
592                         break
593                     high_addr = int(cur_addr/65536)
594                     if high_addr > high_ofs:
595                         break
596
597         # end-of-file record
598         fwrite(":00000001FF\n")
599         if fclose:
600             fclose()
601
602     def tofile(self, fobj, format):
603         """Write data to hex or bin file. Preferred method over tobin or tohex.
604
605         @param  fobj        file name or file-like object
606         @param  format      file format ("hex" or "bin")
607         """
608         if format == 'hex':
609             self.write_hex_file(fobj)
610         elif format == 'bin':
611             self.tobinfile(fobj)
612         else:
613             raise ValueError('format should be either "hex" or "bin";'
614                 ' got %r instead' % format)
615
616     def gets(self, addr, length):
617         """Get string of bytes from given address. If any entries are blank
618         from addr through addr+length, a NotEnoughDataError exception will
619         be raised. Padding is not used."""
620         a = array('B', '\0'*length)
621         try:
622             for i in xrange(length):
623                 a[i] = self._buf[addr+i]
624         except KeyError:
625             raise NotEnoughDataError(address=addr, length=length)
626         return a.tostring()
627
628     def puts(self, addr, s):
629         """Put string of bytes at given address. Will overwrite any previous
630         entries.
631         """
632         a = array('B', s)
633         for i in xrange(len(s)):
634             self._buf[addr+i] = a[i]
635
636     def getsz(self, addr):
637         """Get zero-terminated string from given address. Will raise 
638         NotEnoughDataError exception if a hole is encountered before a 0.
639         """
640         i = 0
641         try:
642             while True:
643                 if self._buf[addr+i] == 0:
644                     break
645                 i += 1
646         except KeyError:
647             raise NotEnoughDataError(msg=('Bad access at 0x%X: '
648                 'not enough data to read zero-terminated string') % addr)
649         return self.gets(addr, i)
650
651     def putsz(self, addr, s):
652         """Put string in object at addr and append terminating zero at end."""
653         self.puts(addr, s)
654         self._buf[addr+len(s)] = 0
655
656     def dump(self, tofile=None):
657         """Dump object content to specified file or to stdout if None. Format
658         is a hexdump with some header information at the beginning, addresses
659         on the left, and data on right.
660
661         @param  tofile        file-like object to dump to
662         """
663
664         if tofile is None:
665             import sys
666             tofile = sys.stdout
667         # start addr possibly
668         if self.start_addr is not None:
669             cs = self.start_addr.get('CS')
670             ip = self.start_addr.get('IP')
671             eip = self.start_addr.get('EIP')
672             if eip is not None and cs is None and ip is None:
673                 tofile.write('EIP = 0x%08X\n' % eip)
674             elif eip is None and cs is not None and ip is not None:
675                 tofile.write('CS = 0x%04X, IP = 0x%04X\n' % (cs, ip))
676             else:
677                 tofile.write('start_addr = %r\n' % start_addr)
678         # actual data
679         addresses = self._buf.keys()
680         if addresses:
681             addresses.sort()
682             minaddr = addresses[0]
683             maxaddr = addresses[-1]
684             startaddr = int(minaddr/16)*16
685             endaddr = int(maxaddr/16+1)*16
686             maxdigits = max(len(str(endaddr)), 4)
687             templa = '%%0%dX' % maxdigits
688             range16 = range(16)
689             for i in xrange(startaddr, endaddr, 16):
690                 tofile.write(templa % i)
691                 tofile.write(' ')
692                 s = []
693                 for j in range16:
694                     x = self._buf.get(i+j)
695                     if x is not None:
696                         tofile.write(' %02X' % x)
697                         if 32 <= x < 128:
698                             s.append(chr(x))
699                         else:
700                             s.append('.')
701                     else:
702                         tofile.write(' --')
703                         s.append(' ')
704                 tofile.write('  |' + ''.join(s) + '|\n')
705
706     def merge(this, other, overlap='error'):
707         """Merge content of other IntelHex object to this object.
708         @param  other   other IntelHex object.
709         @param  overlap action on overlap of data or starting addr:
710                         - error: raising OverlapError;
711                         - ignore: ignore other data and keep this data
712                                   in overlapping region;
713                         - replace: replace this data with other data
714                                   in overlapping region.
715
716         @raise  TypeError       if other is not instance of IntelHex
717         @raise  ValueError      if other is the same object as this
718         @raise  ValueError      if overlap argument has incorrect value
719         @raise  AddressOverlapError    on overlapped data
720         """
721         # check args
722         if not isinstance(other, IntelHex):
723             raise TypeError('other should be IntelHex object')
724         if other is this:
725             raise ValueError("Can't merge itself")
726         if overlap not in ('error', 'ignore', 'replace'):
727             raise ValueError("overlap argument should be either "
728                 "'error', 'ignore' or 'replace'")
729         # merge data
730         this_buf = this._buf
731         other_buf = other._buf
732         for i in other_buf:
733             if i in this_buf:
734                 if overlap == 'error':
735                     raise AddressOverlapError(
736                         'Data overlapped at address 0x%X' % i)
737                 elif overlap == 'ignore':
738                     continue
739             this_buf[i] = other_buf[i]
740         # merge start_addr
741         if this.start_addr != other.start_addr:
742             if this.start_addr is None:     # set start addr from other
743                 this.start_addr = other.start_addr
744             elif other.start_addr is None:  # keep this start addr
745                 pass
746             else:                           # conflict
747                 if overlap == 'error':
748                     raise AddressOverlapError(
749                         'Starting addresses are different')
750                 elif overlap == 'replace':
751                     this.start_addr = other.start_addr
752 #/IntelHex
753
754
755 class IntelHex16bit(IntelHex):
756     """Access to data as 16-bit words."""
757
758     def __init__(self, source):
759         """Construct class from HEX file
760         or from instance of ordinary IntelHex class. If IntelHex object
761         is passed as source, the original IntelHex object should not be used
762         again because this class will alter it. This class leaves padding
763         alone unless it was precisely 0xFF. In that instance it is sign
764         extended to 0xFFFF.
765
766         @param  source  file name of HEX file or file object
767                         or instance of ordinary IntelHex class.
768                         Will also accept dictionary from todict method.
769         """
770         if isinstance(source, IntelHex):
771             # from ihex8
772             self.padding = source.padding
773             # private members
774             self._buf = source._buf
775             self._offset = source._offset
776         else:
777             IntelHex.__init__(self, source)
778
779         if self.padding == 0x0FF:
780             self.padding = 0x0FFFF
781
782     def __getitem__(self, addr16):
783         """Get 16-bit word from address.
784         Raise error if only one byte from the pair is set.
785         We assume a Little Endian interpretation of the hex file.
786
787         @param  addr16  address of word (addr8 = 2 * addr16).
788         @return         word if bytes exists in HEX file, or self.padding
789                         if no data found.
790         """
791         addr1 = addr16 * 2
792         addr2 = addr1 + 1
793         byte1 = self._buf.get(addr1, None)
794         byte2 = self._buf.get(addr2, None)
795
796         if byte1 != None and byte2 != None:
797             return byte1 | (byte2 << 8)     # low endian
798
799         if byte1 == None and byte2 == None:
800             return self.padding
801
802         raise BadAccess16bit(address=addr16)
803
804     def __setitem__(self, addr16, word):
805         """Sets the address at addr16 to word assuming Little Endian mode.
806         """
807         addr_byte = addr16 * 2
808         bytes = divmod(word, 256)
809         self._buf[addr_byte] = bytes[1]
810         self._buf[addr_byte+1] = bytes[0]
811
812     def minaddr(self):
813         '''Get minimal address of HEX content in 16-bit mode.
814
815         @return         minimal address used in this object 
816         '''
817         aa = self._buf.keys()
818         if aa == []:
819             return 0
820         else:
821             return min(aa)/2
822
823     def maxaddr(self):
824         '''Get maximal address of HEX content in 16-bit mode.
825
826         @return         maximal address used in this object 
827         '''
828         aa = self._buf.keys()
829         if aa == []:
830             return 0
831         else:
832             return max(aa)/2
833
834 #/class IntelHex16bit
835
836
837 def hex2bin(fin, fout, start=None, end=None, size=None, pad=0xFF):
838     """Hex-to-Bin convertor engine.
839     @return     0   if all OK
840
841     @param  fin     input hex file (filename or file-like object)
842     @param  fout    output bin file (filename or file-like object)
843     @param  start   start of address range (optional)
844     @param  end     end of address range (optional)
845     @param  size    size of resulting file (in bytes) (optional)
846     @param  pad     padding byte (optional)
847     """
848     try:
849         h = IntelHex(fin)
850     except HexReaderError, e:
851         print "ERROR: bad HEX file: %s" % str(e)
852         return 1
853
854     # start, end, size
855     if size != None and size != 0:
856         if end == None:
857             if start == None:
858                 start = h.minaddr()
859             end = start + size - 1
860         else:
861             if (end+1) >= size:
862                 start = end + 1 - size
863             else:
864                 start = 0
865
866     try:
867         h.tobinfile(fout, start, end, pad)
868     except IOError, e:
869         print "ERROR: Could not write to file: %s: %s" % (fout, str(e))
870         return 1
871
872     return 0
873 #/def hex2bin
874
875
876 def bin2hex(fin, fout, offset=0):
877     """Simple bin-to-hex convertor.
878     @return     0   if all OK
879
880     @param  fin     input bin file (filename or file-like object)
881     @param  fout    output hex file (filename or file-like object)
882     @param  offset  starting address offset for loading bin
883     """
884     h = IntelHex()
885     try:
886         h.loadbin(fin, offset)
887     except IOError, e:
888         print 'ERROR: unable to load bin file:', str(e)
889         return 1
890
891     try:
892         h.tofile(fout, format='hex')
893     except IOError, e:
894         print "ERROR: Could not write to file: %s: %s" % (fout, str(e))
895         return 1
896
897     return 0
898 #/def bin2hex
899
900
901 class Record(object):
902     """Helper methods to build valid ihex records."""
903
904     def _from_bytes(bytes):
905         """Takes a list of bytes, computes the checksum, and outputs the entire
906         record as a string. bytes should be the hex record without the colon
907         or final checksum.
908
909         @param  bytes   list of byte values so far to pack into record.
910         @return         String representation of one HEX record
911         """
912         assert len(bytes) >= 4
913         # calculate checksum
914         s = (-sum(bytes)) & 0x0FF
915         bin = array('B', bytes + [s])
916         return ':' + hexlify(bin.tostring()).upper()
917     _from_bytes = staticmethod(_from_bytes)
918
919     def data(offset, bytes):
920         """Return Data record. This constructs the full record, including
921         the length information, the record type (0x00), the
922         checksum, and the offset.
923
924         @param  offset  load offset of first byte.
925         @param  bytes   list of byte values to pack into record.
926
927         @return         String representation of one HEX record
928         """
929         assert 0 <= offset < 65536
930         assert 0 < len(bytes) < 256
931         b = [len(bytes), (offset>>8)&0x0FF, offset&0x0FF, 0x00] + bytes
932         return Record._from_bytes(b)
933     data = staticmethod(data)
934
935     def eof():
936         """Return End of File record as a string.
937         @return         String representation of Intel Hex EOF record 
938         """
939         return ':00000001FF'
940     eof = staticmethod(eof)
941
942     def extended_segment_address(usba):
943         """Return Extended Segment Address Record.
944         @param  usba     Upper Segment Base Address.
945
946         @return         String representation of Intel Hex USBA record.
947         """
948         b = [2, 0, 0, 0x02, (usba>>8)&0x0FF, usba&0x0FF]
949         return Record._from_bytes(b)
950     extended_segment_address = staticmethod(extended_segment_address)
951
952     def start_segment_address(cs, ip):
953         """Return Start Segment Address Record.
954         @param  cs      16-bit value for CS register.
955         @param  ip      16-bit value for IP register.
956
957         @return         String representation of Intel Hex SSA record.
958         """
959         b = [4, 0, 0, 0x03, (cs>>8)&0x0FF, cs&0x0FF,
960              (ip>>8)&0x0FF, ip&0x0FF]
961         return Record._from_bytes(b)
962     start_segment_address = staticmethod(start_segment_address)
963
964     def extended_linear_address(ulba):
965         """Return Extended Linear Address Record.
966         @param  ulba    Upper Linear Base Address.
967
968         @return         String representation of Intel Hex ELA record.
969         """
970         b = [2, 0, 0, 0x04, (ulba>>8)&0x0FF, ulba&0x0FF]
971         return Record._from_bytes(b)
972     extended_linear_address = staticmethod(extended_linear_address)
973
974     def start_linear_address(eip):
975         """Return Start Linear Address Record.
976         @param  eip     32-bit linear address for the EIP register.
977
978         @return         String representation of Intel Hex SLA record.
979         """
980         b = [4, 0, 0, 0x05, (eip>>24)&0x0FF, (eip>>16)&0x0FF,
981              (eip>>8)&0x0FF, eip&0x0FF]
982         return Record._from_bytes(b)
983     start_linear_address = staticmethod(start_linear_address)
984
985
986 class _BadFileNotation(Exception):
987     """Special error class to use with _get_file_and_addr_range."""
988     pass
989
990 def _get_file_and_addr_range(s, _support_drive_letter=None):
991     """Special method for hexmerge.py script to split file notation
992     into 3 parts: (filename, start, end)
993
994     @raise _BadFileNotation  when string cannot be safely split.
995     """
996     if _support_drive_letter is None:
997         _support_drive_letter = (os.name == 'nt')
998     drive = ''
999     if _support_drive_letter:
1000         if s[1:2] == ':' and s[0].upper() in ''.join([chr(i) for i in range(ord('A'), ord('Z')+1)]):
1001             drive = s[:2]
1002             s = s[2:]
1003     parts = s.split(':')
1004     n = len(parts)
1005     if n == 1:
1006         fname = parts[0]
1007         fstart = None
1008         fend = None
1009     elif n != 3:
1010         raise _BadFileNotation
1011     else:
1012         fname = parts[0]
1013         def ascii_hex_to_int(ascii):
1014             if ascii is not None:
1015                 try:
1016                     return int(ascii, 16)
1017                 except ValueError:
1018                     raise _BadFileNotation
1019             return ascii
1020         fstart = ascii_hex_to_int(parts[1] or None)
1021         fend = ascii_hex_to_int(parts[2] or None)
1022     return drive+fname, fstart, fend
1023
1024
1025 ##
1026 # IntelHex Errors Hierarchy:
1027 #
1028 #  IntelHexError    - basic error
1029 #       HexReaderError  - general hex reader error
1030 #           AddressOverlapError - data for the same address overlap
1031 #           HexRecordError      - hex record decoder base error
1032 #               RecordLengthError    - record has invalid length
1033 #               RecordTypeError      - record has invalid type (RECTYP)
1034 #               RecordChecksumError  - record checksum mismatch
1035 #               EOFRecordError              - invalid EOF record (type 01)
1036 #               ExtendedAddressRecordError  - extended address record base error
1037 #                   ExtendedSegmentAddressRecordError   - invalid extended segment address record (type 02)
1038 #                   ExtendedLinearAddressRecordError    - invalid extended linear address record (type 04)
1039 #               StartAddressRecordError     - start address record base error
1040 #                   StartSegmentAddressRecordError      - invalid start segment address record (type 03)
1041 #                   StartLinearAddressRecordError       - invalid start linear address record (type 05)
1042 #                   DuplicateStartAddressRecordError    - start address record appears twice
1043 #                   InvalidStartAddressValueError       - invalid value of start addr record
1044 #       _EndOfFile  - it's not real error, used internally by hex reader as signal that EOF record found
1045 #       BadAccess16bit - not enough data to read 16 bit value (deprecated, see NotEnoughDataError)
1046 #       NotEnoughDataError - not enough data to read N contiguous bytes
1047
1048 class IntelHexError(Exception):
1049     '''Base Exception class for IntelHex module'''
1050
1051     _fmt = 'IntelHex base error'   #: format string
1052
1053     def __init__(self, msg=None, **kw):
1054         """Initialize the Exception with the given message.
1055         """
1056         self.msg = msg
1057         for key, value in kw.items():
1058             setattr(self, key, value)
1059
1060     def __str__(self):
1061         """Return the message in this Exception."""
1062         if self.msg:
1063             return self.msg
1064         try:
1065             return self._fmt % self.__dict__
1066         except (NameError, ValueError, KeyError), e:
1067             return 'Unprintable exception %s: %s' \
1068                 % (self.__class__.__name__, str(e))
1069
1070 class _EndOfFile(IntelHexError):
1071     """Used for internal needs only."""
1072     _fmt = 'EOF record reached -- signal to stop read file'
1073
1074 class HexReaderError(IntelHexError):
1075     _fmt = 'Hex reader base error'
1076
1077 class AddressOverlapError(HexReaderError):
1078     _fmt = 'Hex file has data overlap at address 0x%(address)X on line %(line)d'
1079
1080 # class NotAHexFileError was removed in trunk.revno.54 because it's not used
1081
1082
1083 class HexRecordError(HexReaderError):
1084     _fmt = 'Hex file contains invalid record at line %(line)d'
1085
1086
1087 class RecordLengthError(HexRecordError):
1088     _fmt = 'Record at line %(line)d has invalid length'
1089
1090 class RecordTypeError(HexRecordError):
1091     _fmt = 'Record at line %(line)d has invalid record type'
1092
1093 class RecordChecksumError(HexRecordError):
1094     _fmt = 'Record at line %(line)d has invalid checksum'
1095
1096 class EOFRecordError(HexRecordError):
1097     _fmt = 'File has invalid End-of-File record'
1098
1099
1100 class ExtendedAddressRecordError(HexRecordError):
1101     _fmt = 'Base class for extended address exceptions'
1102
1103 class ExtendedSegmentAddressRecordError(ExtendedAddressRecordError):
1104     _fmt = 'Invalid Extended Segment Address Record at line %(line)d'
1105
1106 class ExtendedLinearAddressRecordError(ExtendedAddressRecordError):
1107     _fmt = 'Invalid Extended Linear Address Record at line %(line)d'
1108
1109
1110 class StartAddressRecordError(HexRecordError):
1111     _fmt = 'Base class for start address exceptions'
1112
1113 class StartSegmentAddressRecordError(StartAddressRecordError):
1114     _fmt = 'Invalid Start Segment Address Record at line %(line)d'
1115
1116 class StartLinearAddressRecordError(StartAddressRecordError):
1117     _fmt = 'Invalid Start Linear Address Record at line %(line)d'
1118
1119 class DuplicateStartAddressRecordError(StartAddressRecordError):
1120     _fmt = 'Start Address Record appears twice at line %(line)d'
1121
1122 class InvalidStartAddressValueError(StartAddressRecordError):
1123     _fmt = 'Invalid start address value: %(start_addr)s'
1124
1125
1126 class NotEnoughDataError(IntelHexError):
1127     _fmt = ('Bad access at 0x%(address)X: '
1128             'not enough data to read %(length)d contiguous bytes')
1129
1130 class BadAccess16bit(NotEnoughDataError):
1131     _fmt = 'Bad access at 0x%(address)X: not enough data to read 16 bit value'