esp-dsp: master 7cc5073 esp-face: master 420fc7e esp-rainmaker: f1b82c7 esp32-camera: master 6f8489e esp_littlefs: master b58f00c
554 lines
20 KiB
Python
Executable File
554 lines
20 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# ESP32 partition table generation tool
|
|
#
|
|
# Converts partition tables to/from CSV and binary formats.
|
|
#
|
|
# 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
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http:#www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
from __future__ import division, print_function, unicode_literals
|
|
|
|
import argparse
|
|
import binascii
|
|
import errno
|
|
import hashlib
|
|
import os
|
|
import re
|
|
import struct
|
|
import sys
|
|
|
|
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
|
|
|
|
MIN_PARTITION_SUBTYPE_APP_OTA = 0x10
|
|
NUM_PARTITION_SUBTYPE_APP_OTA = 16
|
|
|
|
__version__ = '1.2'
|
|
|
|
APP_TYPE = 0x00
|
|
DATA_TYPE = 0x01
|
|
|
|
TYPES = {
|
|
'app': APP_TYPE,
|
|
'data': DATA_TYPE,
|
|
}
|
|
|
|
|
|
def get_ptype_as_int(ptype):
|
|
""" Convert a string which might be numeric or the name of a partition type to an integer """
|
|
try:
|
|
return TYPES[ptype]
|
|
except KeyError:
|
|
try:
|
|
return int(ptype, 0)
|
|
except TypeError:
|
|
return ptype
|
|
|
|
|
|
# Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h
|
|
SUBTYPES = {
|
|
APP_TYPE: {
|
|
'factory': 0x00,
|
|
'test': 0x20,
|
|
},
|
|
DATA_TYPE: {
|
|
'ota': 0x00,
|
|
'phy': 0x01,
|
|
'nvs': 0x02,
|
|
'coredump': 0x03,
|
|
'nvs_keys': 0x04,
|
|
'efuse': 0x05,
|
|
'esphttpd': 0x80,
|
|
'fat': 0x81,
|
|
'spiffs': 0x82,
|
|
},
|
|
}
|
|
|
|
|
|
def get_subtype_as_int(ptype, subtype):
|
|
""" Convert a string which might be numeric or the name of a partition subtype to an integer """
|
|
try:
|
|
return SUBTYPES[get_ptype_as_int(ptype)][subtype]
|
|
except KeyError:
|
|
try:
|
|
return int(subtype, 0)
|
|
except TypeError:
|
|
return subtype
|
|
|
|
|
|
quiet = False
|
|
md5sum = True
|
|
secure = False
|
|
offset_part_table = 0
|
|
|
|
|
|
def status(msg):
|
|
""" Print status message to stderr """
|
|
if not quiet:
|
|
critical(msg)
|
|
|
|
|
|
def critical(msg):
|
|
""" Print critical message to stderr """
|
|
sys.stderr.write(msg)
|
|
sys.stderr.write('\n')
|
|
|
|
|
|
class PartitionTable(list):
|
|
def __init__(self):
|
|
super(PartitionTable, self).__init__(self)
|
|
|
|
@classmethod
|
|
def from_file(cls, f):
|
|
data = f.read()
|
|
data_is_binary = data[0:2] == PartitionDefinition.MAGIC_BYTES
|
|
if data_is_binary:
|
|
status('Parsing binary partition input...')
|
|
return cls.from_binary(data), True
|
|
|
|
data = data.decode()
|
|
status('Parsing CSV input...')
|
|
return cls.from_csv(data), False
|
|
|
|
@classmethod
|
|
def from_csv(cls, csv_contents):
|
|
res = PartitionTable()
|
|
lines = csv_contents.splitlines()
|
|
|
|
def expand_vars(f):
|
|
f = os.path.expandvars(f)
|
|
m = re.match(r'(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)', f)
|
|
if m:
|
|
raise InputError("unknown variable '%s'" % m.group(1))
|
|
return f
|
|
|
|
for line_no in range(len(lines)):
|
|
line = expand_vars(lines[line_no]).strip()
|
|
if line.startswith('#') or len(line) == 0:
|
|
continue
|
|
try:
|
|
res.append(PartitionDefinition.from_csv(line, line_no + 1))
|
|
except InputError as e:
|
|
raise InputError('Error at line %d: %s' % (line_no + 1, e))
|
|
except Exception:
|
|
critical('Unexpected error parsing CSV line %d: %s' % (line_no + 1, line))
|
|
raise
|
|
|
|
# fix up missing offsets & negative sizes
|
|
last_end = offset_part_table + PARTITION_TABLE_SIZE # first offset after partition table
|
|
for e in res:
|
|
if e.offset is not None and e.offset < last_end:
|
|
if e == res[0]:
|
|
raise InputError('CSV Error: First partition offset 0x%x overlaps end of partition table 0x%x'
|
|
% (e.offset, last_end))
|
|
else:
|
|
raise InputError('CSV Error: Partitions overlap. Partition at line %d sets offset 0x%x. Previous partition ends 0x%x'
|
|
% (e.line_no, e.offset, last_end))
|
|
if e.offset is None:
|
|
pad_to = 0x10000 if e.type == APP_TYPE else 4
|
|
if last_end % pad_to != 0:
|
|
last_end += pad_to - (last_end % pad_to)
|
|
e.offset = last_end
|
|
if e.size < 0:
|
|
e.size = -e. |