Merge branch 'feature/docs_partition_ota_flash' into 'master'

Partition/SPI/OTA docs & OTA new functionality

* Update partition, SPI flash & OTA docs to reflect functionality changes
* Refactor OTA implementation to perform checks mentioned in API doc
* Add new functions to OTA API: esp_ota_get_running_partition() & esp_ota_get_next_update_partition() functions
* Add spi_flash_cache2phys() & spi_flash_phys2cache() functions to support esp_ota_get_running_partition()


See merge request !513
This commit is contained in:
Ivan Grokhotkov 2017-03-03 11:27:01 +08:00
commit 8911e666a0
27 changed files with 1057 additions and 348 deletions

View file

@ -33,6 +33,7 @@
#include "esp_ota_ops.h"
#include "rom/queue.h"
#include "rom/crc.h"
#include "soc/dport_reg.h"
#include "esp_log.h"
@ -42,7 +43,7 @@
typedef struct ota_ops_entry_ {
uint32_t handle;
esp_partition_t part;
const esp_partition_t *part;
uint32_t erased_size;
uint32_t wrote_size;
#ifdef CONFIG_FLASH_ENCRYPTION_ENABLED
@ -68,21 +69,38 @@ static ota_select s_ota_select[2];
const static char *TAG = "esp_ota_ops";
/* Return true if this is an OTA app partition */
static bool is_ota_partition(const esp_partition_t *p)
{
return (p != NULL
&& p->type == ESP_PARTITION_TYPE_APP
&& p->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_0
&& p->subtype < ESP_PARTITION_SUBTYPE_APP_OTA_MAX);
}
esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle)
{
ota_ops_entry_t *new_entry;
esp_err_t ret = ESP_OK;
if ((partition == NULL) || (out_handle == NULL)) {
return ESP_ERR_INVALID_ARG;
}
ota_ops_entry_t *new_entry = (ota_ops_entry_t *) calloc(sizeof(ota_ops_entry_t), 1);
if (new_entry == 0) {
return ESP_ERR_NO_MEM;
partition = esp_partition_verify(partition);
if (partition == NULL) {
return ESP_ERR_NOT_FOUND;
}
// if input image size is 0 or OTA_SIZE_UNKNOWN, will erase all areas in this partition
if (!is_ota_partition(partition)) {
return ESP_ERR_INVALID_ARG;
}
if (partition == esp_ota_get_running_partition()) {
return ESP_ERR_OTA_PARTITION_CONFLICT;
}
// If input image size is 0 or OTA_SIZE_UNKNOWN, erase entire partition
if ((image_size == 0) || (image_size == OTA_SIZE_UNKNOWN)) {
ret = esp_partition_erase_range(partition, 0, partition->size);
} else {
@ -90,11 +108,14 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp
}
if (ret != ESP_OK) {
free(new_entry);
new_entry = NULL;
return ret;
}
new_entry = (ota_ops_entry_t *) calloc(sizeof(ota_ops_entry_t), 1);
if (new_entry == NULL) {
return ESP_ERR_NO_MEM;
}
LIST_INSERT_HEAD(&s_ota_ops_entries_head, new_entry, entries);
if ((image_size == 0) || (image_size == OTA_SIZE_UNKNOWN)) {
@ -103,7 +124,7 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp
new_entry->erased_size = image_size;
}
memcpy(&new_entry->part, partition, sizeof(esp_partition_t));
new_entry->part = partition;
new_entry->handle = ++s_ota_ops_last_handle;
*out_handle = new_entry->handle;
return ESP_OK;
@ -165,7 +186,7 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
}
#endif
ret = esp_partition_write(&it->part, it->wrote_size, data_bytes, size);
ret = esp_partition_write(it->part, it->wrote_size, data_bytes, size);
if(ret == ESP_OK){
it->wrote_size += size;
}
@ -215,13 +236,13 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle)
}
#endif
if (esp_image_basic_verify(it->part.address, true, &image_size) != ESP_OK) {
if (esp_image_basic_verify(it->part->address, true, &image_size) != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
}
#ifdef CONFIG_SECURE_BOOT_ENABLED
ret = esp_secure_boot_verify_signature(it->part.address, image_size);
ret = esp_secure_boot_verify_signature(it->part->address, image_size);
if (ret != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
@ -301,7 +322,7 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
//so current ota app sub type id is x , dest bin subtype is y,total ota app count is n
//seq will add (x + n*1 + 1 - seq)%n
if (SUB_TYPE_ID(subtype) >= ota_app_count) {
return ESP_ERR_NOT_FOUND;
return ESP_ERR_INVALID_ARG;
}
ret = esp_partition_mmap(find_partition, 0, find_partition->size, SPI_FLASH_MMAP_DATA, &result, &ota_data_map);
@ -321,9 +342,9 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
}
if (s_ota_select[0].ota_seq >= s_ota_select[1].ota_seq) {
return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 0, find_partition);
} else {
return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 1, find_partition);
} else {
return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 0, find_partition);
}
} else if (ota_select_valid(&s_ota_select[0])) {
@ -446,3 +467,79 @@ const esp_partition_t *esp_ota_get_boot_partition(void)
return esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
}
}
const esp_partition_t* esp_ota_get_running_partition(void)
{
/* Find the flash address of this exact function. By definition that is part
of the currently running firmware. Then find the enclosing partition. */
size_t phys_offs = spi_flash_cache2phys(esp_ota_get_running_partition);
assert (phys_offs != SPI_FLASH_CACHE2PHYS_FAIL); /* indicates cache2phys lookup is buggy */
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_ANY,
NULL);
assert(it != NULL); /* has to be at least one app partition */
while (it != NULL) {
const esp_partition_t *p = esp_partition_get(it);
if (p->address <= phys_offs && p->address + p->size > phys_offs) {
esp_partition_iterator_release(it);
return p;
}
it = esp_partition_next(it);
}
abort(); /* Partition table is invalid or corrupt */
}
const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from)
{
const esp_partition_t *default_ota = NULL;
bool next_is_result = false;
if (start_from == NULL) {
start_from = esp_ota_get_running_partition();
} else {
start_from = esp_partition_verify(start_from);
}
assert (start_from != NULL);
/* at this point, 'start_from' points to actual partition table data in flash */
/* Two possibilities: either we want the OTA partition immediately after the current running OTA partition, or we
want the first OTA partition in the table (for the case when the last OTA partition is the running partition, or
if the current running partition is not OTA.)
This loop iterates subtypes instead of using esp_partition_find, so we
get all OTA partitions in a known order (low slot to high slot).
*/
for (esp_partition_subtype_t t = ESP_PARTITION_SUBTYPE_APP_OTA_0;
t != ESP_PARTITION_SUBTYPE_APP_OTA_MAX;
t++) {
const esp_partition_t *p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, t, NULL);
if (p == NULL) {
continue;
}
if (default_ota == NULL) {
/* Default to first OTA partition we find,
will be used if nothing else matches */
default_ota = p;
}
if (p == start_from) {
/* Next OTA partition is the one to use */
next_is_result = true;
}
else if (next_is_result) {
return p;
}
}
return default_ota;
}

View file

