From 0313d2a5b431b645a8614f0e083b83552d44dc21 Mon Sep 17 00:00:00 2001 From: Cao Sen Miao Date: Tue, 23 Aug 2022 10:10:32 +0800 Subject: [PATCH] esp_flash: Add ext flash on ESP32-C2 --- .gitlab/ci/target-test.yml | 40 + components/spi_flash/test/test_esp_flash.c | 620 +------------ .../test_apps/esp_flash/CMakeLists.txt | 5 + .../spi_flash/test_apps/esp_flash/README.md | 2 + .../test_apps/esp_flash/main/CMakeLists.txt | 7 + .../test_apps/esp_flash/main/test_app_main.c | 56 ++ .../esp_flash/main/test_esp_flash_def.h | 242 +++++ .../esp_flash/main/test_esp_flash_drv.c | 850 ++++++++++++++++++ .../test_apps/esp_flash/partitions.csv | 5 + .../test_apps/esp_flash/pytest_esp_flash.py | 61 ++ .../esp_flash/sdkconfig.ci.flash_qio | 4 + .../test_apps/esp_flash/sdkconfig.ci.release | 7 + .../test_apps/esp_flash/sdkconfig.ci.rom_impl | 4 + .../test_apps/esp_flash/sdkconfig.defaults | 3 + pytest.ini | 1 + 15 files changed, 1291 insertions(+), 616 deletions(-) create mode 100644 components/spi_flash/test_apps/esp_flash/CMakeLists.txt create mode 100644 components/spi_flash/test_apps/esp_flash/README.md create mode 100644 components/spi_flash/test_apps/esp_flash/main/CMakeLists.txt create mode 100644 components/spi_flash/test_apps/esp_flash/main/test_app_main.c create mode 100644 components/spi_flash/test_apps/esp_flash/main/test_esp_flash_def.h create mode 100644 components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c create mode 100644 components/spi_flash/test_apps/esp_flash/partitions.csv create mode 100644 components/spi_flash/test_apps/esp_flash/pytest_esp_flash.py create mode 100644 components/spi_flash/test_apps/esp_flash/sdkconfig.ci.flash_qio create mode 100644 components/spi_flash/test_apps/esp_flash/sdkconfig.ci.release create mode 100644 components/spi_flash/test_apps/esp_flash/sdkconfig.ci.rom_impl create mode 100644 components/spi_flash/test_apps/esp_flash/sdkconfig.defaults diff --git a/.gitlab/ci/target-test.yml b/.gitlab/ci/target-test.yml index 68acb5097a..f8a65d7123 100644 --- a/.gitlab/ci/target-test.yml +++ b/.gitlab/ci/target-test.yml @@ -252,6 +252,14 @@ component_ut_pytest_esp32_flash_encryption: - build_pytest_components_esp32 tags: [ esp32, flash_encryption ] +component_ut_pytest_esp32_flash_multi: + extends: + - .pytest_components_dir_template + - .rules:test:component_ut-esp32 + needs: + - build_pytest_components_esp32 + tags: [ esp32, flash_mutli ] + component_ut_pytest_esp32s2_generic: extends: - .pytest_components_dir_template @@ -260,6 +268,14 @@ component_ut_pytest_esp32s2_generic: - build_pytest_components_esp32s2 tags: [ esp32s2, generic ] +component_ut_pytest_esp32s2_flash_multi: + extends: + - .pytest_components_dir_template + - .rules:test:component_ut-esp32s2 + needs: + - build_pytest_components_esp32s2 + tags: [ esp32s2, flash_mutli ] + component_ut_pytest_esp32s3_generic: extends: - .pytest_components_dir_template @@ -292,6 +308,14 @@ component_ut_pytest_esp32s3_flash_encryption_f8r8: - build_pytest_components_esp32s3 tags: [ esp32s3, flash_encryption_f8r8 ] +component_ut_pytest_esp32s3_flash_multi: + extends: + - .pytest_components_dir_template + - .rules:test:component_ut-esp32s3 + needs: + - build_pytest_components_esp32s3 + tags: [ esp32s3, flash_mutli ] + component_ut_pytest_esp32c2_generic: extends: - .pytest_components_dir_template @@ -324,6 +348,14 @@ component_ut_pytest_esp32c3_flash_encryption: - build_pytest_components_esp32c3 tags: [ esp32c3, flash_encryption ] +component_ut_pytest_esp32c3_flash_multi: + extends: + - .pytest_components_dir_template + - .rules:test:component_ut-esp32c3 + needs: + - build_pytest_components_esp32c3 + tags: [ esp32c3, flash_mutli ] + .pytest_test_apps_dir_template: extends: .pytest_template variables: @@ -988,6 +1020,14 @@ UT_C2_26M: - UT_T1_1 - xtal_26mhz +UT_C2_FLASH: + extends: + - .unit_test_esp32c2_template + tags: + - ESP32C2_IDF + - UT_T1_ESP_FLASH + - xtal_40mhz + UT_C3: extends: .unit_test_esp32c3_template parallel: 31 diff --git a/components/spi_flash/test/test_esp_flash.c b/components/spi_flash/test/test_esp_flash.c index 5e11e1d9dc..52ac167001 100644 --- a/components/spi_flash/test/test_esp_flash.c +++ b/components/spi_flash/test/test_esp_flash.c @@ -12,6 +12,7 @@ #include #include "esp_log.h" + #include #include "unity.h" @@ -38,8 +39,6 @@ #define FUNC_SPI 1 -static uint8_t sector_buf[4096]; - #define MAX_ADDR_24BIT 0x1000000 #define TEST_SPI_SPEED 10 #define TEST_SPI_READ_MODE SPI_FLASH_FASTRD @@ -146,9 +145,6 @@ typedef void (*flash_test_func_t)(const esp_partition_t *part); #if defined(CONFIG_SPIRAM) //SPI1 CS1 occupied by PSRAM #define BYPASS_MULTIPLE_CHIP 1 -#elif TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2) -//IDF-5049 -#define BYPASS_MULTIPLE_CHIP 1 #endif #if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 @@ -167,15 +163,15 @@ typedef void (*flash_test_func_t)(const esp_partition_t *part); #else #if CONFIG_FREERTOS_SMP // IDF-5260 #define TEST_CASE_MULTI_FLASH(STR, FUNC_TO_RUN) \ - TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_3][test_env=UT_T1_ESP_FLASH][timeout=60]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} + TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_multi][test_env=UT_T1_ESP_FLASH][timeout=60]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} #else #define TEST_CASE_MULTI_FLASH(STR, FUNC_TO_RUN) \ - TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_3][test_env=UT_T1_ESP_FLASH][timeout=35]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} + TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_multi][test_env=UT_T1_ESP_FLASH][timeout=35]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} #endif #endif #define TEST_CASE_MULTI_FLASH_IGNORE(STR, FUNC_TO_RUN) \ - TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_3][test_env=UT_T1_ESP_FLASH][ignore]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} + TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_multi][test_env=UT_T1_ESP_FLASH][ignore]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} //currently all the configs are the same with esp_flash_spi_device_config_t, no more information required @@ -467,550 +463,12 @@ static void flash_test_func(flash_test_func_t func, int test_num) ESP_LOGI(TAG, "Completed %d configs", test_num); } -/* ---------- Test code start ------------*/ - -static void test_metadata(const esp_partition_t* part) -{ - esp_flash_t* chip = part->flash_chip; - uint32_t id, size; - TEST_ESP_OK(esp_flash_read_id(chip, &id)); - TEST_ESP_OK(esp_flash_get_size(chip, &size)); - printf("Flash ID %08x detected size %d bytes\n", id, size); -} - -TEST_CASE_FLASH("SPI flash metadata functions", test_metadata); -TEST_CASE_MULTI_FLASH("SPI flash metadata functions", test_metadata); - -static uint32_t erase_test_region(const esp_partition_t *part, int num_sectors) -{ - esp_flash_t* chip = part->flash_chip; - uint32_t offs = part->address; - - /* chip should be initialised */ - TEST_ASSERT(esp_flash_default_chip != NULL - && esp_flash_chip_driver_initialized(esp_flash_default_chip)); - - TEST_ASSERT(num_sectors * 4096 <= part->size); - - bzero(sector_buf, sizeof(sector_buf)); - - printf("Erase @ 0x%x...\n", offs); - TEST_ASSERT_EQUAL_HEX32(ESP_OK, esp_flash_erase_region(chip, offs, num_sectors * 4096) ); - - printf("Verify erased...\n"); - for (int i = 0; i < num_sectors; i++) { - TEST_ASSERT_EQUAL_HEX32(ESP_OK, esp_flash_read(chip, sector_buf, offs + i * 4096, sizeof(sector_buf))); - - printf("Buffer starts 0x%02x 0x%02x 0x%02x 0x%02x\n", sector_buf[0], sector_buf[1], sector_buf[2], sector_buf[3]); - for (int i = 0; i < sizeof(sector_buf); i++) { - TEST_ASSERT_EQUAL_HEX8(0xFF, sector_buf[i]); - } - } - - return offs; -} - -void test_simple_read_write(const esp_partition_t* part) -{ - esp_flash_t* chip = part->flash_chip; - uint32_t offs = erase_test_region(part, 1); - - const int test_seed = 778; - srand(test_seed); - for (int i = 0 ; i < sizeof(sector_buf); i++) { - sector_buf[i] = rand(); - } - - printf("Write %p...\n", (void *)offs); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, sector_buf, offs, sizeof(sector_buf)) ); - - bzero(sector_buf, sizeof(sector_buf)); - - printf("Read back...\n"); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, sector_buf, offs, sizeof(sector_buf)) ); - - printf("Buffer starts 0x%02x 0x%02x 0x%02x 0x%02x\n", sector_buf[0], sector_buf[1], sector_buf[2], sector_buf[3]); - - srand(test_seed); - for (int i = 0; i < sizeof(sector_buf); i++) { - uint8_t data = rand(); - TEST_ASSERT_EQUAL_HEX8(data, sector_buf[i]); - } -} - -TEST_CASE_FLASH("SPI flash simple read/write", test_simple_read_write); -TEST_CASE_MULTI_FLASH("SPI flash simple read/write", test_simple_read_write); - -void test_unaligned_read_write(const esp_partition_t* part) -{ - esp_flash_t* chip = part->flash_chip; - uint32_t offs = erase_test_region(part, 2); - - const char *msg = "i am a message"; - TEST_ASSERT(strlen(msg) + 1 % 4 != 0); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, msg, offs + 1, strlen(msg) + 1) ); - - char buf[strlen(msg) + 1]; - - memset(buf, 0xEE, sizeof(buf)); - - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, buf, offs + 1, strlen(msg) + 1) ); - TEST_ASSERT_EQUAL_STRING_LEN(msg, buf, strlen(msg)); - TEST_ASSERT(memcmp(buf, msg, strlen(msg) + 1) == 0); -} - -TEST_CASE_FLASH("SPI flash unaligned read/write", test_unaligned_read_write); -TEST_CASE_MULTI_FLASH("SPI flash unaligned read/write", test_unaligned_read_write); - -void test_single_read_write(const esp_partition_t* part) -{ - esp_flash_t* chip = part->flash_chip; - uint32_t offs = erase_test_region(part, 2); - - const int seed = 699; - srand(seed); - for (unsigned v = 0; v < 512; v++) { - uint32_t data = rand(); - TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_flash_write(chip, &data, offs + v, 1) ); - } - - srand(seed); - for (unsigned v = 0; v < 512; v++) { - uint8_t readback; - uint32_t data = rand(); - TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_flash_read(chip, &readback, offs + v, 1) ); - TEST_ASSERT_EQUAL_HEX8(data, readback); - } -} - -TEST_CASE_FLASH("SPI flash single byte reads/writes", test_single_read_write); -TEST_CASE_MULTI_FLASH("SPI flash single byte reads/writes", test_single_read_write); - - -/* this test is notable because it generates a lot of unaligned reads/writes, - and also reads/writes across both a sector boundary & many page boundaries. -*/ -void test_three_byte_read_write(const esp_partition_t* part) -{ - esp_flash_t* chip = part->flash_chip; - uint32_t offs = erase_test_region(part, 2); - - const int seed = 700; - esp_rom_printf("offs:%X\n", offs); - - srand(seed); - for (uint32_t v = 0; v < 86; v++) { - uint32_t data = rand(); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, &data, offs + 3 * v, 3) ); - } - - srand(seed); - for (uint32_t v = 0; v < 1; v++) { - uint32_t readback; - uint32_t data = rand(); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, offs + 3 * v, 3) ); - TEST_ASSERT_EQUAL_HEX32(data & 0xFFFFFF, readback & 0xFFFFFF); - } -} - -TEST_CASE_FLASH("SPI flash three byte reads/writes", test_three_byte_read_write); -TEST_CASE_MULTI_FLASH("SPI flash three byte reads/writes", test_three_byte_read_write); - -void test_erase_large_region(const esp_partition_t *part) -{ - esp_flash_t* chip = part->flash_chip; - - /* Write some noise at the start and the end of the region */ - const char *ohai = "OHAI"; - uint32_t readback; - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, ohai, part->address, 5)); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, ohai, part->address + part->size - 5, 5)); - - /* sanity check what we just wrote. since the partition may haven't been erased, we only check the part which is written to 0. */ - uint32_t written_data = *((const uint32_t *)ohai); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address + part->size - 5, 4)); - TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address, 4)); - TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); - - /* Erase zero bytes, check that nothing got erased */ - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_erase_region(chip, part->address, 0)); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address + part->size - 5, 4)); - TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address, 4)); - TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); - - /* Erase whole region */ - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_erase_region(chip, part->address, part->size)); - - /* ensure both areas we wrote are now all-FFs */ - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address, 4)); - TEST_ASSERT_EQUAL_HEX32(0xFFFFFFFF, readback); - - TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address + part->size - 5, 4)); - TEST_ASSERT_EQUAL_HEX32(0xFFFFFFFF, readback); -} - -TEST_CASE_FLASH("SPI flash erase large region", test_erase_large_region); -TEST_CASE_MULTI_FLASH("SPI flash erase large region", test_erase_large_region); - -#if CONFIG_SPI_FLASH_AUTO_SUSPEND -void esp_test_for_suspend(void) -{ - /*clear content in cache*/ -#if !CONFIG_IDF_TARGET_ESP32C3 - Cache_Invalidate_DCache_All(); -#endif - Cache_Invalidate_ICache_All(); - ESP_LOGI(TAG, "suspend test begins:"); - printf("run into test suspend function\n"); - printf("print something when flash is erasing:\n"); - printf("aaaaa bbbbb zzzzz fffff qqqqq ccccc\n"); -} - -static volatile bool task_erase_end, task_suspend_end = false; -void task_erase_large_region(void *arg) -{ - esp_partition_t *part = (esp_partition_t *)arg; - test_erase_large_region(part); - task_erase_end = true; - vTaskDelete(NULL); -} - -void task_request_suspend(void *arg) -{ - vTaskDelay(2); - ESP_LOGI(TAG, "flash go into suspend"); - esp_test_for_suspend(); - task_suspend_end = true; - vTaskDelete(NULL); -} - -static void test_flash_suspend_resume(const esp_partition_t* part) -{ - xTaskCreatePinnedToCore(task_request_suspend, "suspend", 2048, (void *)"test_for_suspend", UNITY_FREERTOS_PRIORITY + 3, NULL, 0); - xTaskCreatePinnedToCore(task_erase_large_region, "test", 2048, (void *)part, UNITY_FREERTOS_PRIORITY + 2, NULL, 0); - while (!task_erase_end || !task_suspend_end) { - } - vTaskDelay(200); -} - -TEST_CASE("SPI flash suspend and resume test", "[esp_flash][test_env=UT_T1_Flash_Suspend]") -{ - flash_test_func(test_flash_suspend_resume, 1 /* first index reserved for main flash */ ); -} - -#endif //CONFIG_SPI_FLASH_AUTO_SUSPEND - -static void test_write_protection(const esp_partition_t* part) -{ - esp_flash_t* chip = part->flash_chip; - - bool wp = true; - esp_err_t ret = ESP_OK; - ret = esp_flash_get_chip_write_protect(chip, &wp); - TEST_ESP_OK(ret); - - for (int i = 0; i < 4; i ++) { - bool wp_write = !wp; - ret = esp_flash_set_chip_write_protect(chip, wp_write); - TEST_ESP_OK(ret); - - bool wp_read; - ret = esp_flash_get_chip_write_protect(chip, &wp_read); - TEST_ESP_OK(ret); - TEST_ASSERT(wp_read == wp_write); - wp = wp_read; - } -} - -TEST_CASE_FLASH("Test esp_flash can enable/disable write protetion", test_write_protection); -TEST_CASE_MULTI_FLASH("Test esp_flash can enable/disable write protetion", test_write_protection); - -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 -}; - -static void test_write_large_buffer(const esp_partition_t *part, const uint8_t *source, size_t length); -static void write_large_buffer(const esp_partition_t *part, const uint8_t *source, size_t length); -static void read_and_check(const esp_partition_t *part, const uint8_t *source, size_t length); - -// Internal functions for testing, from esp_flash_api.c -#if !CONFIG_ESPTOOLPY_OCT_FLASH -esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe); -esp_err_t esp_flash_get_io_mode(esp_flash_t* chip, bool* qe); -esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id); -esp_err_t spi_flash_chip_mxic_probe(esp_flash_t *chip, uint32_t flash_id); - -static bool is_winbond_chip(esp_flash_t* chip) -{ - uint32_t flash_id; - esp_err_t ret = esp_flash_read_chip_id(chip, &flash_id); - TEST_ESP_OK(ret); - if ((flash_id >> 16) == 0xEF) { - return true; - } else { - return false; - } -} - -static bool is_mxic_chip(esp_flash_t* chip) -{ - uint32_t flash_id; - esp_err_t ret = esp_flash_read_chip_id(chip, &flash_id); - TEST_ESP_OK(ret); - return (spi_flash_chip_mxic_probe(chip, flash_id)==ESP_OK); -} - -IRAM_ATTR NOINLINE_ATTR static void test_toggle_qe(const esp_partition_t* part) -{ - esp_flash_t* chip = part->flash_chip; - - bool qe; - if (chip == NULL) { - chip = esp_flash_default_chip; - } - esp_flash_io_mode_t io_mode_before = chip->read_mode; - esp_err_t ret = esp_flash_get_io_mode(chip, &qe); - TEST_ESP_OK(ret); - - bool allow_failure = is_winbond_chip(chip) || is_mxic_chip(chip); - - for (int i = 0; i < 4; i ++) { - esp_rom_printf(DRAM_STR("write qe: %d->%d\n"), qe, !qe); - qe = !qe; - chip->read_mode = qe? SPI_FLASH_QOUT: SPI_FLASH_SLOWRD; - ret = esp_flash_set_io_mode(chip, qe); - if (allow_failure && !qe && ret == ESP_ERR_FLASH_NO_RESPONSE) { - //allows clear qe failure for Winbond chips - ret = ESP_OK; - } - TEST_ESP_OK(ret); - - bool qe_read; - ret = esp_flash_get_io_mode(chip, &qe_read); - TEST_ESP_OK(ret); - ESP_LOGD(TAG, "qe read: %d", qe_read); - if (!qe && qe_read) { - if (allow_failure) { - ESP_LOGW(TAG, "cannot clear QE bit for known permanent QE (Winbond or MXIC) chips."); - } else { - ESP_LOGE(TAG, "cannot clear QE bit, please make sure force clearing QE option is enabled in `spi_flash_common_set_io_mode`, and this chip is not a permanent QE one."); - } - chip->read_mode = io_mode_before; - return; - } - TEST_ASSERT_EQUAL(qe, qe_read); - } - //restore the io_mode after test - chip->read_mode = io_mode_before; -} - -// These tests show whether the QE is permanent or not for the chip tested. -// To test the behaviour of a new SPI flash chip, enable force_check flag in generic driver -// `spi_flash_common_set_io_mode` and then run this test. -TEST_CASE_FLASH_IGNORE("Test esp_flash_write can toggle QE bit", test_toggle_qe); -TEST_CASE_MULTI_FLASH_IGNORE("Test esp_flash_write can toggle QE bit", test_toggle_qe); -#endif //CONFIG_ESPTOOLPY_OCT_FLASH - -// This table could be chip specific in the future. -uint8_t flash_frequency_table[6] = {5, 10, 20, 26, 40, 80}; -#define TEST_FLASH_SPEED_MIN 5 -void test_permutations_part(const flashtest_config_t* config, esp_partition_t* part, void* source_buf, size_t length) -{ - int clock_index = 0; - if (config->host_id != -1) { - while (clock_index < sizeof(flash_frequency_table)/sizeof(uint8_t)) { - uint8_t speed = flash_frequency_table[clock_index]; - //test io_mode in the inner loop to test QE set/clear function, since - //the io mode will switch frequently. - esp_flash_io_mode_t io_mode = SPI_FLASH_READ_MODE_MIN; - while (io_mode != SPI_FLASH_QIO + 1) { - if (io_mode > SPI_FLASH_FASTRD && - !SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(config->host_id)) { - io_mode++; - continue; - } - - esp_flash_t* chip; - flashtest_config_t temp_config = *config; - temp_config.io_mode = io_mode; - temp_config.freq_mhz = flash_frequency_table[clock_index]; - setup_new_chip(&temp_config, &chip); - ESP_LOGI(TAG, "test flash io mode: %d, speed: %d", io_mode, speed); - - part->flash_chip = chip; - read_and_check(part, source_buf, length); - teardown_test_chip(chip); - - io_mode++; - } - clock_index++; - } - } else { - //test main flash - part->flash_chip = NULL; - read_and_check(part, source_buf, length); - } -} - -void test_permutations_chip(const flashtest_config_t* config) -{ - esp_log_level_set("gpio", ESP_LOG_NONE); - esp_flash_t* chip; - flashtest_config_t temp_config = *config; - // Use the lowest speed to read configs, data and write data to make sure success - temp_config.io_mode = SPI_FLASH_READ_MODE_MIN; - temp_config.freq_mhz = TEST_FLASH_SPEED_MIN; - setup_new_chip(&temp_config, &chip); - - //Get size to determine whether to test one extra partition - uint32_t size; - esp_err_t err = esp_flash_get_size(chip, &size); - TEST_ESP_OK(err); - ESP_LOGI(TAG, "Flash size: 0x%08X", size); - bool addr_32bit = (size > MAX_ADDR_24BIT); - - // Get test partition, and locate temporary partitions according to the default one - const esp_partition_t* test_part = get_test_data_partition(); - const int length = sizeof(large_const_buffer); - TEST_ASSERT(test_part->size > length + 2 + SPI_FLASH_SEC_SIZE); - - esp_partition_t part[2] = {}; - part[0] = *test_part; - part[0].flash_chip = chip; - // For flash with size over 16MB, add one extra round of test for the 32-bit address area - if (addr_32bit) { - part[1] = *test_part; - part[1].flash_chip = chip; - part[1].address = 0x1030000; - part[1].size = 0x0010000; - } else { - part[1].size = 0; - } - - // Prepare test data and write to the specified region - uint8_t *source_buf = malloc(length); - TEST_ASSERT_NOT_NULL(source_buf); - srand(778); - for (int i = 0; i < length; i++) { - source_buf[i] = rand(); - } - - for (int i = 0; i < 2; i++) { - if (part[i].size == 0) continue; - write_large_buffer(&part[i], source_buf, length); - } - - teardown_test_chip(chip); - - for (int i = 0; i < 2; i++) { - if (part[i].size == 0) continue; - - part[i].flash_chip = (esp_flash_t*)-1; - ESP_LOGI(TAG, "Testing address 0x%08X...", part[i].address); - test_permutations_part(config, &part[i], source_buf, length); - } - - free(source_buf); -} - -TEST_CASE("SPI flash test reading with all speed/mode permutations", "[esp_flash]") -{ - test_permutations_chip(&config_list[0]); -} - -#ifndef CONFIG_SPIRAM -#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2) -//IDF-5049 -TEST_CASE("SPI flash test reading with all speed/mode permutations, 3 chips", "[esp_flash_3][test_env=UT_T1_ESP_FLASH]") -{ - for (int i = 0; i < TEST_CONFIG_NUM; i++) { - test_permutations_chip(&config_list[i]); - } -} -#endif -#endif - - -static void test_write_large_const_buffer(const esp_partition_t* part) -{ - test_write_large_buffer(part, large_const_buffer, sizeof(large_const_buffer)); -} - -TEST_CASE_FLASH("Test esp_flash_write large const buffer", test_write_large_const_buffer); -TEST_CASE_MULTI_FLASH("Test esp_flash_write large const buffer", test_write_large_const_buffer); - -static void test_write_large_ram_buffer(const esp_partition_t* part) -{ - // buffer in RAM - uint8_t *source_buf = malloc(sizeof(large_const_buffer)); - TEST_ASSERT_NOT_NULL(source_buf); - memcpy(source_buf, large_const_buffer, sizeof(large_const_buffer)); - test_write_large_buffer(part, source_buf, sizeof(large_const_buffer)); - free(source_buf); -} - -TEST_CASE_FLASH("Test esp_flash_write large RAM buffer", test_write_large_ram_buffer); -TEST_CASE_MULTI_FLASH("Test esp_flash_write large RAM buffer", test_write_large_ram_buffer); - -static void write_large_buffer(const esp_partition_t *part, const uint8_t *source, size_t length) -{ - esp_flash_t* chip = part->flash_chip; - - printf("Writing chip %p %p, %d bytes from source %p\n", chip, (void*)part->address, length, source); - ESP_ERROR_CHECK( esp_flash_erase_region(chip, part->address, (length + SPI_FLASH_SEC_SIZE) & ~(SPI_FLASH_SEC_SIZE - 1)) ); - - // note writing to unaligned address - ESP_ERROR_CHECK( esp_flash_write(chip, source, part->address + 1, length) ); -} - -static void read_and_check(const esp_partition_t *part, const uint8_t *source, size_t length) -{ - esp_flash_t* chip = part->flash_chip; - printf("Checking chip %p 0x%08X, %d bytes\n", chip, part->address, length); - uint8_t *buf = malloc(length); - TEST_ASSERT_NOT_NULL(buf); - ESP_ERROR_CHECK( esp_flash_read(chip, buf, part->address + 1, length) ); - TEST_ASSERT_EQUAL_HEX8_ARRAY(source, buf, length); - free(buf); - - // check nothing was written at beginning or end - uint8_t ends[8]; - - ESP_ERROR_CHECK( esp_flash_read(chip, ends, part->address, sizeof(ends)) ); - TEST_ASSERT_EQUAL_HEX8(0xFF, ends[0]); - TEST_ASSERT_EQUAL_HEX8(source[0], ends[1]); - - ESP_ERROR_CHECK( esp_flash_read(chip, ends, part->address + length, sizeof(ends)) ); - - TEST_ASSERT_EQUAL_HEX8(source[length - 1], ends[0]); - TEST_ASSERT_EQUAL_HEX8(0xFF, ends[1]); - TEST_ASSERT_EQUAL_HEX8(0xFF, ends[2]); - TEST_ASSERT_EQUAL_HEX8(0xFF, ends[3]); -} - -static void test_write_large_buffer(const esp_partition_t* part, const uint8_t *source, size_t length) -{ - TEST_ASSERT(part->size > length + 2 + SPI_FLASH_SEC_SIZE); - - write_large_buffer(part, source, length); - read_and_check(part, source, length); -} typedef struct { uint32_t us_start; size_t len; const char* name; } time_meas_ctx_t; - static void time_measure_start(time_meas_ctx_t* ctx) { ctx->us_start = esp_timer_get_time(); @@ -1181,73 +639,3 @@ TEST_CASE("Test esp_flash read/write performance", "[esp_flash][test_env=UT_T1_E #endif TEST_CASE_MULTI_FLASH("Test esp_flash read/write performance", test_flash_read_write_performance); - -#ifdef CONFIG_SPIRAM_USE_MALLOC - -/* Utility: Read into a small internal RAM buffer using esp_flash_read() and compare what - we read with 'buffer' */ -static void s_test_compare_flash_contents_small_reads(esp_flash_t *chip, const uint8_t *buffer, size_t offs, size_t len) -{ - const size_t INTERNAL_BUF_SZ = 1024; // Should fit in internal RAM - uint8_t *ibuf = heap_caps_malloc(INTERNAL_BUF_SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); - TEST_ASSERT_NOT_NULL(ibuf); - - for (int i = 0; i < len; i += INTERNAL_BUF_SZ) { - size_t to_read = MIN(INTERNAL_BUF_SZ, len - i); - ESP_ERROR_CHECK( esp_flash_read(chip, ibuf, offs + i, to_read) ); - TEST_ASSERT_EQUAL_HEX8_ARRAY(buffer + i, ibuf, to_read); - } - - free(ibuf); -} - -static void test_flash_read_large_psram_buffer(const esp_partition_t *part) -{ - esp_flash_t* chip = part->flash_chip; - const size_t BUF_SZ = 256 * 1024; // Too large for internal RAM - const size_t TEST_OFFS = 0x1000; // Can be any offset, really - - uint8_t *buf = heap_caps_malloc(BUF_SZ, MALLOC_CAP_8BIT|MALLOC_CAP_SPIRAM); - TEST_ASSERT_NOT_NULL(buf); - - ESP_ERROR_CHECK( esp_flash_read(chip, buf, TEST_OFFS, BUF_SZ) ); - - // Read back the same into smaller internal memory buffer and check it all matches - s_test_compare_flash_contents_small_reads(chip, buf, TEST_OFFS, BUF_SZ); - - free(buf); -} - -TEST_CASE_FLASH("esp_flash_read large PSRAM buffer", test_flash_read_large_psram_buffer); - - -/* similar to above test, but perform it under memory pressure */ -static void test_flash_read_large_psram_buffer_low_internal_mem(const esp_partition_t *part) -{ - esp_flash_t* chip = part->flash_chip; - const size_t BUF_SZ = 256 * 1024; // Too large for internal RAM - const size_t REMAINING_INTERNAL = 1024; // Exhaust internal memory until maximum free block is less than this - const size_t TEST_OFFS = 0x8000; - - /* Exhaust the available free internal memory */ - test_utils_exhaust_memory_rec erec = test_utils_exhaust_memory(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT, REMAINING_INTERNAL); - - uint8_t *buf = heap_caps_malloc(BUF_SZ, MALLOC_CAP_8BIT|MALLOC_CAP_SPIRAM); - TEST_ASSERT_NOT_NULL(buf); - - /* Calling esp_flash_read() here will need to allocate a small internal buffer, - so check it works. */ - ESP_ERROR_CHECK( esp_flash_read(chip, buf, TEST_OFFS, BUF_SZ) ); - - test_utils_free_exhausted_memory(erec); - - // Read back the same into smaller internal memory buffer and check it all matches - s_test_compare_flash_contents_small_reads(chip, buf, TEST_OFFS, BUF_SZ); - - free(buf); -} - -TEST_CASE_FLASH("esp_flash_read large PSRAM buffer low memory", test_flash_read_large_psram_buffer_low_internal_mem); - - -#endif diff --git a/components/spi_flash/test_apps/esp_flash/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash/CMakeLists.txt new file mode 100644 index 0000000000..2f29d7008a --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/CMakeLists.txt @@ -0,0 +1,5 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_esp_flash_drv) diff --git a/components/spi_flash/test_apps/esp_flash/README.md b/components/spi_flash/test_apps/esp_flash/README.md new file mode 100644 index 0000000000..b5be4985c5 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | diff --git a/components/spi_flash/test_apps/esp_flash/main/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash/main/CMakeLists.txt new file mode 100644 index 0000000000..9058d7ac19 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/main/CMakeLists.txt @@ -0,0 +1,7 @@ +set(srcs "test_app_main.c" + "test_esp_flash_drv.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/esp_flash/main/test_app_main.c b/components/spi_flash/test_apps/esp_flash/main/test_app_main.c new file mode 100644 index 0000000000..83b782c198 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/main/test_app_main.c @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in flash encryption, the threadhold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-300) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +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); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + + // ,------. ,---. ,------. ,------.,--. ,--. + // | .---'' .-' | .--. ' | .---'| | ,--,--. ,---. | ,---. + // | `--, `. `-. | '--' | | `--, | |' ,-. |( .-' | .-. | + // | `---..-' || | --' | |` | |\ '-' |.-' `)| | | | + // `------'`-----' `--' `--' `--' `--`--'`----' `--' `--' + + + printf(",------. ,---. ,------. ,------.,--. ,--. \n"); + printf("| .---'' .-' | .--. ' | .---'| | ,--,--. ,---. | ,---. \n"); + printf("| `--, `. `-. | '--' | | `--, | |' ,-. |( .-' | .-. | \n"); + printf("| `---..-' || | --' | |` | |\\ '-' |.-' `)| | | | \n"); + printf("`------'`-----' `--' `--' `--' `--`--'`----' `--' `--' \n"); + + + unity_run_menu(); +} diff --git a/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_def.h b/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_def.h new file mode 100644 index 0000000000..b0ac1c8f32 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_def.h @@ -0,0 +1,242 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "sdkconfig.h" + +#define MAX_ADDR_24BIT 0x1000000 +#define TEST_SPI_SPEED 10 +#define TEST_SPI_READ_MODE SPI_FLASH_FASTRD +// #define FORCE_GPIO_MATRIX + +#if CONFIG_IDF_TARGET_ESP32 +#define EXTRA_SPI1_CLK_IO 17 //the pin which is usually used by the PSRAM clk +#define SPI1_CS_IO 16 //the pin which is usually used by the PSRAM cs + +#define HSPI_PIN_NUM_MOSI HSPI_IOMUX_PIN_NUM_MOSI +#define HSPI_PIN_NUM_MISO HSPI_IOMUX_PIN_NUM_MISO +#define HSPI_PIN_NUM_CLK HSPI_IOMUX_PIN_NUM_CLK +#define HSPI_PIN_NUM_HD HSPI_IOMUX_PIN_NUM_HD +#define HSPI_PIN_NUM_WP HSPI_IOMUX_PIN_NUM_WP +#define HSPI_PIN_NUM_CS HSPI_IOMUX_PIN_NUM_CS + +#define VSPI_PIN_NUM_MOSI VSPI_IOMUX_PIN_NUM_MOSI +#define VSPI_PIN_NUM_MISO VSPI_IOMUX_PIN_NUM_MISO +#define VSPI_PIN_NUM_CLK VSPI_IOMUX_PIN_NUM_CLK +#define VSPI_PIN_NUM_HD VSPI_IOMUX_PIN_NUM_HD +#define VSPI_PIN_NUM_WP VSPI_IOMUX_PIN_NUM_WP +#define VSPI_PIN_NUM_CS VSPI_IOMUX_PIN_NUM_CS + +#elif CONFIG_IDF_TARGET_ESP32S2 +#define SPI1_CS_IO 26 //the pin which is usually used by the PSRAM cs +#define SPI1_HD_IO 27 //the pin which is usually used by the PSRAM hd +#define SPI1_WP_IO 28 //the pin which is usually used by the PSRAM wp + +#define FSPI_PIN_NUM_MOSI 35 +#define FSPI_PIN_NUM_MISO 37 +#define FSPI_PIN_NUM_CLK 36 +#define FSPI_PIN_NUM_HD 33 +#define FSPI_PIN_NUM_WP 38 +#define FSPI_PIN_NUM_CS 34 + +// Just use the same pins for HSPI +#define HSPI_PIN_NUM_MOSI FSPI_PIN_NUM_MOSI +#define HSPI_PIN_NUM_MISO FSPI_PIN_NUM_MISO +#define HSPI_PIN_NUM_CLK FSPI_PIN_NUM_CLK +#define HSPI_PIN_NUM_HD FSPI_PIN_NUM_HD +#define HSPI_PIN_NUM_WP FSPI_PIN_NUM_WP +#define HSPI_PIN_NUM_CS FSPI_PIN_NUM_CS + +#elif CONFIG_IDF_TARGET_ESP32S3 +#define SPI1_CS_IO 26 //the pin which is usually used by the PSRAM cs +#define SPI1_HD_IO 27 //the pin which is usually used by the PSRAM hd +#define SPI1_WP_IO 28 //the pin which is usually used by the PSRAM wp + +#define FSPI_PIN_NUM_MOSI 11 +#define FSPI_PIN_NUM_MISO 13 +#define FSPI_PIN_NUM_CLK 12 +#define FSPI_PIN_NUM_HD 9 +#define FSPI_PIN_NUM_WP 14 +#define FSPI_PIN_NUM_CS 10 + +// Just use the same pins for HSPI +#define HSPI_PIN_NUM_MOSI FSPI_PIN_NUM_MOSI +#define HSPI_PIN_NUM_MISO FSPI_PIN_NUM_MISO +#define HSPI_PIN_NUM_CLK FSPI_PIN_NUM_CLK +#define HSPI_PIN_NUM_HD FSPI_PIN_NUM_HD +#define HSPI_PIN_NUM_WP FSPI_PIN_NUM_WP +#define HSPI_PIN_NUM_CS FSPI_PIN_NUM_CS + +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 +#define SPI1_CS_IO 26 //the pin which is usually used by the PSRAM cs +#define SPI1_HD_IO 27 //the pin which is usually used by the PSRAM hd +#define SPI1_WP_IO 28 //the pin which is usually used by the PSRAM wp + +#define FSPI_PIN_NUM_MOSI 7 +#define FSPI_PIN_NUM_MISO 2 +#define FSPI_PIN_NUM_CLK 6 +#define FSPI_PIN_NUM_HD 4 +#define FSPI_PIN_NUM_WP 5 +#define FSPI_PIN_NUM_CS 10 + +// Just use the same pins for HSPI +#define HSPI_PIN_NUM_MOSI FSPI_PIN_NUM_MOSI +#define HSPI_PIN_NUM_MISO FSPI_PIN_NUM_MISO +#define HSPI_PIN_NUM_CLK FSPI_PIN_NUM_CLK +#define HSPI_PIN_NUM_HD FSPI_PIN_NUM_HD +#define HSPI_PIN_NUM_WP FSPI_PIN_NUM_WP +#define HSPI_PIN_NUM_CS FSPI_PIN_NUM_CS +#endif + +#define TEST_CONFIG_NUM (sizeof(config_list)/sizeof(flashtest_config_t)) + +typedef void (*flash_test_func_t)(const esp_partition_t *part); + +/* Use TEST_CASE_FLASH for SPI flash tests that only use the main SPI flash chip +*/ +#define TEST_CASE_FLASH(STR, FUNC_TO_RUN) \ + TEST_CASE(STR, "[esp_flash]") {flash_test_func(FUNC_TO_RUN, 1 /* first index reserved for main flash */ );} + +#define TEST_CASE_FLASH_IGNORE(STR, FUNC_TO_RUN) \ + TEST_CASE(STR, "[esp_flash][ignore]") {flash_test_func(FUNC_TO_RUN, 1 /* first index reserved for main flash */ );} + +/* Use TEST_CASE_MULTI_FLASH for tests which also run on external flash, which sits in the place of PSRAM + (these tests are incompatible with PSRAM) + + These tests run for all the flash chip configs shown in config_list, below (internal and external). + */ +#define IDF_LOG_PERFORMANCE(item, value_fmt, value, ...) \ + printf("[Performance][%s]: " value_fmt "\n", item, value, ##__VA_ARGS__) + + +#if defined(CONFIG_SPIRAM) +//SPI1 CS1 occupied by PSRAM +#define BYPASS_MULTIPLE_CHIP 1 +#endif + +#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +//chips without PSRAM +#define TEST_CHIP_NUM 2 +#elif CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#define TEST_CHIP_NUM 3 +#endif + +#define _STRINGIFY(s) #s +#define STRINGIFY(s) _STRINGIFY(s) +#define TEST_CHIP_NUM_STR STRINGIFY(TEST_CHIP_NUM) + +#if BYPASS_MULTIPLE_CHIP +#define TEST_CASE_MULTI_FLASH TEST_CASE_MULTI_FLASH_IGNORE +#else +#if CONFIG_FREERTOS_SMP // IDF-5260 +#define TEST_CASE_MULTI_FLASH(STR, FUNC_TO_RUN) \ + TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_multi][test_env=UT_T1_ESP_FLASH][timeout=60]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} +#else +#define TEST_CASE_MULTI_FLASH(STR, FUNC_TO_RUN) \ + TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_multi][test_env=UT_T1_ESP_FLASH][timeout=35]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} +#endif +#endif + +#define TEST_CASE_MULTI_FLASH_IGNORE(STR, FUNC_TO_RUN) \ + TEST_CASE(STR", "TEST_CHIP_NUM_STR" chips", "[esp_flash_multi][test_env=UT_T1_ESP_FLASH][ignore]") {flash_test_func(FUNC_TO_RUN, TEST_CONFIG_NUM);} + + +//currently all the configs are the same with esp_flash_spi_device_config_t, no more information required +typedef esp_flash_spi_device_config_t flashtest_config_t; + +static const char TAG[] = "test_esp_flash"; + +#define FLASHTEST_CONFIG_COMMON \ + /* 0 always reserved for main flash */ \ + { \ + /* no need to init */ \ + .host_id = -1, \ + } \ + , \ + { \ + .io_mode = TEST_SPI_READ_MODE,\ + .freq_mhz = TEST_SPI_SPEED, \ + .host_id = SPI1_HOST, \ + .cs_id = 1, \ + /* the pin which is usually used by the PSRAM */ \ + .cs_io_num = SPI1_CS_IO, \ + .input_delay_ns = 0, \ + } + +#if CONFIG_IDF_TARGET_ESP32 +flashtest_config_t config_list[] = { + FLASHTEST_CONFIG_COMMON, + /* current runner doesn't have a flash on HSPI */ + // { + // .io_mode = TEST_SPI_READ_MODE, + // .freq_mhz = TEST_SPI_SPEED, + // .host_id = HSPI_HOST, + // .cs_id = 0, + // // uses GPIO matrix on esp32s2 regardless if FORCE_GPIO_MATRIX + // .cs_io_num = HSPI_PIN_NUM_CS, + // .input_delay_ns = 20, + // }, + { + .io_mode = TEST_SPI_READ_MODE, + .freq_mhz = TEST_SPI_SPEED, + .host_id = VSPI_HOST, + .cs_id = 0, + .cs_io_num = VSPI_PIN_NUM_CS, + .input_delay_ns = 0, + }, +}; +#elif CONFIG_IDF_TARGET_ESP32S2 +flashtest_config_t config_list[] = { + FLASHTEST_CONFIG_COMMON, + { + .io_mode = TEST_SPI_READ_MODE, + .freq_mhz = TEST_SPI_SPEED, + .host_id = FSPI_HOST, + .cs_id = 0, + .cs_io_num = FSPI_PIN_NUM_CS, + .input_delay_ns = 0, + }, + { + .io_mode = TEST_SPI_READ_MODE, + .freq_mhz = TEST_SPI_SPEED, + .host_id = HSPI_HOST, + .cs_id = 0, + // uses GPIO matrix on esp32s2 regardless of FORCE_GPIO_MATRIX + .cs_io_num = HSPI_PIN_NUM_CS, + .input_delay_ns = 0, + }, +}; +#elif CONFIG_IDF_TARGET_ESP32S3 +flashtest_config_t config_list[] = { + /* No SPI1 CS1 flash on esp32S3 test */ + { + /* no need to init */ + .host_id = -1, + }, + { + .io_mode = TEST_SPI_READ_MODE, + .freq_mhz = TEST_SPI_SPEED, + .host_id = SPI2_HOST, + .cs_id = 0, + .cs_io_num = FSPI_PIN_NUM_CS, + .input_delay_ns = 0, + }, +}; +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 +flashtest_config_t config_list[] = { + /* No SPI1 CS1 flash on esp32c3 test */ + { + /* no need to init */ + .host_id = -1, + }, + { + .io_mode = TEST_SPI_READ_MODE, + .freq_mhz = TEST_SPI_SPEED, + .host_id = SPI2_HOST, + .cs_id = 0, + .cs_io_num = FSPI_PIN_NUM_CS, + .input_delay_ns = 0, + }, +}; +#endif diff --git a/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c b/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c new file mode 100644 index 0000000000..65516d1dce --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c @@ -0,0 +1,850 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include +#include +#include + +#include +#include "esp_flash.h" +#include "esp_private/spi_common_internal.h" +#include "esp_flash_spi_init.h" +#include "memspi_host_driver.h" +#include +#include "esp_log.h" + +#include "unity.h" +#include "driver/gpio.h" +#include "soc/io_mux_reg.h" +#include "sdkconfig.h" + +#include "esp_partition.h" +#include "esp_rom_gpio.h" +#include "esp_rom_sys.h" +#include "esp_timer.h" +#include "test_esp_flash_def.h" + +#if CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32C3 +#include "esp32c3/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32H2 +#include "esp32h2/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32C2 +#include "esp32c2/rom/cache.h" +#endif + +#define FUNC_SPI 1 + +static uint8_t sector_buf[4096]; + +const esp_partition_t *get_test_data_partition(void) +{ + /* This finds "flash_test" partition defined in partition_table_unit_test_app.csv */ + const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, "flash_test"); + TEST_ASSERT_NOT_NULL(result); /* means partition table set wrong */ + return result; +} + +static void get_chip_host(esp_flash_t* chip, spi_host_device_t* out_host_id, int* out_cs_id) +{ + spi_host_device_t host_id; + int cs_id; + if (chip == NULL) { + host_id = SPI1_HOST; + cs_id = 0; + } else { + spi_flash_hal_context_t* host_data = (spi_flash_hal_context_t*)chip->host; + host_id = spi_flash_ll_hw_get_id(host_data->spi); + cs_id = host_data->cs_num; + } + if (out_host_id) { + *out_host_id = host_id; + } + if (out_cs_id) { + *out_cs_id = cs_id; + } +} + +#if CONFIG_IDF_TARGET_ESP32 +static void setup_bus(spi_host_device_t host_id) +{ + if (host_id == SPI1_HOST) { + ESP_LOGI(TAG, "setup flash on SPI1 CS1...\n"); + //no need to initialize the bus, however the CLK may need one more output if it's on the usual place of PSRAM + esp_rom_gpio_connect_out_signal(EXTRA_SPI1_CLK_IO, SPICLK_OUT_IDX, 0, 0); + //currently the SPI bus for main flash chip is initialized through GPIO matrix + } else if (host_id == SPI2_HOST) { + ESP_LOGI(TAG, "setup flash on SPI%u (HSPI) CS0...\n", host_id + 1); + spi_bus_config_t hspi_bus_cfg = { + .mosi_io_num = HSPI_PIN_NUM_MOSI, + .miso_io_num = HSPI_PIN_NUM_MISO, + .sclk_io_num = HSPI_PIN_NUM_CLK, + .quadhd_io_num = HSPI_PIN_NUM_HD, + .quadwp_io_num = HSPI_PIN_NUM_WP, + .max_transfer_sz = 64, + }; + esp_err_t ret = spi_bus_initialize(host_id, &hspi_bus_cfg, 0); + TEST_ESP_OK(ret); + } else if (host_id == SPI3_HOST) { + ESP_LOGI(TAG, "setup flash on SPI%u (VSPI) CS0...\n", host_id + 1); + spi_bus_config_t vspi_bus_cfg = { + .mosi_io_num = VSPI_PIN_NUM_MOSI, + .miso_io_num = VSPI_PIN_NUM_MISO, + .sclk_io_num = VSPI_PIN_NUM_CLK, + .quadhd_io_num = VSPI_PIN_NUM_HD, + .quadwp_io_num = VSPI_PIN_NUM_WP, + .max_transfer_sz = 64, + }; + esp_err_t ret = spi_bus_initialize(host_id, &vspi_bus_cfg, 0); + TEST_ESP_OK(ret); + } else { + ESP_LOGE(TAG, "invalid bus"); + } +} +#else // FOR ESP32-S2, ESP32-S3, ESP32-C3 +static void setup_bus(spi_host_device_t host_id) +{ + if (host_id == SPI1_HOST) { + ESP_LOGI(TAG, "setup flash on SPI1 CS1...\n"); +#if !CONFIG_ESPTOOLPY_FLASHMODE_QIO && !CONFIG_ESPTOOLPY_FLASHMODE_QOUT + //Initialize the WP and HD pins, which are not automatically initialized on ESP32-S2. + int wp_pin = spi_periph_signal[host_id].spiwp_iomux_pin; + int hd_pin = spi_periph_signal[host_id].spihd_iomux_pin; + gpio_iomux_in(wp_pin, spi_periph_signal[host_id].spiwp_in); + gpio_iomux_out(wp_pin, spi_periph_signal[host_id].func, false); + gpio_iomux_in(hd_pin, spi_periph_signal[host_id].spihd_in); + gpio_iomux_out(hd_pin, spi_periph_signal[host_id].func, false); +#endif //CONFIG_ESPTOOLPY_FLASHMODE_QIO || CONFIG_ESPTOOLPY_FLASHMODE_QOUT + //currently the SPI bus for main flash chip is initialized through GPIO matrix + } else if (host_id == SPI2_HOST) { + ESP_LOGI(TAG, "setup flash on SPI%u (FSPI) CS0...\n", host_id + 1); + spi_bus_config_t fspi_bus_cfg = { + .mosi_io_num = FSPI_PIN_NUM_MOSI, + .miso_io_num = FSPI_PIN_NUM_MISO, + .sclk_io_num = FSPI_PIN_NUM_CLK, + .quadhd_io_num = FSPI_PIN_NUM_HD, + .quadwp_io_num = FSPI_PIN_NUM_WP, + .max_transfer_sz = 64, + }; + esp_err_t ret = spi_bus_initialize(host_id, &fspi_bus_cfg, 0); + TEST_ESP_OK(ret); + } else if (host_id == SPI3_HOST) { + ESP_LOGI(TAG, "setup flash on SPI%u (HSPI) CS0...\n", host_id + 1); + spi_bus_config_t hspi_bus_cfg = { + .mosi_io_num = HSPI_PIN_NUM_MOSI, + .miso_io_num = HSPI_PIN_NUM_MISO, + .sclk_io_num = HSPI_PIN_NUM_CLK, + .quadhd_io_num = HSPI_PIN_NUM_HD, + .quadwp_io_num = HSPI_PIN_NUM_WP, + .max_transfer_sz = 64, + }; + esp_err_t ret = spi_bus_initialize(host_id, &hspi_bus_cfg, 0); + TEST_ESP_OK(ret); + + // HSPI have no multiline mode, use GPIO to pull those pins up + gpio_set_direction(HSPI_PIN_NUM_HD, GPIO_MODE_OUTPUT); + gpio_set_level(HSPI_PIN_NUM_HD, 1); + + gpio_set_direction(HSPI_PIN_NUM_WP, GPIO_MODE_OUTPUT); + gpio_set_level(HSPI_PIN_NUM_WP, 1); + } else { + ESP_LOGE(TAG, "invalid bus"); + } +} +#endif // CONFIG_IDF_TARGET_ESP32 + +static void release_bus(int host_id) +{ + //SPI1 bus can't be deinitialized + if (host_id == SPI2_HOST || host_id == SPI3_HOST) { + spi_bus_free(host_id); + } +} + +static void setup_new_chip(const flashtest_config_t* test_cfg, esp_flash_t** out_chip) +{ + //the bus should be initialized before the flash is attached to the bus + if (test_cfg->host_id == -1) { + *out_chip = NULL; + return; + } + setup_bus(test_cfg->host_id); + + esp_flash_spi_device_config_t dev_cfg = { + .host_id = test_cfg->host_id, + .io_mode = test_cfg->io_mode, + .freq_mhz = test_cfg->freq_mhz, + .cs_id = test_cfg->cs_id, + .cs_io_num = test_cfg->cs_io_num, + .input_delay_ns = test_cfg->input_delay_ns, + }; + esp_flash_t* init_chip; + esp_err_t err = spi_bus_add_flash_device(&init_chip, &dev_cfg); + TEST_ESP_OK(err); + err = esp_flash_init(init_chip); + TEST_ESP_OK(err); + *out_chip = init_chip; +} + +static void teardown_test_chip(esp_flash_t* chip) +{ + spi_host_device_t host_id; + get_chip_host(chip, &host_id, NULL); + //happen to work when chip==NULL + spi_bus_remove_flash_device(chip); + release_bus(host_id); +} + +static void flash_test_core(flash_test_func_t func, const flashtest_config_t* config) +{ + esp_flash_t* chip; + setup_new_chip(config, &chip); + + uint32_t size; + esp_err_t err = esp_flash_get_size(chip, &size); + TEST_ESP_OK(err); + ESP_LOGI(TAG, "Flash size: 0x%08lX", size); + + const esp_partition_t* test_part = get_test_data_partition(); + TEST_ASSERT_NOT_EQUAL(NULL, test_part->flash_chip); + + esp_partition_t part = *test_part; + part.flash_chip = chip; + + ESP_LOGI(TAG, "Testing chip %p, address 0x%08lX...", part.flash_chip, part.address); + (*func)(&part); + + // For flash with size over 16MB, add one extra round of test for the 32-bit address area + if (size > MAX_ADDR_24BIT) { + part.address = 0x1030000; + part.size = 0x0010000; + ESP_LOGI(TAG, "Testing chip %p, address 0x%08lX...", part.flash_chip, part.address); + (*func)(&part); + } + + teardown_test_chip(chip); +} + +static void flash_test_func(flash_test_func_t func, int test_num) +{ + esp_log_level_set("gpio", ESP_LOG_NONE); + for (int i = 0; i < test_num; i++) { + ESP_LOGI(TAG, "Testing config %u/%u", i+1, test_num); + flash_test_core(func, &config_list[i]); + } + ESP_LOGI(TAG, "Completed %u configs", test_num); +} + +/* ---------- Test code start ------------*/ + +static void test_metadata(const esp_partition_t* part) +{ + esp_flash_t* chip = part->flash_chip; + uint32_t id, size; + TEST_ESP_OK(esp_flash_read_id(chip, &id)); + TEST_ESP_OK(esp_flash_get_size(chip, &size)); + printf("Flash ID %08lx detected size %" PRIu32 "bytes\n", id, size); +} + +TEST_CASE_FLASH("SPI flash metadata functions", test_metadata); +TEST_CASE_MULTI_FLASH("SPI flash metadata functions", test_metadata); + +static uint32_t erase_test_region(const esp_partition_t *part, int num_sectors) +{ + esp_flash_t* chip = part->flash_chip; + uint32_t offs = part->address; + + /* chip should be initialised */ + TEST_ASSERT(esp_flash_default_chip != NULL + && esp_flash_chip_driver_initialized(esp_flash_default_chip)); + + TEST_ASSERT(num_sectors * 4096 <= part->size); + + bzero(sector_buf, sizeof(sector_buf)); + + printf("Erase @ 0x%lx...\n", offs); + TEST_ASSERT_EQUAL_HEX32(ESP_OK, esp_flash_erase_region(chip, offs, num_sectors * 4096) ); + + printf("Verify erased...\n"); + for (int i = 0; i < num_sectors; i++) { + TEST_ASSERT_EQUAL_HEX32(ESP_OK, esp_flash_read(chip, sector_buf, offs + i * 4096, sizeof(sector_buf))); + + printf("Buffer starts 0x%02x 0x%02x 0x%02x 0x%02x\n", sector_buf[0], sector_buf[1], sector_buf[2], sector_buf[3]); + for (int i = 0; i < sizeof(sector_buf); i++) { + TEST_ASSERT_EQUAL_HEX8(0xFF, sector_buf[i]); + } + } + + return offs; +} + +void test_simple_read_write(const esp_partition_t* part) +{ + esp_flash_t* chip = part->flash_chip; + uint32_t offs = erase_test_region(part, 1); + + const int test_seed = 778; + srand(test_seed); + for (int i = 0 ; i < sizeof(sector_buf); i++) { + sector_buf[i] = rand(); + } + + printf("Write %p...\n", (void *)offs); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, sector_buf, offs, sizeof(sector_buf)) ); + + bzero(sector_buf, sizeof(sector_buf)); + + printf("Read back...\n"); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, sector_buf, offs, sizeof(sector_buf)) ); + + printf("Buffer starts 0x%02x 0x%02x 0x%02x 0x%02x\n", sector_buf[0], sector_buf[1], sector_buf[2], sector_buf[3]); + + srand(test_seed); + for (int i = 0; i < sizeof(sector_buf); i++) { + uint8_t data = rand(); + TEST_ASSERT_EQUAL_HEX8(data, sector_buf[i]); + } +} + +TEST_CASE_FLASH("SPI flash simple read/write", test_simple_read_write); +TEST_CASE_MULTI_FLASH("SPI flash simple read/write", test_simple_read_write); + +void test_unaligned_read_write(const esp_partition_t* part) +{ + esp_flash_t* chip = part->flash_chip; + uint32_t offs = erase_test_region(part, 2); + + const char *msg = "i am a message"; + TEST_ASSERT(strlen(msg) + 1 % 4 != 0); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, msg, offs + 1, strlen(msg) + 1) ); + + char buf[strlen(msg) + 1]; + + memset(buf, 0xEE, sizeof(buf)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, buf, offs + 1, strlen(msg) + 1) ); + TEST_ASSERT_EQUAL_STRING_LEN(msg, buf, strlen(msg)); + TEST_ASSERT(memcmp(buf, msg, strlen(msg) + 1) == 0); +} + +TEST_CASE_FLASH("SPI flash unaligned read/write", test_unaligned_read_write); +TEST_CASE_MULTI_FLASH("SPI flash unaligned read/write", test_unaligned_read_write); + +void test_single_read_write(const esp_partition_t* part) +{ + esp_flash_t* chip = part->flash_chip; + uint32_t offs = erase_test_region(part, 2); + + const int seed = 699; + srand(seed); + for (unsigned v = 0; v < 512; v++) { + uint32_t data = rand(); + TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_flash_write(chip, &data, offs + v, 1) ); + } + + srand(seed); + for (unsigned v = 0; v < 512; v++) { + uint8_t readback; + uint32_t data = rand(); + TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_flash_read(chip, &readback, offs + v, 1) ); + TEST_ASSERT_EQUAL_HEX8(data, readback); + } +} + +TEST_CASE_FLASH("SPI flash single byte reads/writes", test_single_read_write); +TEST_CASE_MULTI_FLASH("SPI flash single byte reads/writes", test_single_read_write); + + +/* this test is notable because it generates a lot of unaligned reads/writes, + and also reads/writes across both a sector boundary & many page boundaries. +*/ +void test_three_byte_read_write(const esp_partition_t* part) +{ + esp_flash_t* chip = part->flash_chip; + uint32_t offs = erase_test_region(part, 2); + + const int seed = 700; + esp_rom_printf("offs:%X\n", offs); + + srand(seed); + for (uint32_t v = 0; v < 86; v++) { + uint32_t data = rand(); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, &data, offs + 3 * v, 3) ); + } + + srand(seed); + for (uint32_t v = 0; v < 1; v++) { + uint32_t readback; + uint32_t data = rand(); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, offs + 3 * v, 3) ); + TEST_ASSERT_EQUAL_HEX32(data & 0xFFFFFF, readback & 0xFFFFFF); + } +} + +TEST_CASE_FLASH("SPI flash three byte reads/writes", test_three_byte_read_write); +TEST_CASE_MULTI_FLASH("SPI flash three byte reads/writes", test_three_byte_read_write); + +void test_erase_large_region(const esp_partition_t *part) +{ + esp_flash_t* chip = part->flash_chip; + + /* Write some noise at the start and the end of the region */ + const char *ohai = "OHAI"; + uint32_t readback; + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, ohai, part->address, 5)); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_write(chip, ohai, part->address + part->size - 5, 5)); + + /* sanity check what we just wrote. since the partition may haven't been erased, we only check the part which is written to 0. */ + uint32_t written_data = *((const uint32_t *)ohai); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address + part->size - 5, 4)); + TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address, 4)); + TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); + + /* Erase zero bytes, check that nothing got erased */ + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_erase_region(chip, part->address, 0)); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address + part->size - 5, 4)); + TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address, 4)); + TEST_ASSERT_EQUAL_HEX32(0, readback & (~written_data)); + + /* Erase whole region */ + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_erase_region(chip, part->address, part->size)); + + /* ensure both areas we wrote are now all-FFs */ + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address, 4)); + TEST_ASSERT_EQUAL_HEX32(0xFFFFFFFF, readback); + + TEST_ASSERT_EQUAL(ESP_OK, esp_flash_read(chip, &readback, part->address + part->size - 5, 4)); + TEST_ASSERT_EQUAL_HEX32(0xFFFFFFFF, readback); +} + +TEST_CASE_FLASH("SPI flash erase large region", test_erase_large_region); +TEST_CASE_MULTI_FLASH("SPI flash erase large region", test_erase_large_region); + +#if CONFIG_SPI_FLASH_AUTO_SUSPEND +void esp_test_for_suspend(void) +{ + /*clear content in cache*/ +#if !CONFIG_IDF_TARGET_ESP32C3 + Cache_Invalidate_DCache_All(); +#endif + Cache_Invalidate_ICache_All(); + ESP_LOGI(TAG, "suspend test begins:"); + printf("run into test suspend function\n"); + printf("print something when flash is erasing:\n"); + printf("aaaaa bbbbb zzzzz fffff qqqqq ccccc\n"); +} + +static volatile bool task_erase_end, task_suspend_end = false; +void task_erase_large_region(void *arg) +{ + esp_partition_t *part = (esp_partition_t *)arg; + test_erase_large_region(part); + task_erase_end = true; + vTaskDelete(NULL); +} + +void task_request_suspend(void *arg) +{ + vTaskDelay(2); + ESP_LOGI(TAG, "flash go into suspend"); + esp_test_for_suspend(); + task_suspend_end = true; + vTaskDelete(NULL); +} + +static void test_flash_suspend_resume(const esp_partition_t* part) +{ + xTaskCreatePinnedToCore(task_request_suspend, "suspend", 2048, (void *)"test_for_suspend", UNITY_FREERTOS_PRIORITY + 3, NULL, 0); + xTaskCreatePinnedToCore(task_erase_large_region, "test", 2048, (void *)part, UNITY_FREERTOS_PRIORITY + 2, NULL, 0); + while (!task_erase_end || !task_suspend_end) { + } + vTaskDelay(200); +} + +TEST_CASE("SPI flash suspend and resume test", "[esp_flash][test_env=UT_T1_Flash_Suspend]") +{ + flash_test_func(test_flash_suspend_resume, 1 /* first index reserved for main flash */ ); +} + +#endif //CONFIG_SPI_FLASH_AUTO_SUSPEND + +static void test_write_protection(const esp_partition_t* part) +{ + esp_flash_t* chip = part->flash_chip; + + bool wp = true; + esp_err_t ret = ESP_OK; + ret = esp_flash_get_chip_write_protect(chip, &wp); + TEST_ESP_OK(ret); + + for (int i = 0; i < 4; i ++) { + bool wp_write = !wp; + ret = esp_flash_set_chip_write_protect(chip, wp_write); + TEST_ESP_OK(ret); + + bool wp_read; + ret = esp_flash_get_chip_write_protect(chip, &wp_read); + TEST_ESP_OK(ret); + TEST_ASSERT(wp_read == wp_write); + wp = wp_read; + } +} + +TEST_CASE_FLASH("Test esp_flash can enable/disable write protetion", test_write_protection); +TEST_CASE_MULTI_FLASH("Test esp_flash can enable/disable write protetion", test_write_protection); + +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 +}; + +static void test_write_large_buffer(const esp_partition_t *part, const uint8_t *source, size_t length); +static void write_large_buffer(const esp_partition_t *part, const uint8_t *source, size_t length); +static void read_and_check(const esp_partition_t *part, const uint8_t *source, size_t length); + +// Internal functions for testing, from esp_flash_api.c +#if !CONFIG_ESPTOOLPY_OCT_FLASH +esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe); +esp_err_t esp_flash_get_io_mode(esp_flash_t* chip, bool* qe); +esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id); +esp_err_t spi_flash_chip_mxic_probe(esp_flash_t *chip, uint32_t flash_id); + +static bool is_winbond_chip(esp_flash_t* chip) +{ + uint32_t flash_id; + esp_err_t ret = esp_flash_read_chip_id(chip, &flash_id); + TEST_ESP_OK(ret); + if ((flash_id >> 16) == 0xEF) { + return true; + } else { + return false; + } +} + +static bool is_mxic_chip(esp_flash_t* chip) +{ + uint32_t flash_id; + esp_err_t ret = esp_flash_read_chip_id(chip, &flash_id); + TEST_ESP_OK(ret); + return (spi_flash_chip_mxic_probe(chip, flash_id)==ESP_OK); +} + +IRAM_ATTR NOINLINE_ATTR static void test_toggle_qe(const esp_partition_t* part) +{ + esp_flash_t* chip = part->flash_chip; + + bool qe; + if (chip == NULL) { + chip = esp_flash_default_chip; + } + esp_flash_io_mode_t io_mode_before = chip->read_mode; + esp_err_t ret = esp_flash_get_io_mode(chip, &qe); + TEST_ESP_OK(ret); + + bool allow_failure = is_winbond_chip(chip) || is_mxic_chip(chip); + + for (int i = 0; i < 4; i ++) { + esp_rom_printf(DRAM_STR("write qe: %" PRIu32 "->%" PRIu32 "\n"), qe, !qe); + qe = !qe; + chip->read_mode = qe? SPI_FLASH_QOUT: SPI_FLASH_SLOWRD; + ret = esp_flash_set_io_mode(chip, qe); + if (allow_failure && !qe && ret == ESP_ERR_FLASH_NO_RESPONSE) { + //allows clear qe failure for Winbond chips + ret = ESP_OK; + } + TEST_ESP_OK(ret); + + bool qe_read; + ret = esp_flash_get_io_mode(chip, &qe_read); + TEST_ESP_OK(ret); + ESP_LOGD(TAG, "qe read: %u", qe_read); + if (!qe && qe_read) { + if (allow_failure) { + ESP_LOGW(TAG, "cannot clear QE bit for known permanent QE (Winbond or MXIC) chips."); + } else { + ESP_LOGE(TAG, "cannot clear QE bit, please make sure force clearing QE option is enabled in `spi_flash_common_set_io_mode`, and this chip is not a permanent QE one."); + } + chip->read_mode = io_mode_before; + return; + } + TEST_ASSERT_EQUAL(qe, qe_read); + } + //restore the io_mode after test + chip->read_mode = io_mode_before; +} + +// These tests show whether the QE is permanent or not for the chip tested. +// To test the behaviour of a new SPI flash chip, enable force_check flag in generic driver +// `spi_flash_common_set_io_mode` and then run this test. +TEST_CASE_FLASH_IGNORE("Test esp_flash_write can toggle QE bit", test_toggle_qe); +TEST_CASE_MULTI_FLASH_IGNORE("Test esp_flash_write can toggle QE bit", test_toggle_qe); +#endif //CONFIG_ESPTOOLPY_OCT_FLASH + +// This table could be chip specific in the future. +uint8_t flash_frequency_table[6] = {5, 10, 20, 26, 40, 80}; +#define TEST_FLASH_SPEED_MIN 5 +void test_permutations_part(const flashtest_config_t* config, esp_partition_t* part, void* source_buf, size_t length) +{ + int clock_index = 0; + if (config->host_id != -1) { + while (clock_index < sizeof(flash_frequency_table)/sizeof(uint8_t)) { + uint8_t speed = flash_frequency_table[clock_index]; + //test io_mode in the inner loop to test QE set/clear function, since + //the io mode will switch frequently. + esp_flash_io_mode_t io_mode = SPI_FLASH_READ_MODE_MIN; + while (io_mode != SPI_FLASH_QIO + 1) { + if (io_mode > SPI_FLASH_FASTRD && + !SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(config->host_id)) { + io_mode++; + continue; + } + + esp_flash_t* chip; + flashtest_config_t temp_config = *config; + temp_config.io_mode = io_mode; + temp_config.freq_mhz = flash_frequency_table[clock_index]; + setup_new_chip(&temp_config, &chip); + ESP_LOGI(TAG, "test flash io mode: %u, speed: %u", io_mode, speed); + + part->flash_chip = chip; + read_and_check(part, source_buf, length); + teardown_test_chip(chip); + + io_mode++; + } + clock_index++; + } + } else { + //test main flash + part->flash_chip = NULL; + read_and_check(part, source_buf, length); + } +} + +void test_permutations_chip(const flashtest_config_t* config) +{ + esp_log_level_set("gpio", ESP_LOG_NONE); + esp_flash_t* chip; + flashtest_config_t temp_config = *config; + // Use the lowest speed to read configs, data and write data to make sure success + temp_config.io_mode = SPI_FLASH_READ_MODE_MIN; + temp_config.freq_mhz = TEST_FLASH_SPEED_MIN; + setup_new_chip(&temp_config, &chip); + + //Get size to determine whether to test one extra partition + uint32_t size; + esp_err_t err = esp_flash_get_size(chip, &size); + TEST_ESP_OK(err); + ESP_LOGI(TAG, "Flash size: 0x%08lX", size); + bool addr_32bit = (size > MAX_ADDR_24BIT); + + // Get test partition, and locate temporary partitions according to the default one + const esp_partition_t* test_part = get_test_data_partition(); + const int length = sizeof(large_const_buffer); + TEST_ASSERT(test_part->size > length + 2 + SPI_FLASH_SEC_SIZE); + + esp_partition_t part[2] = {}; + part[0] = *test_part; + part[0].flash_chip = chip; + // For flash with size over 16MB, add one extra round of test for the 32-bit address area + if (addr_32bit) { + part[1] = *test_part; + part[1].flash_chip = chip; + part[1].address = 0x1030000; + part[1].size = 0x0010000; + } else { + part[1].size = 0; + } + + // Prepare test data and write to the specified region + uint8_t *source_buf = malloc(length); + TEST_ASSERT_NOT_NULL(source_buf); + srand(778); + for (int i = 0; i < length; i++) { + source_buf[i] = rand(); + } + + for (int i = 0; i < 2; i++) { + if (part[i].size == 0) continue; + write_large_buffer(&part[i], source_buf, length); + } + + teardown_test_chip(chip); + + for (int i = 0; i < 2; i++) { + if (part[i].size == 0) continue; + + part[i].flash_chip = (esp_flash_t*)-1; + ESP_LOGI(TAG, "Testing address 0x%08lX...", part[i].address); + test_permutations_part(config, &part[i], source_buf, length); + } + + free(source_buf); +} + +TEST_CASE("SPI flash test reading with all speed/mode permutations", "[esp_flash]") +{ + test_permutations_chip(&config_list[0]); +} + +#ifndef CONFIG_SPIRAM +TEST_CASE("SPI flash test reading with all speed/mode permutations, 3 chips", "[esp_flash_multi][test_env=UT_T1_ESP_FLASH]") +{ + for (int i = 0; i < TEST_CONFIG_NUM; i++) { + test_permutations_chip(&config_list[i]); + } +} +#endif + + +static void test_write_large_const_buffer(const esp_partition_t* part) +{ + test_write_large_buffer(part, large_const_buffer, sizeof(large_const_buffer)); +} + +TEST_CASE_FLASH("Test esp_flash_write large const buffer", test_write_large_const_buffer); +TEST_CASE_MULTI_FLASH("Test esp_flash_write large const buffer", test_write_large_const_buffer); + +static void test_write_large_ram_buffer(const esp_partition_t* part) +{ + // buffer in RAM + uint8_t *source_buf = malloc(sizeof(large_const_buffer)); + TEST_ASSERT_NOT_NULL(source_buf); + memcpy(source_buf, large_const_buffer, sizeof(large_const_buffer)); + test_write_large_buffer(part, source_buf, sizeof(large_const_buffer)); + free(source_buf); +} + +TEST_CASE_FLASH("Test esp_flash_write large RAM buffer", test_write_large_ram_buffer); +TEST_CASE_MULTI_FLASH("Test esp_flash_write large RAM buffer", test_write_large_ram_buffer); + +static void write_large_buffer(const esp_partition_t *part, const uint8_t *source, size_t length) +{ + esp_flash_t* chip = part->flash_chip; + + printf("Writing chip %p %p, %u bytes from source %p\n", chip, (void*)part->address, length, source); + ESP_ERROR_CHECK( esp_flash_erase_region(chip, part->address, (length + SPI_FLASH_SEC_SIZE) & ~(SPI_FLASH_SEC_SIZE - 1)) ); + + // note writing to unaligned address + ESP_ERROR_CHECK( esp_flash_write(chip, source, part->address + 1, length) ); +} + +static void read_and_check(const esp_partition_t *part, const uint8_t *source, size_t length) +{ + esp_flash_t* chip = part->flash_chip; + printf("Checking chip %p 0x%08lX, %u bytes\n", chip, part->address, length); + uint8_t *buf = malloc(length); + TEST_ASSERT_NOT_NULL(buf); + ESP_ERROR_CHECK( esp_flash_read(chip, buf, part->address + 1, length) ); + TEST_ASSERT_EQUAL_HEX8_ARRAY(source, buf, length); + free(buf); + + // check nothing was written at beginning or end + uint8_t ends[8]; + + ESP_ERROR_CHECK( esp_flash_read(chip, ends, part->address, sizeof(ends)) ); + TEST_ASSERT_EQUAL_HEX8(0xFF, ends[0]); + TEST_ASSERT_EQUAL_HEX8(source[0], ends[1]); + + ESP_ERROR_CHECK( esp_flash_read(chip, ends, part->address + length, sizeof(ends)) ); + + TEST_ASSERT_EQUAL_HEX8(source[length - 1], ends[0]); + TEST_ASSERT_EQUAL_HEX8(0xFF, ends[1]); + TEST_ASSERT_EQUAL_HEX8(0xFF, ends[2]); + TEST_ASSERT_EQUAL_HEX8(0xFF, ends[3]); +} + +static void test_write_large_buffer(const esp_partition_t* part, const uint8_t *source, size_t length) +{ + TEST_ASSERT(part->size > length + 2 + SPI_FLASH_SEC_SIZE); + + write_large_buffer(part, source, length); + read_and_check(part, source, length); +} + +#ifdef CONFIG_SPIRAM_USE_MALLOC + +/* Utility: Read into a small internal RAM buffer using esp_flash_read() and compare what + we read with 'buffer' */ +static void s_test_compare_flash_contents_small_reads(esp_flash_t *chip, const uint8_t *buffer, size_t offs, size_t len) +{ + const size_t INTERNAL_BUF_SZ = 1024; // Should fit in internal RAM + uint8_t *ibuf = heap_caps_malloc(INTERNAL_BUF_SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(ibuf); + + for (int i = 0; i < len; i += INTERNAL_BUF_SZ) { + size_t to_read = MIN(INTERNAL_BUF_SZ, len - i); + ESP_ERROR_CHECK( esp_flash_read(chip, ibuf, offs + i, to_read) ); + TEST_ASSERT_EQUAL_HEX8_ARRAY(buffer + i, ibuf, to_read); + } + + free(ibuf); +} + +static void test_flash_read_large_psram_buffer(const esp_partition_t *part) +{ + esp_flash_t* chip = part->flash_chip; + const size_t BUF_SZ = 256 * 1024; // Too large for internal RAM + const size_t TEST_OFFS = 0x1000; // Can be any offset, really + + uint8_t *buf = heap_caps_malloc(BUF_SZ, MALLOC_CAP_8BIT|MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(buf); + + ESP_ERROR_CHECK( esp_flash_read(chip, buf, TEST_OFFS, BUF_SZ) ); + + // Read back the same into smaller internal memory buffer and check it all matches + s_test_compare_flash_contents_small_reads(chip, buf, TEST_OFFS, BUF_SZ); + + free(buf); +} + +TEST_CASE_FLASH("esp_flash_read large PSRAM buffer", test_flash_read_large_psram_buffer); + + +/* similar to above test, but perform it under memory pressure */ +static void test_flash_read_large_psram_buffer_low_internal_mem(const esp_partition_t *part) +{ + esp_flash_t* chip = part->flash_chip; + const size_t BUF_SZ = 256 * 1024; // Too large for internal RAM + const size_t REMAINING_INTERNAL = 1024; // Exhaust internal memory until maximum free block is less than this + const size_t TEST_OFFS = 0x8000; + + /* Exhaust the available free internal memory */ + test_utils_exhaust_memory_rec erec = test_utils_exhaust_memory(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT, REMAINING_INTERNAL); + + uint8_t *buf = heap_caps_malloc(BUF_SZ, MALLOC_CAP_8BIT|MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(buf); + + /* Calling esp_flash_read() here will need to allocate a small internal buffer, + so check it works. */ + ESP_ERROR_CHECK( esp_flash_read(chip, buf, TEST_OFFS, BUF_SZ) ); + + test_utils_free_exhausted_memory(erec); + + // Read back the same into smaller internal memory buffer and check it all matches + s_test_compare_flash_contents_small_reads(chip, buf, TEST_OFFS, BUF_SZ); + + free(buf); +} + +TEST_CASE_FLASH("esp_flash_read large PSRAM buffer low memory", test_flash_read_large_psram_buffer_low_internal_mem); + + +#endif diff --git a/components/spi_flash/test_apps/esp_flash/partitions.csv b/components/spi_flash/test_apps/esp_flash/partitions.csv new file mode 100644 index 0000000000..c941d8f4f1 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/partitions.csv @@ -0,0 +1,5 @@ +# 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, +factory, 0, 0, 0x10000, 1M +flash_test, data, fat, , 528K diff --git a/components/spi_flash/test_apps/esp_flash/pytest_esp_flash.py b/components/spi_flash/test_apps/esp_flash/pytest_esp_flash.py new file mode 100644 index 0000000000..1100b61fad --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/pytest_esp_flash.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.esp32c2 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + 'flash_qio', + ], + indirect=True, +) +def test_esp_flash(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('[esp_flash]') + dut.expect_unity_test_output() + + +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.esp32c2 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'rom_impl', + ], + indirect=True, +) +def test_esp_flash_rom(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('[esp_flash]') + dut.expect_unity_test_output() + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.esp32c2 +@pytest.mark.flash_mutli +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_esp_flash_multi(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('[esp_flash_multi]') + dut.expect_unity_test_output() diff --git a/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.flash_qio b/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.flash_qio new file mode 100644 index 0000000000..45d97b3c1d --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.flash_qio @@ -0,0 +1,4 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y diff --git a/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.release b/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.release new file mode 100644 index 0000000000..9a2221effe --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.release @@ -0,0 +1,7 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" diff --git a/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.rom_impl b/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.rom_impl new file mode 100644 index 0000000000..1f73cb4152 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/sdkconfig.ci.rom_impl @@ -0,0 +1,4 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_SPI_FLASH_ROM_IMPL=y diff --git a/components/spi_flash/test_apps/esp_flash/sdkconfig.defaults b/components/spi_flash/test_apps/esp_flash/sdkconfig.defaults new file mode 100644 index 0000000000..e58745b57a --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" diff --git a/pytest.ini b/pytest.ini index 44bd640734..18a3d1222c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -42,6 +42,7 @@ markers = flash_encryption: Flash Encryption runners flash_encryption_f4r8: Flash Encryption runners with 4-line flash and 8-line psram flash_encryption_f8r8: Flash Encryption runners with 8-line flash and 8-line psram + flash_mutli: Multiple flash chips tests psram: Chip has 4-line psram ir_transceiver: runners with a pair of IR transmitter and receiver flash_encryption_wifi_high_traffic: Flash Encryption runners with wifi high traffic support