#!/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(" 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()