SPI Flash APIs

Overview

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 partitions defined in the partition table.

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.

SPI flash access APIs

This is the set of APIs for working with data in flash:

  • spi_flash_read used to read data from flash to RAM
  • spi_flash_write used to write data from RAM to flash
  • spi_flash_erase_sector used to erase individual sectors of 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

Generally, try to avoid using the raw SPI flash functions in favour of partition-specific functions.

SPI Flash Size

The SPI flash size is configured by writing a field in the software bootloader image header, flashed at offset 0x1000.

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).

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.

Concurrency Constraints

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.

Refer to the application 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

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 interrupt handler is registered.

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 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 here.

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_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

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 Flash Encryption documentation for more details.

Memory mapping APIs

ESP32 features memory hardware which allows regions of flash memory to be mapped into instruction and data address spaces. This mapping works only for read operations, it is not possible to modify contents of flash memory by writing to mapped memory region. Mapping happens in 64KB pages. Memory mapping hardware can map up to 4 megabytes of flash into data address space, and up to 16 megabytes of flash into instruction address space. See the technical reference manual for more details 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.

Memory mapping APIs are declared in esp_spi_flash.h and esp_partition.h:

  • spi_flash_mmap maps a region of physical flash addresses into instruction space or data space of the CPU
  • spi_flash_munmap unmaps previously mapped region
  • esp_partition_mmap maps part of a partition into the instruction space or data space of the CPU

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

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.

其它

API 参考手册

ESP_ERR_FLASH_BASE
ESP_ERR_FLASH_OP_FAIL
ESP_ERR_FLASH_OP_TIMEOUT
SPI_FLASH_SEC_SIZE

SPI Flash sector size

SPI_FLASH_MMU_PAGE_SIZE

Flash cache MMU mapping page size

ESP_PARTITION_SUBTYPE_OTA(i)

Convenience macro to get esp_partition_subtype_t value for the i-th OTA partition.

SPI_FLASH_CACHE2PHYS_FAIL

类型定义

typedef uint32_t spi_flash_mmap_handle_t

Opaque handle for memory region obtained from spi_flash_mmap.

typedef struct esp_partition_iterator_opaque_ *esp_partition_iterator_t

Opaque partition iterator type.

枚举

enum spi_flash_mmap_memory_t

Enumeration which specifies memory space requested in an mmap call.

Values:

SPI_FLASH_MMAP_DATA

map to data memory (Vaddr0), allows byte-aligned access, 4 MB total

SPI_FLASH_MMAP_INST

map to instruction memory (Vaddr1-3), allows only 4-byte-aligned access, 11 MB total

enum esp_partition_type_t

Partition type.

Note
Keep this enum in sync with PartitionDefinition class gen_esp32part.py

Values:

ESP_PARTITION_TYPE_APP = 0x00

Application partition type.

ESP_PARTITION_TYPE_DATA = 0x01

Data partition type.

enum esp_partition_subtype_t

Partition subtype.

Note
Keep this enum in sync with PartitionDefinition class gen_esp32part.py

Values:

ESP_PARTITION_SUBTYPE_APP_FACTORY = 0x00

Factory application partition.

ESP_PARTITION_SUBTYPE_APP_OTA_MIN = 0x10

Base for OTA partition subtypes.

ESP_PARTITION_SUBTYPE_APP_OTA_0 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 0

OTA partition 0.

ESP_PARTITION_SUBTYPE_APP_OTA_1 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 1

OTA partition 1.

ESP_PARTITION_SUBTYPE_APP_OTA_2 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 2

OTA partition 2.

ESP_PARTITION_SUBTYPE_APP_OTA_3 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 3

OTA partition 3.

ESP_PARTITION_SUBTYPE_APP_OTA_4 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 4

OTA partition 4.

ESP_PARTITION_SUBTYPE_APP_OTA_5 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 5

OTA partition 5.

ESP_PARTITION_SUBTYPE_APP_OTA_6 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 6

OTA partition 6.

ESP_PARTITION_SUBTYPE_APP_OTA_7 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 7

OTA partition 7.

ESP_PARTITION_SUBTYPE_APP_OTA_8 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 8

OTA partition 8.

