fix(partition_table): Check partition size for type APP

The size of partition of type APP should be multiple of 4 KB. Partition
generation tool now make this as a mandatory requirement. This is
minimum flash erase size. If the size of the APP type partition is not
aligned to 4 KB then the last erase operation could go beyond the allocated
partition and hence may fail. This issue would only be observed when the
firmware size grows very close to the allocated partition size, and hence
causing the OTA update to fail.

For already deployed devices on-field with the size of APP partition not
aligned to flash sector boundary, it is best to ensure that firmware
size always remains within the lower 4 KB boundary of the total
allocated space. While migrating to ESP-IDF 5.3 release, partition table
for an existing project can be adjusted accordingly for the build to
succeed.

Found during discussion in https://github.com/espressif/esp-idf/pull/12460
This commit is contained in:
Harshit Malpani 2023-10-30 18:04:25 +05:30
parent 7c738c0658
commit bbbede07d0
No known key found for this signature in database
GPG Key ID: 441A8ACC7853D493
10 changed files with 50 additions and 34 deletions

View File

@ -98,14 +98,18 @@ def get_alignment_offset_for_type(ptype):
def get_alignment_size_for_type(ptype):
if ptype == APP_TYPE and secure == SECURE_V1:
if ptype == APP_TYPE:
if secure == SECURE_V1:
# For secure boot v1 case, app partition must be 64K aligned
# signature block (68 bytes) lies at the very end of 64K block
return 0x10000
if ptype == APP_TYPE and secure == SECURE_V2:
elif secure == SECURE_V2:
# For secure boot v2 case, app partition must be 4K aligned
# signature block (4K) is kept after padding the unsigned image to 64K boundary
return 0x1000
else:
# For no secure boot enabled case, app partition must be 4K aligned (min. flash erase size)
return 0x1000
# No specific size alignement requirement as such
return 0x1
@ -441,7 +445,7 @@ class PartitionDefinition(object):
offset_align = get_alignment_offset_for_type(self.type)
if self.offset % offset_align:
raise ValidationError(self, 'Offset 0x%x is not aligned to 0x%x' % (self.offset, offset_align))
if self.type == APP_TYPE and secure is not SECURE_NONE:
if self.type == APP_TYPE:
size_align = get_alignment_size_for_type(self.type)
if self.size % size_align:
raise ValidationError(self, 'Size 0x%x is not aligned to 0x%x' % (self.size, size_align))

View File

@ -410,14 +410,13 @@ ota_1, app, ota_1, , 0x100800
def rge(args):
return self._run_genesp32(sample_csv, args)
# Valid test that would pass with the above partition table
partfile = tempfile.mktemp()
self.assertEqual(rge([partfile]), b'Parsing CSV input...\nVerifying table...')
os.remove(partfile)
# Failure case 1, incorrect ota_0 partition size
# Failure case 1, incorrect ota_1 partition size
self.assertEqual(rge(['-q']),
b'Partition ota_1 invalid: Size 0x100800 is not aligned to 0x1000')
# Failure case 2, incorrect ota_0 partition size
self.assertEqual(rge(['-q', '--secure', 'v1']),
b'Partition ota_0 invalid: Size 0x101000 is not aligned to 0x10000')
# Failure case 2, incorrect ota_1 partition size
# Failure case 3, incorrect ota_1 partition size with Secure Boot V2
self.assertEqual(rge(['-q', '--secure', 'v2']),
b'Partition ota_1 invalid: Size 0x100800 is not aligned to 0x1000')

View File

