Concurrency Constraints for flash on SPI1

The SPI0/1 bus is shared between the instruction & data cache (for firmware execution) and the SPI1 peripheral (controlled by the drivers including this SPI Flash driver). Hence, operations to SPI1 will cause significant influence to the whole system. This kind of operations include calling SPI Flash API or other drivers on SPI1 bus, any operations like read/write/erase or other user defined SPI operations, regardless to the main flash or other SPI slave devices.

On ESP32-C3, the config option CONFIG_SPI_FLASH_AUTO_SUSPEND (enabled by default) allows the cache to read flash & PSRAM concurrently with SPI1 operations. See When auto suspend is enabled for more details.

If this option is disabled, the caches must be disabled while reading/writing/erasing operations. There are some constraints using driver on the SPI1 bus, see When the caches are disabled. This constraints will cause more IRAM/DRAM usages.

When the caches are disabled

This means that all CPUs must be running code from IRAM and must only be reading data from DRAM while flash write operations occur. If you use the API functions documented here, then the caches will be disabled automatically and transparently. However, note that it will have some performance impact on other tasks in the system.

There are no such constraints and impacts for flash chips on other SPI buses than SPI0/1.

For differences between IRAM, DRAM, and flash cache, please refer to the application memory layout documentation.

See also OS functions, SPI Bus Lock.

IRAM-Safe Interrupt Handlers

If you have an interrupt handler that you want to execute while 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 that all data and functions accessed by these interrupt handlers, including the ones that handlers call, are located in IRAM or DRAM. There are two approaches to do this:

Using Attribute Macros

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 recognize that a variable or expression is constant (even if it is not marked const) and optimize it into flash, unless it is marked with DRAM_ATTR.

Using Linker Scripts

See Linker Script Generation for details.

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

When auto suspend is enabled

When auto suspend is enabled, the cache will be kept enabled while accessing the SPI1 bus (e.g. erasing/writing/reading main flash). The hardware handles the arbitration between them.

If SPI1 operation is short (like reading operation), the CPU and the cache will wait until the SPI1 operation is done. However if it’s an erasing, auto suspend will happen, interrupting the erasing, making the CPU able to read from cache in limited time.

This way some code/variables can be put into the flash/psram instead of IRAM/DRAM, while still able to be executed during flash erasing. This reduces the some usage of IRAM/DRAM.

Please note this feature has the overhead of the flash suspend/resume. The flash erasing can be extremely long if the erasing is interrupted too often. Use FreeRTOS task priorities to ensure that only real-time critical tasks are executed at higher priority than flash erase, to allow the flash erase to complete in reasonable time.

In other words, there are three kinds of code:

  1. Critical code: inside IRAM/DRAM. This kind of code usually has high performance requirements, related to cache/flash/psram, or called very often.

  2. Cached code: inside flash/psram. This kind of code has lower performance requirements or called less often. They will execute during erasing, with some overhead.

  3. Low priority code: inside flash/psram and disabled during erasing. This kind of code should be forbidden from executed to avoid affecting the flash erasing, by setting a lower task priority than the erasing task.