ESP_PARTITION_SUBTYPE_APP_OTA_9 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 9

OTA partition 9.

ESP_PARTITION_SUBTYPE_APP_OTA_10 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 10

OTA partition 10.

ESP_PARTITION_SUBTYPE_APP_OTA_11 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 11

OTA partition 11.

ESP_PARTITION_SUBTYPE_APP_OTA_12 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 12

OTA partition 12.

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 = 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.

ESP_PARTITION_SUBTYPE_DATA_PHY = 0x01

PHY init data partition.

ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02

NVS partition.

ESP_PARTITION_SUBTYPE_DATA_COREDUMP = 0x03

COREDUMP partition.

ESP_PARTITION_SUBTYPE_DATA_ESPHTTPD = 0x80

ESPHTTPD partition.

ESP_PARTITION_SUBTYPE_DATA_FAT = 0x81

FAT partition.

ESP_PARTITION_SUBTYPE_DATA_SPIFFS = 0x82

SPIFFS partition.

ESP_PARTITION_SUBTYPE_ANY = 0xff

Used to search for partitions with any subtype.

结构体

struct esp_partition_t

partition information structure

This is not the format in flash, that format is esp_partition_info_t.

However, this is the format used by this API.

函数

void spi_flash_init()

Initialize SPI flash access driver.

This function must be called exactly once, before any other spi_flash_* functions are called. Currently this function is called from startup code. There is no need to call it from application code.

size_t spi_flash_get_chip_size()

Get flash chip size, as set in binary image header.

Note
This value does not necessarily match real flash size.
Return
size of flash chip, in bytes

esp_err_t spi_flash_erase_sector(size_t sector)

Erase the Flash sector.

Return
esp_err_t
Parameters
  • sector: Sector number, the count starts at sector 0, 4KB per sector.

esp_err_t spi_flash_erase_range(size_t start_address, size_t size)

Erase a range of flash sectors.

Return
esp_err_t
Parameters
  • start_address: Address where erase operation has to start. Must be 4kB-aligned
  • size: Size of erased range, in bytes. Must be divisible by 4kB.

esp_err_t spi_flash_write(size_t dest_addr, const void *src, size_t size)

Write data to Flash.

Note
If source address is in DROM, this function will return ESP_ERR_INVALID_ARG.
Return
esp_err_t
Parameters
  • dest_addr: destination address in Flash. Must be a multiple of 4 bytes.
  • src: pointer to the source buffer.
  • size: length of data, in bytes. Must be a multiple of 4 bytes.

esp_err_t spi_flash_write_encrypted(size_t dest_addr, const void *src, size_t size)

Write data encrypted to Flash.

Note
Flash encryption must be enabled for this function to work.
Note
Flash encryption must be enabled when calling this function. If flash encryption is disabled, the function returns ESP_ERR_INVALID_STATE. Use esp_flash_encryption_enabled() function to determine if flash encryption is enabled.
Note
Both dest_addr and size must be multiples of 16 bytes. For absolute best performance, both dest_addr and size arguments should be multiples of 32 bytes.
Return
esp_err_t
Parameters
  • dest_addr: destination address in Flash. Must be a multiple of 16 bytes.
  • src: pointer to the source buffer.
  • size: length of data, in bytes. Must be a multiple of 16 bytes.

esp_err_t spi_flash_read(size_t src_addr, void *dest, size_t size)

Read data from Flash.

Return
esp_err_t
Parameters
  • src_addr: source address of the data in Flash.
  • dest: pointer to the destination buffer
  • size: length of data

esp_err_t spi_flash_read_encrypted(size_t src, void *dest, size_t size)

Read data from Encrypted Flash.

If flash encryption is enabled, this function will transparently decrypt data as it is read. If flash encryption is not enabled, this function behaves the same as spi_flash_read().

See esp_flash_encryption_enabled() for a function to check if flash encryption is enabled.

Return
esp_err_t
Parameters
  • src: source address of the data in Flash.
  • dest: pointer to the destination buffer
  • size: length of data

esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t memory, const void **out_ptr, spi_flash_mmap_handle_t *out_handle)

Map region of flash memory into data or instruction address space.

