dfu-convert.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. #!/usr/bin/env python
  2. # Written by Antonio Galea - 2010/11/18
  3. # Distributed under Gnu LGPL 3.0
  4. # see http://www.gnu.org/licenses/lgpl-3.0.txt
  5. #
  6. # Removed ihex functionality and dependency, Bitcraze - 2014-12-11
  7. # Added support for python3, Bitcraze - 2020-02-18
  8. import sys,struct,zlib,os
  9. from optparse import OptionParser
  10. python3 = False
  11. if (sys.version_info > (3, 0)):
  12. python3 = True
  13. DEFAULT_DEVICE="0x0483:0xdf11"
  14. def named(tuple,names):
  15. return dict(zip(names.split(),tuple))
  16. def consume(fmt,data,names):
  17. n = struct.calcsize(fmt)
  18. return named(struct.unpack(fmt,data[:n]),names),data[n:]
  19. def cstring(string):
  20. return string.split('\0',1)[0]
  21. def compute_crc(data):
  22. return 0xFFFFFFFF & -zlib.crc32(data) -1
  23. def parse(file,dump_images=False):
  24. print('File: "%s"' % file)
  25. data = open(file,'rb').read()
  26. crc = compute_crc(data[:-4])
  27. prefix, data = consume('<5sBIB',data,'signature version size targets')
  28. print('%(signature)s v%(version)d, image size: %(size)d, targets: %(targets)d' % prefix)
  29. for t in range(prefix['targets']):
  30. tprefix, data = consume('<6sBI255s2I',data,'signature altsetting named name size elements')
  31. tprefix['num'] = t
  32. if tprefix['named']:
  33. tprefix['name'] = cstring(tprefix['name'])
  34. else:
  35. tprefix['name'] = ''
  36. print('%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d' % tprefix)
  37. tsize = tprefix['size']
  38. target, data = data[:tsize], data[tsize:]
  39. for e in range(tprefix['elements']):
  40. eprefix, target = consume('<2I',target,'address size')
  41. eprefix['num'] = e
  42. print(' %(num)d, address: 0x%(address)08x, size: %(size)d' % eprefix)
  43. esize = eprefix['size']
  44. image, target = target[:esize], target[esize:]
  45. if dump_images:
  46. out = '%s.target%d.image%d.bin' % (file,t,e)
  47. open(out,'wb').write(image)
  48. print(' DUMPED IMAGE TO "%s"' % out)
  49. if len(target):
  50. print("target %d: PARSE ERROR" % t)
  51. suffix = named(struct.unpack('<4H3sBI',data[:16]),'device product vendor dfu ufd len crc')
  52. print('usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x' % suffix)
  53. if crc != suffix['crc']:
  54. print("CRC ERROR: computed crc32 is 0x%08x" % crc)
  55. data = data[16:]
  56. if data:
  57. print("PARSE ERROR")
  58. def build(file,targets,device=DEFAULT_DEVICE):
  59. data = ''
  60. if (python3):
  61. data = b''
  62. for t,target in enumerate(targets):
  63. tdata = ''
  64. if (python3):
  65. tdata = b''
  66. for image in target:
  67. tdata += struct.pack('<2I',image['address'],len(image['data']))+image['data']
  68. trgt = 'Target'
  69. st = 'ST...'
  70. if (python3):
  71. trgt = b'Target'
  72. st = b'ST...'
  73. tdata = struct.pack('<6sBI255s2I',trgt,0,1,st,len(tdata),len(target)) + tdata
  74. data += tdata
  75. dfu_se = 'DfuSe'
  76. ufd = 'UFD'
  77. if (python3):
  78. dfu_se = b'DfuSe'
  79. ufd = b'UFD'
  80. data = struct.pack('<5sBIB',dfu_se,1,len(data)+11,len(targets)) + data
  81. v,d=map(lambda x: int(x,0) & 0xFFFF, device.split(':',1))
  82. data += struct.pack('<4H3sB',0,d,v,0x011a,ufd,16)
  83. crc = compute_crc(data)
  84. data += struct.pack('<I',crc)
  85. open(file,'wb').write(data)
  86. if __name__=="__main__":
  87. usage = """
  88. %prog [-d|--dump] infile.dfu
  89. %prog {-b|--build} address:file.bin [-b address:file.bin ...] [{-D|--device}=vendor:device] outfile.dfu"""
  90. parser = OptionParser(usage=usage)
  91. parser.add_option("-b", "--build", action="append", dest="binfiles",
  92. help="build a DFU file from given BINFILES", metavar="BINFILES")
  93. parser.add_option("-D", "--device", action="store", dest="device",
  94. help="build for DEVICE, defaults to %s" % DEFAULT_DEVICE, metavar="DEVICE")
  95. parser.add_option("-d", "--dump", action="store_true", dest="dump_images",
  96. default=False, help="dump contained images to current directory")
  97. (options, args) = parser.parse_args()
  98. if options.binfiles and len(args)==1:
  99. target = []
  100. if options.binfiles:
  101. for arg in options.binfiles:
  102. try:
  103. address,binfile = arg.split(':',1)
  104. except ValueError:
  105. print("Address:file couple '%s' invalid." % arg)
  106. sys.exit(1)
  107. try:
  108. address = int(address,0) & 0xFFFFFFFF
  109. except ValueError:
  110. print("Address %s invalid." % address)
  111. sys.exit(1)
  112. if not os.path.isfile(binfile):
  113. print("Unreadable file '%s'." % binfile)
  114. sys.exit(1)
  115. target.append({ 'address': address, 'data': open(binfile,'rb').read() })
  116. outfile = args[0]
  117. device = DEFAULT_DEVICE
  118. if options.device:
  119. device=options.device
  120. try:
  121. v,d=map(lambda x: int(x,0) & 0xFFFF, device.split(':',1))
  122. except:
  123. print("Invalid device '%s'." % device)
  124. sys.exit(1)
  125. build(outfile,[target],device)
  126. elif len(args)==1:
  127. infile = args[0]
  128. if not os.path.isfile(infile):
  129. print("Unreadable file '%s'." % infile)
  130. sys.exit(1)
  131. parse(infile, dump_images=options.dump_images)
  132. else:
  133. parser.print_help()
  134. sys.exit(1)