diff --git a/components/spi_flash/test_apps/flash_suspend/CMakeLists.txt b/components/spi_flash/test_apps/flash_suspend/CMakeLists.txt new file mode 100644 index 0000000000..46da0ba372 --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/CMakeLists.txt @@ -0,0 +1,7 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_flash_suspend) diff --git a/components/spi_flash/test_apps/flash_suspend/README.md b/components/spi_flash/test_apps/flash_suspend/README.md new file mode 100644 index 0000000000..f39c17bef3 --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -------- | -------- | diff --git a/components/spi_flash/test_apps/flash_suspend/main/CMakeLists.txt b/components/spi_flash/test_apps/flash_suspend/main/CMakeLists.txt new file mode 100644 index 0000000000..e017e8371e --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/main/CMakeLists.txt @@ -0,0 +1,7 @@ +set(srcs "test_app_main.c" + "test_flash_suspend.c") + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + WHOLE_ARCHIVE) diff --git a/components/spi_flash/test_apps/flash_suspend/main/test_app_main.c b/components/spi_flash/test_apps/flash_suspend/main/test_app_main.c new file mode 100644 index 0000000000..a626f5e8cd --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/main/test_app_main.c @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "unity.h" +#include "unity_test_utils.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (1000) + +static size_t before_free_8bit; +static size_t before_free_32bit; + + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + unity_utils_check_leak(before_free_8bit, after_free_8bit, "8BIT", TEST_MEMORY_LEAK_THRESHOLD); + unity_utils_check_leak(before_free_32bit, after_free_32bit, "32BIT", TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + + /* + ______ _ _____ _ _ _____ _ _ _____ _____ ______ _ _ _____ + | ____| | /\ / ____| | | | / ____| | | |/ ____| __ \| ____| \ | | __ \ + | |__ | | / \ | (___ | |__| | | (___ | | | | (___ | |__) | |__ | \| | | | | + | __| | | / /\ \ \___ \| __ | \___ \| | | |\___ \| ___/| __| | . ` | | | | + | | | |____ / ____ \ ____) | | | | ____) | |__| |____) | | | |____| |\ | |__| | + |_| |______/_/ \_\_____/|_| |_| |_____/ \____/|_____/|_| |______|_| \_|_____/ + + + */ + + printf(" ______ _ _____ _ _ _____ _ _ _____ _____ ______ _ _ _____ \n"); + printf("| ____| | /\\ / ____| | | | / ____| | | |/ ____| __ \\| ____| \\ | | __ \\ \n"); + printf("| |__ | | / \\ | (___ | |__| | | (___ | | | | (___ | |__) | |__ | \\| | | | |\n"); + printf("| __| | | / /\\ \\ \\___ \\| __ | \\___ \\| | | |\\___ \\| ___/| __| | . ` | | | |\n"); + printf("| | | |____ / ____ \\ ____) | | | | ____) | |__| |____) | | | |____| |\\ | |__| |\n"); + printf("|_| |______/_/ \\_\\_____/|_| |_| |_____/ \\____/|_____/|_| |______|_| \\_|_____/ \n"); + + unity_run_menu(); +} diff --git a/components/spi_flash/test_apps/flash_suspend/main/test_flash_suspend.c b/components/spi_flash/test_apps/flash_suspend/main/test_flash_suspend.c new file mode 100644 index 0000000000..cde14fe00a --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/main/test_flash_suspend.c @@ -0,0 +1,163 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * In this test, we show you a way to make an ISR-based callback work during Flash operations, when the ISR-based + * callback is put in Flash. + * + * Please read the README.md to know more details about this feature! + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_attr.h" +#include "esp_cpu.h" +#include "esp_partition.h" +#include "driver/gptimer.h" +#include "esp_flash.h" +#include "hal/gpio_hal.h" +#include "rom/cache.h" + +#include "test_utils.h" + +#define TIMER_RESOLUTION_HZ (1 * 1000 * 1000) // 1MHz resolution +#define TIMER_ALARM_PERIOD_S 1 // Alarm period 1s + +#define RECORD_TIME_PREPARE() uint32_t __t1, __t2 +#define RECORD_TIME_START() do {__t1 = esp_cpu_get_cycle_count();} while(0) +#define RECORD_TIME_END(p_time) do{__t2 = esp_cpu_get_cycle_count(); p_time = (__t2 - __t1);} while(0) +#define GET_US_BY_CCOUNT(t) ((double)(t)/CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ) + +const static char *TAG = "flash_suspend test"; +DRAM_ATTR static uint32_t s_flash_func_t1; +DRAM_ATTR static uint32_t s_flash_func_t2; +DRAM_ATTR static uint32_t s_flash_func_time; +DRAM_ATTR static uint32_t s_isr_t1; +DRAM_ATTR static uint32_t s_isr_t2; +DRAM_ATTR static uint32_t s_isr_time; +DRAM_ATTR static uint32_t s_isr_interval_t1; +DRAM_ATTR static uint32_t s_isr_interval_t2; +DRAM_ATTR static uint32_t s_isr_interval_time; +DRAM_ATTR static uint32_t times = 0; + + +static NOINLINE_ATTR void func_in_flash(void) +{ + /** + * - Here we will have few instructions in .flash.text + * - Cache will read from Flash to get the instructions synchronized. + * - CPU will execute around 90000 times. + */ + + for (int i = 0; i < 50000; i++) { + asm volatile("nop"); + } + + s_flash_func_t2 = esp_cpu_get_cycle_count(); +} + +static bool IRAM_ATTR gptimer_alarm_suspend_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) +{ + s_isr_t1 = esp_cpu_get_cycle_count(); + if (s_isr_interval_t1 != 0 ) { + s_isr_interval_t2 = esp_cpu_get_cycle_count(); + s_isr_interval_time += (s_isr_interval_t2 - s_isr_interval_t1); + } + s_isr_interval_t1 = esp_cpu_get_cycle_count(); + + /*clear content in cache*/ +#if CONFIG_IDF_TARGET_ESP32S3 + Cache_Invalidate_DCache_All(); +#endif + Cache_Invalidate_ICache_All(); + s_flash_func_t1 = esp_cpu_get_cycle_count(); + func_in_flash(); + + s_flash_func_time += (s_flash_func_t2 - s_flash_func_t1); + times++; + s_isr_t2 = esp_cpu_get_cycle_count(); + s_isr_time += (s_isr_t2 - s_isr_t1); + return false; +} + +static const esp_partition_t *s_get_partition(void) +{ + //Find the "storage1" partition defined in `partitions.csv` + const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage1"); + if (!result) { + ESP_LOGE(TAG, "Can't find the partition, please define it correctly in `partitions.csv`"); + abort(); + } + return result; +} + +static const uint8_t large_const_buffer[16400] = { + 203, // first byte + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + [50 ... 99] = 2, + [1600 ... 2000] = 3, + [8000 ... 9000] = 77, + [15000 ... 16398] = 8, + 43 // last byte +}; + +TEST_CASE("flash suspend test", "[spi_flash][suspend]") +{ + //Get the partition used for SPI1 erase operation + const esp_partition_t *part = s_get_partition(); + ESP_LOGI(TAG, "found partition '%s' at offset 0x%"PRIx32" with size 0x%"PRIx32, part->label, part->address, part->size); + gptimer_handle_t gptimer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = TIMER_RESOLUTION_HZ, + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer)); + + /** + set alarm_count is 2500. + So, in this case, ISR duration time is around 2240 and ISR interval time is around 2470 + so ISR_interval - ISR_duration is around 230us. Just a little bit larger than `tsus` value. + */ + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = 2500, + .flags.auto_reload_on_alarm = true, + }; + TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config)); + + gptimer_event_callbacks_t cbs = { + .on_alarm = gptimer_alarm_suspend_cb, + }; + bool is_flash = true; + TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, &is_flash)); + TEST_ESP_OK(gptimer_enable(gptimer)); + TEST_ESP_OK(gptimer_start(gptimer)); + + uint32_t erase_time = 0; + RECORD_TIME_PREPARE(); + + RECORD_TIME_START(); + TEST_ESP_OK(esp_flash_erase_region(part->flash_chip, part->address, part->size)); + TEST_ESP_OK(esp_flash_write(part->flash_chip, large_const_buffer, part->address, sizeof(large_const_buffer))); + + RECORD_TIME_END(erase_time); + + TEST_ESP_OK(gptimer_stop(gptimer)); + ESP_LOGI(TAG, "Flash Driver Erase Operation finishes, duration:\n\t\t%0.2f us", GET_US_BY_CCOUNT(erase_time)); + ESP_LOGI(TAG, "During Erase, ISR callback function(in flash) response time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_flash_func_time / times)); + ESP_LOGI(TAG, "During Erase, ISR duration time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_isr_time / times)); + ESP_LOGI(TAG, "During Erase, ISR interval time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_isr_interval_time / (times - 1))); + + + ESP_LOGI(TAG, "Finish"); + TEST_ESP_OK(gptimer_disable(gptimer)); + TEST_ESP_OK(gptimer_del_timer(gptimer)); +} diff --git a/components/spi_flash/test_apps/flash_suspend/partitions.csv b/components/spi_flash/test_apps/flash_suspend/partitions.csv new file mode 100644 index 0000000000..6b057cd354 --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage1, data, fat, , 512K, diff --git a/components/spi_flash/test_apps/flash_suspend/pytest_flash_auto_suspend.py b/components/spi_flash/test_apps/flash_suspend/pytest_flash_auto_suspend.py new file mode 100644 index 0000000000..f24ef1b8bc --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/pytest_flash_auto_suspend.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c3 +@pytest.mark.flash_suspend +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_flash_auto_suspend(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=30) diff --git a/components/spi_flash/test_apps/flash_suspend/sdkconfig.ci.release b/components/spi_flash/test_apps/flash_suspend/sdkconfig.ci.release new file mode 100644 index 0000000000..b451578520 --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_SPI_FLASH_AUTO_SUSPEND=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/spi_flash/test_apps/flash_suspend/sdkconfig.defaults b/components/spi_flash/test_apps/flash_suspend/sdkconfig.defaults new file mode 100644 index 0000000000..cdb818d6f9 --- /dev/null +++ b/components/spi_flash/test_apps/flash_suspend/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_SPI_FLASH_AUTO_SUSPEND=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"