7c155ab647
fixes for issues observed when using spi_flash This MR fixes three unrelated issues: - Race condition in spi_flash_enable_interrupts_caches_and_other_cpu when operations on unpinned tasks are performed. The issue is reported in https://github.com/espressif/esp-idf/pull/258 - esp_intr_noniram_disable doesn’t disable interrupts when compiled in release mode. This issue manifested itself with an illegal instruction exception when task WDT ISR was called at the time when flash was disabled. Fixes https://github.com/espressif/esp-idf/issues/263. - Tick hooks on CPU1 were not called if CPU0 scheduler was disabled for significant amount of time (which could happen when doing flash erase). The issue manifested itself as “INT WDT timeout on core 1” error. Fixes https://github.com/espressif/esp-idf/issues/219. See merge request !441 |
||
---|---|---|
.. | ||
include | ||
test | ||
cache_utils.c | ||
cache_utils.h | ||
component.mk | ||
flash_mmap.c | ||
flash_ops.c | ||
Kconfig | ||
partition.c | ||
README.rst | ||
spi_flash_rom_patch.c |
SPI flash related APIs ====================== Overview -------- 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. 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 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 There are some data alignment limitations which need to be considered when using spi_flash_read/spi_flash_write functions: - buffer in RAM must be 4-byte aligned - size must be 4-byte aligned - address in flash must be 4-byte aligned These alignment limitations are purely software, and should be removed in future versions. 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. 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. 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. To place a function into IRAM, use ``IRAM_ATTR`` attribute, e.g.:: #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. Partition table APIs -------------------- ESP-IDF uses 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. 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. 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``. 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.