diff --git a/components/spi_flash/cache_utils.c b/components/spi_flash/cache_utils.c index bc4e8885e..56038de37 100644 --- a/components/spi_flash/cache_utils.c +++ b/components/spi_flash/cache_utils.c @@ -110,6 +110,10 @@ void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() assert(other_cpuid == 1); spi_flash_disable_cache(other_cpuid, &s_flash_op_cache_state[other_cpuid]); } else { + // Temporarily raise current task priority to prevent a deadlock while + // waiting for IPC task to start on the other CPU + int old_prio = uxTaskPriorityGet(NULL); + vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1); // 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. s_flash_op_can_start = false; @@ -121,6 +125,8 @@ void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu() } // Disable scheduler on the current CPU vTaskSuspendAll(); + // Can now set the priority back to the normal one + vTaskPrioritySet(NULL, old_prio); // This is guaranteed to run on CPU because the other CPU is now // occupied by highest priority task assert(xPortGetCoreID() == cpuid); diff --git a/components/spi_flash/test/test_spi_flash.c b/components/spi_flash/test/test_spi_flash.c index e47cdf840..b1e738d41 100644 --- a/components/spi_flash/test/test_spi_flash.c +++ b/components/spi_flash/test/test_spi_flash.c @@ -167,3 +167,61 @@ TEST_CASE("spi flash functions can run along with IRAM interrupts", "[spi_flash] free(read_arg.buf); } + +#if portNUM_PROCESSORS > 1 +TEST_CASE("spi_flash deadlock with high priority busy-waiting task", "[spi_flash]") +{ + typedef struct { + QueueHandle_t queue; + volatile bool done; + } deadlock_test_arg_t; + + /* Create two tasks: high-priority consumer on CPU0, low-priority producer on CPU1. + * Consumer polls the queue until it gets some data, then yields. + * Run flash operation on CPU0. Check that when IPC1 task blocks out the producer, + * the task which does flash operation does not get blocked by the consumer. + */ + + void producer_task(void* varg) + { + int dummy = 0; + deadlock_test_arg_t* arg = (deadlock_test_arg_t*) varg; + while (!arg->done) { + xQueueSend(arg->queue, &dummy, 0); + vTaskDelay(1); + } + vTaskDelete(NULL); + } + + void consumer_task(void* varg) + { + int dummy; + deadlock_test_arg_t* arg = (deadlock_test_arg_t*) varg; + while (!arg->done) { + if (xQueueReceive(arg->queue, &dummy, 0) == pdTRUE) { + vTaskDelay(1); + } + } + vTaskDelete(NULL); + } + deadlock_test_arg_t arg = { + .queue = xQueueCreate(32, sizeof(int)), + .done = false + }; + + TEST_ASSERT(xTaskCreatePinnedToCore(&producer_task, "producer", 4096, &arg, 5, NULL, 1)); + TEST_ASSERT(xTaskCreatePinnedToCore(&consumer_task, "consumer", 4096, &arg, 10, NULL, 0)); + + for (int i = 0; i < 1000; i++) { + uint32_t dummy; + TEST_ESP_OK(spi_flash_read(0, &dummy, sizeof(dummy))); + } + + arg.done = true; + vTaskDelay(5); + vQueueDelete(arg.queue); + + /* Check that current task priority is still correct */ + TEST_ASSERT_EQUAL_INT(uxTaskPriorityGet(NULL), UNITY_FREERTOS_PRIORITY); +} +#endif // portNUM_PROCESSORS > 1