@ -150,16 +150,19 @@ Extra Partition SubTypes
A component can define a new partition subtype by setting the ``EXTRA_PARTITION_SUBTYPES`` property. This property is a CMake list, each entry of which is a comma separated string with ``<type>, <subtype>, <value>`` format. The build system uses this property to add extra subtypes and creates fields named ``ESP_PARTITION_SUBTYPE_<type>_<subtype>`` in :cpp:type:`esp_partition_subtype_t`. The project can use this subtype to define partitions in the partitions table CSV file and use the new fields in :cpp:type:`esp_partition_subtype_t`.
.. _partition-offset-and-size:
Offset & Size
~~~~~~~~~~~~~
The offset represents the partition address in the SPI flash, which sector size is 0x1000 (4 KB). Thus, the offset must be a multiple of 4 KB.
.. list::
Partitions with blank offsets in the CSV file will start after the previous partition, or after the partition table in the case of the first partition.
Partitions of type ``app`` have to be placed at offsets aligned to 0x10000 (64 K). If you leave the offset field blank, ``gen_esp32part.py`` will automatically align the partition. If you specify an unaligned offset for an app partition, the tool will return an error.
Sizes and offsets can be specified as decimal numbers, hex numbers with the prefix 0x, or size multipliers K or M (1024 and 1024*1024 bytes).
- The offset represents the partition address in the SPI flash, which sector size is 0x1000 (4 KB). Thus, the offset must be a multiple of 4 KB.
- Partitions with blank offsets in the CSV file will start after the previous partition, or after the partition table in the case of the first partition.
- Partitions of type ``app`` have to be placed at offsets aligned to 0x10000 (64 KB). If you leave the offset field blank, ``gen_esp32part.py`` will automatically align the partition. If you specify an unaligned offset for an ``app`` partition, the tool will return an error.
- Partitions of type ``app`` should have the size aligned to the flash sector size (4 KB). If you specify an unaligned size for an ``app`` partition, the tool will return an error.
:SOC_SECURE_BOOT_V1: - If Secure Boot V1 is enabled, then the partition of type ``app`` needs to have size aligned to 0x10000 (64 KB) boundary.
- Sizes and offsets can be specified as decimal numbers, hex numbers with the prefix 0x, or size multipliers K or M (1024 and 1024*1024 bytes).
If you want the partitions in the partition table to work relative to any placement (:ref:`CONFIG_PARTITION_TABLE_OFFSET`) of the table itself, leave the offset field (in CSV file) for all partitions blank. Similarly, if changing the partition table offset then be aware that all blank partition offsets may change to match, and that any fixed offsets may now collide with the partition table (causing an error).

View File

@ -22,3 +22,10 @@ For example, the following code:
will now result in a compilation error. To fix this, add a semicolon at the end of the statement:
TEST_ASSERT(some_func() == ESP_OK);
Partition Table
---------------
Partition Table generation tool has been fixed to ensure that the size of partition of type ``app`` is having flash sector (minimum erase size) aligned size (please see :ref:`partition-offset-and-size`). If the partition does not have aligned size, partition table generator tool will raise an error. This fix ensures that OTA updates for a case where the file size is close or equal to the size of partition works correctly (erase operation does not go beyond the partition size).
In case you have the ``app`` partition size which is not a multiple of the 4 KB then please note that while migrating to ESP-IDF 5.3, you must align this size to its lower 4 KB boundary for the build to succeed. This does not impact the partition table for existing devices as such but ensures that generated firmware size remains within the OTA update capablilty limit.

View File

