nodemcu-partition.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. #!/usr/bin/env python
  2. #
  3. # ESP8266 LFS Loader Utility
  4. #
  5. # Copyright (C) 2019 Terry Ellison, NodeMCU Firmware Community Project. drawing
  6. # heavily from and including content from esptool.py with full acknowledgement
  7. # under GPL 2.0, with said content: Copyright (C) 2014-2016 Fredrik Ahlberg, Angus
  8. # Gratton, Espressif Systems (Shanghai) PTE LTD, other contributors as noted.
  9. # https:# github.com/espressif/esptool
  10. #
  11. # This program is free software; you can redistribute it and/or modify it under
  12. # the terms of the GNU General Public License as published by the Free Software
  13. # Foundation; either version 2 of the License, or (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful, but WITHOUT
  16. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  17. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License along with
  20. # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
  21. # Street, Fifth Floor, Boston, MA 02110-1301 USA.
  22. import os
  23. import sys
  24. print os.path.dirname(os.path.realpath(__file__))
  25. sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/toolchains/')
  26. import esptool
  27. import io
  28. import tempfile
  29. import shutil
  30. from pprint import pprint
  31. import argparse
  32. import gzip
  33. import copy
  34. import inspect
  35. import struct
  36. import string
  37. import math
  38. __version__ = '1.0'
  39. __program__ = 'nodemcu-partition.py'
  40. ROM0_Seg = 0x010000
  41. FLASH_PAGESIZE = 0x001000
  42. FLASH_BASE_ADDR = 0x40200000
  43. PARTITION_TYPE = {
  44. 4: 'RF_CAL',
  45. 5: 'PHY_DATA',
  46. 6: 'SYSTEM_PARAMETER',
  47. 101: 'EAGLEROM',
  48. 102: 'IROM0TEXT',
  49. 103: 'LFS0',
  50. 104: 'LFS1',
  51. 105: 'TLSCERT',
  52. 106: 'SPIFFS0',
  53. 107: 'SPIFFS1'}
  54. IROM0TEXT = 102
  55. LFS = 103
  56. SPIFFS = 106
  57. MAX_PT_SIZE = 20*3
  58. FLASH_SIG = 0xfafaa150
  59. FLASH_SIG_MASK = 0xfffffff0
  60. FLASH_SIG_ABSOLUTE = 0x00000001
  61. WORDSIZE = 4
  62. WORDBITS = 32
  63. DEFAULT_FLASH_SIZE = 4*1024*1024
  64. PLATFORM_RCR_DELETED = 0x0
  65. PLATFORM_RCR_PT = 0x1
  66. PLATFORM_RCR_FREE = 0xFF
  67. SPIFFS_USE_ALL = 0xFFFFFFFF
  68. PACK_INT = struct.Struct("<I")
  69. class FatalError(RuntimeError):
  70. def __init__(self, message):
  71. RuntimeError.__init__(self, message)
  72. def WithResult(message, result):
  73. message += " (result was %s)" % hexify(result)
  74. return FatalError(message)
  75. def alignPT(n):
  76. return 2*FLASH_PAGESIZE*int(math.ceil(n/2/FLASH_PAGESIZE))
  77. def unpack_RCR(data):
  78. RCRword,recs, i = [PACK_INT.unpack_from(data,i)[0] \
  79. for i in range(0, FLASH_PAGESIZE, WORDSIZE)], \
  80. [],0
  81. while RCRword[i] % 256 != PLATFORM_RCR_FREE:
  82. Rlen, Rtype = RCRword[i] % 256, (RCRword[i]/256) % 256
  83. if Rtype != PLATFORM_RCR_DELETED:
  84. rec = [Rtype,[RCRword[j] for j in range(i+1,i+1+Rlen)]]
  85. if Rtype == PLATFORM_RCR_PT:
  86. PTrec = rec[1]
  87. else:
  88. recs.append(rec)
  89. i = i + Rlen + 1
  90. if PTrec is not None:
  91. return PTrec,recs
  92. FatalError("No partition table found")
  93. def repack_RCR(recs):
  94. data = []
  95. for r in recs:
  96. Rtype, Rdata = r
  97. data.append(256*Rtype + len(Rdata))
  98. data.extend(Rdata)
  99. return ''.join([PACK_INT.pack(i) for i in data])
  100. def load_PT(data, args):
  101. """
  102. Load the Flash copy of the Partition Table from the first segment of the IROM0
  103. segment, that is at 0x10000. If nececessary the LFS partition is then correctly
  104. positioned and adjusted according to the optional start and len arguments.
  105. The (possibly) updated PT is then returned with the LFS sizing.
  106. """
  107. PTrec,recs = unpack_RCR(data)
  108. flash_size = args.fs if args.fs is not None else DEFAULT_FLASH_SIZE
  109. # The partition table format is a set of 3*uint32 fields (type, addr, size),
  110. # with the optional last slot being an end marker (0,size,0) where size is
  111. # of the firmware image.
  112. if PTrec[-3] == 0: # Pick out the ROM size and remove the marker
  113. defaultIROM0size = PTrec[-2] - FLASH_BASE_ADDR
  114. del PTrec[-3:]
  115. else:
  116. defaultIROM0size = None
  117. # The SDK objects to zero-length partitions so if the developer sets the
  118. # size of the LFS and/or the SPIFFS partition to 0 then this is removed.
  119. # If it is subsequently set back to non-zero then it needs to be reinserted.
  120. # In reality the sizing algos assume that the LFS follows the IROM0TEXT one
  121. # and SPIFFS is the last partition. We will need to revisit these algos if
  122. # we adopt a more flexible partiton allocation policy. *** BOTCH WARNING ***
  123. for i in range (0, len(PTrec), 3):
  124. if PTrec[i] == IROM0TEXT and args.ls is not None and \
  125. (len(PTrec) == i+3 or PTrec[i+3] != LFS):
  126. PTrec[i+3:i+3] = [LFS, 0, 0]
  127. break
  128. if PTrec[-3] != SPIFFS:
  129. PTrec.extend([SPIFFS, 0, 0])
  130. lastEnd, newPT, map = 0,[], dict()
  131. print " Partition Start Size \n ------------------ ------ ------"
  132. for i in range (0, len(PTrec), 3):
  133. Ptype, Paddr, Psize = PTrec[i:i+3]
  134. if Ptype == IROM0TEXT:
  135. # If the IROM0 partition size is 0 then compute from the IROM0_SIZE.
  136. # Note that this script uses the size in the end-marker as a default
  137. if Psize == 0:
  138. if defaultIROM0size is None:
  139. raise FatalError("Cannot set the IROM0 partition size")
  140. Psize = alignPT(defaultIROM0size)
  141. elif Ptype == LFS:
  142. # Properly align the LFS partition size and make it consecutive to
  143. # the previous partition.
  144. if args.la is not None:
  145. Paddr = args.la
  146. if args.ls is not None:
  147. Psize = args.ls
  148. Psize = alignPT(Psize)
  149. if Paddr == 0:
  150. Paddr = lastEnd
  151. if Psize > 0:
  152. map['LFS'] = {"addr" : Paddr, "size" : Psize}
  153. elif Ptype == SPIFFS:
  154. # The logic here is convolved. Explicit start and length can be
  155. # set, but the SPIFFS region is aslo contrained by the end of the
  156. # previos partition and the end of Flash. The size = -1 value
  157. # means use up remaining flash and the SPIFFS will be moved to the
  158. # 1Mb boundary if the address is default and the specified size
  159. # allows this.
  160. if args.sa is not None:
  161. Paddr = args.sa
  162. if args.ss is not None:
  163. Psize = args.ss if args.ss >= 0 else SPIFFS_USE_ALL
  164. if Psize == SPIFFS_USE_ALL:
  165. # This allocate all the remaining flash to SPIFFS
  166. if Paddr < lastEnd:
  167. Paddr = lastEnd
  168. Psize = flash_size - Paddr
  169. else:
  170. if Paddr == 0:
  171. # if the is addr not specified then start SPIFFS at 1Mb
  172. # boundary if the size will fit otherwise make it consecutive
  173. # to the previous partition.
  174. Paddr = 0x100000 if Psize <= flash_size - 0x100000 else lastEnd
  175. elif Paddr < lastEnd:
  176. Paddr = lastEnd
  177. if Psize > flash_size - Paddr:
  178. Psize = flash_size - Paddr
  179. if Psize > 0:
  180. map['SPIFFS'] = {"addr" : Paddr, "size" : Psize}
  181. if Psize > 0:
  182. Pname = PARTITION_TYPE[Ptype] if Ptype in PARTITION_TYPE \
  183. else ("Type %d" % Ptype)
  184. print(" %-18s %06x %06x"% (Pname, Paddr, Psize))
  185. # Do consistency tests on the partition
  186. if (Paddr & (FLASH_PAGESIZE - 1)) > 0 or \
  187. (Psize & (FLASH_PAGESIZE - 1)) > 0 or \
  188. Paddr < lastEnd or \
  189. Paddr + Psize > flash_size:
  190. print (lastEnd, flash_size)
  191. raise FatalError("Partition %u invalid alignment\n" % (i/3))
  192. newPT.extend([Ptype, Paddr, Psize])
  193. lastEnd = Paddr + Psize
  194. recs.append([PLATFORM_RCR_PT,newPT])
  195. return recs, map
  196. def relocate_lfs(data, addr, size):
  197. """
  198. The unpacked LFS image comprises the relocatable image itself, followed by a bit
  199. map (one bit per word) flagging if the corresponding word of the image needs
  200. relocating. The image and bitmap are enumerated with any addresses being
  201. relocated by the LFS base address. (Note that the PIC format of addresses is word
  202. aligned and so first needs scaling by the wordsize.)
  203. """
  204. addr += FLASH_BASE_ADDR
  205. w = [PACK_INT.unpack_from(data,i)[0] for i in range(0, len(data),WORDSIZE)]
  206. flash_sig, flash_size = w[0], w[1]
  207. assert ((flash_sig & FLASH_SIG_MASK) == FLASH_SIG and
  208. (flash_sig & FLASH_SIG_ABSOLUTE) == 0 and
  209. flash_size % WORDSIZE == 0)
  210. flash_size //= WORDSIZE
  211. flags_size = (flash_size + WORDBITS - 1) // WORDBITS
  212. print WORDSIZE*flash_size, size, len(data), WORDSIZE*(flash_size + flags_size)
  213. assert (WORDSIZE*flash_size <= size and
  214. len(data) == WORDSIZE*(flash_size + flags_size))
  215. image,flags,j = w[0:flash_size], w[flash_size:], 0
  216. for i in range(0,len(image)):
  217. if i % WORDBITS == 0:
  218. flag_word = flags[j]
  219. j += 1
  220. if (flag_word & 1) == 1:
  221. o = image[i]
  222. image[i] = WORDSIZE*image[i] + addr
  223. flag_word >>= 1
  224. return ''.join([PACK_INT.pack(i) for i in image])
  225. def main():
  226. def arg_auto_int(x):
  227. ux = x.upper()
  228. if "M" in ux:
  229. return int(ux[:ux.index("M")]) * 1024 * 1024
  230. elif "K" in ux:
  231. return int(ux[:ux.index("K")]) * 1024
  232. else:
  233. return int(ux, 0)
  234. print('%s V%s' %(__program__, __version__))
  235. # ---------- process the arguments ---------- #
  236. a = argparse.ArgumentParser(
  237. description='%s V%s - ESP8266 NodeMCU Loader Utility' %
  238. (__program__, __version__),
  239. prog=__program__)
  240. a.add_argument('--port', '-p', help='Serial port device')
  241. a.add_argument('--baud', '-b', type=arg_auto_int,
  242. help='Serial port baud rate used when flashing/reading')
  243. a.add_argument('--flash_size', '-fs', dest="fs", type=arg_auto_int,
  244. help='Flash size used in SPIFFS allocation (Default 4MB)')
  245. a.add_argument('--lfs_addr', '-la', dest="la", type=arg_auto_int,
  246. help='(Overwrite) start address of LFS partition')
  247. a.add_argument('--lfs_size', '-ls', dest="ls", type=arg_auto_int,
  248. help='(Overwrite) length of LFS partition')
  249. a.add_argument('--lfs_file', '-lf', dest="lf", help='LFS image file')
  250. a.add_argument('--spiffs_addr', '-sa', dest="sa", type=arg_auto_int,
  251. help='(Overwrite) start address of SPIFFS partition')
  252. a.add_argument('--spiffs_size', '-ss', dest="ss", type=arg_auto_int,
  253. help='(Overwrite) length of SPIFFS partition')
  254. a.add_argument('--spiffs_file', '-sf', dest="sf", help='SPIFFS image file')
  255. arg = a.parse_args()
  256. if arg.lf is not None:
  257. if not os.path.exists(arg.lf):
  258. raise FatalError("LFS image %s does not exist" % arg.lf)
  259. if arg.sf is not None:
  260. if not os.path.exists(arg.sf):
  261. raise FatalError("SPIFFS image %s does not exist" % arg.sf)
  262. base = [] if arg.port is None else ['--port',arg.port]
  263. if arg.baud is not None: base.extend(['--baud',str(arg.baud)])
  264. # ---------- Use esptool to read the PT ---------- #
  265. tmpdir = tempfile.mkdtemp()
  266. pt_file = tmpdir + '/pt.dmp'
  267. espargs = base+['--after', 'no_reset', 'read_flash', '--no-progress',
  268. str(ROM0_Seg), str(FLASH_PAGESIZE), pt_file]
  269. esptool.main(espargs)
  270. with open(pt_file,"rb") as f:
  271. data = f.read()
  272. # ---------- Update the PT if necessary ---------- #
  273. recs, pt_map = load_PT(data, arg)
  274. odata = repack_RCR(recs)
  275. odata = odata + "\xFF" * (FLASH_PAGESIZE - len(odata))
  276. # ---------- If the PT has changed then use esptool to rewrite it ---------- #
  277. if odata != data:
  278. print("PT updated")
  279. pt_file = tmpdir + '/opt.dmp'
  280. with open(pt_file,"wb") as f:
  281. f.write(odata)
  282. espargs = base+['--after', 'no_reset', 'write_flash', '--no-progress',
  283. str(ROM0_Seg), pt_file]
  284. esptool.main(espargs)
  285. if arg.lf is not None:
  286. if 'LFS' not in pt_map:
  287. raise FatalError("No LFS partition; cannot write LFS image")
  288. la,ls = pt_map['LFS']['addr'], pt_map['LFS']['size']
  289. # ---------- Read and relocate the LFS image ---------- #
  290. with gzip.open(arg.lf) as f:
  291. lfs = f.read()
  292. if len(lfs) > ls:
  293. raise FatalError("LFS partition to small for LFS image")
  294. lfs = relocate_lfs(lfs, la, ls)
  295. # ---------- Write to a temp file and use esptool to write it to flash ---------- #
  296. img_file = tmpdir + '/lfs.img'
  297. espargs = base + ['write_flash', str(la), img_file]
  298. with open(img_file,"wb") as f:
  299. f.write(lfs)
  300. esptool.main(espargs)
  301. if arg.sf is not None:
  302. if 'SPIFFS' not in pt_map:
  303. raise FatalError("No SPIFSS partition; cannot write SPIFFS image")
  304. sa,ss = pt_map['SPIFFS']['addr'], pt_map['SPIFFS']['size']
  305. # ---------- Write to a temp file and use esptool to write it to flash ---------- #
  306. spiffs_file = arg.sf
  307. espargs = base + ['write_flash', str(sa), spiffs_file]
  308. esptool.main(espargs)
  309. # ---------- Clean up temp directory ---------- #
  310. # espargs = base + ['--after', 'hard_reset', 'flash_id']
  311. # esptool.main(espargs)
  312. shutil.rmtree(tmpdir)
  313. def _main():
  314. main()
  315. if __name__ == '__main__':
  316. _main()