123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- #!/usr/bin/env python
- #
- # ESP8266 LFS Loader Utility
- #
- # Copyright (C) 2019 Terry Ellison, NodeMCU Firmware Community Project. drawing
- # heavily from and including content from esptool.py with full acknowledgement
- # under GPL 2.0, with said content: Copyright (C) 2014-2016 Fredrik Ahlberg, Angus
- # Gratton, Espressif Systems (Shanghai) PTE LTD, other contributors as noted.
- # https:# github.com/espressif/esptool
- #
- # This program 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 2 of the License, or (at your option) any later version.
- #
- # This program 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
- # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
- # Street, Fifth Floor, Boston, MA 02110-1301 USA.
- import os
- import sys
- print os.path.dirname(os.path.realpath(__file__))
- sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/toolchains/')
- import esptool
- import io
- import tempfile
- import shutil
- from pprint import pprint
- import argparse
- import gzip
- import copy
- import inspect
- import struct
- import string
- import math
- __version__ = '1.0'
- __program__ = 'nodemcu-partition.py'
- ROM0_Seg = 0x010000
- FLASH_PAGESIZE = 0x001000
- FLASH_BASE_ADDR = 0x40200000
- PARTITION_TYPE = {
- 4: 'RF_CAL',
- 5: 'PHY_DATA',
- 6: 'SYSTEM_PARAMETER',
- 101: 'EAGLEROM',
- 102: 'IROM0TEXT',
- 103: 'LFS0',
- 104: 'LFS1',
- 105: 'TLSCERT',
- 106: 'SPIFFS0',
- 107: 'SPIFFS1'}
- SYSTEM_PARAMETER = 6
- IROM0TEXT = 102
- LFS = 103
- SPIFFS = 106
- MAX_PT_SIZE = 20*3
- FLASH_SIG = 0xfafaa150
- FLASH_SIG_MASK = 0xfffffff0
- FLASH_SIG_ABSOLUTE = 0x00000001
- WORDSIZE = 4
- WORDBITS = 32
- DEFAULT_FLASH_SIZE = 4*1024*1024
- PLATFORM_RCR_DELETED = 0x0
- PLATFORM_RCR_PT = 0x1
- PLATFORM_RCR_FREE = 0xFF
- SPIFFS_USE_ALL = 0xFFFFFFFF
- PACK_INT = struct.Struct("<I")
- class FatalError(RuntimeError):
- def __init__(self, message):
- RuntimeError.__init__(self, message)
- def WithResult(message, result):
- message += " (result was %s)" % hexify(result)
- return FatalError(message)
- def alignPT(n):
- return 2*FLASH_PAGESIZE*int(math.ceil(n/2/FLASH_PAGESIZE))
- def unpack_RCR(data):
- RCRword,recs, i = [PACK_INT.unpack_from(data,i)[0] \
- for i in range(0, FLASH_PAGESIZE, WORDSIZE)], \
- [],0
- while RCRword[i] % 256 != PLATFORM_RCR_FREE:
- Rlen, Rtype = RCRword[i] % 256, (RCRword[i]/256) % 256
- if Rtype != PLATFORM_RCR_DELETED:
- rec = [Rtype,[RCRword[j] for j in range(i+1,i+1+Rlen)]]
- if Rtype == PLATFORM_RCR_PT:
- PTrec = rec[1]
- else:
- recs.append(rec)
- i = i + Rlen + 1
- if PTrec is not None:
- return PTrec,recs
- FatalError("No partition table found")
- def repack_RCR(recs):
- data = []
- for r in recs:
- Rtype, Rdata = r
- data.append(256*Rtype + len(Rdata))
- data.extend(Rdata)
- return ''.join([PACK_INT.pack(i) for i in data])
- def load_PT(data, args):
- """
- Load the Flash copy of the Partition Table from the first segment of the IROM0
- segment, that is at 0x10000. If nececessary the LFS partition is then correctly
- positioned and adjusted according to the optional start and len arguments.
- The (possibly) updated PT is then returned with the LFS sizing.
- """
- PTrec,recs = unpack_RCR(data)
- flash_size = args.fs if args.fs is not None else DEFAULT_FLASH_SIZE
- # The partition table format is a set of 3*uint32 fields (type, addr, size),
- # with the optional last slot being an end marker (0,size,0) where size is
- # of the firmware image.
- if PTrec[-3] == 0: # Pick out the ROM size and remove the marker
- defaultIROM0size = PTrec[-2] - FLASH_BASE_ADDR
- del PTrec[-3:]
- else:
- defaultIROM0size = None
- # The SDK objects to zero-length partitions so if the developer sets the
- # size of the LFS and/or the SPIFFS partition to 0 then this is removed.
- # If it is subsequently set back to non-zero then it needs to be reinserted.
- # In reality the sizing algos assume that the LFS follows the IROM0TEXT one
- # and SPIFFS is the last partition. We will need to revisit these algos if
- # we adopt a more flexible partiton allocation policy. *** BOTCH WARNING ***
- i = 0
- while i < len(PTrec):
- if PTrec[i] == IROM0TEXT and args.ls is not None and \
- (len(PTrec) == i+3 or PTrec[i+3] != LFS):
- PTrec[i+3:i+3] = [LFS, 0, 0]
- i += 3
-
- if PTrec[-6] != SPIFFS:
- PTrec[-6:-6] = [SPIFFS, PTrec[-5] + PTrec[-4], 0x1000]
- lastEnd, newPT, map = 0,[], dict()
- print " Partition Start Size \n ------------------ ------ ------"
- for i in range (0, len(PTrec), 3):
- Ptype, Paddr, Psize = PTrec[i:i+3]
- if Ptype == IROM0TEXT:
- # If the IROM0 partition size is 0 then compute from the IROM0_SIZE.
- # Note that this script uses the size in the end-marker as a default
- if Psize == 0:
- if defaultIROM0size is None:
- raise FatalError("Cannot set the IROM0 partition size")
- Psize = alignPT(defaultIROM0size)
- elif Ptype == LFS:
- # Properly align the LFS partition size and make it consecutive to
- # the previous partition.
- if args.la is not None:
- Paddr = args.la
- if args.ls is not None:
- Psize = args.ls
- Psize = alignPT(Psize)
- if Paddr == 0:
- Paddr = lastEnd
- if Psize > 0:
- map['LFS'] = {"addr" : Paddr, "size" : Psize}
- elif Ptype == SPIFFS:
- # The logic here is convolved. Explicit start and length can be
- # set, but the SPIFFS region is aslo contrained by the end of the
- # previos partition and the end of Flash. The size = -1 value
- # means use up remaining flash and the SPIFFS will be moved to the
- # 1Mb boundary if the address is default and the specified size
- # allows this.
- if args.sa is not None:
- Paddr = args.sa
- if args.ss is not None:
- Psize = args.ss if args.ss >= 0 else SPIFFS_USE_ALL
- if Psize == SPIFFS_USE_ALL:
- # This allocate all the remaining flash to SPIFFS
- if Paddr < lastEnd:
- Paddr = lastEnd
- Psize = flash_size - Paddr
- else:
- if Paddr == 0:
- # if the is addr not specified then start SPIFFS at 1Mb
- # boundary if the size will fit otherwise make it consecutive
- # to the previous partition.
- Paddr = 0x100000 if Psize <= flash_size - 0x100000 else lastEnd
- elif Paddr < lastEnd:
- Paddr = lastEnd
- if Psize > flash_size - Paddr:
- Psize = flash_size - Paddr
- if Psize > 0:
- map['SPIFFS'] = {"addr" : Paddr, "size" : Psize}
- elif Ptype == SYSTEM_PARAMETER and Paddr == 0:
- Paddr = flash_size - Psize
-
- if Psize > 0:
- Pname = PARTITION_TYPE[Ptype] if Ptype in PARTITION_TYPE \
- else ("Type %d" % Ptype)
- print(" %-18s %06x %06x"% (Pname, Paddr, Psize))
- # Do consistency tests on the partition
- if (Paddr & (FLASH_PAGESIZE - 1)) > 0 or \
- (Psize & (FLASH_PAGESIZE - 1)) > 0 or \
- Paddr < lastEnd or \
- Paddr + Psize > flash_size:
- print (lastEnd, flash_size)
- raise FatalError("Partition %u invalid alignment\n" % (i/3))
- newPT.extend([Ptype, Paddr, Psize])
- lastEnd = Paddr + Psize
- recs.append([PLATFORM_RCR_PT,newPT])
- return recs, map
- def relocate_lfs(data, addr, size):
- """
- The unpacked LFS image comprises the relocatable image itself, followed by a bit
- map (one bit per word) flagging if the corresponding word of the image needs
- relocating. The image and bitmap are enumerated with any addresses being
- relocated by the LFS base address. (Note that the PIC format of addresses is word
- aligned and so first needs scaling by the wordsize.)
- """
- addr += FLASH_BASE_ADDR
- w = [PACK_INT.unpack_from(data,i)[0] for i in range(0, len(data),WORDSIZE)]
- flash_sig, flash_size = w[0], w[1]
- assert ((flash_sig & FLASH_SIG_MASK) == FLASH_SIG and
- (flash_sig & FLASH_SIG_ABSOLUTE) == 0 and
- flash_size % WORDSIZE == 0)
- flash_size //= WORDSIZE
- flags_size = (flash_size + WORDBITS - 1) // WORDBITS
- print WORDSIZE*flash_size, size, len(data), WORDSIZE*(flash_size + flags_size)
- assert (WORDSIZE*flash_size <= size and
- len(data) == WORDSIZE*(flash_size + flags_size))
- image,flags,j = w[0:flash_size], w[flash_size:], 0
- for i in range(0,len(image)):
- if i % WORDBITS == 0:
- flag_word = flags[j]
- j += 1
- if (flag_word & 1) == 1:
- o = image[i]
- image[i] = WORDSIZE*image[i] + addr
- flag_word >>= 1
- return ''.join([PACK_INT.pack(i) for i in image])
- def main():
- def arg_auto_int(x):
- ux = x.upper()
- if "M" in ux:
- return int(ux[:ux.index("M")]) * 1024 * 1024
- elif "K" in ux:
- return int(ux[:ux.index("K")]) * 1024
- else:
- return int(ux, 0)
- print('%s V%s' %(__program__, __version__))
- # ---------- process the arguments ---------- #
- a = argparse.ArgumentParser(
- description='%s V%s - ESP8266 NodeMCU Loader Utility' %
- (__program__, __version__),
- prog=__program__)
- a.add_argument('--port', '-p', help='Serial port device')
- a.add_argument('--baud', '-b', type=arg_auto_int,
- help='Serial port baud rate used when flashing/reading')
- a.add_argument('--flash_size', '-fs', dest="fs", type=arg_auto_int,
- help='Flash size used in SPIFFS allocation (Default 4MB)')
- a.add_argument('--lfs_addr', '-la', dest="la", type=arg_auto_int,
- help='(Overwrite) start address of LFS partition')
- a.add_argument('--lfs_size', '-ls', dest="ls", type=arg_auto_int,
- help='(Overwrite) length of LFS partition')
- a.add_argument('--lfs_file', '-lf', dest="lf", help='LFS image file')
- a.add_argument('--spiffs_addr', '-sa', dest="sa", type=arg_auto_int,
- help='(Overwrite) start address of SPIFFS partition')
- a.add_argument('--spiffs_size', '-ss', dest="ss", type=arg_auto_int,
- help='(Overwrite) length of SPIFFS partition')
- a.add_argument('--spiffs_file', '-sf', dest="sf", help='SPIFFS image file')
- arg = a.parse_args()
- if arg.lf is not None:
- if not os.path.exists(arg.lf):
- raise FatalError("LFS image %s does not exist" % arg.lf)
- if arg.sf is not None:
- if not os.path.exists(arg.sf):
- raise FatalError("SPIFFS image %s does not exist" % arg.sf)
- base = [] if arg.port is None else ['--port',arg.port]
- if arg.baud is not None: base.extend(['--baud',str(arg.baud)])
- # ---------- Use esptool to read the PT ---------- #
- tmpdir = tempfile.mkdtemp()
- pt_file = tmpdir + '/pt.dmp'
- espargs = base+['--after', 'no_reset', 'read_flash', '--no-progress',
- str(ROM0_Seg), str(FLASH_PAGESIZE), pt_file]
-
- esptool.main(espargs)
- with open(pt_file,"rb") as f:
- data = f.read()
- # ---------- Update the PT if necessary ---------- #
- recs, pt_map = load_PT(data, arg)
- odata = repack_RCR(recs)
- odata = odata + "\xFF" * (FLASH_PAGESIZE - len(odata))
- # ---------- If the PT has changed then use esptool to rewrite it ---------- #
- if odata != data:
- print("PT updated")
- pt_file = tmpdir + '/opt.dmp'
- with open(pt_file,"wb") as f:
- f.write(odata)
- espargs = base+['--after', 'no_reset', 'write_flash', '--no-progress',
- str(ROM0_Seg), pt_file]
- esptool.main(espargs)
- if arg.lf is not None:
- if 'LFS' not in pt_map:
- raise FatalError("No LFS partition; cannot write LFS image")
- la,ls = pt_map['LFS']['addr'], pt_map['LFS']['size']
- # ---------- Read and relocate the LFS image ---------- #
- with gzip.open(arg.lf) as f:
- lfs = f.read()
- if len(lfs) > ls:
- raise FatalError("LFS partition to small for LFS image")
- lfs = relocate_lfs(lfs, la, ls)
- # ---------- Write to a temp file and use esptool to write it to flash ---------- #
- img_file = tmpdir + '/lfs.img'
- espargs = base + ['write_flash', str(la), img_file]
- with open(img_file,"wb") as f:
- f.write(lfs)
- esptool.main(espargs)
- if arg.sf is not None:
- if 'SPIFFS' not in pt_map:
- raise FatalError("No SPIFSS partition; cannot write SPIFFS image")
- sa,ss = pt_map['SPIFFS']['addr'], pt_map['SPIFFS']['size']
- # ---------- Write to a temp file and use esptool to write it to flash ---------- #
- spiffs_file = arg.sf
- espargs = base + ['write_flash', str(sa), spiffs_file]
- esptool.main(espargs)
- # ---------- Clean up temp directory ---------- #
- # espargs = base + ['--after', 'hard_reset', 'flash_id']
- # esptool.main(espargs)
- shutil.rmtree(tmpdir)
- def _main():
- main()
- if __name__ == '__main__':
- _main()
|