diff --git a/components/spi_flash/esp_spi_flash.c b/components/spi_flash/esp_spi_flash.c index df919bc2d0..e3d09acaf0 100644 --- a/components/spi_flash/esp_spi_flash.c +++ b/components/spi_flash/esp_spi_flash.c @@ -21,6 +21,194 @@ #include #include #include +#include "sdkconfig.h" + +/* + Driver for SPI flash read/write/erase operations + + 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. + + SPI flash driver starts two tasks (spi_flash_op_block_task), one pinned to + each CPU. Each task is associated with its own semaphore. + + When SPI flash API is called on CPU A (can be PRO or APP), we wake up the task + on CPU B by "giving" the semaphore associated with it. Tasks resumes, disables + cache on CPU B, and acknowledges that it has taken the semaphore by setting + a flag (s_flash_op_can_start). Flash API function running on CPU A waits for + this flag to be set. Once the flag is set, it disables cache on CPU A and + starts flash operation. + + While flash operation is running, interrupts can still run on CPU B. + We assume that all interrupt code is placed into RAM. + + 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. Task on CPU B returns + to suspended state by "taking" the semaphore. + + 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. +*/ + +static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc); +extern void Cache_Flush(int); + +#ifndef CONFIG_FREERTOS_UNICORE + +static TaskHandle_t s_flash_op_tasks[2]; +static SemaphoreHandle_t s_flash_op_mutex; +static SemaphoreHandle_t s_flash_op_sem[2]; +static bool s_flash_op_can_start = false; +static bool s_flash_op_complete = false; + + +// Task whose duty is to block other tasks from running on a given CPU +static void IRAM_ATTR spi_flash_op_block_task(void* arg) +{ + uint32_t cpuid = (uint32_t) arg; + while (true) { + // Wait for flash operation to be initiated. + // This will be indicated by giving the semaphore corresponding to + // this CPU. + if (xSemaphoreTake(s_flash_op_sem[cpuid], portMAX_DELAY) != pdTRUE) { + // TODO: when can this happen? + abort(); + } + // Disable cache on this CPU + Cache_Read_Disable(cpuid); + // Signal to the flash API function that flash operation can start + s_flash_op_can_start = true; + while (!s_flash_op_complete) { + // until we have a way to use interrupts for inter-CPU communication, + // busy loop here and wait for the other CPU to finish flash operation + } + // Flash operation is complete, re-enable cache + Cache_Read_Enable(cpuid); + } + // TODO: currently this is unreachable code. Introduce spi_flash_uninit + // function which will signal to both tasks that they can shut down. + // Not critical at this point, we don't have a use case for stopping + // SPI flash driver yet. + // Also need to delete the semaphore here. + vTaskDelete(NULL); +} + +void spi_flash_init() +{ + s_flash_op_can_start = false; + s_flash_op_complete = false; + s_flash_op_sem[0] = xSemaphoreCreateBinary(); + s_flash_op_sem[1] = xSemaphoreCreateBinary(); + s_flash_op_mutex = xSemaphoreCreateMutex(); + // Start two tasks, one on each CPU, with max priorities + // TODO: optimize stack usage. Stack size 512 is too small. + xTaskCreatePinnedToCore(spi_flash_op_block_task, "flash_op_pro", 1024, (void*) 0, + configMAX_PRIORITIES - 1, &s_flash_op_tasks[0], 0); + xTaskCreatePinnedToCore(spi_flash_op_block_task, "flash_op_app", 1024, (void*) 1, + configMAX_PRIORITIES - 1, &s_flash_op_tasks[1], 1); +} + +static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() +{ + // Take the API lock + xSemaphoreTake(s_flash_op_mutex, portMAX_DELAY); + const uint32_t cpuid = xPortGetCoreID(); + uint32_t other_cpuid = !cpuid; + s_flash_op_can_start = false; + s_flash_op_complete = false; + // Signal to the spi_flash_op_block_task on the other CPU that we need it to + // disable cache there and block other tasks from executing. + xSemaphoreGive(s_flash_op_sem[other_cpuid]); + while (!s_flash_op_can_start) { + // Busy loop and wait for spi_flash_op_block_task to take the semaphore on the + // other CPU. + } + vTaskSuspendAll(); + // Disable cache on this CPU as well + Cache_Read_Disable(cpuid); +} + +static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() +{ + uint32_t cpuid = xPortGetCoreID(); + // Signal to spi_flash_op_block_task that flash operation is complete + s_flash_op_complete = true; + // Re-enable cache on this CPU + Cache_Read_Enable(cpuid); + // Release API lock + xSemaphoreGive(s_flash_op_mutex); + xTaskResumeAll(); +} + +#else // CONFIG_FREERTOS_UNICORE + +void spi_flash_init() +{ + // No-op in single core mode +} + +static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() +{ + vTaskSuspendAll(); + Cache_Read_Disable(0); + Cache_Read_Disable(1); +} + +static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() +{ + Cache_Read_Enable(0); + Cache_Read_Enable(1); + xTaskResumeAll(); +} + +#endif // CONFIG_FREERTOS_UNICORE + + +esp_err_t IRAM_ATTR spi_flash_erase_sector(uint16_t sec) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + SpiFlashOpResult rc; + rc = SPIUnlock(); + if (rc == SPI_FLASH_RESULT_OK) { + rc = SPIEraseSector(sec); + } + Cache_Flush(0); + Cache_Flush(1); + spi_flash_enable_interrupts_caches_and_other_cpu(); + return spi_flash_translate_rc(rc); +} + +esp_err_t IRAM_ATTR spi_flash_write(uint32_t dest_addr, const uint32_t *src, uint32_t size) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + SpiFlashOpResult rc; + rc = SPIUnlock(); + if (rc == SPI_FLASH_RESULT_OK) { + rc = SPIWrite(dest_addr, src, (int32_t) size); + } + Cache_Flush(0); + Cache_Flush(1); + spi_flash_enable_interrupts_caches_and_other_cpu(); + return spi_flash_translate_rc(rc); +} + +esp_err_t IRAM_ATTR spi_flash_read(uint32_t src_addr, uint32_t *dest, uint32_t size) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + SpiFlashOpResult rc; + rc = SPIRead(src_addr, dest, (int32_t) size); + Cache_Flush(0); + Cache_Flush(1); + spi_flash_enable_interrupts_caches_and_other_cpu(); + return spi_flash_translate_rc(rc); +} static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc) { @@ -34,73 +222,3 @@ static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc) return ESP_ERR_FLASH_OP_FAIL; } } - -extern void Cache_Flush(int); - -static void IRAM_ATTR spi_flash_disable_interrupts_caches_and_app_cpu() -{ - vTaskSuspendAll(); - // SET_PERI_REG_MASK(APPCPU_CTRL_REG_C, DPORT_APPCPU_RUNSTALL); - Cache_Read_Disable(0); - Cache_Read_Disable(1); -} - -static void IRAM_ATTR spi_flash_enable_interrupts_caches_and_app_cpu() -{ - Cache_Read_Enable(0); - Cache_Read_Enable(1); - // CLEAR_PERI_REG_MASK(APPCPU_CTRL_REG_C, DPORT_APPCPU_RUNSTALL); - xTaskResumeAll(); -} - -esp_err_t IRAM_ATTR spi_flash_erase_sector(uint16_t sec) -{ - spi_flash_disable_interrupts_caches_and_app_cpu(); - SpiFlashOpResult rc; - if (xPortGetCoreID() == 1) { - rc = SPI_FLASH_RESULT_ERR; - } - else { - rc = SPIUnlock(); - if (rc == SPI_FLASH_RESULT_OK) { - rc = SPIEraseSector(sec); - } - } - Cache_Flush(0); - Cache_Flush(1); - spi_flash_enable_interrupts_caches_and_app_cpu(); - return spi_flash_translate_rc(rc); -} - -esp_err_t IRAM_ATTR spi_flash_write(uint32_t dest_addr, const uint32_t *src, uint32_t size) -{ - spi_flash_disable_interrupts_caches_and_app_cpu(); - SpiFlashOpResult rc; - if (xPortGetCoreID() == 1) { - rc = SPI_FLASH_RESULT_ERR; - } - else { - rc = SPIUnlock(); - if (rc == SPI_FLASH_RESULT_OK) { - rc = SPIWrite(dest_addr, src, (int32_t) size); - } - } - Cache_Flush(0); - Cache_Flush(1); - spi_flash_enable_interrupts_caches_and_app_cpu(); - return spi_flash_translate_rc(rc); -} - -esp_err_t IRAM_ATTR spi_flash_read(uint32_t src_addr, uint32_t *dest, uint32_t size) -{ - spi_flash_disable_interrupts_caches_and_app_cpu(); - SpiFlashOpResult rc; - if (xPortGetCoreID() == 1) { - rc = SPI_FLASH_RESULT_ERR; - } - else { - rc = SPIRead(src_addr, dest, (int32_t) size); - } - spi_flash_enable_interrupts_caches_and_app_cpu(); - return spi_flash_translate_rc(rc); -} diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index aa12b2aa42..da6b03c0d1 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -28,6 +28,15 @@ extern "C" { #define SPI_FLASH_SEC_SIZE 4096 /**< SPI Flash sector size */ +/** + * @brief Initialize SPI flash access driver + * + * This function must be called exactly once, before any other + * spi_flash_* functions are called. + * + */ +void spi_flash_init(); + /** * @brief Erase the Flash sector. *