@ -27,57 +27,76 @@ extern "C"
{
#endif
#define OTA_SIZE_UNKNOWN 0xffffffff
#define OTA_SIZE_UNKNOWN 0xffffffff /*!< Used for esp_ota_begin() if new image size is unknown */
#define ESP_ERR_OTA_BASE 0x1500 /*!< base error code for ota_ops api */
#define ESP_ERR_OTA_PARTITION_CONFLICT (ESP_ERR_OTA_BASE + 0x01) /*!< want to write or erase current running partition */
#define ESP_ERR_OTA_SELECT_INFO_INVALID (ESP_ERR_OTA_BASE + 0x02) /*!< ota data partition info is error */
#define ESP_ERR_OTA_VALIDATE_FAILED (ESP_ERR_OTA_BASE + 0x03) /*!< validate ota image failed */
#define ESP_ERR_OTA_BASE 0x1500 /*!< Base error code for ota_ops api */
#define ESP_ERR_OTA_PARTITION_CONFLICT (ESP_ERR_OTA_BASE + 0x01) /*!< Error if request was to write or erase the current running partition */
#define ESP_ERR_OTA_SELECT_INFO_INVALID (ESP_ERR_OTA_BASE + 0x02) /*!< Error if OTA data partition contains invalid content */
#define ESP_ERR_OTA_VALIDATE_FAILED (ESP_ERR_OTA_BASE + 0x03) /*!< Error if OTA app image is invalid */
/**
* @brief Opaque handle for application update obtained from app_ops.
* @brief Opaque handle for an application OTA update
*
* esp_ota_begin() returns a handle which is then used for subsequent
* calls to esp_ota_write() and esp_ota_end().
*/
typedef uint32_t esp_ota_handle_t;
/**
* @brief format input partition in flash to 0xFF as input image size,
* if unkown image size ,pass 0x0 or 0xFFFFFFFF, it will erase all the
* partition ,Otherwise, erase the required range
*
* @param partition Pointer to partition structure which need to be updated
* Must be non-NULL.
* @param image_size size of image need to be updated
* @param out_handle handle which should be used for esp_ota_write or esp_ota_end call
* @brief Commence an OTA update writing to the specified partition.
* @return:
* - ESP_OK: if format ota image OK
* - ESP_ERR_OTA_PARTITION_CONFLICT: operate current running bin
* - ESP_ERR_OTA_SELECT_INFO_INVALID: ota bin select info invalid
* The specified partition is erased to the specified image size.
*
* If image size is not yet known, pass OTA_SIZE_UNKNOWN which will
* cause the entire partition to be erased.
*
* On success, this function allocates memory that remains in use
* until esp_ota_end() is called with the returned handle.
*
* @param partition Pointer to info for partition which will receive the OTA update. Required.
* @param image_size Size of new OTA app image. Partition will be erased in order to receive this size of image. If 0 or OTA_SIZE_UNKNOWN, the entire partition is erased.
* @param out_handle On success, returns a handle which should be used for subsequent esp_ota_write() and esp_ota_end() calls.
* @return
* - ESP_OK: OTA operation commenced successfully.
* - ESP_ERR_INVALID_ARG: partition or out_handle arguments were NULL, or partition doesn't point to an OTA app partition.
* - ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation.
* - ESP_ERR_OTA_PARTITION_CONFLICT: Partition holds the currently running firmware, cannot update in place.
* - ESP_ERR_NOT_FOUND: Partition argument not found in partition table.
* - ESP_ERR_OTA_SELECT_INFO_INVALID: The OTA data partition contains invalid data.
* - ESP_ERR_INVALID_SIZE: Partition doesn't fit in configured flash size.
* - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed.
*/
esp_err_t esp_ota_begin(const esp_partition_t* partition, size_t image_size, esp_ota_handle_t* out_handle);
/**
* @brief Write data to input input partition
* @brief Write OTA update data to partition
*
* @param handle Handle obtained from esp_ota_begin
* @param data Pointer to data write to flash
* @param size data size of recieved data
* This function can be called multiple times as
* data is received during the OTA operation. Data is written
* sequentially to the partition.
*
* @return:
* - ESP_OK: if write flash data OK
* - ESP_ERR_OTA_PARTITION_CONFLICT: operate current running bin
* - ESP_ERR_OTA_SELECT_INFO_INVALID: ota bin select info invalid
* @param handle Handle obtained from esp_ota_begin
* @param data Data buffer to write
* @param size Size of data buffer in bytes.
*
* @return
* - ESP_OK: Data was written to flash successfully.
* - ESP_ERR_INVALID_ARG: handle is invalid.
* - ESP_ERR_OTA_VALIDATE_FAILED: First byte of image contains invalid app image magic byte.
* - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed.
* - ESP_ERR_OTA_SELECT_INFO_INVALID: OTA data partition has invalid contents
*/
esp_err_t esp_ota_write(esp_ota_handle_t handle, const void* data, size_t size);
/**
* @brief Finish the update and validate written data
* @brief Finish OTA update and validate newly written app image.
*
* @param handle Handle obtained from esp_ota_begin.
* @param handle Handle obtained from esp_ota_begin().
*
* @note After calling esp_ota_end(), the handle is no longer valid and any memory associated with it is freed (regardless of result).
*
* @return:
* @return
* - ESP_OK: Newly written OTA app image is valid.
* - ESP_ERR_NOT_FOUND: OTA handle was not found.
* - ESP_ERR_INVALID_ARG: Handle was never written to.
@ -87,27 +106,61 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void* data, size_t size);
esp_err_t esp_ota_end(esp_ota_handle_t handle);
/**
* @brief Set next boot partition, call system_restart() will switch to run it
* @brief Configure OTA data for a new boot partition
*
* @note if you want switch to run a bin file
* has never been checked before,please validate it's signature firstly
* @note If this function returns ESP_OK, calling esp_restart() will boot the newly configured app partition.
*
* @param partition Pointer to partition structure which need to boot
* @param partition Pointer to info for partition containing app image to boot.
*
* @return:
* - ESP_OK: if set next boot partition OK
* - ESP_ERR_OTA_SELECT_INFO_INVALID: ota bin select info invalid
* @return
* - ESP_OK: OTA data updated, next reboot will use specified partition.
* - ESP_ERR_INVALID_ARG: partition argument was NULL or didn't point to a valid OTA partition of type "app".
* - ESP_ERR_OTA_VALIDATE_FAILED: Partition contained invalid app image. Also returned if secure boot is enabled and signature validation failed.
* - ESP_ERR_NOT_FOUND: OTA data partition not found.
* - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash erase or write failed.
*/
esp_err_t esp_ota_set_boot_partition(const esp_partition_t* partition);
/**
* @brief Get partition info of current running image
*
* @return pointer to esp_partition_t structure, or NULL if no partition is found or
* operate flash failed,This pointer is valid for the lifetime of the application.
* @brief Get partition info of currently configured boot app
*
* If esp_ota_set_boot_partition() has been called, the partition which was set by that function will be returned.
*
* If esp_ota_set_boot_partition() has not been called, the result is
* equivalent to esp_ota_get_running_partition().
*
* @return Pointer to info for partition structure, or NULL if no partition is found or flash read operation failed. Returned pointer is valid for the lifetime of the application.
*/
const esp_partition_t* esp_ota_get_boot_partition(void);
/**
* @brief Get partition info of currently running app
*
* This function is different to esp_ota_get_boot_partition() in that
* it ignores any change of selected boot partition caused by
* esp_ota_set_boot_partition(). Only the app whose code is currently
* running will have its partition information returned.
*
* @return Pointer to info for partition structure, or NULL if no partition is found or flash read operation failed. Returned pointer is valid for the lifetime of the application.
*/
const esp_partition_t* esp_ota_get_running_partition(void);
/**
* @brief Return the next OTA app partition which should be written with a new firmware.
*
* Call this function to find an OTA app partition which can be passed to esp_ota_begin().
*
* Finds next partition round-robin, starting from the current running partition.
*
* @param start_from If set, treat this partition info as describing the current running partition. Can be NULL, in which case esp_ota_get_running_partition() is used to find the currently running partition. The result of this function is never the same as this argument.
*
* @return Pointer to info for partition which should be updated next. NULL result indicates invalid OTA data partition, or that no eligible OTA app slot partition was found.
*
*/
const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,5 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View file

@ -0,0 +1,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <unity.h>
#include <test_utils.h>
#include <esp_ota_ops.h>
/* These OTA tests currently don't assume an OTA partition exists
on the device, so they're a bit limited
*/
TEST_CASE("esp_ota_begin() verifies arguments", "[ota]")
{
const esp_partition_t *running = esp_ota_get_running_partition();
esp_partition_t partition;
static esp_ota_handle_t handle = 0;
if (handle != 0) { /* clean up from any previous test */
esp_ota_end(handle);
handle = 0;
}
/* running partition & configured boot partition are same */
TEST_ASSERT_NOT_NULL(running);
/* trying to 'begin' on running partition fails */
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_ota_begin(running, OTA_SIZE_UNKNOWN, &handle));
TEST_ASSERT_EQUAL(0, handle);
memcpy(&partition, running, sizeof(esp_partition_t));
partition.address--;
/* non existent partition fails */
TEST_ASSERT_EQUAL_HEX(ESP_ERR_NOT_FOUND, esp_ota_begin(&partition, OTA_SIZE_UNKNOWN, &handle));
TEST_ASSERT_EQUAL(0, handle);
}
TEST_CASE("esp_ota_get_next_update_partition logic", "[ota]")
{
const esp_partition_t *running = esp_ota_get_running_partition();
const esp_partition_t *factory = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
const esp_partition_t *ota_0 = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
const esp_partition_t *ota_1 = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);
const esp_partition_t *ota_2 = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_2, NULL);
TEST_ASSERT_NOT_NULL(running);
TEST_ASSERT_NOT_NULL(factory);
TEST_ASSERT_NOT_NULL(ota_0);
TEST_ASSERT_NOT_NULL(ota_1);
TEST_ASSERT_NULL(ota_2); /* this partition shouldn't exist in test partition table */
TEST_ASSERT_EQUAL_PTR(factory, running); /* this may not be true if/when we get OTA tests that do OTA updates */
/* (The test steps verify subtypes before verifying pointer equality, because the failure messages are more readable
this way.)
*/
/* Factory app OTA updates OTA 0 slot */
const esp_partition_t *p = esp_ota_get_next_update_partition(NULL);
TEST_ASSERT_EQUAL_HEX8(ESP_PARTITION_SUBTYPE_APP_OTA_0, p->subtype);
TEST_ASSERT_EQUAL_PTR(ota_0, p);
p = esp_ota_get_next_update_partition(factory);
TEST_ASSERT_EQUAL_HEX8(ESP_PARTITION_SUBTYPE_APP_OTA_0, p->subtype);
TEST_ASSERT_EQUAL_PTR(ota_0, p);
/* OTA slot 0 updates OTA slot 1 */
p = esp_ota_get_next_update_partition(ota_0);
TEST_ASSERT_EQUAL_HEX8(ESP_PARTITION_SUBTYPE_APP_OTA_1, p->subtype);
TEST_ASSERT_EQUAL_PTR(ota_1, p);
/* OTA slot 1 updates OTA slot 0 */
p = esp_ota_get_next_update_partition(ota_1);
TEST_ASSERT_EQUAL_HEX8(ESP_PARTITION_SUBTYPE_APP_OTA_0, p->subtype);;
TEST_ASSERT_EQUAL_PTR(ota_0, p);
}

View file

