Двоичный образ приложения, который записывается в раздел OTA [2], состоит из следующих структур:
1. esp_image_header_t: структура, описывающая режим SPI flash и количество сегментов памяти.
Структура esp_image_header_t содержит 24 байта, и находится в самом начале (со смещением 0) файла образа. Пример содержимого структуры в файле образа приложения (дамп 24 первых байт двоичного образа прошивки):
E9 - магическое число, маркер начала структуры esp_image_header_t.
07 - 7 сегментов памяти.
02 - режим программирования DIO для SPI flash (02 соответствует ESP_IMAGE_SPI_MODE_DIO из перечисления esp_image_spi_mode_t).
22 - скорость SPI flash 20 МГц (2 соответствует ESP_IMAGE_SPI_SPEED_20M из перечисления esp_image_spi_freq_t), размер SPI flash 4 мегабайта (2 соответствует ESP_IMAGE_FLASH_SIZE_4MB из перечисления esp_image_flash_size_t).
02 04 38 40 - адрес входа в приложение 0x40380402.
EE - означает, что режим защиты памяти flash от записи запрещен.
00 00 00 - нагрузочная способность ножек обмена с памятью SPI.
05 00 - приложение для чипа ESP32-C3 (0x0005 соответствует ESP_CHIP_ID_ESP32C3 из перечисления esp_chip_id_t).
01 - после контрольной суммы добавлена цифровая подпись SHA256.
2. esp_image_segment_header_t: структура, описывающая каждый сегмент, его длину и размещение в памяти ESP32, за которой идут данные длиной data_len.
За структурой esp_image_header_t непосредственно следует структура esp_image_segment_header_t, состоящая из 8 байт, и за ней идут данные сегмента. Пример расшифровки дампа первого сегмента приложения (дамп первых 320 байт двоичного файла прошивки приложения):
20 00 10 3С - адрес загрузки первого сегмента 0x3C100020.
38 63 06 00 - размер первого сегмента 0x00066338 байт.
Далее идут байты 32 54 CD AB, по ним мы узнаем магическое число маркера ESP_APP_DESC_MAGIC_WORD (==0xABCD5432) начала структуры esp_app_desc_t с описанием приложения:
32 54 CD AB - константа 0xABCD5432 (ESP_APP_DESC_MAGIC_WORD).
typedefstruct
{
uint8_t magic; /*!< Магическая константа ESP_IMAGE_HEADER_MAGIC (0xE9) */uint8_t segment_count; /*!< Количество сегментов памяти */uint8_t spi_mode; /*!< Режим чтения flash (esp_image_spi_mode_t как uint8_t) */uint8_t spi_speed:4; /*!< Частота flash (esp_image_spi_freq_t как uint8_t) */uint8_t spi_size:4; /*!< Размер микросхемы flash (esp_image_flash_size_t как uint8_t) */uint32_t entry_addr; /*!< Адрес входа */uint8_t wp_pin; /*!< Ножка WP, когда выводы SPI установлены через efuse (считываются из ROM bootloader,
IDF bootloader использует программное обеспечение для конфигурирования вывода WP,
и устанавливает это поле в 0xEE=disabled) */uint8_t spi_pin_drv[3]; /*!< Нагрузочная способность выводов SPI flash (считывается из ROM bootloader) */esp_chip_id_t chip_id; /*!< Идентификатор чипа */uint8_t min_chip_rev; /*!< Минимальная ревизия чипа, поддерживаемая образом(1) */uint8_t reserved[8]; /*!< Зарезервированные байты в заголовке, в настоящее время не используются(2) */uint8_t hash_appended; /*!< Если 1, то цифровая подпись SHA256 "simple hash" (всего образа) добавлена
после контрольной суммы. Подпись включена в длину образа. Эта подпись
отдельная для secure boot, и используется только для проверки целостности
образа. Для подписанных образов secure boot эта подпись добавляется после
этого (и simple hash добавлены в подписанные данные). */
} __attribute__((packed)) esp_image_header_t;
Примечания:
(1) Начиная с момента, когда Major и Minor ревизии чипа были представлены в eFuse чипа, это поле больше не используется. Однако из соображений совместимости разработчики оставили это поле и данные в нем. Вместо этого поля используйте min_chip_rev_full. ПО интерпретирует это поле как Major-версия чипов, и как Minor-версию для ESP32-C3. (2) В первых 4 байтах поля reserved могут находится поля для uint16_t min_chip_rev_full и uint16_t max_chip_rev_full. В них кодируются минимальная и максимальная версии чипа следующим образом: major * 100 + minor. В этом случае поле reserved[4] идет за полями min_chip_rev_full и max_chip_rev_full.
Идентификаторы чипа, сохраняемые в поле chip_id:
/**
* @brief ESP chip ID
*/
typedefenum
{
ESP_CHIP_ID_ESP32 =0x0000, /*!< ESP32 */
ESP_CHIP_ID_ESP32S2 =0x0002, /*!< ESP32-S2 */
ESP_CHIP_ID_ESP32C3 =0x0005, /*!< ESP32-C3 */
ESP_CHIP_ID_ESP32S3 =0x0009, /*!< ESP32-S3 */
ESP_CHIP_ID_ESP32H2 =0x000A, /*!< ESP32-H2 */// ESP32H2-TODO: IDF-3475
ESP_CHIP_ID_INVALID =0xFFFF/*!< Недопустимый chip ID (это значение было определено для гарантии,
что у esp_chip_id_t размер всегда будет равен 2 байтам) */
} __attribute__((packed)) esp_chip_id_t;
Структура esp_image_segment_header_t:
/**
* @brief Заголовок сегмента двоичного образа
*/
typedefstruct
{
uint32_t load_addr; /*!< Адрес сегмента */uint32_t data_len; /*!< Длина данных сегмента */
} esp_image_segment_header_t;
Смещение данных для каждого сегмента в образе вычисляется следующим образом:
Смещение для сегмента 0 = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t). Смещение для сегмента 1 = Смещение для сегмента 0 + длина сегмента 0 + sizeof(esp_image_segment_header_t). Смещение для сегмента 2 = Смещение для сегмента 1 + длина сегмента 1 + sizeof(esp_image_segment_header_t). ...
Количество сегментов определено в поле segment_count структуры esp_image_header_t. Оно не может быть больше константы ESP_IMAGE_MAX_SEGMENTS (в настоящее время 16).
Чтобы получить список сегментов вашего образа, запустите следующую команду:
Segment 1: len 0x13ce0 load 0x3f400020 file_offs 0x00000018 SOC_DROM
Segment 2: len 0x00000 load 0x3ff80000 file_offs 0x00013d00 SOC_RTC_DRAM
Segment 3: len 0x00000 load 0x3ff80000 file_offs 0x00013d08 SOC_RTC_DRAM
Segment 4: len 0x028e0 load 0x3ffb0000 file_offs 0x00013d10 DRAM
Segment 5: len 0x00000 load 0x3ffb28e0 file_offs 0x000165f8 DRAM
Segment 6: len 0x00400 load 0x40080000 file_offs 0x00016600 SOC_IRAM
Segment 7: len 0x09600 load 0x40080400 file_offs 0x00016a08 SOC_IRAM
Segment 8: len 0x62e4c load 0x400d0018 file_offs 0x00020010 SOC_IROM
Segment 9: len 0x06cec load 0x40089a00 file_offs 0x00082e64 SOC_IROM
Segment 10: len 0x00000 load 0x400c0000 file_offs 0x00089b58 SOC_RTC_IRAM
Segment 11: len 0x00004 load 0x50000000 file_offs 0x00089b60 SOC_RTC_DATA
Segment 12: len 0x00000 load 0x50000004 file_offs 0x00089b6c SOC_RTC_DATA
Segment 13: len 0x00000 load 0x50000004 file_offs 0x00089b74 SOC_RTC_DATA
Checksum: e8 (valid)
Validation Hash: 407089ca0eae2bbf83b4120979d3354b1c938a49cb7a0c997f240474ef2ec76b (valid)
Вы также можете увидеть информацию о сегментах в логе ESP-IDF, когда загружается ваше приложение:
Для дополнительной информации о типах сегментов памяти и их диапазонах адресов см. руководство по используемому чипу (например, для ESP32-C3 это PDF-документ "ESP32-C3 Technical Reference Manual", раздел System and Memory -> Embedded Memory).
3. В образе есть один байт контрольной суммы, который находится после последнего сегмента. Этот байт записывается на границу памяти, выровненную на 16 байт, поэтому образу приложения может понадобиться дополнение.
4. Если установлено в 1 поле hash_appended структуры esp_image_header_t, то добавляется контрольная сумма SHA256. Значение хэша SHA256 вычисляется по диапазону от первого байта и данных SHA256. Длина данных SHA256 составляет 32 байта.
5. Если опция CONFIG_SECURE_SIGNED_APPS_SCHEME установлена в ECDSA, то у образа имеется 68 дополнительных байт для сигнатуры ECDSA, которые включают version word (4 байта) и signature data (64 байта).
6. Если опция CONFIG_SECURE_SIGNED_APPS_SCHEME установлена в RSA или ECDSA (V2), то у образа приложения будет дополнительный сектор сигнатуры размером 4 килобайта. Для дополнительной информации по этому сектору сигнатуры см. документацию [3].
[Описание приложения]
Сегмент DROM бинарника приложения начинается со структуры esp_app_desc_t, которая содержит специальные поля, описывающие приложение:
/**
* @brief Описание приложения.
*/
typedefstruct
{
uint32_t magic_word; /*!< Магическое слово ESP_APP_DESC_MAGIC_WORD (==0xABCD5432) */uint32_t secure_version; /*!< Secure version */uint32_t reserv1[2]; /*!< reserv1 */char version[32]; /*!< Версия приложения */char project_name[32]; /*!< Имя проекта */char time[16]; /*!< Время компиляции */char date[16]; /*!< Дата компиляции */char idf_ver[32]; /*!< Версия IDF */uint8_t app_elf_sha256[32]; /*!< sha256 elf-файла */uint32_t reserv2[20]; /*!< reserv2 */
} esp_app_desc_t;
magic_word - 32-битная константа, маркер начала структуры описания приложения. secure_version - см. описание технологии Anti-rollback [4]. version - см. App version в статье [5](*). project_name - это имя приложения, заполненное из PROJECT_NAME(*). time, date - время и дата момента компиляции. idf_ver - версия ESP-IDF(*). app_elf_sha256 - содержит хэш sha256 для ELF-файла приложения.
Примечание (*): ASCIIZ-строка, максимальная длина которой 32 символа, включая null-символ терминатора. Например, если длина PROJECT_NAME превышает 31 символ, то лишние символы отбрасываются.
Эта структура полезна для идентификации образов, загруженных по радиоканалу (Over-the-Air, OTA [4]), и у неё фиксированное смещение = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t). Как только устройство примет первый фрагмент образа, содержащий эту структуру, у него уже будет вся информация об образе приложения, на основе которой может быть принято решение, должно ли обновление продолжиться, или нет.
Чтобы получить структуру esp_app_desc_t для текущего работающего приложения, используйте esp_app_get_description(). Чтобы получить структуру esp_app_desc_t для другого раздела OTA, используйте esp_ota_get_partition_description().
[Добавление пользовательской структуры в приложение]
Пользователи также имеют возможность применить аналогичную структуру с фиксированным смещением относительно начала образа. Для добавления пользовательской структуры к образу можно использовать следующий шаблон:
Смещение для пользовательской структуры будет равно sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t).
Для гарантии, что пользовательская структура находится в образе, даже если она не используется, необходимо в файл CMakeLists.txt добавить target_link_libraries(${COMPONENT_TARGET} "-u custom_app_desc").
В заголовочном файле components/bootloader_support/include/esp_app_format.h описаны все типы данных, используемые для определения формата образа приложения. Для дополнительной информации см. документацию [1].