This function allocates sufficient number of 64k MMU pages and configures them to map request region of flash memory into data address space or into instruction address space. It may reuse MMU pages which already provide required mapping. As with any allocator, there is possibility of fragmentation of address space if mmap/munmap are heavily used. To troubleshoot issues with page allocation, use spi_flash_mmap_dump function.

Return
ESP_OK on success, ESP_ERR_NO_MEM if pages can not be allocated
Parameters
  • src_addr: Physical address in flash where requested region starts. This address must be aligned to 64kB boundary (SPI_FLASH_MMU_PAGE_SIZE).
  • size: Size of region which has to be mapped. This size will be rounded up to a 64k boundary.
  • memory: Memory space where the region should be mapped
  • out_ptr: Output, pointer to the mapped memory region
  • out_handle: Output, handle which should be used for spi_flash_munmap call

void spi_flash_munmap(spi_flash_mmap_handle_t handle)

Release region previously obtained using spi_flash_mmap.

Note
Calling this function will not necessarily unmap memory region. Region will only be unmapped when there are no other handles which reference this region. In case of partially overlapping regions it is possible that memory will be unmapped partially.
Parameters
  • handle: Handle obtained from spi_flash_mmap

void spi_flash_mmap_dump()

Display information about mapped regions.

This function lists handles obtained using spi_flash_mmap, along with range of pages allocated to each handle. It also lists all non-zero entries of MMU table and corresponding reference counts.

size_t spi_flash_cache2phys(const void *cached)

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.

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
Parameters
  • cached: Pointer to flashed cached memory.

const void *spi_flash_phys2cache(size_t phys_offs, spi_flash_mmap_memory_t memory)

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.
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.
Parameters
  • phys_offs: Physical offset in flash memory to look up.
  • memory: Memory type to look up a flash cache address mapping for (IROM or DROM)

bool spi_flash_cache_enabled()

Check at runtime if flash cache is enabled on both CPUs.

Return
true if both CPUs have flash cache enabled, false otherwise.

esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, esp_partition_subtype_t subtype, const char *label)

Find partition based on one or more parameters.

Return
iterator which can be used to enumerate all the partitions found, or NULL if no partitions were found. Iterator obtained through this function has to be released using esp_partition_iterator_release when not used any more.
Parameters
  • type: Partition type, one of esp_partition_type_t values
  • subtype: Partition subtype, one of esp_partition_subtype_t values. To find all partitions of given type, use ESP_PARTITION_SUBTYPE_ANY.
  • label: (optional) Partition label. Set this value if looking for partition with a specific name. Pass NULL otherwise.

const esp_partition_t *esp_partition_find_first(esp_partition_type_t type, esp_partition_subtype_t subtype, const char *label)

Find first partition based on one or more parameters.

Return
pointer to esp_partition_t structure, or NULL if no partition is found. This pointer is valid for the lifetime of the application.
Parameters
  • type: Partition type, one of esp_partition_type_t values
  • subtype: Partition subtype, one of esp_partition_subtype_t values. To find all partitions of given type, use ESP_PARTITION_SUBTYPE_ANY.
  • label: (optional) Partition label. Set this value if looking for partition with a specific name. Pass NULL otherwise.

const esp_partition_t *esp_partition_get(esp_partition_iterator_t iterator)

Get esp_partition_t structure for given partition.

Return
pointer to esp_partition_t structure. This pointer is valid for the lifetime of the application.
Parameters
  • iterator: Iterator obtained using esp_partition_find. Must be non-NULL.

esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator)

Move partition iterator to the next partition found.

Any copies of the iterator will be invalid after this call.

Return
NULL if no partition was found, valid esp_partition_iterator_t otherwise.
Parameters
  • iterator: Iterator obtained using esp_partition_find. Must be non-NULL.

void esp_partition_iterator_release(esp_partition_iterator_t iterator)

Release partition iterator.

Parameters
  • iterator: Iterator obtained using esp_partition_find. Must be non-NULL.

esp_err_t esp_partition_read(const esp_partition_t *partition, size_t src_offset, void *dst, size_t size)

Read data from the partition.

