diff --git a/components/bootloader_support/include/esp_flash_partitions.h b/components/bootloader_support/include/esp_flash_partitions.h index d05ded654f..5a6da221b0 100644 --- a/components/bootloader_support/include/esp_flash_partitions.h +++ b/components/bootloader_support/include/esp_flash_partitions.h @@ -32,6 +32,7 @@ extern "C" { #define PART_TYPE_BOOTLOADER 0x02 #define PART_SUBTYPE_BOOTLOADER_PRIMARY 0x00 #define PART_SUBTYPE_BOOTLOADER_OTA 0x01 +#define PART_SUBTYPE_BOOTLOADER_RECOVERY 0x02 #define PART_TYPE_PARTITION_TABLE 0x03 #define PART_SUBTYPE_PARTITION_TABLE_PRIMARY 0x00 diff --git a/components/bootloader_support/src/bootloader_utility.c b/components/bootloader_support/src/bootloader_utility.c index 3071db642b..2d62c0b92c 100644 --- a/components/bootloader_support/src/bootloader_utility.c +++ b/components/bootloader_support/src/bootloader_utility.c @@ -208,6 +208,9 @@ bool bootloader_utility_load_partition_table(bootloader_state_t *bs) case PART_SUBTYPE_BOOTLOADER_OTA: partition_usage = "ota bootloader"; break; + case PART_SUBTYPE_BOOTLOADER_RECOVERY: + partition_usage = "recovery bootloader"; + break; } break; /* PART_TYPE_BOOTLOADER */ case PART_TYPE_PARTITION_TABLE: /* Partition table partition */ diff --git a/components/esp_partition/include/esp_partition.h b/components/esp_partition/include/esp_partition.h index 2a108ac0ac..d53d0cbaac 100644 --- a/components/esp_partition/include/esp_partition.h +++ b/components/esp_partition/include/esp_partition.h @@ -69,6 +69,7 @@ typedef enum { typedef enum { ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY = 0x00, //!< Primary Bootloader ESP_PARTITION_SUBTYPE_BOOTLOADER_OTA = 0x01, //!< Temporary OTA storage for Bootloader, where the OTA uploads a new Bootloader image + ESP_PARTITION_SUBTYPE_BOOTLOADER_RECOVERY = 0x02, //!< Recovery Bootloader ESP_PARTITION_SUBTYPE_PARTITION_TABLE_PRIMARY = 0x00, //!< Primary Partition table ESP_PARTITION_SUBTYPE_PARTITION_TABLE_OTA = 0x01, //!< Temporary OTA storage for Partition table, where the OTA uploads a new Partition table image diff --git a/components/esp_partition/partition_linux.c b/components/esp_partition/partition_linux.c index daeba66931..5301002760 100644 --- a/components/esp_partition/partition_linux.c +++ b/components/esp_partition/partition_linux.c @@ -80,6 +80,7 @@ const char *esp_partition_subtype_to_str(const uint32_t type, const uint32_t sub switch (subtype) { case PART_SUBTYPE_BOOTLOADER_PRIMARY: return "primary"; case PART_SUBTYPE_BOOTLOADER_OTA: return "ota"; + case PART_SUBTYPE_BOOTLOADER_RECOVERY: return "recovery"; default: return "unknown"; } case PART_TYPE_PARTITION_TABLE: diff --git a/components/partition_table/CMakeLists.txt b/components/partition_table/CMakeLists.txt index 89633d19e4..61fe492c7b 100644 --- a/components/partition_table/CMakeLists.txt +++ b/components/partition_table/CMakeLists.txt @@ -32,6 +32,10 @@ else() set(final_partition_target "build_partition_table") endif() +if(CONFIG_BOOTLOADER_RECOVERY_OFFSET) + set(recovery_bootloader_option --recovery-bootloader-offset ${CONFIG_BOOTLOADER_RECOVERY_OFFSET}) +endif() + if(NOT CONFIG_PARTITION_TABLE_MD5) set(md5_opt --disable-md5sum) endif() @@ -67,6 +71,7 @@ set(gen_partition_table "${python}" "${CMAKE_CURRENT_SOURCE_DIR}/gen_esp32part.p "-q" "--offset" "${PARTITION_TABLE_OFFSET}" "--primary-bootloader-offset" "${BOOTLOADER_OFFSET}" + "${recovery_bootloader_option}" "${md5_opt}" "${flashsize_opt}" "${partition_secure_opt}" ${extra_partition_subtypes} "--") diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index dea0d8d725..d8d19d6e7e 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -29,7 +29,7 @@ SECURE_NONE = None SECURE_V1 = 'v1' SECURE_V2 = 'v2' -__version__ = '1.3' +__version__ = '1.4' APP_TYPE = 0x00 DATA_TYPE = 0x01 @@ -60,6 +60,7 @@ SUBTYPES = { BOOTLOADER_TYPE: { 'primary': 0x00, 'ota': 0x01, + 'recovery': 0x02, }, PARTITION_TABLE_TYPE: { 'primary': 0x00, @@ -154,6 +155,7 @@ md5sum = True secure = SECURE_NONE offset_part_table = 0 primary_bootloader_offset = None +recovery_bootloader_offset = None def status(msg): @@ -464,10 +466,15 @@ class PartitionDefinition(object): return parse_int(strval) def parse_address(self, strval, ptype, psubtype): - if ptype == BOOTLOADER_TYPE and psubtype == SUBTYPES[ptype]['primary']: - if primary_bootloader_offset is None: - raise InputError(f'Primary bootloader offset is not defined. Please use --primary-bootloader-offset') - return primary_bootloader_offset + if ptype == BOOTLOADER_TYPE: + if psubtype == SUBTYPES[ptype]['primary']: + if primary_bootloader_offset is None: + raise InputError(f'Primary bootloader offset is not defined. Please use --primary-bootloader-offset') + return primary_bootloader_offset + if psubtype == SUBTYPES[ptype]['recovery']: + if recovery_bootloader_offset is None: + raise InputError(f'Recovery bootloader offset is not defined. Please use --recovery-bootloader-offset') + return recovery_bootloader_offset if ptype == PARTITION_TABLE_TYPE and psubtype == SUBTYPES[ptype]['primary']: return offset_part_table if strval == '': @@ -590,6 +597,7 @@ def main(): global offset_part_table global secure global primary_bootloader_offset + global recovery_bootloader_offset parser = argparse.ArgumentParser(description='ESP32 partition table utility') parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash', @@ -601,6 +609,7 @@ def main(): parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true') parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000') parser.add_argument('--primary-bootloader-offset', help='Set primary bootloader offset', default=None) + parser.add_argument('--recovery-bootloader-offset', help='Set recovery bootloader offset', default=None) parser.add_argument('--secure', help='Require app partitions to be suitable for secure boot', nargs='?', const=SECURE_V1, choices=[SECURE_V1, SECURE_V2]) parser.add_argument('--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*') parser.add_argument('input', help='Path to CSV or binary file to parse.', type=argparse.FileType('rb')) @@ -620,6 +629,8 @@ def main(): f'Unsupported configuration. Primary bootloader must be below partition table. ' f'Check --primary-bootloader-offset={primary_bootloader_offset:#x} and --offset={offset_part_table:#x}' ) + if args.recovery_bootloader_offset is not None: + recovery_bootloader_offset = int(args.recovery_bootloader_offset, 0) if args.extra_partition_subtypes: add_extra_subtypes(args.extra_partition_subtypes) diff --git a/components/partition_table/parttool.py b/components/partition_table/parttool.py index a979de98b9..03ef892058 100755 --- a/components/partition_table/parttool.py +++ b/components/partition_table/parttool.py @@ -14,7 +14,7 @@ import tempfile import gen_esp32part as gen -__version__ = '2.1' +__version__ = '2.2' COMPONENTS_PATH = os.path.expandvars(os.path.join('$IDF_PATH', 'components')) ESPTOOL_PY = os.path.join(COMPONENTS_PATH, 'esptool_py', 'esptool', 'esptool.py') @@ -56,13 +56,14 @@ PARTITION_BOOT_DEFAULT = _PartitionId() class ParttoolTarget(): - def __init__(self, port=None, baud=None, partition_table_offset=PARTITION_TABLE_OFFSET, primary_bootloader_offset=None, partition_table_file=None, - esptool_args=[], esptool_write_args=[], esptool_read_args=[], esptool_erase_args=[]): + def __init__(self, port=None, baud=None, partition_table_offset=PARTITION_TABLE_OFFSET, primary_bootloader_offset=None, recovery_bootloader_offset=None, + partition_table_file=None, esptool_args=[], esptool_write_args=[], esptool_read_args=[], esptool_erase_args=[]): self.port = port self.baud = baud gen.offset_part_table = partition_table_offset gen.primary_bootloader_offset = primary_bootloader_offset + gen.recovery_bootloader_offset = recovery_bootloader_offset def parse_esptool_args(esptool_args): results = list() @@ -241,6 +242,7 @@ def main(): parser.add_argument('--partition-table-offset', '-o', help='offset to read the partition table from', type=str) parser.add_argument('--primary-bootloader-offset', help='offset for primary bootloader', type=str) + parser.add_argument('--recovery-bootloader-offset', help='offset for recovery bootloader', type=str) parser.add_argument('--partition-table-file', '-f', help='file (CSV/binary) to read the partition table from; \ overrides device attached to specified port as the partition table source when defined') @@ -318,6 +320,9 @@ def main(): if args.primary_bootloader_offset: target_args['primary_bootloader_offset'] = int(args.primary_bootloader_offset, 0) + if args.recovery_bootloader_offset: + target_args['recovery_bootloader_offset'] = int(args.recovery_bootloader_offset, 0) + if args.esptool_args: target_args['esptool_args'] = args.esptool_args diff --git a/components/partition_table/project_include.cmake b/components/partition_table/project_include.cmake index bcbd571c35..41f8b2a70e 100644 --- a/components/partition_table/project_include.cmake +++ b/components/partition_table/project_include.cmake @@ -7,6 +7,12 @@ if(NOT DEFINED BOOTLOADER_OFFSET) # For Linux target endif() endif() +if(CONFIG_BOOTLOADER_RECOVERY_OFFSET) + set(RECOVERY_BOOTLOADER_OPTION --recovery-bootloader-offset ${CONFIG_BOOTLOADER_RECOVERY_OFFSET}) +else() + set(RECOVERY_BOOTLOADER_OPTION "") +endif() + set(PARTITION_TABLE_CHECK_SIZES_TOOL_PATH "${CMAKE_CURRENT_LIST_DIR}/check_sizes.py") idf_build_get_property(build_dir BUILD_DIR) @@ -66,6 +72,7 @@ function(partition_table_get_partition_info result get_part_info_args part_info) ${idf_path}/components/partition_table/parttool.py -q --partition-table-offset ${PARTITION_TABLE_OFFSET} --primary-bootloader-offset ${BOOTLOADER_OFFSET} + ${RECOVERY_BOOTLOADER_OPTION} --partition-table-file ${PARTITION_CSV_PATH} get_partition_info ${get_part_info_args} --info ${part_info} ${extra_partition_subtypes} diff --git a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py index 13ea53149e..0c930a8c42 100755 --- a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py +++ b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py @@ -64,6 +64,7 @@ def _strip_trailing_ffs(binary_table): class CSVParserTests(Py23TestCase): def tearDown(self): gen_esp32part.primary_bootloader_offset = None + gen_esp32part.recovery_bootloader_offset = None gen_esp32part.offset_part_table = 0 def test_simple_partition(self): @@ -197,8 +198,10 @@ partition_table, partition_table, primary, N/A, N/A FactoryApp, app, factory, , 1M OtaBTLDR, bootloader, ota, , N/A OtaPrtTable, partition_table, ota, , N/A +RecoveryBTLDR, bootloader, recovery, N/A, N/A """ gen_esp32part.primary_bootloader_offset = 0x1000 + gen_esp32part.recovery_bootloader_offset = 0x200000 gen_esp32part.offset_part_table = 0x9000 part_table_size = 0x1000 bootloader_size = gen_esp32part.offset_part_table - gen_esp32part.primary_bootloader_offset @@ -218,6 +221,9 @@ OtaPrtTable, partition_table, ota, , N/A # OtaPrtTable self.assertEqual(t[4].offset, 0x118000) self.assertEqual(t[4].size, part_table_size) + # RecoveryBTLDR + self.assertEqual(t[5].offset, gen_esp32part.recovery_bootloader_offset) + self.assertEqual(t[5].size, bootloader_size) class BinaryOutputTests(Py23TestCase): diff --git a/docs/en/api-guides/partition-tables.rst b/docs/en/api-guides/partition-tables.rst index e2c5ec148b..664e0cb0cc 100644 --- a/docs/en/api-guides/partition-tables.rst +++ b/docs/en/api-guides/partition-tables.rst @@ -84,6 +84,7 @@ Here is an example of a CSV partition table that includes bootloader and partiti nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 1M, + recoveryBloader, bootloader, recovery, N/A, N/A, The ``gen_esp32part.py`` tool will replace each ``N/A`` with appropriate values based on the selected Kconfig options: {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH} for the bootloader offset and :ref:`CONFIG_PARTITION_TABLE_OFFSET` for the partition table offset. @@ -136,6 +137,7 @@ See enum :cpp:type:`esp_partition_subtype_t` for the full list of subtypes defin - ``primary`` (0x00). This is the 2nd stage bootloader, located at the {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH} address in flash memory. The tool automatically determines the appropriate size and offset for this subtype, so any size or offset specified for this subtype will be ignored. You can either leave these fields blank or use ``N/A`` as a placeholder. - ``ota`` (0x01). This is a temporary bootloader partition used by the bootloader OTA update functionality to download a new image. The tool ignores the size for this subtype, allowing you to leave it blank or use ``N/A``. You can only specify an offset, or leave it blank to have the tool calculate it based on the offsets of previously used partitions. + - ``recovery`` (0x02). This is the recovery bootloader partition used for safely performing OTA updates to the bootloader. The ``gen_esp32part.py`` tool automatically determines the address and size for this partition, so you can leave these fields blank or use ``N/A`` as a placeholder. The address must match an eFuse field, which is defined through a Kconfig option. If the normal bootloader loading path fails, the ROM bootloader will attempt to load the recovery partition at the address specified by the eFuse field. The size of the bootloader type is calculated by the ``gen_esp32part.py`` tool based on the specified ``--offset`` (the partition table offset) and ``--primary-partition-offset`` arguments. Specifically, the bootloader size is defined as (:ref:`CONFIG_PARTITION_TABLE_OFFSET` - {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH}). This calculated size applies to all subtypes of the bootloader.