@ -107,14 +107,13 @@ void IRAM_ATTR call_start_cpu0()
}
/**
* @function : load_partition_table
* @description: Parse partition table, get useful data such as location of
* OTA info sector, factory app sector, and test app sector.
/** @brief Load partition table
*
* @inputs: bs bootloader state structure used to save the data
* @return: return true, if the partition table is loaded (and MD5 checksum is valid)
* Parse partition table, get useful data such as location of
* OTA data partition, factory app partition, and test app partition.
*
* @param bs bootloader state structure used to save read data
* @return return true if the partition table was succesfully loaded and MD5 checksum is valid.
*/
bool load_partition_table(bootloader_state_t* bs)
{

View file

@ -33,7 +33,7 @@
*
* @return true if flash encryption is enabled.
*/
static inline IRAM_ATTR bool esp_flash_encryption_enabled(void) {
static inline /** @cond */ IRAM_ATTR /** @endcond */ bool esp_flash_encryption_enabled(void) {
uint32_t flash_crypt_cnt = REG_GET_FIELD(EFUSE_BLK0_RDATA0_REG, EFUSE_RD_FLASH_CRYPT_CNT);
/* __builtin_parity is in flash, so we calculate parity inline */
bool enabled = false;

View file

@ -108,6 +108,16 @@ esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *p
*p_length = 0;
}
if (src_addr % SPI_FLASH_MMU_PAGE_SIZE != 0) {
/* Image must start on a 64KB boundary
(This is not a technical limitation, only the flash mapped regions need to be 64KB aligned. But the most
consistent way to do this is to have all the offsets internal to the image correctly 64KB aligned, and then
start the image on a 64KB boundary also.)
*/
return ESP_ERR_INVALID_ARG;
}
err = esp_image_load_header(src_addr, log_errors, &image_header);
if (err != ESP_OK) {
return err;

View file

@ -1,15 +1,15 @@
SPI flash related APIs
======================
SPI Flash APIs
==============
Overview
--------
Spi_flash component contains APIs related to reading, writing, erasing,
The spi_flash component contains APIs related to reading, writing, erasing,
memory mapping data in the external SPI flash. It also has higher-level
APIs which work with partition table and partitions.
APIs which work with partitions defined in the :doc:`partition table </partition-tables>`.
Note that all the functionality is limited to the "main" flash chip,
i.e. the flash chip from which program runs. For ``spi_flash_*`` functions,
this is software limitation. Underlying ROM functions which work with SPI flash
Note that all the functionality is limited to the "main" SPI flash chip,
the same SPI flash chip from which program runs. For ``spi_flash_*`` functions,
this is a software limitation. The underlying ROM functions which work with SPI flash
do not have provisions for working with flash chips attached to SPI peripherals
other than SPI0.
@ -24,74 +24,113 @@ This is the set of APIs for working with data in flash:
- ``spi_flash_erase_range`` used to erase range of addresses in flash
- ``spi_flash_get_chip_size`` returns flash chip size, in bytes, as configured in menuconfig
There are some data alignment limitations which need to be considered when using
spi_flash_read/spi_flash_write functions:
Generally, try to avoid using the raw SPI flash functions in favour of
partition-specific functions.
- buffer in RAM must be 4-byte aligned
- size must be 4-byte aligned
- address in flash must be 4-byte aligned
SPI Flash Size
--------------
These alignment limitations are purely software, and should be removed in future
versions.
The SPI flash size is configured by writing a field in the software bootloader
image header, flashed at offset 0x1000.
It is assumed that correct SPI flash chip size is set at compile time using
menuconfig. While run-time detection of SPI flash chip size is possible, it is
not implemented yet. Applications which need this (e.g. to provide one firmware
binary for different flash sizes) can do flash chip size detection and set
the correct flash chip size in ``chip_size`` member of ``g_rom_flashchip``
structure. This size is used by ``spi_flash_*`` functions for bounds checking.
By default, the SPI flash size is detected by esptool.py when this bootloader is
written to flash, and the header is updated with the correct
size. Alternatively, it is possible to generate a fixed flash size by disabling
detection in ``make menuconfig`` (under Serial Flasher Config).
SPI flash APIs disable instruction and data caches while reading/writing/erasing.
See implementation notes below on details how this happens. For application
this means that at some periods of time, code can not be run from flash,
and constant data can not be fetched from flash by the CPU. This is not an
issue for normal code which runs in a task, because SPI flash APIs prevent
other tasks from running while caches are disabled. This is an issue for
interrupt handlers, which can still be called while flash operation is in
progress. If the interrupt handler is not placed into IRAM, there is a
possibility that interrupt will happen at the time when caches are disabled,
which will cause an illegal instruction exception.
If it is necessary to override the configured flash size at runtime, is is
possible to set the ``chip_size`` member of ``g_rom_flashchip`` structure. This
size is used by ``spi_flash_*`` functions (in both software & ROM) for bounds
checking.
To prevent this, make sure that all ISR code, and all functions called from ISR
code are placed into IRAM, or are located in ROM. Most useful C library
functions are located in ROM, so they can be called from ISR.
Concurrency Constraints
-----------------------
To place a function into IRAM, use ``IRAM_ATTR`` attribute, e.g.::
Because the SPI flash is also used for firmware execution (via the instruction &
data caches), these caches much be disabled while reading/writing/erasing. This
means that both CPUs must be running code from IRAM and only reading data from
DRAM while flash write operations occur.
#include "esp_attr.h"
void IRAM_ATTR gpio_isr_handler(void* arg)
{
// ...
}
When flash encryption is enabled, ``spi_flash_read`` will read data as it is
stored in flash (without decryption), and ``spi_flash_write`` will write data
in plain text. In other words, ``spi_flash_read/write`` APIs don't have
provisions to deal with encrypted data.
Refer to the :ref:`application memory layout <memory-layout>` documentation for
an explanation of the differences between IRAM, DRAM and flash cache.
To avoid reading flash cache accidentally, when one CPU commences a flash write
or erase operation the other CPU is put into a blocked state and all
non-IRAM-safe interrupts are disabled on both CPUs, until the flash operation
completes.
.. _iram-safe-interrupt-handlers:
IRAM-Safe Interrupt Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have an interrupt handler that you want to execute even when a flash
operation is in progress (for example, for low latency operations), set the
``ESP_INTR_FLAG_IRAM`` flag when the :doc:`interrupt handler is registered
</api/system/intr_alloc>`.
You must ensure all data and functions accessed by these interrupt handlers are
located in IRAM or DRAM. This includes any functions that the handler calls.
Use the ``IRAM_ATTR`` attribute for functions::
#include "esp_attr.h"
void IRAM_ATTR gpio_isr_handler(void* arg)
{
// ...
}
Use the ``DRAM_ATTR`` and ``DRAM_STR`` attributes for constant data::
void IRAM_ATTR gpio_isr_handler(void* arg)
{
const static DRAM_ATTR uint8_t INDEX_DATA[] = { 45, 33, 12, 0 };
const static char *MSG = DRAM_STR("I am a string stored in RAM");
}
Note that knowing which data should be marked with ``DRAM_ATTR`` can be hard,
the compiler will sometimes recognise that a variable or expression is constant
(even if it is not marked ``const``) and optimise it into flash, unless it is
marked with ``DRAM_ATTR``.
If a function or symbol is not correctly put into IRAM/DRAM and the interrupt
handler reads from the flash cache during a flash operation, it will cause a
crash due to Illegal Instruction exception (for code which should be in IRAM) or
garbage data to be read (for constant data which should be in DRAM).
Partition table APIs
--------------------
ESP-IDF uses partition table to maintain information about various regions of
ESP-IDF projects use a partition table to maintain information about various regions of
SPI flash memory (bootloader, various application binaries, data, filesystems).
More information about partition tables can be found in docs/partition_tables.rst.
More information about partition tables can be found :doc:`here </partition-tables>`.
This component provides APIs to enumerate partitions found in the partition table
and perform operations on them. These functions are declared in ``esp_partition.h``:
- ``esp_partition_find`` used to search partition table for entries with specific type, returns an opaque iterator
- ``esp_partition_find`` used to search partition table for entries with
specific type, returns an opaque iterator
- ``esp_partition_get`` returns a structure describing the partition, for the given iterator
- ``esp_partition_next`` advances iterator to the next partition found
- ``esp_partition_iterator_release`` releases iterator returned by ``esp_partition_find``
- ``esp_partition_find_first`` is a convenience function which returns structure describing the first partition found by esp_partition_find
- ``esp_partition_read``, ``esp_partition_write``, ``esp_partition_erase_range`` are equivalent to ``spi_flash_read``, ``spi_flash_write``, ``spi_flash_erase_range``, but operate within partition boundaries
- ``esp_partition_find_first`` is a convenience function which returns structure
describing the first partition found by esp_partition_find
- ``esp_partition_read``, ``esp_partition_write``, ``esp_partition_erase_range``
are equivalent to ``spi_flash_read``, ``spi_flash_write``,
``spi_flash_erase_range``, but operate within partition boundaries
Most application code should use ``esp_partition_*`` APIs instead of lower level
``spi_flash_*`` APIs. Partition APIs do bounds checking and calculate correct
offsets in flash based on data stored in partition table.
SPI Flash Encryption
--------------------
It is possible to encrypt SPI flash contents, and have it transparenlty decrypted by hardware.
Refer to the :doc:`Flash Encryption documentation </security/flash-encryption>` for more details.
Memory mapping APIs
-------------------
@ -105,10 +144,10 @@ about memory mapping hardware.
Note that some number of 64KB pages is used to map the application
itself into memory, so the actual number of available 64KB pages may be less.
Reading data from flash using a memory mapped region is the only way to decrypt
contents of flash when flash encryption is enabled. Decryption is performed at
hardware level.
contents of flash when :doc:`flash encryption </security/flash-encryption>` is enabled.
Decryption is performed at hardware level.
Memory mapping APIs are declared in ``esp_spi_flash.h`` and ``esp_partition.h``:
@ -119,40 +158,8 @@ Memory mapping APIs are declared in ``esp_spi_flash.h`` and ``esp_partition.h``:
Differences between ``spi_flash_mmap`` and ``esp_partition_mmap`` are as follows:
- ``spi_flash_mmap`` must be given a 64KB aligned physical address
- ``esp_partition_mmap`` may be given an arbitrary offset within the partition, it will adjust returned pointer to mapped memory as necessary
- ``esp_partition_mmap`` may be given an arbitrary offset within the partition,
it will adjust returned pointer to mapped memory as necessary
Note that because memory mapping happens in 64KB blocks, it may be possible to
read data outside of the partition provided to ``esp_partition_mmap``.
Implementation notes
--------------------
In order to perform some flash operations, we need to make sure both CPUs
are not running any code from flash for the duration of the flash operation.
In a single-core setup this is easy: we disable interrupts/scheduler and do
the flash operation. In the dual-core setup this is slightly more complicated.
We need to make sure that the other CPU doesn't run any code from flash.
When SPI flash API is called on CPU A (can be PRO or APP), we start
spi_flash_op_block_func function on CPU B using esp_ipc_call API. This API
wakes up high priority task on CPU B and tells it to execute given function,
in this case spi_flash_op_block_func. This function disables cache on CPU B and
signals that cache is disabled by setting s_flash_op_can_start flag.
Then the task on CPU A disables cache as well, and proceeds to execute flash
operation.
While flash operation is running, interrupts can still run on CPUs A and B.
We assume that all interrupt code is placed into RAM. Once interrupt allocation
API is added, we should add a flag to request interrupt to be disabled for
the duration of flash operations.
Once flash operation is complete, function on CPU A sets another flag,
s_flash_op_complete, to let the task on CPU B know that it can re-enable
cache and release the CPU. Then the function on CPU A re-enables the cache on
CPU A as well and returns control to the calling code.
Additionally, all API functions are protected with a mutex (s_flash_op_mutex).
In a single core environment (CONFIG_FREERTOS_UNICORE enabled), we simply
disable both caches, no inter-CPU communication takes place.
read data outside of the partition provided to ``esp_partition_mmap``.

View file

@ -74,16 +74,23 @@ static uint32_t s_mmap_last_handle = 0;
static void IRAM_ATTR spi_flash_mmap_init()
{
if (s_mmap_page_refcnt[0] != 0) {
return; /* mmap data already initialised */
}
for (int i = 0; i < REGIONS_COUNT * PAGES_PER_REGION; ++i) {
uint32_t entry_pro = DPORT_PRO_FLASH_MMU_TABLE[i];
uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i];
if (entry_pro != entry_app) {
// clean up entries used by boot loader
entry_pro = 0;
DPORT_PRO_FLASH_MMU_TABLE[i] = 0;
entry_pro = INVALID_ENTRY_VAL;
DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
}
if ((entry_pro & 0x100) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) {
if ((entry_pro & INVALID_ENTRY_VAL) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) {
s_mmap_page_refcnt[i] = 1;
} else {
DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
DPORT_APP_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
}
}
}
@ -108,9 +115,7 @@ esp_err_t IRAM_ATTR spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_
did_flush = spi_flash_ensure_unmodified_region(src_addr, size);
if (s_mmap_page_refcnt[0] == 0) {
spi_flash_mmap_init();
}
spi_flash_mmap_init();
// figure out the memory region where we should look for pages
int region_begin; // first page to check
int region_size; // number of pages to check
@ -139,7 +144,7 @@ esp_err_t IRAM_ATTR spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_
int pos;
for (pos = start; pos < start + page_count; ++pos, ++page) {
int table_val = (int) DPORT_PRO_FLASH_MMU_TABLE[pos];
uint8_t refcnt = s_mmap_page_refcnt[pos];
uint8_t refcnt = s_mmap_page_refcnt[pos];
if (refcnt != 0 && table_val != page) {
break;
}
@ -229,9 +234,7 @@ void IRAM_ATTR spi_flash_munmap(spi_flash_mmap_handle_t handle)
void spi_flash_mmap_dump()
{
if (s_mmap_page_refcnt[0] == 0) {
spi_flash_mmap_init();
}
spi_flash_mmap_init();
mmap_entry_t* it;
for (it = LIST_FIRST(&s_mmap_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
printf("handle=%d page=%d count=%d\n", it->handle, it->page, it->count);
@ -305,3 +308,62 @@ static inline IRAM_ATTR bool update_written_pages(size_t start_addr, size_t leng
}
return false;
}
uint32_t spi_flash_cache2phys(const void *cached)
{
intptr_t c = (intptr_t)cached;
size_t cache_page;
if (c >= VADDR1_START_ADDR && c < VADDR1_FIRST_USABLE_ADDR) {
/* IRAM address, doesn't map to flash */
return SPI_FLASH_CACHE2PHYS_FAIL;
}
else if (c < VADDR1_FIRST_USABLE_ADDR) {
/* expect cache is in DROM */
cache_page = (c - VADDR0_START_ADDR) / SPI_FLASH_MMU_PAGE_SIZE;
} else {
/* expect cache is in IROM */
cache_page = (c - VADDR1_START_ADDR) / SPI_FLASH_MMU_PAGE_SIZE + 64;
}
if (cache_page >= 256) {
/* cached address was not in IROM or DROM */
return SPI_FLASH_CACHE2PHYS_FAIL;
}
uint32_t phys_page = DPORT_PRO_FLASH_MMU_TABLE[cache_page];
if (phys_page == INVALID_ENTRY_VAL) {
/* page is not mapped */
return SPI_FLASH_CACHE2PHYS_FAIL;
}
uint32_t phys_offs = phys_page * SPI_FLASH_MMU_PAGE_SIZE;
return phys_offs | (c & (SPI_FLASH_MMU_PAGE_SIZE-1));
}
const void *spi_flash_phys2cache(uint32_t phys_offs, spi_flash_mmap_memory_t memory)
{
uint32_t phys_page = phys_offs / SPI_FLASH_MMU_PAGE_SIZE;
int start, end, page_delta;
intptr_t base;
if (memory == SPI_FLASH_MMAP_DATA) {
start = 0;
end = 64;
base = VADDR0_START_ADDR;
page_delta = 0;
} else {
start = PRO_IRAM0_FIRST_USABLE_PAGE;
end = 256;
base = VADDR1_START_ADDR;
page_delta = 64;
}
for (int i = start; i < end; i++) {
if (DPORT_PRO_FLASH_MMU_TABLE[i] == phys_page) {
i -= page_delta;
intptr_t cache_page = base + (SPI_FLASH_MMU_PAGE_SIZE * i);
return (const void *) (cache_page | (phys_offs & (SPI_FLASH_MMU_PAGE_SIZE-1)));
}
}
return NULL;
}

View file

@ -63,7 +63,7 @@ typedef enum {
ESP_PARTITION_SUBTYPE_APP_OTA_13 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 13,//!< OTA partition 13
ESP_PARTITION_SUBTYPE_APP_OTA_14 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 14,//!< OTA partition 14
ESP_PARTITION_SUBTYPE_APP_OTA_15 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 15,//!< OTA partition 15
ESP_PARTITION_SUBTYPE_APP_OTA_MAX = 15, //!< Max subtype of OTA partition
ESP_PARTITION_SUBTYPE_APP_OTA_MAX = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 16,//!< Max subtype of OTA partition
ESP_PARTITION_SUBTYPE_APP_TEST = 0x20, //!< Test application partition
ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00, //!< OTA selection partition
@ -165,6 +165,26 @@ esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator);
*/
void esp_partition_iterator_release(esp_partition_iterator_t iterator);
/**
* @brief Verify partition data
*
* Given a pointer to partition data, verify this partition exists in the partition table (all fields match.)
*
* This function is also useful to take partition data which may be in a RAM buffer and convert it to a pointer to the
* permanent partition data stored in flash.
*
* Pointers returned from this function can be compared directly to the address of any pointer returned from
* esp_partition_get(), as a test for equality.
*
* @param partition Pointer to partition data to verify. Must be non-NULL. All fields of this structure must match the
* partition table entry in flash for this function to return a successful match.
*
* @return
* - If partition not found, returns NULL.
* - If found, returns a pointer to the esp_partition_t structure in flash. This pointer is always valid for the lifetime of the application.
*/
const esp_partition_t *esp_partition_verify(const esp_partition_t *partition);
/**
* @brief Read data from the partition
*

View file

@ -197,6 +197,42 @@ void spi_flash_munmap(spi_flash_mmap_handle_t handle);
*/
void spi_flash_mmap_dump();
#define SPI_FLASH_CACHE2PHYS_FAIL UINT32_MAX /*<! Result from spi_flash_cache2phys() if flash cache address is invalid */
/**
* @brief Given a memory address where flash is mapped, return the corresponding physical flash offset.
*
* Cache address does not have have been assigned via spi_flash_mmap(), any address in flash map space can be looked up.
*
* @param cached Pointer to flashed cached memory.
*
* @return
* - SPI_FLASH_CACHE2PHYS_FAIL If cache address is outside flash cache region, or the address is not mapped.
* - Otherwise, returns physical offset in flash
*/
size_t spi_flash_cache2phys(const void *cached);
/** @brief Given a physical offset in flash, return the address where it is mapped in the memory space.
*
* Physical address does not have to have been assigned via spi_flash_mmap(), any address in flash can be looked up.
*
* @note Only the first matching cache address is returned. If MMU flash cache table is configured so multiple entries
* point to the same physical address, there may be more than one cache address corresponding to that physical
* address. It is also possible for a single physical address to be mapped to both the IROM and DROM regions.
*
* @note This function doesn't impose any alignment constraints, but if memory argument is SPI_FLASH_MMAP_INST and
* phys_offs is not 4-byte aligned, then reading from the returned pointer will result in a crash.
*
* @param phys_offs Physical offset in flash memory to look up.
* @param memory Memory type to look up a flash cache address mapping for (IROM or DROM)
*
* @return
* - NULL if the physical address is invalid or not mapped to flash cache of the specified memory type.
* - Cached memory address (in IROM or DROM space) corresponding to phys_offs.
*/
const void *spi_flash_phys2cache(size_t phys_offs, spi_flash_mmap_memory_t memory);
/**
* @brief SPI flash critical section enter function.
*/

View file

@ -85,6 +85,7 @@ esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t it)
assert(it);
// iterator reached the end of linked list?
if (it->next_item == NULL) {
esp_partition_iterator_release(it);
return NULL;
}
_lock_acquire(&s_partition_list_lock);
@ -200,6 +201,28 @@ const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator)
return iterator->info;
}
const esp_partition_t *esp_partition_verify(const esp_partition_t *partition)
{
assert(partition != NULL);
const char *label = (strlen(partition->label) > 0) ? partition->label : NULL;
esp_partition_iterator_t it = esp_partition_find(partition->type,
partition->subtype,
label);
while (it != NULL) {
const esp_partition_t *p = esp_partition_get(it);
/* Can't memcmp() whole structure here as padding contents may be different */
if (p->address == partition->address
&& partition->size == p->size
&& partition->encrypted == p->encrypted) {
esp_partition_iterator_release(it);
return p;
}
it = esp_partition_next(it);
}
esp_partition_iterator_release(it);
return NULL;
}
esp_err_t esp_partition_read(const esp_partition_t* partition,
size_t src_offset, void* dst, size_t size)
{

View file

@ -4,23 +4,35 @@
#include <freertos/semphr.h>
#include <unity.h>
#include <test_utils.h>
#include <esp_spi_flash.h>
#include <esp_attr.h>
#include <esp_flash_encrypt.h>
#include "test_config.h"
static void test_encrypted_write(size_t offset, const uint8_t *data, size_t length);
static void verify_erased_flash(size_t offset, size_t length);
static size_t start;
static void setup_tests()
{
if (start == 0) {
const esp_partition_t *part = get_test_data_partition();
start = part->address;
printf("Test data partition @ 0x%x\n", start);
}
}
TEST_CASE("test 16 byte encrypted writes", "[spi_flash]")
{
setup_tests();
if (!esp_flash_encryption_enabled()) {
TEST_IGNORE_MESSAGE("flash encryption disabled, skipping spi_flash_write_encrypted() tests");
}
TEST_ASSERT_EQUAL_HEX(ESP_OK,
spi_flash_erase_sector(TEST_REGION_START / SPI_FLASH_SEC_SIZE));
spi_flash_erase_sector(start / SPI_FLASH_SEC_SIZE));
uint8_t fortyeight_bytes[0x30]; // 0, 1, 2, 3, 4... 47
for(int i = 0; i < sizeof(fortyeight_bytes); i++) {
@ -29,38 +41,38 @@ TEST_CASE("test 16 byte encrypted writes", "[spi_flash]")
/* Verify unaligned start or length fails */
TEST_ASSERT_EQUAL_HEX(ESP_ERR_INVALID_ARG,
spi_flash_write_encrypted(TEST_REGION_START+1, fortyeight_bytes, 32));
spi_flash_write_encrypted(start+1, fortyeight_bytes, 32));
TEST_ASSERT_EQUAL_HEX(ESP_ERR_INVALID_SIZE,
spi_flash_write_encrypted(TEST_REGION_START, fortyeight_bytes, 15));
spi_flash_write_encrypted(start, fortyeight_bytes, 15));
/* ensure nothing happened to the flash yet */
verify_erased_flash(TEST_REGION_START, 0x20);
verify_erased_flash(start, 0x20);
/* Write 32 byte block, this is the "normal" encrypted write */
test_encrypted_write(TEST_REGION_START, fortyeight_bytes, 0x20);
verify_erased_flash(TEST_REGION_START + 0x20, 0x20);
test_encrypted_write(start, fortyeight_bytes, 0x20);
verify_erased_flash(start + 0x20, 0x20);
/* Slip in an unaligned spi_flash_read_encrypted() test */
uint8_t buf[0x10];
spi_flash_read_encrypted(TEST_REGION_START+0x10, buf, 0x10);
spi_flash_read_encrypted(start+0x10, buf, 0x10);
TEST_ASSERT_EQUAL_HEX8_ARRAY(fortyeight_bytes+0x10, buf, 16);
/* Write 16 bytes unaligned */
test_encrypted_write(TEST_REGION_START + 0x30, fortyeight_bytes, 0x10);
test_encrypted_write(start + 0x30, fortyeight_bytes, 0x10);
/* the 16 byte regions before and after the 16 bytes we just wrote should still be 0xFF */
verify_erased_flash(TEST_REGION_START + 0x20, 0x10);
verify_erased_flash(TEST_REGION_START + 0x40, 0x10);
verify_erased_flash(start + 0x20, 0x10);
verify_erased_flash(start + 0x40, 0x10);
/* Write 48 bytes starting at a 32-byte aligned offset */
test_encrypted_write(TEST_REGION_START + 0x40, fortyeight_bytes, 0x30);
test_encrypted_write(start + 0x40, fortyeight_bytes, 0x30);
/* 16 bytes after this write should still be 0xFF -unencrypted- */
verify_erased_flash(TEST_REGION_START + 0x70, 0x10);
verify_erased_flash(start + 0x70, 0x10);
/* Write 48 bytes starting at a 16-byte aligned offset */
test_encrypted_write(TEST_REGION_START + 0x90, fortyeight_bytes, 0x30);
test_encrypted_write(start + 0x90, fortyeight_bytes, 0x30);
/* 16 bytes after this write should still be 0xFF -unencrypted- */
verify_erased_flash(TEST_REGION_START + 0x120, 0x10);
verify_erased_flash(start + 0x120, 0x10);
}
static void test_encrypted_write(size_t offset, const uint8_t *data, size_t length)

View file

@ -8,43 +8,79 @@
#include <unity.h>
#include <esp_spi_flash.h>
#include <esp_attr.h>
#include <esp_partition.h>
#include <esp_flash_encrypt.h>
#include "test_config.h"
#include "test_utils.h"
static uint32_t buffer[1024];
/* read-only region used for mmap tests */
static const uint32_t start = 0x100000;
static const uint32_t end = 0x200000;
/* read-only region used for mmap tests, intialised in setup_mmap_tests() */
static uint32_t start;
static uint32_t end;
static spi_flash_mmap_handle_t handle1, handle2, handle3;
TEST_CASE("Prepare data for mmap tests", "[mmap]")
static void setup_mmap_tests()
{
if (start == 0) {
const esp_partition_t *part = get_test_data_partition();
start = part->address;
end = part->address + part->size;
printf("Test data partition @ 0x%x - 0x%x\n", start, end);
}
TEST_ASSERT(end > start);
TEST_ASSERT(end - start >= 512*1024);
/* clean up any mmap handles left over from failed tests */
if (handle1) {
spi_flash_munmap(handle1);
handle1 = 0;
}
if (handle2) {
spi_flash_munmap(handle2);
handle2 = 0;
}
if (handle3) {
spi_flash_munmap(handle3);
handle3 = 0;
}
/* prepare flash contents */
srand(0);
for (int block = start / 0x10000; block < end / 0x10000; ++block) {
printf("Writing block %d\n", block);
for (int sector = 0; sector < 16; ++sector) {
uint32_t abs_sector = (block * 16) + sector;
uint32_t sector_offs = abs_sector * SPI_FLASH_SEC_SIZE;
bool sector_needs_write = false;
ESP_ERROR_CHECK( spi_flash_read(sector_offs, buffer, sizeof(buffer)) );
for (uint32_t word = 0; word < 1024; ++word) {
uint32_t val = rand();
if (block == start / 0x10000 && sector == 0 && word == 0) {
printf("first word: %08x\n", val);
printf("setup_mmap_tests(): first prepped word: 0x%08x (flash holds 0x%08x)\n", val, buffer[word]);
}
if (buffer[word] != val) {
buffer[word] = val;
sector_needs_write = true;
}
buffer[word] = val;
}
uint32_t abs_sector = (block) * 16 + sector;
printf("Writing sector %d\n", abs_sector);
ESP_ERROR_CHECK( spi_flash_erase_sector((uint16_t) abs_sector) );
ESP_ERROR_CHECK( spi_flash_write(abs_sector * SPI_FLASH_SEC_SIZE, (const uint8_t *) buffer, sizeof(buffer)) );
/* Only rewrite the sector if it has changed */
if (sector_needs_write) {
printf("setup_mmap_tests(): Prepping sector %d\n", abs_sector);
ESP_ERROR_CHECK( spi_flash_erase_sector((uint16_t) abs_sector) );
ESP_ERROR_CHECK( spi_flash_write(sector_offs, (const uint8_t *) buffer, sizeof(buffer)) );
}
}
}
}
TEST_CASE("Can mmap into data address space", "[mmap]")
TEST_CASE("Can mmap into data address space", "[spi_flash]")
{
setup_mmap_tests();
printf("Mapping %x (+%x)\n", start, end - start);
spi_flash_mmap_handle_t handle1;
const void *ptr1;
ESP_ERROR_CHECK( spi_flash_mmap(start, end - start, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
printf("mmap_res: handle=%d ptr=%p\n", handle1, ptr1);
@ -54,36 +90,50 @@ TEST_CASE("Can mmap into data address space", "[mmap]")
srand(0);
const uint32_t *data = (const uint32_t *) ptr1;
for (int block = 0; block < (end - start) / 0x10000; ++block) {
printf("block %d\n", block);
for (int sector = 0; sector < 16; ++sector) {
printf("sector %d\n", sector);
for (uint32_t word = 0; word < 1024; ++word) {
TEST_ASSERT_EQUAL_UINT32(rand(), data[(block * 16 + sector) * 1024 + word]);
TEST_ASSERT_EQUAL_HEX32(rand(), data[(block * 16 + sector) * 1024 + word]);
}
}
}
printf("Mapping %x (+%x)\n", start - 0x10000, 0x20000);
spi_flash_mmap_handle_t handle2;
const void *ptr2;
ESP_ERROR_CHECK( spi_flash_mmap(start - 0x10000, 0x20000, SPI_FLASH_MMAP_DATA, &ptr2, &handle2) );
printf("mmap_res: handle=%d ptr=%p\n", handle2, ptr2);
TEST_ASSERT_EQUAL_HEX32(start - 0x10000, spi_flash_cache2phys(ptr2));
TEST_ASSERT_EQUAL_PTR(ptr2, spi_flash_phys2cache(start - 0x10000, SPI_FLASH_MMAP_DATA));
spi_flash_mmap_dump();
printf("Mapping %x (+%x)\n", start, 0x10000);
spi_flash_mmap_handle_t handle3;
const void *ptr3;
ESP_ERROR_CHECK( spi_flash_mmap(start, 0x10000, SPI_FLASH_MMAP_DATA, &ptr3, &handle3) );
printf("mmap_res: handle=%d ptr=%p\n", handle3, ptr3);
TEST_ASSERT_EQUAL_HEX32(start, spi_flash_cache2phys(ptr3));
TEST_ASSERT_EQUAL_PTR(ptr3, spi_flash_phys2cache(start, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR((intptr_t)ptr3 + 0x4444, spi_flash_phys2cache(start + 0x4444, SPI_FLASH_MMAP_DATA));
spi_flash_mmap_dump();
printf("Unmapping handle1\n");
spi_flash_munmap(handle1);
handle1 = 0;
spi_flash_mmap_dump();
printf("Unmapping handle2\n");
spi_flash_munmap(handle2);
handle2 = 0;
spi_flash_mmap_dump();
printf("Unmapping handle3\n");
spi_flash_munmap(handle3);
handle3 = 0;
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(start, SPI_FLASH_MMAP_DATA));
}
TEST_CASE("Can mmap into instruction address space", "[mmap]")
@ -134,19 +184,20 @@ TEST_CASE("Can mmap into instruction address space", "[mmap]")
TEST_CASE("flash_mmap invalidates just-written data", "[spi_flash]")
{
spi_flash_mmap_handle_t handle1;
const void *ptr1;
const size_t test_size = 128;
setup_mmap_tests();
if (esp_flash_encryption_enabled()) {
TEST_IGNORE_MESSAGE("flash encryption enabled, spi_flash_write_encrypted() test won't pass as-is");
}
ESP_ERROR_CHECK( spi_flash_erase_sector(TEST_REGION_START / SPI_FLASH_SEC_SIZE) );
ESP_ERROR_CHECK( spi_flash_erase_sector(start / SPI_FLASH_SEC_SIZE) );
/* map erased test region to ptr1 */
ESP_ERROR_CHECK( spi_flash_mmap(TEST_REGION_START, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
ESP_ERROR_CHECK( spi_flash_mmap(start, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
printf("mmap_res ptr1: handle=%d ptr=%p\n", handle1, ptr1);
/* verify it's all 0xFF */
@ -156,18 +207,19 @@ TEST_CASE("flash_mmap invalidates just-written data", "[spi_flash]")
/* unmap the erased region */
spi_flash_munmap(handle1);
handle1 = 0;
/* write flash region to 0xEE */
uint8_t buf[test_size];
memset(buf, 0xEE, test_size);
ESP_ERROR_CHECK( spi_flash_write(TEST_REGION_START, buf, test_size) );
ESP_ERROR_CHECK( spi_flash_write(start, buf, test_size) );
/* re-map the test region at ptr1.
this is a fresh mmap call so should trigger a cache flush,
ensuring we see the updated flash.
*/
ESP_ERROR_CHECK( spi_flash_mmap(TEST_REGION_START, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
ESP_ERROR_CHECK( spi_flash_mmap(start, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
printf("mmap_res ptr1 #2: handle=%d ptr=%p\n", handle1, ptr1);
/* assert that ptr1 now maps to the new values on flash,
@ -176,4 +228,63 @@ TEST_CASE("flash_mmap invalidates just-written data", "[spi_flash]")
TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, ptr1, test_size);
spi_flash_munmap(handle1);
handle1 = 0;
}
TEST_CASE("phys2cache/cache2phys basic checks", "[spi_flash]")
{
uint8_t buf[64];
static const uint8_t constant_data[] = { 1, 2, 3, 7, 11, 16, 3, 88 };
/* esp_partition_find is in IROM */
uint32_t phys = spi_flash_cache2phys(esp_partition_find);
TEST_ASSERT_NOT_EQUAL(SPI_FLASH_CACHE2PHYS_FAIL, phys);
TEST_ASSERT_EQUAL_PTR(esp_partition_find, spi_flash_phys2cache(phys, SPI_FLASH_MMAP_INST));
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(phys, SPI_FLASH_MMAP_DATA));
/* Read the flash @ 'phys' and compare it to the data we get via regular cache access */
spi_flash_read(phys, buf, sizeof(buf));
TEST_ASSERT_EQUAL_HEX32_ARRAY((void *)esp_partition_find, buf, sizeof(buf)/sizeof(uint32_t));
/* spi_flash_mmap is in IRAM */
printf("%p\n", spi_flash_mmap);
TEST_ASSERT_EQUAL_HEX32(SPI_FLASH_CACHE2PHYS_FAIL,
spi_flash_cache2phys(spi_flash_mmap));
/* 'constant_data' should be in DROM */
phys = spi_flash_cache2phys(&constant_data);
TEST_ASSERT_NOT_EQUAL(SPI_FLASH_CACHE2PHYS_FAIL, phys);
TEST_ASSERT_EQUAL_PTR(&constant_data,
spi_flash_phys2cache(phys, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(phys, SPI_FLASH_MMAP_INST));
/* Read the flash @ 'phys' and compare it to the data we get via normal cache access */
spi_flash_read(phys, buf, sizeof(constant_data));
TEST_ASSERT_EQUAL_HEX8_ARRAY(constant_data, buf, sizeof(constant_data));
}
TEST_CASE("mmap consistent with phys2cache/cache2phys", "[spi_flash]")
{
const void *ptr = NULL;
const size_t test_size = 2 * SPI_FLASH_MMU_PAGE_SIZE;
setup_mmap_tests();
TEST_ASSERT_EQUAL_HEX(SPI_FLASH_CACHE2PHYS_FAIL, spi_flash_cache2phys(ptr));
ESP_ERROR_CHECK( spi_flash_mmap(start, test_size, SPI_FLASH_MMAP_DATA, &ptr, &handle1) );
TEST_ASSERT_NOT_NULL(ptr);
TEST_ASSERT_NOT_EQUAL(0, handle1);
TEST_ASSERT_EQUAL_HEX(start, spi_flash_cache2phys(ptr));
TEST_ASSERT_EQUAL_HEX(start + 1024, spi_flash_cache2phys((void *)((intptr_t)ptr + 1024)));
TEST_ASSERT_EQUAL_HEX(start + 3000, spi_flash_cache2phys((void *)((intptr_t)ptr + 3000)));
/* this pointer lands in a different MMU table entry */
TEST_ASSERT_EQUAL_HEX(start + test_size - 4, spi_flash_cache2phys((void *)((intptr_t)ptr + test_size - 4)));
spi_flash_munmap(handle1);
handle1 = 0;
TEST_ASSERT_EQUAL_HEX(SPI_FLASH_CACHE2PHYS_FAIL, spi_flash_cache2phys(ptr));
}

View file

@ -21,16 +21,24 @@
#include <sys/param.h>
#include <unity.h>
#include <test_utils.h>
#include <esp_spi_flash.h>
#include <rom/spi_flash.h>
#include "../cache_utils.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
#include "test_config.h"
/* Base offset in flash for tests. */
#define FLASH_BASE TEST_REGION_START
static size_t start;
static void setup_tests()
{
if (start == 0) {
const esp_partition_t *part = get_test_data_partition();
start = part->address;
printf("Test data partition @ 0x%x\n", start);
}
}
#ifndef CONFIG_SPI_FLASH_MINIMAL_TEST
#define CONFIG_SPI_FLASH_MINIMAL_TEST 1
@ -66,21 +74,22 @@ static void IRAM_ATTR test_read(int src_off, int dst_off, int len)
fprintf(stderr, "src=%d dst=%d len=%d\n", src_off, dst_off, len);
memset(src_buf, 0xAA, sizeof(src_buf));
fill(((char *) src_buf) + src_off, src_off, len);
ESP_ERROR_CHECK(spi_flash_erase_sector((FLASH_BASE + src_off) / SPI_FLASH_SEC_SIZE));
ESP_ERROR_CHECK(spi_flash_erase_sector((start + src_off) / SPI_FLASH_SEC_SIZE));
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc = SPIWrite(FLASH_BASE, src_buf, sizeof(src_buf));
SpiFlashOpResult rc = SPIWrite(start, src_buf, sizeof(src_buf));
spi_flash_enable_interrupts_caches_and_other_cpu();
TEST_ASSERT_EQUAL_INT(rc, SPI_FLASH_RESULT_OK);
memset(dst_buf, 0x55, sizeof(dst_buf));
memset(dst_gold, 0x55, sizeof(dst_gold));
fill(dst_gold + dst_off, src_off, len);
ESP_ERROR_CHECK(spi_flash_read(FLASH_BASE + src_off, dst_buf + dst_off, len));
ESP_ERROR_CHECK(spi_flash_read(start + src_off, dst_buf + dst_off, len));
TEST_ASSERT_EQUAL_INT(cmp_or_dump(dst_buf, dst_gold, sizeof(dst_buf)), 0);
}
TEST_CASE("Test spi_flash_read", "[spi_flash_read]")
{
setup_tests();
#if CONFIG_SPI_FLASH_MINIMAL_TEST
test_read(0, 0, 0);
test_read(0, 0, 4);
@ -137,7 +146,7 @@ static void IRAM_ATTR test_write(int dst_off, int src_off, int len)
memset(src_buf, 0x55, sizeof(src_buf));
fill(src_buf + src_off, src_off, len);
// Fills with 0xff
ESP_ERROR_CHECK(spi_flash_erase_sector((FLASH_BASE + dst_off) / SPI_FLASH_SEC_SIZE));
ESP_ERROR_CHECK(spi_flash_erase_sector((start + dst_off) / SPI_FLASH_SEC_SIZE));
memset(dst_gold, 0xff, sizeof(dst_gold));
if (len > 0) {
int pad_left_off = (dst_off & ~3U);
@ -148,9 +157,9 @@ static void IRAM_ATTR test_write(int dst_off, int src_off, int len)
}
fill(dst_gold + dst_off, src_off, len);
}
ESP_ERROR_CHECK(spi_flash_write(FLASH_BASE + dst_off, src_buf + src_off, len));
ESP_ERROR_CHECK(spi_flash_write(start + dst_off, src_buf + src_off, len));
spi_flash_disable_interrupts_caches_and_other_cpu();
SpiFlashOpResult rc = SPIRead(FLASH_BASE, dst_buf, sizeof(dst_buf));
SpiFlashOpResult rc = SPIRead(start, dst_buf, sizeof(dst_buf));
spi_flash_enable_interrupts_caches_and_other_cpu();
TEST_ASSERT_EQUAL_INT(rc, SPI_FLASH_RESULT_OK);
TEST_ASSERT_EQUAL_INT(cmp_or_dump(dst_buf, dst_gold, sizeof(dst_buf)), 0);
@ -158,6 +167,7 @@ static void IRAM_ATTR test_write(int dst_off, int src_off, int len)
TEST_CASE("Test spi_flash_write", "[spi_flash_write]")
{
setup_tests();
#if CONFIG_SPI_FLASH_MINIMAL_TEST
test_write(0, 0, 0);
test_write(0, 0, 4);
@ -202,8 +212,8 @@ TEST_CASE("Test spi_flash_write", "[spi_flash_write]")
* NB: At the moment these only support aligned addresses, because memcpy
* is not aware of the 32-but load requirements for these regions.
*/
ESP_ERROR_CHECK(spi_flash_write(FLASH_BASE, (char *) 0x40000000, 16));
ESP_ERROR_CHECK(spi_flash_write(FLASH_BASE, (char *) 0x40070000, 16));
ESP_ERROR_CHECK(spi_flash_write(FLASH_BASE, (char *) 0x40078000, 16));
ESP_ERROR_CHECK(spi_flash_write(FLASH_BASE, (char *) 0x40080000, 16));
ESP_ERROR_CHECK(spi_flash_write(start, (char *) 0x40000000, 16));
ESP_ERROR_CHECK(spi_flash_write(start, (char *) 0x40070000, 16));
ESP_ERROR_CHECK(spi_flash_write(start, (char *) 0x40078000, 16));
ESP_ERROR_CHECK(spi_flash_write(start, (char *) 0x40080000, 16));
}

View file

@ -7,8 +7,6 @@
#include <esp_spi_flash.h>
#include <esp_attr.h>
#include "test_config.h"
struct flash_test_ctx {
uint32_t offset;
bool fail;

View file

@ -1,5 +1,12 @@
.. include:: ../../../components/spi_flash/README.rst
See also
--------
- :doc:`Partition Table documentation </partition-tables>`
- :doc:`Over The Air Update (OTA) API </api/system/ota>` provides high-level API for updating app firmware stored in flash.
- :doc:`Non-Volatile Storage (NVS) API <nvs_flash>` provides a structured API for storing small items of data in SPI flash.
API Reference
-------------
@ -19,6 +26,7 @@ Macros
.. doxygendefine:: SPI_FLASH_SEC_SIZE
.. doxygendefine:: SPI_FLASH_MMU_PAGE_SIZE
.. doxygendefine:: ESP_PARTITION_SUBTYPE_OTA
.. doxygendefine:: SPI_FLASH_CACHE2PHYS_FAIL
Type Definitions
^^^^^^^^^^^^^^^^
@ -52,6 +60,8 @@ Functions
.. doxygenfunction:: spi_flash_mmap
.. doxygenfunction:: spi_flash_munmap
.. doxygenfunction:: spi_flash_mmap_dump
.. doxygenfunction:: spi_flash_cache2phys
.. doxygenfunction:: spi_flash_phys2cache
.. doxygenfunction:: esp_partition_find
.. doxygenfunction:: esp_partition_find_first
.. doxygenfunction:: esp_partition_get
@ -63,3 +73,37 @@ Functions
.. doxygenfunction:: esp_partition_mmap
.. doxygenfunction:: esp_flash_encryption_enabled
.. _spi-flash-implementation-details:
Implementation details
----------------------
In order to perform some flash operations, we need to make sure both CPUs
are not running any code from flash for the duration of the flash operation.
In a single-core setup this is easy: we disable interrupts/scheduler and do
the flash operation. In the dual-core setup this is slightly more complicated.
We need to make sure that the other CPU doesn't run any code from flash.
When SPI flash API is called on CPU A (can be PRO or APP), we start
spi_flash_op_block_func function on CPU B using esp_ipc_call API. This API
wakes up high priority task on CPU B and tells it to execute given function,
in this case spi_flash_op_block_func. This function disables cache on CPU B and
signals that cache is disabled by setting s_flash_op_can_start flag.
Then the task on CPU A disables cache as well, and proceeds to execute flash
operation.
While flash operation is running, interrupts can still run on CPUs A and B.
We assume that all interrupt code is placed into RAM. Once interrupt allocation
API is added, we should add a flag to request interrupt to be disabled for
the duration of flash operations.
Once flash operation is complete, function on CPU A sets another flag,
s_flash_op_complete, to let the task on CPU B know that it can re-enable
cache and release the CPU. Then the function on CPU A re-enables the cache on
CPU A as well and returns control to the calling code.
Additionally, all API functions are protected with a mutex (s_flash_op_mutex).
In a single core environment (CONFIG_FREERTOS_UNICORE enabled), we simply
disable both caches, no inter-CPU communication takes place.

View file

@ -7,7 +7,7 @@ System API
Memory Allocation <mem_alloc>
Interrupt Allocation <intr_alloc>
Watchdogs <wdts>
OTA <ota>
Over The Air Updates (OTA) <ota>
Deep Sleep <deep_sleep>
Logging <log>

View file

@ -32,30 +32,56 @@ even when the int for DevB was cleared) the interrupt is never serviced.)
Multicore issues
----------------
Peripherals that can generate interrupts can be divided in two types: external peripherals, outside the Xtensa
cores in the ESP32, and internal peripherals, inside the ESP32. Interrupt handling differs slightly between
these two types of peripherals.
Peripherals that can generate interrupts can be divided in two types:
Each Xtensa core has its own set of internal peripherals: three timer comparators, a performance monitor and two
software interrupts. These peripherals can only be configured from the core they are associated with. When
generating an interrupt, the interrupt they generate is hard-wired to their associated core; it's not possible
to have e.g. an internal timer comparator of one core generate an interrupt on another core. That is why these
sources can only be managed using a task running on that specific core. Internal interrupt sources are still
allocatable using esp_intr_alloc as normal, but they cannot be shared and will always have a fixed interrupt
level (namely, the one associated in hardware with the peripheral). Internal interrupt sources are defined
in esp_intr_alloc.h as ETS_INTERNAL_*_INTR_SOURCE.
- External peripherals, within the ESP32 but outside the Xtensa cores themselves. Most ESP32 peripherals are of this type.
- Internal peripherals, part of the Xtensa CPU cores themselves.
The remaining interrupt slots in both cores are wired to an interrupt multiplexer, which can be used to
route any external interrupt source to any of these interrupt slots. Allocating an external interrupt will always
allocate it on the core that does the allocation, and freeing the interrupt should always happen on the same
core. Disabling and enabling the interrupt from another core is allowed, however. External interrupts can
share an interrupt slot bu passing ESP_INTR_FLAG_SHARED as a flag to esp_intr_alloc. External interrupt sources
are defined in soc/soc.h as ETS_*_INTR_SOURCE.
Interrupt handling differs slightly between these two types of peripherals.
Care should be taken when allocating an interrupt using a task not pinned to a certain core; while running
code not in a critical secion, these tasks can migrate between cores at any moment, possibly making an
interrupt operation fail because of the reasons mentioned above. It is advised to always use
xTaskCreatePinnedToCore with a specific CoreID argument to create tasks that will handle interrupts.
Internal peripheral interrupts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Each Xtensa CPU core has its own set of six internal peripherals:
- Three timer comparators
- A performance monitor
- Two software interrupts.
Internal interrupt sources are defined in esp_intr_alloc.h as ``ETS_INTERNAL_*_INTR_SOURCE``.
These peripherals can only be configured from the core they are associated with. When generating an interrupt,
the interrupt they generate is hard-wired to their associated core; it's not possible to have e.g. an internal
timer comparator of one core generate an interrupt on another core. That is why these sources can only be managed
using a task running on that specific core. Internal interrupt sources are still allocatable using esp_intr_alloc
as normal, but they cannot be shared and will always have a fixed interrupt level (namely, the one associated in
hardware with the peripheral).
External Peripheral Interrupts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The remaining interrupt sources are from external peripherals. These are defined in soc/soc.h as ``ETS_*_INTR_SOURCE``.
Non-internal interrupt slots in both CPU cores are wired to an interrupt multiplexer, which can be used to
route any external interrupt source to any of these interrupt slots.
- Allocating an external interrupt will always allocate it on the core that does the allocation.
- Freeing an external interrupt must always happen on the same core it was allocated on.
- Disabling and enabling external interrupts from another core is allowed.
- Multiple external interrupt sources can share an interrupt slot by passing ``ESP_INTR_FLAG_SHARED`` as a flag to esp_intr_alloc().
Care should be taken when calling esp_intr_alloc() from a task which is not pinned to a core. During task switching, these tasks can migrate between cores. Therefore it is impossible to tell which CPU the interrupt is allocated on, which makes it difficult to free the interrupt handle and may also cause debugging difficulties. It is advised to use xTaskCreatePinnedToCore() with a specific CoreID argument to create tasks that will allocate interrupts. In the case of internal interrupt sources, this is required.
IRAM-Safe Interrupt Handlers
----------------------------
The ``ESP_INTR_FLAG_IRAM`` flag registers an interrupt handler that always runs from IRAM (and reads all its data from DRAM), and therefore does not need to be disabled during flash erase and write operations.
This is useful for interrupts which need a guaranteed minimum execution latency, as flash write and erase operations can be slow (erases can take tens or hundreds of milliseconds to complete).
It can also be useful to keep an interrupt handler in IRAM if it is called very frequently, to avoid flash cache misses.
Refer to the :ref:`SPI flash API documentation <iram-safe-interrupt-handlers>` for more details.
Application Example
-------------------
@ -86,15 +112,6 @@ Macros
.. doxygendefine:: ESP_INTR_FLAG_IRAM
.. doxygendefine:: ESP_INTR_FLAG_INTRDISABLED
Type Definitions
^^^^^^^^^^^^^^^^
Enumerations
^^^^^^^^^^^^
Structures
^^^^^^^^^^
Functions
^^^^^^^^^

View file

@ -1,10 +1,47 @@
OTA
===
Over The Air Updates (OTA)
==========================
OTA Process Overview
^^^^^^^^^^^^^^^^^^^^
The OTA update mechanism allows a device to update itself based on data received while the normal firmware is running
(for example, over WiFi or Bluetooth.)
OTA requires configuring the :doc:`Partition Table </partition-tables>` of the device with at least two "OTA app slot"
partitions (ie `ota_0` and `ota_1`) and an "OTA Data Partition".
The OTA operation functions write a new app firmware image to whichever OTA app slot is not currently being used for
booting. Once the image is verified, the OTA Data partition is updated to specify that this image should be used for the
next boot.
.. _ota_data_partition:
OTA Data Partition
^^^^^^^^^^^^^^^^^^
An OTA data partition (type ``data``, subtype ``ota``) must be included in the :doc:`Partition Table </partition-tables>`
of any project which uses the OTA functions.
For factory boot settings, the OTA data partition should contain no data (all bytes erased to 0xFF). In this case the
esp-idf software bootloader will boot the factory app if it is present in the the partition table. If no factory app is
included in the partition table, the first available OTA slot (usually ``ota_0``) is booted.
After the first OTA update, the OTA data partition is updated to specify which OTA app slot partition should be booted next.
The OTA data partition is two flash sectors (0x2000 bytes) in size, to prevent problems if there is a power failure
while it is being written. Sectors are independently erased and written with matching data, and if they disagree a
counter field is used to determine which sector was written more recently.
See also
--------
* :doc:`Partition Table documentation </partition-tables>`
* :doc:`Lower-Level SPI Flash/Partition API </api/storage/spi_flash>`
Application Example
-------------------
Demonstration of OTA (over the air) firmware update workflow: :example:`system/ota`.
End-to-end example of OTA firmware update workflow: :example:`system/ota`.
API Reference
-------------
@ -21,6 +58,7 @@ Macros
.. doxygendefine:: ESP_ERR_OTA_PARTITION_CONFLICT
.. doxygendefine:: ESP_ERR_OTA_SELECT_INFO_INVALID
.. doxygendefine:: ESP_ERR_OTA_VALIDATE_FAILED
.. doxygendefine:: OTA_SIZE_UNKNOWN
Type Definitions
^^^^^^^^^^^^^^^^
@ -33,5 +71,7 @@ Functions
.. doxygenfunction:: esp_ota_begin
.. doxygenfunction:: esp_ota_write
.. doxygenfunction:: esp_ota_end
.. doxygenfunction:: esp_ota_get_running_partition
.. doxygenfunction:: esp_ota_set_boot_partition
.. doxygenfunction:: esp_ota_get_boot_partition
.. doxygenfunction:: esp_ota_get_next_update_partition

View file

@ -55,6 +55,7 @@ While PRO CPU does initialization in ``start_cpu0`` function, APP CPU spins in `
Main task is the task which runs ``app_main`` function. Main task stack size and priority can be configured in ``menuconfig``. Application can use this task for initial application-specific setup, for example to launch other tasks. Application can also use main task for event loops and other general purpose activities. If ``app_main`` function returns, main task is deleted.
.. _memory-layout:
Application memory layout
-------------------------

View file

@ -46,7 +46,6 @@ Here is the summary printed for the "Factory app, two OTA definitions" configura
* The type of all three are set as "app", but the subtype varies between the factory app at 0x10000 and the next two "OTA" apps.
* There is also a new "ota data" slot, which holds the data for OTA updates. The bootloader consults this data in order to know which app to execute. If "ota data" is empty, it will execute the factory app.
Creating Custom Tables
----------------------
@ -61,7 +60,7 @@ The CSV format is the same format as printed in the summaries shown above. Howev
factory, app, factory, 0x10000, 1M
ota_0, app, ota_0, , 1M
ota_1, app, ota_1, , 1M
* Whitespace between fields is ignored, and so is any line starting with # (comments).
* Each non-comment line in the CSV file is a partition definition.
* Only the offset for the first partition is supplied. The gen_esp32part.py tool fills in each remaining offset to start after the preceding partition.
@ -74,16 +73,50 @@ Name field can be any meaningful name. It is not significant to the ESP32. Names
Type field
~~~~~~~~~~
Type field can be specified as app (0) or data (1). Or it can be a number 0-254 (or as hex 0x00-0xFE). Types 0x00-0x3F are reserved for Espressif. If your application needs to store data, please add a custom partition type in the range 0x40-0xFE.
Partition type field can be specified as app (0) or data (1). Or it can be a number 0-254 (or as hex 0x00-0xFE). Types 0x00-0x3F are reserved for esp-idf core functions.
The bootloader ignores any types other than 0 & 1.
If your application needs to store data, please add a custom partition type in the range 0x40-0xFE.
The bootloader ignores any partition types other than app (0) & data (1).
Subtype
~~~~~~~
When type is "app", the subtype field can be specified as factory (0), ota_0 (0x10) ... ota_15 (0x1F) and test (0x20). Or it can be any number 0-255 (0x00-0xFF). The bootloader will execute the factory app unless there it sees a partition of type data/ota, in which case it reads this partition to determine which OTA image to boot
The 8-bit subtype field is specific to a given partition type.
When type is "data", the subtype field can be specified as ota (0), phy (1), nvs (2). Or it can be a number 0x00-0xFF. The bootloader ignores all data subtypes except for ota. Subtypes 0-0x7f are reserved for Espressif use. To create custom data partition subtypes use "data" type, and choose any unused subtype in 0x80-0xFF range. If you are porting a filesystem to the ESP-IDF, consider opening a PR to add the new subtype to esp_partition.h file.
esp-idf currently only specifies the meaning of the subtype field for "app" and "data" partition types.
App Subtypes
~~~~~~~~~~~~
When type is "app", the subtype field can be specified as factory (0), ota_0 (0x10) ... ota_15 (0x1F) or test (0x20).
- factory (0) is the default app partition. The bootloader will execute the factory app unless there it sees a partition of type data/ota, in which case it reads this partition to determine which OTA image to boot.
- OTA never updates the factory partition.
- If you want to conserve flash usage in an OTA project, you can remove the factory partition and use ota_0 instead.
- ota_0 (0x10) ... ota_15 (0x1F) are the OTA app slots. Refer to the :doc:`OTA documentation <api/system/ota>` for more details, which then use the OTA data partition to configure which app slot the bootloader should boot. If using OTA, an application should have at least two OTA application slots (ota_0 & ota_1). Refer to the :doc:`OTA documentation <api/system/ota>` for more details.
- test (0x2) is a reserved subtype for factory test procedures. It is not currently supported by the esp-idf bootloader.
Data Subtypes
~~~~~~~~~~~~~
When type is "data", the subtype field can be specified as ota (0), phy (1), nvs (2).
- ota (0) is the :ref:`OTA data partition <ota_data_partition>` which stores information about the currently selected OTA application. This partition should be 0x2000 bytes in size. Refer to the :ref:`OTA documentation <ota_data_partition>` for more details.
- phy (1) is for storing PHY initialisation data. This allows PHY to be configured per-device, instead of in firmware.
- In the default configuration, the phy partition is not used and PHY initialisation data is compiled into the app itself. As such, this partition can be removed from the partition table to save space.
- To load PHY data from this partition, run ``make menuconfig`` and enable "Component Config" -> "PHY" -> "Use a partition to store PHY init data". You will also need to flash your devices with phy init data as the esp-idf build system does not do this automatically.
- nvs (2) is for the :doc:`Non-Volatile Storage (NVS) API <api/storage/nvs_flash>`.
- NVS is used to store per-device PHY calibration data (different to initialisation data).
- NVS is used to store WiFi data if the :doc:`esp_wifi_set_storage(WIFI_STORAGE_FLASH) <api/wifi/esp_wifi>` initialisation function is used.
- The NVS API can also be used for other application data.
- It is strongly recommended that you include an NVS partition of at least 0x3000 bytes in your project.
- If using NVS API to store a lot of data, increase the NVS partition size from the default 0x6000 bytes.
Other data subtypes are reserved for future esp-idf uses.
Offset & Size
~~~~~~~~~~~~~
@ -92,28 +125,26 @@ Only the first offset field is required (we recommend using 0x10000). Partitions
App partitions have to be at offsets aligned to 0x10000 (64K). If you leave the offset field blank, the tool 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 M or K (1024 and 1024*1024 bytes).
NVS data partition has to be at least 0x3000 bytes long, and OTA data parition has to be 0x2000 bytes long. If you are using NVS in your application to store a lot of data, consider using a custom partition table with larger NVS partition.
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).
Generating Binary Partition Table
---------------------------------
The partition table which is flashed to the ESP32 is in a binary format, not CSV. The tool bin/gen_esp32part.py is used to convert between CSV and binary formats.
The partition table which is flashed to the ESP32 is in a binary format, not CSV. The tool :component_file:`partition_table/gen_esp32part.py` is used to convert between CSV and binary formats.
If you configure the partition table CSV name in ``make menuconfig`` and then ``make partition_table``, this conversion is done for you.
If you configure the partition table CSV name in ``make menuconfig`` and then ``make partition_table``, this conversion is done as part of the build process.
To convert CSV to Binary manually::
python bin/gen_esp32part.py --verify input_partitions.csv binary_partitions.bin
python gen_esp32part.py --verify input_partitions.csv binary_partitions.bin
To convert binary format back to CSV::
python bin/gen_esp32part.py --verify binary_partitions.bin input_partitions.csv
python gen_esp32part.py --verify binary_partitions.bin input_partitions.csv
To display the contents of a binary partition table on stdout (this is how the summaries displayed when running `make partition_table` are generated::
python bin/gen_esp32part.py binary_partitions.bin
python gen_esp32part.py binary_partitions.bin
``gen_esp32part.py`` takes one optional argument, ``--verify``, which will also verify the partition table during conversion (checking for overlapping partitions, unaligned partitions, etc.)

View file

@ -33,17 +33,14 @@
static const char *TAG = "ota";
/*an ota data write buffer ready to write to the flash*/
char ota_write_data[BUFFSIZE + 1] = { 0 };
static char ota_write_data[BUFFSIZE + 1] = { 0 };
/*an packet receive buffer*/
char text[BUFFSIZE + 1] = { 0 };
static char text[BUFFSIZE + 1] = { 0 };
/* an image total length*/
int binary_file_length = 0;
static int binary_file_length = 0;
/*socket id*/
int socket_id = -1;
char http_request[64] = {0};
/* operate handle : uninitialized value is zero ,every ota begin would exponential growth*/
esp_ota_handle_t out_handle = 0;
esp_partition_t operate_partition;
static int socket_id = -1;
static char http_request[64] = {0};
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
@ -109,7 +106,7 @@ static int read_until(char *buffer, char delim, int len)
* return true if packet including \r\n\r\n that means http packet header finished,start to receive packet body
* otherwise return false
* */
static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t out_handle)
static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t update_handle)
{
/* i means current position */
int i = 0, i_read_len = 0;
@ -122,7 +119,7 @@ static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t o
/*copy first http packet body to write buffer*/
memcpy(ota_write_data, &(text[i + 2]), i_write_len);
esp_err_t err = esp_ota_write( out_handle, (const void *)ota_write_data, i_write_len);
esp_err_t err = esp_ota_write( update_handle, (const void *)ota_write_data, i_write_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err);
return false;
@ -170,48 +167,6 @@ bool connect_to_http_server()
return false;
}
bool ota_init()
{
esp_err_t err;
const esp_partition_t *esp_current_partition = esp_ota_get_boot_partition();
if (esp_current_partition->type != ESP_PARTITION_TYPE_APP) {
ESP_LOGE(TAG, "Error esp_current_partition->type != ESP_PARTITION_TYPE_APP");
return false;
}
esp_partition_t find_partition;
memset(&operate_partition, 0, sizeof(esp_partition_t));
/*choose which OTA image should we write to*/
switch (esp_current_partition->subtype) {
case ESP_PARTITION_SUBTYPE_APP_FACTORY:
find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0;
break;
case ESP_PARTITION_SUBTYPE_APP_OTA_0:
find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_1;
break;
case ESP_PARTITION_SUBTYPE_APP_OTA_1:
find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0;
break;
default:
break;
}
find_partition.type = ESP_PARTITION_TYPE_APP;
const esp_partition_t *partition = esp_partition_find_first(find_partition.type, find_partition.subtype, NULL);
assert(partition != NULL);
memset(&operate_partition, 0, sizeof(esp_partition_t));
err = esp_ota_begin( partition, OTA_SIZE_UNKNOWN, &out_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed err=0x%x!", err);
return false;
} else {
memcpy(&operate_partition, partition, sizeof(esp_partition_t));
ESP_LOGI(TAG, "esp_ota_begin init OK");
return true;
}
return false;
}
void __attribute__((noreturn)) task_fatal_error()
{
ESP_LOGE(TAG, "Exiting task due to fatal error...");
@ -226,7 +181,19 @@ void __attribute__((noreturn)) task_fatal_error()
void main_task(void *pvParameter)
{
esp_err_t err;
/* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
esp_ota_handle_t update_handle = 0 ;
const esp_partition_t *update_partition = NULL;
ESP_LOGI(TAG, "Starting OTA example...");
const esp_partition_t *configured = esp_ota_get_boot_partition();
const esp_partition_t *running = esp_ota_get_running_partition();
assert(configured == running); /* fresh from reset, should be running from configured boot partition */
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)",
configured->type, configured->subtype, configured->address);
/* Wait for the callback to set the CONNECTED_BIT in the
event group.
*/
@ -252,12 +219,17 @@ void main_task(void *pvParameter)
ESP_LOGI(TAG, "Send GET request to server succeeded");
}
if ( ota_init() ) {
ESP_LOGI(TAG, "OTA Init succeeded");
} else {
ESP_LOGE(TAG, "OTA Init failed");
update_partition = esp_ota_get_next_update_partition(NULL);
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
update_partition->subtype, update_partition->address);
assert(update_partition != NULL);
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed, error=%d", err);
task_fatal_error();
}
ESP_LOGI(TAG, "esp_ota_begin succeeded");
bool resp_body_start = false, flag = true;
/*deal with all receive packet*/
@ -270,10 +242,10 @@ void main_task(void *pvParameter)
task_fatal_error();
} else if (buff_len > 0 && !resp_body_start) { /*deal with response header*/
memcpy(ota_write_data, text, buff_len);
resp_body_start = read_past_http_header(text, buff_len, out_handle);
resp_body_start = read_past_http_header(text, buff_len, update_handle);
} else if (buff_len > 0 && resp_body_start) { /*deal with response body*/
memcpy(ota_write_data, text, buff_len);
err = esp_ota_write( out_handle, (const void *)ota_write_data, buff_len);
err = esp_ota_write( update_handle, (const void *)ota_write_data, buff_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err);
task_fatal_error();
@ -291,11 +263,11 @@ void main_task(void *pvParameter)
ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length);
if (esp_ota_end(out_handle) != ESP_OK) {
if (esp_ota_end(update_handle) != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed!");
task_fatal_error();
}
err = esp_ota_set_boot_partition(&operate_partition);
err = esp_ota_set_boot_partition(update_partition);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%x", err);
task_fatal_error();

View file

@ -1,4 +1,4 @@
// Copyright 2010-2016 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -11,14 +11,14 @@
// 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.
// Common header for SPI flash test data
#pragma once
/* Define a region of flash we can mess up for testing...
// Utilities for esp-idf unit tests
#include <esp_partition.h>
/* Return the 'flash_test' custom data partition (type 0x55)
defined in the custom partition table.
*/
const esp_partition_t *get_test_data_partition();
This is pretty ugly, better to do something with a partition but
this is OK for now.
*/
#define TEST_REGION_START 0x180000
#define TEST_REGION_END 0x1E0000

View file

@ -0,0 +1,25 @@
// 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.
#include "unity.h"
#include "test_utils.h"
const esp_partition_t *get_test_data_partition()
{
/* This user type/subtype (0x55) is set in
partition_table_unit_test_app.csv */
const esp_partition_t *result = esp_partition_find_first(0x55, 0x55, NULL);
TEST_ASSERT_NOT_NULL(result); /* means partition table set wrong */
return result;
}

View file

@ -0,0 +1,12 @@
# Special partition table for unit test app
#
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, 0x9000, 0x4000
otadata, data, ota, 0xd000, 0x2000
phy_init, data, phy, 0xf000, 0x1000
factory, 0, 0, 0x10000, 1M
ota_0, 0, ota_0, , 1M
ota_1, 0, ota_1, , 1M
# flash_test partition used for SPI flash tests
flash_test, 0x55, 0x55, , 512K
1 # Special partition table for unit test app
2 #
3 # Name, Type, SubType, Offset, Size, Flags
4 # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
5 nvs, data, nvs, 0x9000, 0x4000
6 otadata, data, ota, 0xd000, 0x2000
7 phy_init, data, phy, 0xf000, 0x1000
8 factory, 0, 0, 0x10000, 1M
9 ota_0, 0, ota_0, , 1M
10 ota_1, 0, ota_1, , 1M
11 # flash_test partition used for SPI flash tests
12 flash_test, 0x55, 0x55, , 512K

View file

@ -21,12 +21,10 @@ CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
CONFIG_LOG_BOOTLOADER_LEVEL=2
#
# Secure boot configuration
# Security features
#
CONFIG_SECURE_BOOTLOADER_DISABLED=y
# CONFIG_SECURE_BOOTLOADER_ONE_TIME_FLASH is not set
# CONFIG_SECURE_BOOTLOADER_REFLASHABLE is not set
# CONFIG_SECURE_BOOTLOADER_ENABLED is not set
# CONFIG_SECURE_BOOT_ENABLED is not set
# CONFIG_FLASH_ENCRYPTION_ENABLED is not set
#
# Serial flasher config
@ -56,39 +54,58 @@ CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y
CONFIG_ESPTOOLPY_BEFORE_RESET=y
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
# CONFIG_ESPTOOLPY_BEFORE_ESP32R0 is not set
CONFIG_ESPTOOLPY_BEFORE="default_reset"
CONFIG_ESPTOOLPY_AFTER_RESET=y
# CONFIG_ESPTOOLPY_AFTER_NORESET is not set
CONFIG_ESPTOOLPY_AFTER="hard_reset"
# CONFIG_MONITOR_BAUD_9600B is not set
# CONFIG_MONITOR_BAUD_57600B is not set
CONFIG_MONITOR_BAUD_115200B=y
# CONFIG_MONITOR_BAUD_230400B is not set
# CONFIG_MONITOR_BAUD_921600B is not set
# CONFIG_MONITOR_BAUD_2MB is not set
# CONFIG_MONITOR_BAUD_OTHER is not set
CONFIG_MONITOR_BAUD_OTHER_VAL=115200
CONFIG_MONITOR_BAUD=115200
#
# Partition Table
#
CONFIG_PARTITION_TABLE_SINGLE_APP=y
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_CUSTOM is not set
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table_unit_test_app.csv"
CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
CONFIG_PARTITION_TABLE_FILENAME="partition_table_unit_test_app.csv"
CONFIG_APP_OFFSET=0x10000
CONFIG_PHY_DATA_OFFSET=0xf000
CONFIG_PHY_DATA_OFFSET=
CONFIG_OPTIMIZATION_LEVEL_DEBUG=y
# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set
#
# Component config
#
CONFIG_BTC_TASK_STACK_SIZE=2048
# CONFIG_BT_ENABLED is not set
CONFIG_BT_RESERVE_DRAM=0
#
# ESP32-specific config
# ESP32-specific
#
# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set
# CONFIG_ESP32_DEFAULT_CPU_FREQ_160 is not set
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
# CONFIG_ESP32_ENABLE_STACK_WIFI is not set
# CONFIG_ESP32_ENABLE_STACK_BT is not set
CONFIG_MEMMAP_SMP=y
# CONFIG_MEMMAP_TRACEMEM is not set
CONFIG_TRACEMEM_RESERVE_DRAM=0x0
# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set
# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set
CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
# CONFIG_ESP32_ENABLE_COREDUMP is not set
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2048
CONFIG_MAIN_TASK_STACK_SIZE=4096
@ -115,8 +132,17 @@ CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y
# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set
# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set
CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y
CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=0
CONFIG_WIFI_ENABLED=y
CONFIG_ESP32_WIFI_RX_BUFFER_NUM=10
CONFIG_PHY_ENABLED=y
#
# PHY
#
CONFIG_ESP32_PHY_AUTO_INIT=y
# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set
CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP32_PHY_MAX_TX_POWER=20
# CONFIG_ETHERNET is not set
@ -126,7 +152,6 @@ CONFIG_ESP32_PHY_MAX_TX_POWER=20
# CONFIG_FREERTOS_UNICORE is not set
CONFIG_FREERTOS_CORETIMER_0=y
# CONFIG_FREERTOS_CORETIMER_1 is not set
# CONFIG_FREERTOS_CORETIMER_2 is not set
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set
@ -161,7 +186,13 @@ CONFIG_LOG_COLORS=y
CONFIG_LWIP_MAX_SOCKETS=4
CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX=0
# CONFIG_LWIP_SO_REUSE is not set
# CONFIG_LWIP_SO_RCVBUF is not set
CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1
# CONFIG_LWIP_IP_FRAG is not set
# CONFIG_LWIP_IP_REASSEMBLY is not set
CONFIG_TCP_MAXRTX=12
CONFIG_TCP_SYNMAXRTX=6
# CONFIG_LWIP_DHCP_DOES_ARP_CHECK is not set
#
# mbedTLS
@ -175,6 +206,13 @@ CONFIG_MBEDTLS_HARDWARE_SHA=y
CONFIG_MBEDTLS_HAVE_TIME=y
# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set
#
# OpenSSL
#
# CONFIG_OPENSSL_DEBUG is not set
CONFIG_OPENSSL_ASSERT_DO_NOTHING=y
# CONFIG_OPENSSL_ASSERT_EXIT is not set
#
# SPI Flash driver
#