Return
ESP_OK, if data was read successfully; ESP_ERR_INVALID_ARG, if src_offset exceeds partition size; ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition; or one of error codes from lower-level flash driver.
Parameters
  • partition: Pointer to partition structure obtained using esp_partition_find_first or esp_partition_get. Must be non-NULL.
  • dst: Pointer to the buffer where data should be stored. Pointer must be non-NULL and buffer must be at least ‘size’ bytes long.
  • src_offset: Address of the data to be read, relative to the beginning of the partition.
  • size: Size of data to be read, in bytes.

esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offset, const void *src, size_t size)

Write data to the partition.

Before writing data to flash, corresponding region of flash needs to be erased. This can be done using esp_partition_erase_range function.

Partitions marked with an encryption flag will automatically be written via the spi_flash_write_encrypted() function. If writing to an encrypted partition, all write offsets and lengths must be multiples of 16 bytes. See the spi_flash_write_encrypted() function for more details. Unencrypted partitions do not have this restriction.

Note
Prior to writing to flash memory, make sure it has been erased with esp_partition_erase_range call.
Return
ESP_OK, if data was written successfully; ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size; ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; or one of error codes from lower-level flash driver.
Parameters
  • partition: Pointer to partition structure obtained using esp_partition_find_first or esp_partition_get. Must be non-NULL.
  • dst_offset: Address where the data should be written, relative to the beginning of the partition.
  • src: Pointer to the source buffer. Pointer must be non-NULL and buffer must be at least ‘size’ bytes long.
  • size: Size of data to be written, in bytes.

esp_err_t esp_partition_erase_range(const esp_partition_t *partition, uint32_t start_addr, uint32_t size)

Erase part of the partition.

Return
ESP_OK, if the range was erased successfully; ESP_ERR_INVALID_ARG, if iterator or dst are NULL; ESP_ERR_INVALID_SIZE, if erase would go out of bounds of the partition; or one of error codes from lower-level flash driver.
Parameters
  • partition: Pointer to partition structure obtained using esp_partition_find_first or esp_partition_get. Must be non-NULL.
  • start_addr: Address where erase operation should start. Must be aligned to 4 kilobytes.
  • size: Size of the range which should be erased, in bytes. Must be divisible by 4 kilobytes.

esp_err_t esp_partition_mmap(const esp_partition_t *partition, uint32_t offset, uint32_t size, spi_flash_mmap_memory_t memory, const void **out_ptr, spi_flash_mmap_handle_t *out_handle)

Configure MMU to map partition into data memory.

Unlike spi_flash_mmap function, which requires a 64kB aligned base address, this function doesn’t impose such a requirement. If offset results in a flash address which is not aligned to 64kB boundary, address will be rounded to the lower 64kB boundary, so that mapped region includes requested range. Pointer returned via out_ptr argument will be adjusted to point to the requested offset (not necessarily to the beginning of mmap-ed region).

To release mapped memory, pass handle returned via out_handle argument to spi_flash_munmap function.

Return
ESP_OK, if successful
Parameters
  • partition: Pointer to partition structure obtained using esp_partition_find_first or esp_partition_get. Must be non-NULL.
  • offset: Offset from the beginning of partition where mapping should start.
  • size: Size of the area to be mapped.
  • memory: Memory space where the region should be mapped
  • out_ptr: Output, pointer to the mapped memory region
  • out_handle: Output, handle which should be used for spi_flash_munmap call

static bool esp_flash_encryption_enabled(void)

Is flash encryption currently enabled in hardware?

Flash encryption is enabled if the FLASH_CRYPT_CNT efuse has an odd number of bits set.

Return
true if flash encryption is enabled.

实现细节

为了执行某些 flash 操作,我们需要确保两个 CPU 在 flash 操作期间都没有从 flash 运行任何代码。在单核中,这非常简单:禁止中断/调度器,然后执行 flash 操作。在双核中,所谓有点复杂。我们需要确保其它 CPU 没有从 flash 上面运行任何代码。

当 SPI flahs API 在 CPU A(可以是 PRO 或者 APP)上被调用,我们使用 API esp_ipc_call 在 CPU B 上启动函数 spi_flash_op_block_func。这个 API 会唤醒 CPU B 上的高优先级任务,告诉它取执行所给函数,即 spi_flash_op_block_func。该函数子啊 CPU B 上 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.