mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-05-25 02:57:33 +08:00
feat(partition_table): Compiling script gets partition information from partition binary
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
#
|
||||
# Converts partition tables to/from CSV and binary formats.
|
||||
#
|
||||
# See http://esp-idf.readthedocs.io/en/latest/api-guides/partition-tables.html
|
||||
# See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html
|
||||
# for explanation of partition table structure and uses.
|
||||
#
|
||||
# Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
@ -26,12 +26,19 @@ import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import hashlib
|
||||
import binascii
|
||||
|
||||
MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
|
||||
MD5_PARTITION_BEGIN = b"\xEB\xEB" + b"\xFF" * 14 # The first 2 bytes are like magic numbers for MD5 sum
|
||||
PARTITION_TABLE_SIZE = 0x1000 # Size of partition table
|
||||
|
||||
__version__ = '1.0'
|
||||
__version__ = '1.1'
|
||||
|
||||
quiet = False
|
||||
md5sum = True
|
||||
secure = False
|
||||
offset_part_table = 0
|
||||
|
||||
def status(msg):
|
||||
""" Print status message to stderr """
|
||||
@ -73,8 +80,11 @@ class PartitionTable(list):
|
||||
raise
|
||||
|
||||
# fix up missing offsets & negative sizes
|
||||
last_end = 0x5000 # first offset after partition table
|
||||
last_end = offset_part_table + PARTITION_TABLE_SIZE # first offset after partition table
|
||||
for e in res:
|
||||
if offset_part_table != 0 and e.offset is not None and e.offset < last_end:
|
||||
critical("WARNING: 0x%x address in the partition table is below 0x%x" % (e.offset, last_end))
|
||||
e.offset = None
|
||||
if e.offset is None:
|
||||
pad_to = 0x10000 if e.type == PartitionDefinition.APP_TYPE else 4
|
||||
if last_end % pad_to != 0:
|
||||
@ -97,6 +107,38 @@ class PartitionTable(list):
|
||||
else:
|
||||
return super(PartitionTable, self).__getitem__(item)
|
||||
|
||||
def find_by_type(self, ptype, subtype):
|
||||
""" Return a partition by type & subtype, returns
|
||||
None if not found """
|
||||
TYPES = PartitionDefinition.TYPES
|
||||
SUBTYPES = PartitionDefinition.SUBTYPES
|
||||
# convert ptype & subtypes names (if supplied this way) to integer values
|
||||
try:
|
||||
ptype = TYPES[ptype]
|
||||
except KeyError:
|
||||
try:
|
||||
ptypes = int(ptype, 0)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
subtype = SUBTYPES[int(ptype)][subtype]
|
||||
except KeyError:
|
||||
try:
|
||||
ptypes = int(ptype, 0)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
for p in self:
|
||||
if p.type == ptype and p.subtype == subtype:
|
||||
return p
|
||||
return None
|
||||
|
||||
def find_by_name(self, name):
|
||||
for p in self:
|
||||
if p.name == name:
|
||||
return p
|
||||
return None
|
||||
|
||||
def verify(self):
|
||||
# verify each partition individually
|
||||
for p in self:
|
||||
@ -104,14 +146,25 @@ class PartitionTable(list):
|
||||
# check for overlaps
|
||||
last = None
|
||||
for p in sorted(self, key=lambda x:x.offset):
|
||||
if p.offset < 0x5000:
|
||||
raise InputError("Partition offset 0x%x is below 0x5000" % p.offset)
|
||||
if p.offset < offset_part_table + PARTITION_TABLE_SIZE:
|
||||
raise InputError("Partition offset 0x%x is below 0x%x" % (p.offset, offset_part_table + PARTITION_TABLE_SIZE))
|
||||
if last is not None and p.offset < last.offset + last.size:
|
||||
raise InputError("Partition at 0x%x overlaps 0x%x-0x%x" % (p.offset, last.offset, last.offset+last.size-1))
|
||||
last = p
|
||||
|
||||
def flash_size(self):
|
||||
""" Return the size that partitions will occupy in flash
|
||||
(ie the offset the last partition ends at)
|
||||
"""
|
||||
try:
|
||||
last = sorted(self, reverse=True)[0]
|
||||
except IndexError:
|
||||
return 0 # empty table!
|
||||
return last.offset + last.size
|
||||
|
||||
@classmethod
|
||||
def from_binary(cls, b):
|
||||
md5 = hashlib.md5();
|
||||
result = cls()
|
||||
for o in range(0,len(b),32):
|
||||
data = b[o:o+32]
|
||||
@ -119,11 +172,20 @@ class PartitionTable(list):
|
||||
raise InputError("Partition table length must be a multiple of 32 bytes")
|
||||
if data == b'\xFF'*32:
|
||||
return result # got end marker
|
||||
if md5sum and data[:2] == MD5_PARTITION_BEGIN[:2]: #check only the magic number part
|
||||
if data[16:] == md5.digest():
|
||||
continue # the next iteration will check for the end marker
|
||||
else:
|
||||
raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:])))
|
||||
else:
|
||||
md5.update(data)
|
||||
result.append(PartitionDefinition.from_binary(data))
|
||||
raise InputError("Partition table is missing an end-of-table marker")
|
||||
|
||||
def to_binary(self):
|
||||
result = b"".join(e.to_binary() for e in self)
|
||||
if md5sum:
|
||||
result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest()
|
||||
if len(result )>= MAX_PARTITION_LENGTH:
|
||||
raise InputError("Binary partition table length (%d) longer than max" % len(result))
|
||||
result += b"\xFF" * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing
|
||||
@ -154,6 +216,7 @@ class PartitionDefinition(object):
|
||||
"phy" : 0x01,
|
||||
"nvs" : 0x02,
|
||||
"coredump" : 0x03,
|
||||
"nvs_keys" : 0x04,
|
||||
"esphttpd" : 0x80,
|
||||
"fat" : 0x81,
|
||||
"spiffs" : 0x82,
|
||||
@ -173,7 +236,7 @@ class PartitionDefinition(object):
|
||||
"encrypted" : 0
|
||||
}
|
||||
|
||||
# add subtypes for the 16 OTA slot values ("ota_XXX, etc.")
|
||||
# add subtypes for the 16 OTA slot values ("ota_XX, etc.")
|
||||
for ota_slot in range(16):
|
||||
SUBTYPES[TYPES["app"]]["ota_%d" % ota_slot] = 0x10 + ota_slot
|
||||
|
||||
@ -226,6 +289,18 @@ class PartitionDefinition(object):
|
||||
def __cmp__(self, other):
|
||||
return self.offset - other.offset
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.offset < other.offset
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.offset > other.offset
|
||||
|
||||
def __le__(self, other):
|
||||
return self.offset <= other.offset
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.offset >= other.offset
|
||||
|
||||
def parse_type(self, strval):
|
||||
if strval == "":
|
||||
raise InputError("Field 'type' can't be left empty.")
|
||||
@ -251,6 +326,8 @@ class PartitionDefinition(object):
|
||||
align = self.ALIGNMENT.get(self.type, 4)
|
||||
if self.offset % align:
|
||||
raise ValidationError(self, "Offset 0x%x is not aligned to 0x%x" % (self.offset, align))
|
||||
if self.size % align and secure:
|
||||
raise ValidationError(self, "Size 0x%x is not aligned to 0x%x" % (self.size, align))
|
||||
if self.size is None:
|
||||
raise ValidationError(self, "Size field is not set")
|
||||
|
||||
@ -333,11 +410,18 @@ def parse_int(v, keywords={}):
|
||||
|
||||
def main():
|
||||
global quiet
|
||||
global md5sum
|
||||
global offset_part_table
|
||||
global secure
|
||||
parser = argparse.ArgumentParser(description='ESP32 partition table utility')
|
||||
|
||||
parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash',
|
||||
nargs='?', choices=[ '1MB', '2MB', '4MB', '8MB', '16MB' ])
|
||||
parser.add_argument('--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true')
|
||||
parser.add_argument('--verify', '-v', help='Verify partition table fields', default=True, action='store_false')
|
||||
parser.add_argument('--quiet', '-q', help="Don't print status messages to stderr", action='store_true')
|
||||
|
||||
parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000')
|
||||
parser.add_argument('--secure', help="Require app partitions to be suitable for secure boot", action='store_true')
|
||||
parser.add_argument('input', help='Path to CSV or binary file to parse. Will use stdin if omitted.', type=argparse.FileType('rb'), default=sys.stdin)
|
||||
parser.add_argument('output', help='Path to output converted binary or CSV file. Will use stdout if omitted, unless the --display argument is also passed (in which case only the summary is printed.)',
|
||||
nargs='?',
|
||||
@ -346,6 +430,9 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
quiet = args.quiet
|
||||
md5sum = not args.disable_md5sum
|
||||
secure = args.secure
|
||||
offset_part_table = int(args.offset, 0)
|
||||
input = args.input.read()
|
||||
input_is_binary = input[0:2] == PartitionDefinition.MAGIC_BYTES
|
||||
if input_is_binary:
|
||||
@ -360,6 +447,14 @@ def main():
|
||||
status("Verifying table...")
|
||||
table.verify()
|
||||
|
||||
if args.flash_size:
|
||||
size_mb = int(args.flash_size.replace("MB", ""))
|
||||
size = size_mb * 1024 * 1024 # flash memory uses honest megabytes!
|
||||
table_size = table.flash_size()
|
||||
if size < table_size:
|
||||
raise InputError("Partitions defined in '%s' occupy %.1fMB of flash (%d bytes) which does not fit in configured flash size %dMB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu." %
|
||||
(args.input.name, table_size / 1024.0 / 1024.0, table_size, size_mb))
|
||||
|
||||
if input_is_binary:
|
||||
output = table.to_csv()
|
||||
with sys.stdout if args.output == '-' else open(args.output, 'w') as f:
|
||||
|
Reference in New Issue
Block a user