@ -150,16 +150,19 @@ SubType 字段长度为 8 bit内容与具体分区 Type 有关。目前esp
组件可以通过设置 ``EXTRA_PARTITION_SUBTYPES`` 属性来定义额外的分区子类型。 ``EXTRA_PARTITION_SUBTYPES`` 是一个 CMake 列表,其中的每个条目由字符串组成,以逗号为分隔,格式为 ``<type>, <subtype>, <value>``。构建系统通过该属性会自动添加额外的子类型,并在 :cpp:type:`esp_partition_subtype_t` 中插入名为 ``ESP_PARTITION_SUBTYPE_<type>_<subtype>`` 的字段。项目可以使用这个子类型来定义分区表 CSV 文件中的分区,并使用 :cpp:type:`esp_partition_subtype_t` 中的新字段。
偏移地址 (Offset) 和 Size 字段
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. _partition-offset-and-size:
偏移地址表示 SPI flash 中的分区地址,扇区大小为 0x1000 (4 KB)。 因此,偏移地址必须是 4 KB 的倍数。
偏移地址 (Offset) 和 大小 (Size) 字段
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
分区若偏移地址为空,则会紧跟着前一个分区之后开始;若为首个分区,则将紧跟着分区表开始。
.. list::
app 分区的偏移地址必须要与 0x10000 (64 K) 对齐,如果将偏移字段留空,``gen_esp32part.py`` 工具会自动计算得到一个满足对齐要求的偏移地址。如果 app 分区的偏移地址没有与 0x10000 (64 K) 对齐,则该工具会报错。
app 分区的大小和偏移地址可以采用十进制数、以 0x 为前缀的十六进制数,且支持 K 或 M 的倍数单位(分别代表 1024 和 1024*1024 字节)。
- 偏移地址表示 SPI flash 中的分区地址,扇区大小为 0x1000 (4 KB)。因此,偏移地址必须是 4 KB 的倍数。
- 若 CSV 文件中的分区偏移地址为空,则该分区会接在前一个分区之后;若为首个分区,则将接在分区表之后。
- ``app`` 分区的偏移地址必须与 0x10000 (64 KB) 对齐。如果偏移字段留空,则 ``gen_esp32part.py`` 工具会自动计算得到一个满足对齐要求的偏移地址。如果 ``app`` 分区的偏移地址没有与 0x10000 (64 KB) 对齐,则该工具会报错。
- ``app`` 分区的大小必须与 flash 扇区大小对齐。为 ``app`` 分区指定未对齐的大小将返回错误。
:SOC_SECURE_BOOT_V1: - 若启用了安全启动 V1则 ``app`` 分区的大小需与 0x10000 (64 KB) 对齐。
- ``app`` 分区的大小和偏移地址可以采用十进制数或是以 0x 为前缀的十六进制数,且支持 K 或 M 的倍数单位K 和 M 分别代表 1024 和 1024*1024 字节)。
如果你希望允许分区表中的分区采用任意起始偏移量 (:ref:`CONFIG_PARTITION_TABLE_OFFSET`)请将分区表CSV 文件)中所有分区的偏移字段都留空。注意,此时,如果你更改了分区表中任意分区的偏移地址,则其他分区的偏移地址也会跟着改变。这种情况下,如果你之前还曾设定某个分区采用固定偏移地址,则可能造成分区表冲突,从而导致报错。

View File

@ -2,4 +2,4 @@
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1200000,
factory, app, factory, 0x10000, 1200K,

1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1200000, factory, app, factory, 0x10000, 1200K,

View File

@ -2,6 +2,6 @@
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1150K,
factory, app, factory, 0x10000, 1148K,
zb_storage, data, fat, , 16K,
zb_fct, data, fat, , 1K,

1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1150K, factory, app, factory, 0x10000, 1148K,
6 zb_storage, data, fat, , 16K,
7 zb_fct, data, fat, , 1K,

View File

@ -2,6 +2,6 @@
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 650K,
factory, app, factory, 0x10000, 652K,
zb_storage, data, fat, 0xb3000, 16K,
zb_fct, data, fat, 0xb7000, 1K,

1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 650K, factory, app, factory, 0x10000, 652K,
6 zb_storage, data, fat, 0xb3000, 16K,
7 zb_fct, data, fat, 0xb7000, 1K,

View File

@ -2,6 +2,6 @@
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 650K,
factory, app, factory, 0x10000, 652K,
zb_storage, data, fat, 0xb3000, 16K,
zb_fct, data, fat, 0xb7000, 1K,

1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 650K, factory, app, factory, 0x10000, 652K,
6 zb_storage, data, fat, 0xb3000, 16K,
7 zb_fct, data, fat, 0xb7000, 1K,

View File

@ -31,9 +31,9 @@ def test_partition_nearly_full_warning(idf_py: IdfPyFunc, test_app_copy: Path, d
ret = idf_py('build')
# Build a first time to get the binary size and to check that no warning is issued.
assert 'partition is nearly full' not in ret.stdout, 'Warning for nearly full smallest partition was given when the condition is not fulfilled'
# Get the size of the binary, in KB. Add 1 to the total.
# Get the size of the binary, in KB. Convert it to next multiple of 4.
# The goal is to create an app partition which is slightly bigger than the binary itself
updated_file_size = int(os.stat(test_app_copy / 'build' / 'build_test_app.bin').st_size / 1024) + 1
updated_file_size = int((os.stat(test_app_copy / 'build' / 'build_test_app.bin').st_size + 4095) / 4096) * 4
idf_path = Path(default_idf_env['IDF_PATH'])
shutil.copy2(idf_path / 'components' / 'partition_table' / 'partitions_singleapp.csv', test_app_copy / 'partitions.csv')
replace_in_file(test_app_copy / 'partitions.csv',