Merge branch 'ci/move_timer_into_unity' into 'master'

spi_flash: migrate spi_flash UT to pytest component test app

Closes IDF-6730

See merge request espressif/esp-idf!19709
This commit is contained in:
C.S.M 2023-05-16 14:09:25 +08:00
commit 50b4d5c6be
68 changed files with 432 additions and 2153 deletions

View File

@ -2803,19 +2803,6 @@
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-unit_test changes: *patterns-unit_test
.rules:test:unit_test-esp32c3-flash_multi:
rules:
- <<: *if-revert-branch
when: never
- <<: *if-protected
- <<: *if-label-build-only
when: never
- <<: *if-label-target_test
- <<: *if-label-unit_test
- <<: *if-label-unit_test_esp32c3
- <<: *if-dev-push
changes: *patterns-unit_test-flash_multi
.rules:test:unit_test-esp32c3-sdio: .rules:test:unit_test-esp32c3-sdio:
rules: rules:
- <<: *if-revert-branch - <<: *if-revert-branch

View File

@ -1253,12 +1253,6 @@ UT_033:
- UT_T2_Ethernet - UT_T2_Ethernet
- psram - psram
UT_034:
extends: .unit_test_esp32_template
tags:
- ESP32_IDF
- UT_T1_ESP_FLASH
UT_035: UT_035:
extends: .unit_test_esp32s2_template extends: .unit_test_esp32s2_template
parallel: 16 parallel: 16
@ -1266,12 +1260,6 @@ UT_035:
- ESP32S2_IDF - ESP32S2_IDF
- UT_T1_1 - UT_T1_1
UT_038:
extends: .unit_test_esp32s2_template
tags:
- ESP32S2_IDF
- UT_T1_ESP_FLASH
UT_S2_SDSPI: UT_S2_SDSPI:
extends: extends:
- .unit_test_esp32s2_template - .unit_test_esp32s2_template
@ -1288,14 +1276,6 @@ UT_C2:
- UT_T1_1 - UT_T1_1
- xtal_40mhz - xtal_40mhz
UT_C2_FLASH:
extends:
- .unit_test_esp32c2_template
tags:
- ESP32C2_IDF
- UT_T1_ESP_FLASH
- xtal_40mhz
UT_C3: UT_C3:
extends: .unit_test_esp32c3_template extends: .unit_test_esp32c3_template
parallel: 11 parallel: 11
@ -1303,14 +1283,6 @@ UT_C3:
- ESP32C3_IDF - ESP32C3_IDF
- UT_T1_1 - UT_T1_1
UT_C3_FLASH:
extends:
- .unit_test_esp32c3_template
- .rules:test:unit_test-esp32c3-flash_multi
tags:
- ESP32C3_IDF
- UT_T1_ESP_FLASH
UT_C3_SDSPI: UT_C3_SDSPI:
extends: extends:
- .unit_test_esp32c3_template - .unit_test_esp32c3_template
@ -1340,12 +1312,6 @@ UT_S3:
- ESP32S3_IDF - ESP32S3_IDF
- UT_T1_1 - UT_T1_1
UT_S3_FLASH:
extends: .unit_test_esp32s3_template
tags:
- ESP32S3_IDF
- UT_T1_ESP_FLASH
UT_S3_QUAD_PSRAM: UT_S3_QUAD_PSRAM:
extends: .unit_test_esp32s3_template extends: .unit_test_esp32s3_template

View File

@ -0,0 +1,2 @@
dependencies:
ccomp_timer: "^1.0.0"

View File

@ -0,0 +1,2 @@
dependencies:
ccomp_timer: "^1.0.0"

View File

@ -1,11 +1,5 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
components/spi_flash/test_apps/esp_flash:
disable:
- if: IDF_TARGET == "esp32c6" or IDF_TARGET == "esp32h2"
temporary: true
reason: target esp32c6 cannot pass atomic build, target esp32h2 currently doesn't support GPSPI.
components/spi_flash/test_apps/flash_encryption: components/spi_flash/test_apps/flash_encryption:
disable_test: disable_test:
- if: IDF_TARGET in ["esp32c2", "esp32s2", "esp32c6", "esp32h2"] - if: IDF_TARGET in ["esp32c2", "esp32s2", "esp32c6", "esp32h2"]
@ -19,3 +13,8 @@ components/spi_flash/test_apps/flash_suspend:
- if: IDF_TARGET != "esp32c3" - if: IDF_TARGET != "esp32c3"
temporary: true temporary: true
reason: lack of runners reason: lack of runners
components/spi_flash/test_apps/mspi_test:
disable:
- if: CONFIG_NAME == "psram" and SOC_SPIRAM_SUPPORTED != 1
- if: CONFIG_NAME == "xip_psram" and SOC_SPIRAM_SUPPORTED != 1

View File

@ -1,652 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <unity.h>
#include "esp_flash.h"
#include "esp_private/spi_common_internal.h"
#include "spi_flash_mmap.h"
#include "esp_flash_spi_init.h"
#include "memspi_host_driver.h"
#include <esp_attr.h>
#include "esp_log.h"
#include <test_utils.h>
#include "unity.h"
#include "driver/gpio.h"
#include "soc/io_mux_reg.h"
#include "sdkconfig.h"
#include "ccomp_timer.h"
#include "esp_rom_gpio.h"
#include "esp_rom_sys.h"
#include "esp_timer.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_ESP32C2
#include "esp32c2/rom/cache.h"
#endif
#define FUNC_SPI 1
#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
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C6, ESP32H2)
#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).
*/
#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
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%d (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%d (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%d (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);
}
#if SOC_SPI_PERIPH_NUM > 2
else if (host_id == SPI3_HOST) {
ESP_LOGI(TAG, "setup flash on SPI%d (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);
}
#endif
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 SOC_SPI_PERIPH_NUM > 2
if (host_id == SPI2_HOST || host_id == SPI3_HOST)
#else
if (host_id == SPI2_HOST)
#endif
{
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%08X", 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%08X...", 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%08X...", 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 %d/%d", i+1, test_num);
flash_test_core(func, &config_list[i]);
}
ESP_LOGI(TAG, "Completed %d configs", test_num);
}
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();
ccomp_timer_start();
}
static uint32_t time_measure_end(time_meas_ctx_t* ctx)
{
uint32_t c_time_us = ccomp_timer_stop();
uint32_t time_us = esp_timer_get_time() - ctx->us_start;
ESP_LOGI(TAG, "%s: compensated: %.2lf kB/s, typical: %.2lf kB/s", ctx->name, ctx->len / (c_time_us / 1000.), ctx->len / (time_us/1000.));
return ctx->len * 1000 / (c_time_us / 1000);
}
#define TEST_TIMES 20
#define TEST_SECTORS 4
static uint32_t measure_erase(const esp_partition_t* part)
{
const int total_len = SPI_FLASH_SEC_SIZE * TEST_SECTORS;
time_meas_ctx_t time_ctx = {.name = "erase", .len = total_len};
time_measure_start(&time_ctx);
esp_err_t err = esp_flash_erase_region(part->flash_chip, part->address, total_len);
TEST_ESP_OK(err);
return time_measure_end(&time_ctx);
}
// should called after measure_erase
static uint32_t measure_write(const char* name, const esp_partition_t* part, const uint8_t* data_to_write, int seg_len)
{
const int total_len = SPI_FLASH_SEC_SIZE;
time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES};
time_measure_start(&time_ctx);
for (int i = 0; i < TEST_TIMES; i ++) {
// Erase one time, but write 100 times the same data
size_t len = total_len;
int offset = 0;
while (len) {
int len_write = MIN(seg_len, len);
esp_err_t err = esp_flash_write(part->flash_chip, data_to_write + offset, part->address + offset, len_write);
TEST_ESP_OK(err);
offset += len_write;
len -= len_write;
}
}
return time_measure_end(&time_ctx);
}
static uint32_t measure_read(const char* name, const esp_partition_t* part, uint8_t* data_read, int seg_len)
{
const int total_len = SPI_FLASH_SEC_SIZE;
time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES};
time_measure_start(&time_ctx);
for (int i = 0; i < TEST_TIMES; i ++) {
size_t len = total_len;
int offset = 0;
while (len) {
int len_read = MIN(seg_len, len);
esp_err_t err = esp_flash_read(part->flash_chip, data_read + offset, part->address + offset, len_read);
TEST_ESP_OK(err);
offset += len_read;
len -= len_read;
}
}
return time_measure_end(&time_ctx);
}
static const char* get_chip_vendor(uint32_t id)
{
switch (id)
{
case 0x20:
return "XMC";
break;
case 0x68:
return "BOYA";
break;
case 0xC8:
return "GigaDevice";
break;
case 0x9D:
return "ISSI";
break;
case 0xC2:
return "MXIC";
break;
case 0xEF:
return "Winbond";
break;
case 0xA1:
return "Fudan Micro";
break;
default:
break;
}
return "generic";
}
#define MEAS_WRITE(n) (measure_write("write in "#n"-byte chunks", part, data_to_write, n))
#define MEAS_READ(n) (measure_read("read in "#n"-byte chunks", part, data_read, n))
static void test_flash_read_write_performance(const esp_partition_t *part)
{
esp_flash_t* chip = part->flash_chip;
const int total_len = SPI_FLASH_SEC_SIZE;
uint8_t *data_to_write = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *data_read = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
srand(777);
for (int i = 0; i < total_len; i++) {
data_to_write[i] = rand();
}
uint32_t erase_1 = measure_erase(part);
uint32_t speed_WR_4B = MEAS_WRITE(4);
uint32_t speed_RD_4B = MEAS_READ(4);
uint32_t erase_2 = measure_erase(part);
uint32_t speed_WR_2KB = MEAS_WRITE(2048);
uint32_t speed_RD_2KB = MEAS_READ(2048);
TEST_ASSERT_EQUAL_HEX8_ARRAY(data_to_write, data_read, total_len);
#define LOG_DATA(bus, suffix, chip) IDF_LOG_PERFORMANCE("FLASH_SPEED_BYTE_PER_SEC_"#bus#suffix, "%d, flash_chip: %s", speed_##suffix, chip)
#define LOG_ERASE(bus, var, chip) IDF_LOG_PERFORMANCE("FLASH_SPEED_BYTE_PER_SEC_"#bus"ERASE", "%d, flash_chip: %s", var, chip)
// Erase time may vary a lot, can increase threshold if this fails with a reasonable speed
#define LOG_PERFORMANCE(bus, chip) do {\
LOG_DATA(bus, WR_4B, chip); \
LOG_DATA(bus, RD_4B, chip); \
LOG_DATA(bus, WR_2KB, chip); \
LOG_DATA(bus, RD_2KB, chip); \
LOG_ERASE(bus, erase_1, chip); \
LOG_ERASE(bus, erase_2, chip); \
} while (0)
spi_host_device_t host_id;
int cs_id;
uint32_t id;
esp_flash_read_id(chip, &id);
const char *chip_name = get_chip_vendor(id >> 16);
get_chip_host(chip, &host_id, &cs_id);
if (host_id != SPI1_HOST) {
// Chips on other SPI buses
LOG_PERFORMANCE(EXT_, chip_name);
} else if (cs_id == 0) {
// Main flash
LOG_PERFORMANCE(,chip_name);
} else {
// Other cs pins on SPI1
LOG_PERFORMANCE(SPI1_, chip_name);
}
free(data_to_write);
free(data_read);
}
#if !BYPASS_MULTIPLE_CHIP
//To make performance data stable, needs to run on special runner
TEST_CASE("Test esp_flash read/write performance", "[esp_flash][test_env=UT_T1_ESP_FLASH]") {flash_test_func(test_flash_read_write_performance, 1);}
#endif
TEST_CASE_MULTI_FLASH("Test esp_flash read/write performance", test_flash_read_write_performance);
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32C6, ESP32H2)

View File

@ -18,10 +18,10 @@ This code tests the interaction between PSRAM and SPI flash routines.
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "test_utils.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "spi_flash_mmap.h" #include "spi_flash_mmap.h"
#include "esp_partition.h" #include "esp_partition.h"
#include "test_utils.h"
#include "soc/soc.h" #include "soc/soc.h"
#if CONFIG_SPIRAM #if CONFIG_SPIRAM
@ -96,7 +96,6 @@ TEST_CASE("Spiram cache flush on mmap", "[spiram]")
TEST_ASSERT(err[1]==0); TEST_ASSERT(err[1]==0);
} }
#define CYCLES 1024 #define CYCLES 1024
TEST_CASE("Spiram cache flush on write/read", "[spiram]") TEST_CASE("Spiram cache flush on write/read", "[spiram]")

View File

@ -1,5 +1,8 @@
# This is the project CMakeLists.txt file for the test subproject # This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_esp_flash_drv) project(test_esp_flash_drv)

View File

@ -1,2 +1,2 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 | | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |

View File

@ -1,4 +1,5 @@
set(srcs "test_app_main.c" set(srcs "test_app_main.c"
"test_spi_flash.c"
"test_esp_flash_drv.c") "test_esp_flash_drv.c")
# In order for the cases defined by `TEST_CASE` to be linked into the final elf, # In order for the cases defined by `TEST_CASE` to be linked into the final elf,

View File

@ -0,0 +1,2 @@
dependencies:
ccomp_timer: "^1.0.0"

View File

@ -8,8 +8,8 @@
#include "unity_test_utils.h" #include "unity_test_utils.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
// Some resources are lazy allocated in flash encryption, the threadhold is left for that case // Some resources are lazy allocated in esp_flash test, especially ccomp timer test, the threshold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (400) #define TEST_MEMORY_LEAK_THRESHOLD (700)
void setUp(void) void setUp(void)
{ {

View File

@ -126,6 +126,19 @@ typedef void (*flash_test_func_t)(const esp_partition_t *part);
#define IDF_LOG_PERFORMANCE(item, value_fmt, value, ...) \ #define IDF_LOG_PERFORMANCE(item, value_fmt, value, ...) \
printf("[Performance][%s]: " value_fmt "\n", item, value, ##__VA_ARGS__) printf("[Performance][%s]: " value_fmt "\n", item, value, ##__VA_ARGS__)
#define LOG_DATA(bus, suffix, chip) IDF_LOG_PERFORMANCE("FLASH_SPEED_BYTE_PER_SEC_"#bus#suffix, "%ld, flash_chip: %s", speed_##suffix, chip)
#define LOG_ERASE(bus, var, chip) IDF_LOG_PERFORMANCE("FLASH_SPEED_BYTE_PER_SEC_"#bus"ERASE", "%ld, flash_chip: %s", var, chip)
// Erase time may vary a lot, can increase threshold if this fails with a reasonable speed
#define LOG_PERFORMANCE(bus, chip) do {\
LOG_DATA(bus, WR_4B, chip); \
LOG_DATA(bus, RD_4B, chip); \
LOG_DATA(bus, WR_2KB, chip); \
LOG_DATA(bus, RD_2KB, chip); \
LOG_ERASE(bus, erase_1, chip); \
LOG_ERASE(bus, erase_2, chip); \
} while (0)
#if defined(CONFIG_SPIRAM) #if defined(CONFIG_SPIRAM)
//SPI1 CS1 occupied by PSRAM //SPI1 CS1 occupied by PSRAM
@ -145,6 +158,7 @@ typedef void (*flash_test_func_t)(const esp_partition_t *part);
#if BYPASS_MULTIPLE_CHIP #if BYPASS_MULTIPLE_CHIP
#define TEST_CASE_MULTI_FLASH TEST_CASE_MULTI_FLASH_IGNORE #define TEST_CASE_MULTI_FLASH TEST_CASE_MULTI_FLASH_IGNORE
#define TEST_CASE_MULTI_FLASH_LONG TEST_CASE_MULTI_FLASH_IGNORE
#else #else
#if CONFIG_FREERTOS_SMP // IDF-5260 #if CONFIG_FREERTOS_SMP // IDF-5260
#define TEST_CASE_MULTI_FLASH(STR, FUNC_TO_RUN) \ #define TEST_CASE_MULTI_FLASH(STR, FUNC_TO_RUN) \

View File

@ -18,7 +18,7 @@
#include "spi_flash_mmap.h" #include "spi_flash_mmap.h"
#include <esp_attr.h> #include <esp_attr.h>
#include "esp_log.h" #include "esp_log.h"
#include "test_utils.h"
#include "unity.h" #include "unity.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "soc/io_mux_reg.h" #include "soc/io_mux_reg.h"
@ -31,6 +31,7 @@
#include "test_esp_flash_def.h" #include "test_esp_flash_def.h"
#include "spi_flash_mmap.h" #include "spi_flash_mmap.h"
#include "esp_private/spi_flash_os.h" #include "esp_private/spi_flash_os.h"
#include "ccomp_timer.h"
#if CONFIG_IDF_TARGET_ESP32S2 #if CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/cache.h" #include "esp32s2/rom/cache.h"
@ -48,15 +49,6 @@
static uint8_t sector_buf[4096]; 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) 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; spi_host_device_t host_id;
@ -799,6 +791,173 @@ static void test_write_large_buffer(const esp_partition_t* part, const uint8_t *
read_and_check(part, source, length); read_and_check(part, source, length);
} }
#if !CONFIG_SPI_FLASH_WARN_SETTING_ZERO_TO_ONE
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();
ccomp_timer_start();
}
static uint32_t time_measure_end(time_meas_ctx_t* ctx)
{
uint32_t c_time_us = ccomp_timer_stop();
uint32_t time_us = esp_timer_get_time() - ctx->us_start;
ESP_LOGI(TAG, "%s: compensated: %.2lf kB/s, typical: %.2lf kB/s", ctx->name, ctx->len / (c_time_us / 1000.), ctx->len / (time_us/1000.));
return ctx->len * 1000 / (c_time_us / 1000);
}
#define TEST_TIMES 20
#define TEST_SECTORS 4
static uint32_t measure_erase(const esp_partition_t* part)
{
const int total_len = SPI_FLASH_SEC_SIZE * TEST_SECTORS;
time_meas_ctx_t time_ctx = {.name = "erase", .len = total_len};
time_measure_start(&time_ctx);
esp_err_t err = esp_flash_erase_region(part->flash_chip, part->address, total_len);
TEST_ESP_OK(err);
return time_measure_end(&time_ctx);
}
// should called after measure_erase
static uint32_t measure_write(const char* name, const esp_partition_t* part, const uint8_t* data_to_write, int seg_len)
{
const int total_len = SPI_FLASH_SEC_SIZE;
time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES};
time_measure_start(&time_ctx);
for (int i = 0; i < TEST_TIMES; i ++) {
// Erase one time, but write 100 times the same data
size_t len = total_len;
int offset = 0;
while (len) {
int len_write = MIN(seg_len, len);
esp_err_t err = esp_flash_write(part->flash_chip, data_to_write + offset, part->address + offset, len_write);
TEST_ESP_OK(err);
offset += len_write;
len -= len_write;
}
}
return time_measure_end(&time_ctx);
}
static uint32_t measure_read(const char* name, const esp_partition_t* part, uint8_t* data_read, int seg_len)
{
const int total_len = SPI_FLASH_SEC_SIZE;
time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES};
time_measure_start(&time_ctx);
for (int i = 0; i < TEST_TIMES; i ++) {
size_t len = total_len;
int offset = 0;
while (len) {
int len_read = MIN(seg_len, len);
esp_err_t err = esp_flash_read(part->flash_chip, data_read + offset, part->address + offset, len_read);
TEST_ESP_OK(err);
offset += len_read;
len -= len_read;
}
}
return time_measure_end(&time_ctx);
}
static const char* get_chip_vendor(uint32_t id)
{
switch (id)
{
case 0x20:
return "XMC";
break;
case 0x68:
return "BOYA";
break;
case 0xC8:
return "GigaDevice";
break;
case 0x9D:
return "ISSI";
break;
case 0xC2:
return "MXIC";
break;
case 0xEF:
return "Winbond";
break;
case 0xA1:
return "Fudan Micro";
break;
default:
break;
}
return "generic";
}
#define MEAS_WRITE(n) (measure_write("write in "#n"-byte chunks", part, data_to_write, n))
#define MEAS_READ(n) (measure_read("read in "#n"-byte chunks", part, data_read, n))
static void test_flash_read_write_performance(const esp_partition_t *part)
{
esp_flash_t* chip = part->flash_chip;
const int total_len = SPI_FLASH_SEC_SIZE;
uint8_t *data_to_write = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *data_read = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
srand(777);
for (int i = 0; i < total_len; i++) {
data_to_write[i] = rand();
}
uint32_t erase_1 = measure_erase(part);
uint32_t speed_WR_4B = MEAS_WRITE(4);
uint32_t speed_RD_4B = MEAS_READ(4);
uint32_t erase_2 = measure_erase(part);
uint32_t speed_WR_2KB = MEAS_WRITE(2048);
uint32_t speed_RD_2KB = MEAS_READ(2048);
TEST_ASSERT_EQUAL_HEX8_ARRAY(data_to_write, data_read, total_len);
spi_host_device_t host_id;
int cs_id;
uint32_t id;
esp_flash_read_id(chip, &id);
const char *chip_name = get_chip_vendor(id >> 16);
get_chip_host(chip, &host_id, &cs_id);
if (host_id != SPI1_HOST) {
// Chips on other SPI buses
LOG_PERFORMANCE(EXT_, chip_name);
} else if (cs_id == 0) {
// Main flash
LOG_PERFORMANCE(,chip_name);
} else {
// Other cs pins on SPI1
LOG_PERFORMANCE(SPI1_, chip_name);
}
free(data_to_write);
free(data_read);
}
#if !BYPASS_MULTIPLE_CHIP
//To make performance data stable, needs to run on special runner
TEST_CASE("Test esp_flash read/write performance", "[esp_flash][test_env=UT_T1_ESP_FLASH]") {flash_test_func(test_flash_read_write_performance, 1);}
#endif
TEST_CASE_MULTI_FLASH("Test esp_flash read/write performance", test_flash_read_write_performance);
#endif
#ifdef CONFIG_SPIRAM_USE_MALLOC #ifdef CONFIG_SPIRAM_USE_MALLOC
/* Utility: Read into a small internal RAM buffer using esp_flash_read() and compare what /* Utility: Read into a small internal RAM buffer using esp_flash_read() and compare what

View File

@ -1,27 +1,30 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h> #include <stdio.h>
#include <sys/param.h> #include <sys/param.h>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
#include <unity.h> #include "unity.h"
#include <spi_flash_mmap.h> #include "spi_flash_mmap.h"
#include <esp_attr.h> #include "esp_attr.h"
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "test_utils.h"
#include "ccomp_timer.h" #include "ccomp_timer.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_rom_sys.h" #include "esp_rom_sys.h"
#include "esp_rom_spiflash.h" #include "esp_rom_spiflash.h"
#include "esp_timer.h" #include "esp_timer.h"
#include "esp_partition.h"
#include "bootloader_flash.h" //for bootloader_flash_xmc_startup #include "bootloader_flash.h" //for bootloader_flash_xmc_startup
#include "test_utils.h"
#include "sdkconfig.h" #include "sdkconfig.h"
extern const esp_partition_t *get_test_data_partition(void);
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)
// TODO: SPI_FLASH IDF-4025
struct flash_test_ctx { struct flash_test_ctx {
uint32_t offset; uint32_t offset;
bool fail; bool fail;
@ -45,44 +48,44 @@ static void flash_test_task(void *arg)
struct flash_test_ctx *ctx = (struct flash_test_ctx *) arg; struct flash_test_ctx *ctx = (struct flash_test_ctx *) arg;
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
const uint32_t sector = start / SPI_FLASH_SEC_SIZE + ctx->offset; const uint32_t sector = start / SPI_FLASH_SEC_SIZE + ctx->offset;
printf("t%d\n", sector); printf("t%ld\n", sector);
printf("es%d\n", sector); printf("es%ld\n", sector);
if (esp_flash_erase_region(NULL, sector * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE) != ESP_OK) { if (esp_flash_erase_region(NULL, sector * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE) != ESP_OK) {
ctx->fail = true; ctx->fail = true;
printf("Erase failed\r\n"); printf("Erase failed\r\n");
xSemaphoreGive(ctx->done); xSemaphoreGive(ctx->done);
vTaskDelete(NULL); vTaskDelete(NULL);
} }
printf("ed%d\n", sector); printf("ed%ld\n", sector);
vTaskDelay(0 / portTICK_PERIOD_MS); vTaskDelay(0 / portTICK_PERIOD_MS);
uint32_t val = 0xabcd1234; uint32_t val = 0xabcd1234;
for (uint32_t offset = 0; offset < SPI_FLASH_SEC_SIZE; offset += 4) { for (uint32_t offset = 0; offset < SPI_FLASH_SEC_SIZE; offset += 4) {
if (esp_flash_write(NULL, (const uint8_t *) &val, sector * SPI_FLASH_SEC_SIZE + offset, 4) != ESP_OK) { if (esp_flash_write(NULL, (const uint8_t *) &val, sector * SPI_FLASH_SEC_SIZE + offset, 4) != ESP_OK) {
printf("Write failed at offset=%d\r\n", offset); printf("Write failed at offset=%ld\r\n", offset);
ctx->fail = true; ctx->fail = true;
break; break;
} }
} }
printf("wd%d\n", sector); printf("wd%ld\n", sector);
vTaskDelay(0 / portTICK_PERIOD_MS); vTaskDelay(0 / portTICK_PERIOD_MS);
uint32_t val_read; uint32_t val_read;
for (uint32_t offset = 0; offset < SPI_FLASH_SEC_SIZE; offset += 4) { for (uint32_t offset = 0; offset < SPI_FLASH_SEC_SIZE; offset += 4) {
if (esp_flash_read(NULL, (uint8_t *) &val_read, sector * SPI_FLASH_SEC_SIZE + offset, 4) != ESP_OK) { if (esp_flash_read(NULL, (uint8_t *) &val_read, sector * SPI_FLASH_SEC_SIZE + offset, 4) != ESP_OK) {
printf("Read failed at offset=%d\r\n", offset); printf("Read failed at offset=%ld\r\n", offset);
ctx->fail = true; ctx->fail = true;
break; break;
} }
if (val_read != val) { if (val_read != val) {
printf("Read invalid value=%08x at offset=%d\r\n", val_read, offset); printf("Read invalid value=%08lx at offset=%ld\r\n", val_read, offset);
ctx->fail = true; ctx->fail = true;
break; break;
} }
} }
printf("td%d\n", sector); printf("td%ld\n", sector);
xSemaphoreGive(ctx->done); xSemaphoreGive(ctx->done);
vTaskDelete(NULL); vTaskDelete(NULL);
} }
@ -116,135 +119,6 @@ TEST_CASE("flash write and erase work both on PRO CPU and on APP CPU", "[spi_fla
vSemaphoreDelete(done); vSemaphoreDelete(done);
} }
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3)
// TODO ESP32-S3 IDF-2021
static const char TAG[] = "test_spi_flash";
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();
ccomp_timer_start();
}
static uint32_t time_measure_end(time_meas_ctx_t* ctx)
{
uint32_t c_time_us = ccomp_timer_stop();
uint32_t time_us = esp_timer_get_time() - ctx->us_start;
ESP_LOGI(TAG, "%s: compensated: %.2lf kB/s, typical: %.2lf kB/s", ctx->name, ctx->len / (c_time_us/1000.), ctx->len / (time_us/1000.));
return ctx->len * 1000 / (c_time_us / 1000);
}
#define TEST_TIMES 20
#define TEST_SECTORS 4
static uint32_t measure_erase(const esp_partition_t* part)
{
const int total_len = SPI_FLASH_SEC_SIZE * TEST_SECTORS;
time_meas_ctx_t time_ctx = {.name = "erase", .len = total_len};
time_measure_start(&time_ctx);
esp_err_t err = esp_flash_erase_region(NULL, part->address, total_len);
TEST_ESP_OK(err);
return time_measure_end(&time_ctx);
}
// should called after measure_erase
static uint32_t measure_write(const char* name, const esp_partition_t* part, const uint8_t* data_to_write, int seg_len)
{
const int total_len = SPI_FLASH_SEC_SIZE;
time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES};
time_measure_start(&time_ctx);
for (int i = 0; i < TEST_TIMES; i ++) {
// Erase one time, but write 100 times the same data
size_t len = total_len;
int offset = 0;
while (len) {
int len_write = MIN(seg_len, len);
esp_err_t err = esp_flash_write(NULL, data_to_write + offset, part->address + offset, len_write);
TEST_ESP_OK(err);
offset += len_write;
len -= len_write;
}
}
return time_measure_end(&time_ctx);
}
static uint32_t measure_read(const char* name, const esp_partition_t* part, uint8_t* data_read, int seg_len)
{
const int total_len = SPI_FLASH_SEC_SIZE;
time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES};
time_measure_start(&time_ctx);
for (int i = 0; i < TEST_TIMES; i ++) {
size_t len = total_len;
int offset = 0;
while (len) {
int len_read = MIN(seg_len, len);
esp_err_t err = esp_flash_read(NULL, data_read + offset, part->address + offset, len_read);
TEST_ESP_OK(err);
offset += len_read;
len -= len_read;
}
}
return time_measure_end(&time_ctx);
}
#define MEAS_WRITE(n) (measure_write("write in "#n"-byte chunks", part, data_to_write, n))
#define MEAS_READ(n) (measure_read("read in "#n"-byte chunks", part, data_read, n))
TEST_CASE("Test spi_flash read/write performance", "[spi_flash]")
{
const esp_partition_t *part = get_test_data_partition();
const int total_len = SPI_FLASH_SEC_SIZE;
uint8_t *data_to_write = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *data_read = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
srand(777);
for (int i = 0; i < total_len; i++) {
data_to_write[i] = rand();
}
uint32_t erase_1 = measure_erase(part);
uint32_t speed_WR_4B = MEAS_WRITE(4);
uint32_t speed_RD_4B = MEAS_READ(4);
uint32_t erase_2 = measure_erase(part);
uint32_t speed_WR_2KB = MEAS_WRITE(2048);
uint32_t speed_RD_2KB = MEAS_READ(2048);
TEST_ASSERT_EQUAL_HEX8_ARRAY(data_to_write, data_read, total_len);
#define LOG_DATA(suffix) IDF_LOG_PERFORMANCE("FLASH_SPEED_BYTE_PER_SEC_LEGACY_"#suffix, "%d", speed_##suffix)
#define LOG_ERASE(var) IDF_LOG_PERFORMANCE("FLASH_SPEED_BYTE_PER_SEC_LEGACY_ERASE", "%d", var)
LOG_DATA(WR_4B);
LOG_DATA(RD_4B);
LOG_DATA(WR_2KB);
LOG_DATA(RD_2KB);
// Erase time may vary a lot, can increase threshold if this fails with a reasonable speed
LOG_ERASE(erase_1);
LOG_ERASE(erase_2);
free(data_to_write);
free(data_read);
}
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3)
// TODO: This test is disabled on S3 with legacy impl - IDF-3505 // TODO: This test is disabled on S3 with legacy impl - IDF-3505
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32, ESP32S2, ESP32S3, ESP32C3) #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32, ESP32S2, ESP32S3, ESP32C3)
@ -323,14 +197,14 @@ TEST_CASE("rom unlock will not erase QE bit", "[spi_flash]")
{ {
esp_rom_spiflash_chip_t *legacy_chip = &g_rom_flashchip; esp_rom_spiflash_chip_t *legacy_chip = &g_rom_flashchip;
uint32_t status; uint32_t status;
printf("dev_id: %08X \n", legacy_chip->device_id); printf("dev_id: %08lx \n", legacy_chip->device_id);
if (((legacy_chip->device_id >> 16) & 0xff) != 0x9D) { if (((legacy_chip->device_id >> 16) & 0xff) != 0x9D) {
TEST_IGNORE_MESSAGE("This test is only for ISSI chips. Ignore."); TEST_IGNORE_MESSAGE("This test is only for ISSI chips. Ignore.");
} }
bootloader_flash_unlock(); bootloader_flash_unlock();
esp_rom_spiflash_read_status(legacy_chip, &status); esp_rom_spiflash_read_status(legacy_chip, &status);
printf("status: %08x\n", status); printf("status: %08lx\n", status);
TEST_ASSERT(status & 0x40); TEST_ASSERT(status & 0x40);
} }
@ -353,5 +227,3 @@ TEST_CASE("bootloader_flash_xmc_startup can be called when cache disabled", "[sp
{ {
test_xmc_startup(); test_xmc_startup();
} }
#endif //#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)

View File

@ -2,4 +2,4 @@
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000, nvs, data, nvs, 0x9000, 0x6000,
factory, 0, 0, 0x10000, 1M factory, 0, 0, 0x10000, 1M
flash_test, data, fat, , 528K flash_test, data, fat, , 700K

1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 factory, 0, 0, 0x10000, 1M
5 flash_test, data, fat, , 528K flash_test, data, fat, , 700K

View File

@ -5,11 +5,7 @@ import pytest
from pytest_embedded import Dut from pytest_embedded import Dut
@pytest.mark.esp32 @pytest.mark.supported_targets
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32c3
@pytest.mark.esp32c2
@pytest.mark.generic @pytest.mark.generic
@pytest.mark.parametrize( @pytest.mark.parametrize(
'config', 'config',
@ -17,6 +13,7 @@ from pytest_embedded import Dut
'release', 'release',
'flash_qio', 'flash_qio',
'verify', 'verify',
'special',
], ],
indirect=True, indirect=True,
) )

View File

@ -0,0 +1,7 @@
# This config lists merged freertos_flash no_optimization in UT all together.
CONFIG_ESP_TASK_WDT_EN=n
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
CONFIG_COMPILER_OPTIMIZATION_NONE=y

View File

@ -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(mspi_test)

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |

View File

@ -0,0 +1,8 @@
set(srcs "test_cache_disabled.c"
"test_out_of_bounds_write.c"
"test_read_write.c"
"test_large_flash_writes.c"
"test_app_main.c")
idf_component_register(SRCS ${srcs}
WHOLE_ARCHIVE)

View File

@ -0,0 +1,2 @@
dependencies:
ccomp_timer: "^1.0.0"

View File

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2023 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 mspi bus test, the threshold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (-600)
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");
printf("|_| |_|_____/|_| |_____| |_| |______|_____/ |_| \n");
unity_run_menu();
}

View File

@ -56,6 +56,7 @@ TEST_CASE("spi_flash_cache_enabled() works on both CPUs", "[spi_flash][esp_flash
TEST_ASSERT_EQUAL(!do_disable, result); TEST_ASSERT_EQUAL(!do_disable, result);
} }
} }
vTaskDelay(10);
vQueueDelete(result_queue); vQueueDelete(result_queue);
} }
@ -67,6 +68,11 @@ TEST_CASE("spi_flash_cache_enabled() works on both CPUs", "[spi_flash][esp_flash
// DRAM (e.g. size <= 8 bytes && ARCH == RISCV) // DRAM (e.g. size <= 8 bytes && ARCH == RISCV)
static const uint32_t s_in_rodata[8] = { 0x12345678, 0xfedcba98 }; static const uint32_t s_in_rodata[8] = { 0x12345678, 0xfedcba98 };
static void reset_after_invalid_cache(void)
{
TEST_ASSERT_EQUAL(ESP_RST_PANIC, esp_reset_reason());
}
static void IRAM_ATTR cache_access_test_func(void* arg) static void IRAM_ATTR cache_access_test_func(void* arg)
{ {
/* Assert that the array s_in_rodata is in DROM. If not, this test is /* Assert that the array s_in_rodata is in DROM. If not, this test is
@ -79,7 +85,7 @@ static void IRAM_ATTR cache_access_test_func(void* arg)
uint32_t v2 = src[1]; uint32_t v2 = src[1];
bool cache_enabled = spi_flash_cache_enabled(); bool cache_enabled = spi_flash_cache_enabled();
spi_flash_enable_interrupts_caches_and_other_cpu(); spi_flash_enable_interrupts_caches_and_other_cpu();
printf("%d %x %x\n", cache_enabled, v1, v2); printf("%d %lx %lx\n", cache_enabled, v1, v2);
vTaskDelete(NULL); vTaskDelete(NULL);
} }
@ -93,21 +99,26 @@ static void IRAM_ATTR cache_access_test_func(void* arg)
#define CACHE_ERROR_REASON "Cache error,SW_CPU" #define CACHE_ERROR_REASON "Cache error,SW_CPU"
#endif #endif
// These tests works properly if they resets the chip with the // These tests works properly if they resets the chip with the
// "Cache disabled but cached memory region accessed" reason and the correct CPU is logged. // "Cache disabled but cached memory region accessed" reason and the correct CPU is logged.
TEST_CASE("invalid access to cache raises panic (PRO CPU)", "[spi_flash][reset="CACHE_ERROR_REASON"]") static void invalid_access_to_cache_pro_cpu(void)
{ {
xTaskCreatePinnedToCore(&cache_access_test_func, "ia", 2048, NULL, 5, NULL, 0); xTaskCreatePinnedToCore(&cache_access_test_func, "ia", 2048, NULL, 5, NULL, 0);
vTaskDelay(1000/portTICK_PERIOD_MS); vTaskDelay(1000/portTICK_PERIOD_MS);
} }
TEST_CASE_MULTIPLE_STAGES("invalid access to cache raises panic (PRO CPU)", "[spi_flash][reset="CACHE_ERROR_REASON"]", invalid_access_to_cache_pro_cpu, reset_after_invalid_cache);
#ifndef CONFIG_FREERTOS_UNICORE #ifndef CONFIG_FREERTOS_UNICORE
TEST_CASE("invalid access to cache raises panic (APP CPU)", "[spi_flash][reset="CACHE_ERROR_REASON"]") static void invalid_access_to_cache_app_cpu(void)
{ {
xTaskCreatePinnedToCore(&cache_access_test_func, "ia", 2048, NULL, 5, NULL, 1); xTaskCreatePinnedToCore(&cache_access_test_func, "ia", 2048, NULL, 5, NULL, 1);
vTaskDelay(1000/portTICK_PERIOD_MS); vTaskDelay(1000/portTICK_PERIOD_MS);
} }
TEST_CASE_MULTIPLE_STAGES("invalid access to cache raises panic (APP CPU)", "[spi_flash][reset="CACHE_ERROR_REASON"]", invalid_access_to_cache_app_cpu, reset_after_invalid_cache);
#endif // !CONFIG_FREERTOS_UNICORE #endif // !CONFIG_FREERTOS_UNICORE
#endif // !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2) #endif // !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2)

View File

@ -13,14 +13,14 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys/param.h> #include <sys/param.h>
#include <unity.h> #include "unity.h"
#include <test_utils.h> #include "spi_flash_mmap.h"
#include <spi_flash_mmap.h> #include "esp_log.h"
#include <esp_log.h>
#include "esp_rom_spiflash.h" #include "esp_rom_spiflash.h"
#include "esp_private/cache_utils.h" #include "esp_private/cache_utils.h"
#include "soc/timer_periph.h" #include "soc/timer_periph.h"
#include "esp_flash.h" #include "esp_flash.h"
#include "esp_partition.h"
static const uint8_t large_const_buffer[16400] = { static const uint8_t large_const_buffer[16400] = {
203, // first byte 203, // first byte
@ -35,6 +35,15 @@ static const uint8_t large_const_buffer[16400] = {
static void test_write_large_buffer(const uint8_t *source, size_t length); static void test_write_large_buffer(const uint8_t *source, size_t length);
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;
}
TEST_CASE("Test flash write large const buffer", "[spi_flash][esp_flash]") TEST_CASE("Test flash write large const buffer", "[spi_flash][esp_flash]")
{ {
// buffer in flash // buffer in flash

View File

@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h> #include <string.h>
#include "unity.h" #include "unity.h"

View File

@ -12,23 +12,22 @@
#include <string.h> #include <string.h>
#include <sys/param.h> #include <sys/param.h>
#include <unity.h> #include "unity.h"
#include <test_utils.h> #include "spi_flash_mmap.h"
#include <spi_flash_mmap.h>
#include "esp_private/cache_utils.h" #include "esp_private/cache_utils.h"
#include "soc/timer_periph.h" #include "soc/timer_periph.h"
#include "esp_attr.h" #include "esp_attr.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp_rom_spiflash.h" #include "esp_rom_spiflash.h"
#include "esp_flash.h" #include "esp_flash.h"
#include "esp_partition.h"
#if CONFIG_IDF_TARGET_ESP32 #if CONFIG_IDF_TARGET_ESP32
// Used for rom_fix function // Used for rom_fix function
#include "esp32/rom/spi_flash.h" #include "esp32/rom/spi_flash.h"
#endif #endif
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2) extern const esp_partition_t *get_test_data_partition(void);
// TODO: SPI_FLASH IDF-4025
#define MIN_BLOCK_SIZE 12 #define MIN_BLOCK_SIZE 12
/* Base offset in flash for tests. */ /* Base offset in flash for tests. */
@ -144,7 +143,7 @@ TEST_CASE("Test spi_flash_read", "[spi_flash][esp_flash]")
extern void spi_common_set_dummy_output(esp_rom_spiflash_read_mode_t mode); extern void spi_common_set_dummy_output(esp_rom_spiflash_read_mode_t mode);
extern void spi_dummy_len_fix(uint8_t spi, uint8_t freqdiv); extern void spi_dummy_len_fix(uint8_t spi, uint8_t freqdiv);
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C6, ESP32H2)
static void IRAM_ATTR fix_rom_func(void) static void IRAM_ATTR fix_rom_func(void)
{ {
uint32_t freqdiv = 0; uint32_t freqdiv = 0;
@ -281,7 +280,7 @@ TEST_CASE("Test esp_flash_write", "[spi_flash][esp_flash]")
* NB: At the moment these only support aligned addresses, because memcpy * NB: At the moment these only support aligned addresses, because memcpy
* is not aware of the 32-but load requirements for these regions. * is not aware of the 32-but load requirements for these regions.
*/ */
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3 #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C6
#define TEST_SOC_IROM_ADDR (SOC_IROM_LOW) #define TEST_SOC_IROM_ADDR (SOC_IROM_LOW)
#define TEST_SOC_CACHE_RAM_BANK0_ADDR (SOC_IRAM_LOW) #define TEST_SOC_CACHE_RAM_BANK0_ADDR (SOC_IRAM_LOW)
#define TEST_SOC_CACHE_RAM_BANK1_ADDR (SOC_IRAM_LOW + 0x2000) #define TEST_SOC_CACHE_RAM_BANK1_ADDR (SOC_IRAM_LOW + 0x2000)
@ -305,7 +304,6 @@ TEST_CASE("Test esp_flash_write", "[spi_flash][esp_flash]")
ESP_ERROR_CHECK(esp_flash_write(NULL, (char *) 0x40080000, start, 16)); ESP_ERROR_CHECK(esp_flash_write(NULL, (char *) 0x40080000, start, 16));
#endif #endif
} }
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32C6, ESP32H2)
#ifdef CONFIG_SPIRAM #ifdef CONFIG_SPIRAM
@ -386,5 +384,3 @@ TEST_CASE("spi_flash_read less than 16 bytes into buffer in external RAM", "[spi
} }
#endif // CONFIG_SPIRAM #endif // CONFIG_SPIRAM
#endif // #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32C2)

View File

@ -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
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 factory, 0, 0, 0x10000, 1M
5 flash_test, data, fat, , 528K

View File

@ -0,0 +1,54 @@
# SPDX-FileCopyrightText: 2022-2023 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.esp32c6
@pytest.mark.esp32h2
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'release',
'special',
],
indirect=True,
)
def test_mspi_bus(dut: Dut) -> None:
dut.run_all_single_board_cases()
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'xip_psram',
],
indirect=True,
)
def test_mspi_bus_xip_psram(dut: Dut) -> None:
dut.run_all_single_board_cases()
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'psram',
],
indirect=True,
)
def test_mspi_bus_psram(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@ -0,0 +1,11 @@
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_SPI_FLASH_ENABLE_COUNTERS=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_USE_CAPS_ALLOC=y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=y
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n

View File

@ -0,0 +1,6 @@
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_SPI_FLASH_ENABLE_COUNTERS=y
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n

View File

@ -0,0 +1,4 @@
# This config lists merged freertos_flash no_optimization in UT all together.
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
CONFIG_COMPILER_OPTIMIZATION_NONE=y

View File

@ -0,0 +1,4 @@
CONFIG_ESP_TASK_WDT_EN=n
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n

View File

@ -0,0 +1,2 @@
dependencies:
ccomp_timer: "^1.0.0"

View File

@ -1,5 +1,4 @@
set(srcs "ccomp_timer.c" set(srcs "memory_checks.c"
"memory_checks.c"
"test_runner.c" "test_runner.c"
"test_utils.c") "test_utils.c")
@ -14,17 +13,8 @@ else()
list(APPEND srcs "ref_clock_impl_timergroup.c") list(APPEND srcs "ref_clock_impl_timergroup.c")
endif() endif()
if(CONFIG_IDF_TARGET_ARCH_RISCV)
list(APPEND srcs "ccomp_timer_impl_riscv.c")
endif()
if(CONFIG_IDF_TARGET_ARCH_XTENSA)
list(APPEND srcs "ccomp_timer_impl_xtensa.c")
endif()
idf_component_register(SRCS ${srcs} idf_component_register(SRCS ${srcs}
INCLUDE_DIRS include INCLUDE_DIRS include
PRIV_INCLUDE_DIRS private_include
REQUIRES esp_partition idf_test cmock REQUIRES esp_partition idf_test cmock
PRIV_REQUIRES perfmon driver esp_netif) PRIV_REQUIRES perfmon driver esp_netif)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

View File

@ -1,98 +0,0 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ccomp_timer.h"
#include "ccomp_timer_impl.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_intr_alloc.h"
static const char TAG[] = "ccomp_timer";
esp_err_t ccomp_timer_start(void)
{
esp_err_t err = ESP_OK;
ccomp_timer_impl_lock();
if (ccomp_timer_impl_is_init()) {
if (ccomp_timer_impl_is_active()) {
err = ESP_ERR_INVALID_STATE;
}
}
else {
err = ccomp_timer_impl_init();
}
ccomp_timer_impl_unlock();
if (err != ESP_OK) {
goto fail;
}
err = ccomp_timer_impl_reset();
if (err != ESP_OK) {
goto fail;
}
err = ccomp_timer_impl_start();
if (err == ESP_OK) {
return ESP_OK;
}
fail:
ESP_LOGE(TAG, "Unable to start performance timer");
return err;
}
int64_t IRAM_ATTR ccomp_timer_stop(void)
{
esp_err_t err = ESP_OK;
ccomp_timer_impl_lock();
if (!ccomp_timer_impl_is_active()) {
err = ESP_ERR_INVALID_STATE;
}
ccomp_timer_impl_unlock();
if (err != ESP_OK) {
goto fail;
}
err = ccomp_timer_impl_stop();
if (err != ESP_OK) {
goto fail;
}
int64_t t = ccomp_timer_get_time();
err = ccomp_timer_impl_deinit();
if (err == ESP_OK && t != -1) {
return t;
}
fail:
ESP_LOGE(TAG, "Unable to stop performance timer");
return -1;
}
int64_t IRAM_ATTR ccomp_timer_get_time(void)
{
return ccomp_timer_impl_get_time();
}

View File

@ -1,107 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "freertos/portmacro.h"
#include "esp_freertos_hooks.h"
#include "soc/soc_caps.h"
#include "esp_rom_sys.h"
#include "esp_cpu.h"
#include "esp_private/esp_clk.h"
typedef enum {
PERF_TIMER_UNINIT = 0, // timer has not been initialized yet
PERF_TIMER_IDLE, // timer has been initialized but is not tracking elapsed time
PERF_TIMER_ACTIVE // timer is tracking elapsed time
} ccomp_timer_state_t;
typedef struct {
uint32_t last_ccount; // last CCOUNT value, updated every os tick
ccomp_timer_state_t state; // state of the timer
int64_t ccount; // accumulated processors cycles during the time when timer is active
} ccomp_timer_status_t;
// Each core has its independent timer
ccomp_timer_status_t s_status[SOC_CPU_CORES_NUM];
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
static void IRAM_ATTR update_ccount(void)
{
if (s_status[esp_cpu_get_core_id()].state == PERF_TIMER_ACTIVE) {
int64_t new_ccount = esp_cpu_get_cycle_count();
if (new_ccount > s_status[esp_cpu_get_core_id()].last_ccount) {
s_status[esp_cpu_get_core_id()].ccount += new_ccount - s_status[esp_cpu_get_core_id()].last_ccount;
} else {
// CCOUNT has wrapped around
s_status[esp_cpu_get_core_id()].ccount += new_ccount + (UINT32_MAX - s_status[esp_cpu_get_core_id()].last_ccount);
}
s_status[esp_cpu_get_core_id()].last_ccount = new_ccount;
}
}
esp_err_t ccomp_timer_impl_init(void)
{
s_status[esp_cpu_get_core_id()].state = PERF_TIMER_IDLE;
return ESP_OK;
}
esp_err_t ccomp_timer_impl_deinit(void)
{
s_status[esp_cpu_get_core_id()].state = PERF_TIMER_UNINIT;
return ESP_OK;
}
esp_err_t ccomp_timer_impl_start(void)
{
s_status[esp_cpu_get_core_id()].state = PERF_TIMER_ACTIVE;
s_status[esp_cpu_get_core_id()].last_ccount = esp_cpu_get_cycle_count();
// Update elapsed cycles every OS tick
esp_register_freertos_tick_hook_for_cpu(update_ccount, esp_cpu_get_core_id());
return ESP_OK;
}
esp_err_t IRAM_ATTR ccomp_timer_impl_stop(void)
{
esp_deregister_freertos_tick_hook_for_cpu(update_ccount, esp_cpu_get_core_id());
update_ccount();
s_status[esp_cpu_get_core_id()].state = PERF_TIMER_IDLE;
return ESP_OK;
}
int64_t IRAM_ATTR ccomp_timer_impl_get_time(void)
{
update_ccount();
int64_t cycles = s_status[esp_cpu_get_core_id()].ccount;
return (cycles * 1000000) / esp_clk_cpu_freq();
}
esp_err_t ccomp_timer_impl_reset(void)
{
s_status[esp_cpu_get_core_id()].ccount = 0;
s_status[esp_cpu_get_core_id()].last_ccount = 0;
return ESP_OK;
}
bool ccomp_timer_impl_is_init(void)
{
return s_status[esp_cpu_get_core_id()].state != PERF_TIMER_UNINIT;
}
bool IRAM_ATTR ccomp_timer_impl_is_active(void)
{
return s_status[esp_cpu_get_core_id()].state == PERF_TIMER_ACTIVE;
}
void IRAM_ATTR ccomp_timer_impl_lock(void)
{
portENTER_CRITICAL(&s_lock);
}
void IRAM_ATTR ccomp_timer_impl_unlock(void)
{
portEXIT_CRITICAL(&s_lock);
}

View File

@ -1,212 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include "ccomp_timer_impl.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "eri.h"
#include "freertos/FreeRTOS.h"
#include "esp_freertos_hooks.h"
#include "perfmon.h"
#include "xtensa/core-macros.h"
#include "xtensa/xt_perf_consts.h"
#include "xtensa-debug-module.h"
#include "esp_private/esp_clk.h"
#define D_STALL_COUNTER_ID 0
#define I_STALL_COUNTER_ID 1
typedef enum
{
PERF_TIMER_UNINIT = 0, // timer has not been initialized yet
PERF_TIMER_IDLE, // timer has been initialized but is not tracking elapsed time
PERF_TIMER_ACTIVE // timer is tracking elapsed time
} ccomp_timer_state_t;
typedef struct
{
int i_ovfl; // number of times instruction stall counter has overflowed
int d_ovfl; // number of times data stall counter has overflowed
uint32_t last_ccount; // last CCOUNT value, updated every os tick
ccomp_timer_state_t state; // state of the timer
intr_handle_t intr_handle; // handle to allocated handler for perfmon counter overflows, so that it can be freed during deinit
int64_t ccount; // accumulated processors cycles during the time when timer is active
} ccomp_timer_status_t;
// Each core has its independent timer
ccomp_timer_status_t s_status[] = {
(ccomp_timer_status_t){
.i_ovfl = 0,
.d_ovfl = 0,
.ccount = 0,
.last_ccount = 0,
.state = PERF_TIMER_UNINIT,
.intr_handle = NULL,
},
(ccomp_timer_status_t){
.i_ovfl = 0,
.d_ovfl = 0,
.ccount = 0,
.last_ccount = 0,
.state = PERF_TIMER_UNINIT,
.intr_handle = NULL
}
};
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
static void IRAM_ATTR update_ccount(void)
{
if (s_status[xPortGetCoreID()].state == PERF_TIMER_ACTIVE) {
int64_t new_ccount = xthal_get_ccount();
if (new_ccount > s_status[xPortGetCoreID()].last_ccount) {
s_status[xPortGetCoreID()].ccount += new_ccount - s_status[xPortGetCoreID()].last_ccount;
} else {
// CCOUNT has wrapped around
s_status[xPortGetCoreID()].ccount += new_ccount + (UINT32_MAX - s_status[xPortGetCoreID()].last_ccount);
}
s_status[xPortGetCoreID()].last_ccount = new_ccount;
}
}
static void inline update_overflow(int id, int *cnt)
{
uint32_t pmstat = eri_read(ERI_PERFMON_PMSTAT0 + id * sizeof(int32_t));
if (pmstat & PMSTAT_OVFL) {
*cnt += 1;
// Clear overflow and PerfMonInt asserted bits. The only valid bits in PMSTAT is the ones we're trying to clear. So it should be
// ok to just modify the whole register.
eri_write(ERI_PERFMON_PMSTAT0 + id, ~0x0);
}
}
static void IRAM_ATTR perf_counter_overflow_handler(void *args)
{
update_overflow(D_STALL_COUNTER_ID, &s_status[xPortGetCoreID()].d_ovfl);
update_overflow(I_STALL_COUNTER_ID, &s_status[xPortGetCoreID()].i_ovfl);
}
static void set_perfmon_interrupt(bool enable)
{
uint32_t d_pmctrl = eri_read(ERI_PERFMON_PMCTRL0 + D_STALL_COUNTER_ID * sizeof(int32_t));
uint32_t i_pmctrl = eri_read(ERI_PERFMON_PMCTRL0 + I_STALL_COUNTER_ID * sizeof(int32_t));
if (enable) {
d_pmctrl |= PMCTRL_INTEN;
i_pmctrl |= PMCTRL_INTEN;
}
else {
d_pmctrl &= ~PMCTRL_INTEN;
i_pmctrl &= ~PMCTRL_INTEN;
}
eri_write(ERI_PERFMON_PMCTRL0 + D_STALL_COUNTER_ID * sizeof(int32_t), d_pmctrl);
eri_write(ERI_PERFMON_PMCTRL0 + I_STALL_COUNTER_ID * sizeof(int32_t), i_pmctrl);
}
esp_err_t ccomp_timer_impl_init(void)
{
// Keep track of how many times each counter has overflowed.
esp_err_t err = esp_intr_alloc(ETS_INTERNAL_PROFILING_INTR_SOURCE, 0,
perf_counter_overflow_handler, NULL, &s_status[xPortGetCoreID()].intr_handle);
if (err != ESP_OK) {
return err;
}
xtensa_perfmon_init(D_STALL_COUNTER_ID,
XTPERF_CNT_D_STALL,
XTPERF_MASK_D_STALL_BUSY, 0, -1);
xtensa_perfmon_init(I_STALL_COUNTER_ID,
XTPERF_CNT_I_STALL,
XTPERF_MASK_I_STALL_BUSY, 0, -1);
set_perfmon_interrupt(true);
s_status[xPortGetCoreID()].state = PERF_TIMER_IDLE;
return ESP_OK;
}
esp_err_t ccomp_timer_impl_deinit(void)
{
set_perfmon_interrupt(false);
esp_err_t err = esp_intr_free(s_status[xPortGetCoreID()].intr_handle);
if (err != ESP_OK) {
return err;
}
s_status[xPortGetCoreID()].intr_handle = NULL;
s_status[xPortGetCoreID()].state = PERF_TIMER_UNINIT;
return ESP_OK;
}
esp_err_t ccomp_timer_impl_start(void)
{
s_status[xPortGetCoreID()].state = PERF_TIMER_ACTIVE;
s_status[xPortGetCoreID()].last_ccount = xthal_get_ccount();
// Update elapsed cycles every OS tick
esp_register_freertos_tick_hook_for_cpu(update_ccount, xPortGetCoreID());
xtensa_perfmon_start();
return ESP_OK;
}
esp_err_t IRAM_ATTR ccomp_timer_impl_stop(void)
{
xtensa_perfmon_stop();
esp_deregister_freertos_tick_hook_for_cpu(update_ccount, xPortGetCoreID());
update_ccount();
s_status[xPortGetCoreID()].state = PERF_TIMER_IDLE;
return ESP_OK;
}
int64_t IRAM_ATTR ccomp_timer_impl_get_time(void)
{
update_ccount();
int64_t d_stalls = xtensa_perfmon_value(D_STALL_COUNTER_ID) +
s_status[xPortGetCoreID()].d_ovfl * (1 << sizeof(int32_t));
int64_t i_stalls = xtensa_perfmon_value(I_STALL_COUNTER_ID) +
s_status[xPortGetCoreID()].i_ovfl * (1 << sizeof(int32_t));
int64_t stalls = d_stalls + i_stalls;
int64_t cycles = s_status[xPortGetCoreID()].ccount;
return ((cycles - stalls) * 1000000) / esp_clk_cpu_freq();
}
esp_err_t ccomp_timer_impl_reset(void)
{
xtensa_perfmon_reset(D_STALL_COUNTER_ID);
xtensa_perfmon_reset(I_STALL_COUNTER_ID);
s_status[xPortGetCoreID()].d_ovfl = 0;
s_status[xPortGetCoreID()].i_ovfl = 0;
s_status[xPortGetCoreID()].ccount = 0;
s_status[xPortGetCoreID()].last_ccount = 0;
return ESP_OK;
}
bool ccomp_timer_impl_is_init(void)
{
return s_status[xPortGetCoreID()].state != PERF_TIMER_UNINIT;
}
bool IRAM_ATTR ccomp_timer_impl_is_active(void)
{
return s_status[xPortGetCoreID()].state == PERF_TIMER_ACTIVE;
}
void IRAM_ATTR ccomp_timer_impl_lock(void)
{
portENTER_CRITICAL(&s_lock);
}
void IRAM_ATTR ccomp_timer_impl_unlock(void)
{
portEXIT_CRITICAL(&s_lock);
}

View File

@ -1,62 +0,0 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Start the timer on the current core.
*
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_STATE: The timer has already been started previously.
* - Others: Fail
*/
esp_err_t ccomp_timer_start(void);
/**
* @brief Stop the timer on the current core.
*
* @note Returns -1 if an error has occured and stopping the timer failed.
*
* @return The time elapsed from the last ccomp_timer_start call on the current
* core.
*/
int64_t ccomp_timer_stop(void);
/**
* Return the current timer value on the current core without stopping the timer.
*
* @note Returns -1 if an error has occured and stopping the timer failed.
*
* @note If called while timer is active i.e. between ccomp_timer_start and ccomp_timer_stop,
* this function returns the elapsed time from ccomp_timer_start. Once ccomp_timer_stop
* has been called, the timer becomes inactive and stops keeping time. As a result, if this function gets
* called after esp_cccomp_timer_stop, this function will return the same value as when the timer was stopped.
*
* @return The elapsed time from the last ccomp_timer_start call on the current
* core.
*/
int64_t ccomp_timer_get_time(void);
#ifdef __cplusplus
}
#endif

View File

@ -1,102 +0,0 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
/**
* @brief Initialize the underlying implementation for cache compensated timer. This might involve
* setting up architecture-specific event counters and or allocating interrupts that handle events for those counters.
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_STATE: The timer has already been started previously.
* - Others: Fail
*/
esp_err_t ccomp_timer_impl_init(void);
/**
* @brief Deinitialize the underlying implementation for cache compensated timer. This should restore
* the state of the program to before ccomp_timer_impl_init.
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_STATE: The timer has already been started previously.
* - Others: Fail
*/
esp_err_t ccomp_timer_impl_deinit(void);
/**
* @brief Make the underlying implementation start keeping time.
*
* @return
* - ESP_OK: Success
* - Others: Fail
*/
esp_err_t ccomp_timer_impl_start(void);
/**
* @brief Make the underlying implementation stop keeping time.
*
* @return
* - ESP_OK: Success
* - Others: Fail
*/
esp_err_t ccomp_timer_impl_stop(void);
/**
* @brief Reset the timer to its initial state.
*
* @return
* - ESP_OK: Success
* - Others: Fail
*/
esp_err_t ccomp_timer_impl_reset(void);
/**
* @brief Get the elapsed time kept track of by the underlying implementation in microseconds.
*
* @return The elapsed time in microseconds. Set to -1 if the operation is unsuccessful.
*/
int64_t ccomp_timer_impl_get_time(void);
/**
* @brief Obtain an internal critical section used in the implementation. Should be treated
* as a spinlock.
*/
void ccomp_timer_impl_lock(void);
/**
* @brief Start the performance timer on the current core.
*/
void ccomp_timer_impl_unlock(void);
/**
* @brief Check if timer has been initialized.
*
* @return
* - true: the timer has been initialized using ccomp_timer_impl_init
* - false: the timer has not been initialized, or ccomp_timer_impl_deinit has been called recently
*/
bool ccomp_timer_impl_is_init(void);
/**
* @brief Check if timer is keeping time.
*
* @return
* - true: the timer is keeping track of elapsed time from ccomp_timer_impl_start
* - false: the timer is not keeping track of elapsed time since ccomp_timer_impl_start has not yet been called or ccomp_timer_impl_stop has been called recently
*/
bool ccomp_timer_impl_is_active(void);

View File

@ -1,167 +0,0 @@
#include <stdlib.h>
#include <stdint.h>
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "ccomp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifndef CONFIG_FREERTOS_UNICORE
#include "esp_ipc.h"
#endif
#include "unity.h"
#ifndef CONFIG_FREERTOS_UNICORE
static void start_timer(void *param)
{
esp_err_t *err = (esp_err_t *)param;
*err = ccomp_timer_start();
}
static void stop_timer(void *param)
{
int64_t *t = (int64_t *)param;
*t = ccomp_timer_stop();
}
#endif
static void computation(void *param)
{
int *l = (int *)param;
for (volatile int i = 0, a = 0; i < *l; i++)
{
a += i;
}
}
TEST_CASE("starting and stopping works", "[test_utils][ccomp_timer]")
{
esp_err_t err;
int64_t t;
/*
* Test on the same task
*/
err = ccomp_timer_start();
TEST_ASSERT_EQUAL(ESP_OK, err);
// Start an already started timer
err = ccomp_timer_start();
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, err);
t = ccomp_timer_stop();
TEST_ASSERT_GREATER_OR_EQUAL(0, t);
// Stopping a non started timer
t = ccomp_timer_stop();
TEST_ASSERT_EQUAL(-1, t);
#ifndef CONFIG_FREERTOS_UNICORE
/*
* Test on different task on same core
*/
err = ccomp_timer_start();
TEST_ASSERT_EQUAL(ESP_OK, err);
esp_ipc_call_blocking(xPortGetCoreID(), start_timer, &err);
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, err);
t = ccomp_timer_stop();
TEST_ASSERT_GREATER_OR_EQUAL(0, t);
esp_ipc_call_blocking(xPortGetCoreID(), stop_timer, &t);
TEST_ASSERT_EQUAL(-1, t);
/*
* Timer being stopped from another task on the same core
*/
err = ccomp_timer_start();
TEST_ASSERT_EQUAL(ESP_OK, err);
esp_ipc_call_blocking(xPortGetCoreID(), stop_timer, &t);
TEST_ASSERT_GREATER_OR_EQUAL(0, t);
/*
* Test on different task on same core
*/
err = ccomp_timer_start();
TEST_ASSERT_EQUAL(ESP_OK, err);
esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, start_timer, &err);
TEST_ASSERT_EQUAL(ESP_OK, err);
t = ccomp_timer_stop();
TEST_ASSERT_GREATER_OR_EQUAL(0, t);
esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, stop_timer, &t);
TEST_ASSERT_GREATER_OR_EQUAL(0, t);
#endif
}
TEST_CASE("getting the time works", "[test_utils][ccomp_timer]")
{
// Get wall time and start ccomp timer
int64_t start = esp_timer_get_time();
ccomp_timer_start();
int64_t t_a = ccomp_timer_get_time();
int temp = 10000;
computation(&temp);
int64_t t_b = ccomp_timer_get_time();
// Check that ccomp time after computation is more than
// ccomp time before computation.
TEST_ASSERT_LESS_THAN(t_b, t_a);
// Get time diff between wall time and ccomp time
int64_t t_1 = ccomp_timer_stop();
int64_t t_2 = esp_timer_get_time() - start;
// The times should at least be in the same ballpark (at least within 10%)
float diff = (llabs(t_1 - t_2)) / ((float)t_2);
TEST_ASSERT(diff <= 10.0f);
// Since the timer was already stopped, test that ccomp_timer_get_time
// returns the same time as ccomp_timer_stop
int64_t t_c = ccomp_timer_get_time();
TEST_ASSERT_EQUAL(t_1, t_c);
}
#ifndef CONFIG_FREERTOS_UNICORE
TEST_CASE("timers for each core counts independently", "[test_utils][ccomp_timer]")
{
esp_err_t err;
// Start a timer on this core
err = ccomp_timer_start();
TEST_ASSERT_EQUAL(ESP_OK, err);
// Do some work on this core
int temp = 10000;
computation(&temp);
// Start a timer on the other core
esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, start_timer, &err);
TEST_ASSERT_EQUAL(ESP_OK, err);
// Do some work on other core (less work than this core did)
temp = 5000;
esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, computation, &temp);
// Stop timers from both cores
int64_t t_1 = ccomp_timer_stop();
TEST_ASSERT_GREATER_OR_EQUAL(0, t_1);
int64_t t_2;
esp_ipc_call_blocking(xPortGetCoreID() == 0 ? 1 : 0, stop_timer, &t_2);
TEST_ASSERT_GREATER_OR_EQUAL(0, t_2);
// Since this core did more work, it probably has longer measured time
TEST_ASSERT_GREATER_THAN(t_2, t_1);
}
#endif

View File

@ -1,180 +0,0 @@
#include <stdlib.h>
#include <stdint.h>
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "ccomp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_private/esp_clk.h"
#include "test_utils.h"
#include "unity.h"
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32
#define CACHE_WAYS 2
#define CACHE_LINE_SIZE 32
#define CACHE_SIZE (1 << 15)
// Only test half due to lack of memory
#define TEST_SIZE (CACHE_SIZE / 2)
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
// Default cache configuration - no override specified on
// test_utils config
#define CACHE_WAYS 8
#define CACHE_LINE_SIZE 32
#define CACHE_SIZE (1 << 13)
#define TEST_SIZE (CACHE_SIZE)
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6
#define CACHE_WAYS 8
#define CACHE_LINE_SIZE 32
#define CACHE_SIZE (1 << 14)
#define TEST_SIZE (CACHE_SIZE)
#endif
typedef struct {
uint8_t **accesses;
size_t len;
} ccomp_test_access_t;
typedef struct {
int64_t wall;
int64_t ccomp;
} ccomp_test_time_t;
/* No performance monitor in RISCV for now
*/
#if !__riscv
//IDF-5052
static const char* TAG = "test_ccomp_timer";
#if CONFIG_SPIRAM
static uint8_t *flash_mem;
#else
static const uint8_t flash_mem[2 * CACHE_SIZE] = {0};
#endif
static IRAM_ATTR void perform_accesses(ccomp_test_access_t *access)
{
volatile int a = 0;
for (int i = 0; i < access->len; i++) {
a += (int)(*(access->accesses[i]));
}
}
static void prepare_cache(const uint8_t *to_cache)
{
volatile int a = 0;
for (int i = 0; i < CACHE_SIZE; i++) {
a += to_cache[i];
}
}
static void prepare_access_pattern(int hit_rate, const uint8_t *cached, ccomp_test_access_t *out)
{
assert(hit_rate <= 100);
assert(hit_rate >= 0);
int misses = (100 - hit_rate) * CACHE_LINE_SIZE;
int hits = hit_rate * CACHE_LINE_SIZE;
uint8_t **accesses = calloc(TEST_SIZE, sizeof(uint8_t *));
for (int i = 0, h = 0, i_h = 1, m = -1, i_m = 0; i < TEST_SIZE; i++, h += i_h, m += i_m) {
if (i_m) {
accesses[i] = (uint8_t*) (cached + CACHE_SIZE + i);
}
else {
accesses[i] = (uint8_t*) (cached + i);
}
if (h >= hits) {
h = -1;
i_h = 0;
m = 0;
i_m = 1;
}
if (m >= misses) {
m = -1;
i_m = 0;
h = 0;
i_h = 1;
}
}
out->accesses = accesses;
out->len = TEST_SIZE;
}
static ccomp_test_time_t perform_test_at_hit_rate(int hit_rate, const uint8_t *mem)
{
ccomp_test_access_t access;
prepare_access_pattern(hit_rate, mem, &access);
prepare_cache(mem);
int64_t start = esp_timer_get_time();
ccomp_timer_start();
perform_accesses(&access);
ccomp_test_time_t t = {
.ccomp = ccomp_timer_stop(),
.wall = esp_timer_get_time() - start
};
free(access.accesses);
return t;
}
static ccomp_test_time_t ccomp_test_ref_time(void)
{
#if CONFIG_SPIRAM
uint8_t *mem = heap_caps_malloc(2 * CACHE_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT);
#else
uint8_t *mem = heap_caps_malloc(sizeof(flash_mem), MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT);
#endif
ccomp_test_time_t t = perform_test_at_hit_rate(0, mem);
free(mem);
return t;
}
TEST_CASE("data cache hit rate sweep", "[test_utils][ccomp_timer]")
{
ccomp_test_time_t t_ref;
ccomp_test_time_t t_hr;
#if CONFIG_SPIRAM
flash_mem = heap_caps_malloc(2 * CACHE_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
#endif
// Perform accesses on RAM. The time recorded here serves as
// reference.
t_ref = ccomp_test_ref_time();
ESP_LOGI(TAG, "Reference Time(us): %lld", (long long)t_ref.ccomp);
// Measure time at particular hit rates
for (int i = 0; i <= 100; i += 5)
{
t_hr = perform_test_at_hit_rate(i, flash_mem);
float error = (llabs(t_ref.ccomp - t_hr.ccomp) / (float)t_ref.ccomp) * 100.0f;
ESP_LOGI(TAG, "Hit Rate(%%): %d Wall Time(us): %lld Compensated Time(us): %lld Error(%%): %f", i, (long long)t_hr.wall, (long long)t_hr.ccomp, error);
// Check if the measured time is at least within some percent of the
// reference.
TEST_ASSERT(error <= 5.0f);
}
#if CONFIG_SPIRAM
free(flash_mem);
#endif
}
#endif // __riscv

View File

@ -1,232 +0,0 @@
#include <stdlib.h>
#include <stdint.h>
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "ccomp_timer.h"
#include "freertos/FreeRTOS.h"
#include "unity.h"
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32
#define CACHE_WAYS 2
#define CACHE_LINE_SIZE 32
#define CACHE_SIZE (1 << 15)
// Only test half due to lack of memory
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
// Default cache configuration - no override specified on
// test_utils config
#define CACHE_WAYS 8
#define CACHE_LINE_SIZE 32
#define CACHE_SIZE (1 << 13)
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
#define CACHE_WAYS 8
#define CACHE_LINE_SIZE 32
#define CACHE_SIZE (1 << 14)
#endif
typedef void (*ccomp_test_func_t)(void);
static const char* TAG = "test_ccomp_timer";
typedef struct {
int64_t wall;
int64_t ccomp;
} ccomp_test_time_t;
typedef struct {
ccomp_test_func_t *funcs;
size_t len;
} ccomp_test_call_t;
#define FUNC() \
do \
{ \
volatile int a = 0; \
a++; \
} while (0);
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func1(void)
{
FUNC();
}
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func2(void)
{
FUNC();
}
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func3(void)
{
FUNC();
}
#if TEMPORARY_DISABLED_FOR_TARGETS(ESP32)
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func4(void)
{
FUNC();
}
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func5(void)
{
FUNC();
}
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func6(void)
{
FUNC();
}
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func7(void)
{
FUNC();
}
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func8(void)
{
FUNC();
}
__aligned(CACHE_SIZE / CACHE_WAYS) static void test_func9(void)
{
FUNC();
}
#endif
static void IRAM_ATTR iram_func(void)
{
FUNC();
}
static void IRAM_ATTR perform_calls(ccomp_test_call_t *call)
{
for (int i = 0; i < call->len; i++) {
call->funcs[i]();
}
}
static void IRAM_ATTR prepare_cache(ccomp_test_call_t *call)
{
perform_calls(call);
}
static void IRAM_ATTR prepare_calls(int hit_rate, ccomp_test_func_t *alts, size_t alts_len, size_t len, ccomp_test_call_t *out)
{
assert(hit_rate <= 100);
assert(hit_rate >= 0);
int misses = (100 - hit_rate);
int hits = hit_rate;
ccomp_test_func_t *funcs = calloc(len, sizeof(ccomp_test_func_t));
for (int i = 0, h = 0, i_h = 1, m = -1, i_m = 0, l = 0; i < len; i++, h += i_h, m += i_m) {
funcs[i] = alts[l % alts_len];
if (i_m) {
l++;
}
if (h >= hits) {
h = -1;
i_h = 0;
m = 0;
i_m = 1;
}
if (m >= misses) {
m = -1;
i_m = 0;
h = 0;
i_h = 1;
}
}
out->funcs = funcs;
out->len = len;
}
static ccomp_test_time_t IRAM_ATTR perform_test_at_hit_rate(int hit_rate)
{
static portMUX_TYPE m = portMUX_INITIALIZER_UNLOCKED;
ccomp_test_call_t calls;
ccomp_test_func_t alts[] = {test_func1, test_func2, test_func3,
#if TEMPORARY_DISABLED_FOR_TARGETS(ESP32)
test_func4, test_func5, test_func6, test_func7, test_func8, test_func9,
#endif
};
prepare_calls(hit_rate, alts, sizeof(alts)/sizeof(alts[0]), 10000, &calls);
ccomp_test_func_t f[] = {test_func1, test_func2};
ccomp_test_call_t cache = {
.funcs = f,
.len = sizeof(f) / sizeof(f[0])};
portENTER_CRITICAL(&m);
prepare_cache(&cache);
int64_t start = esp_timer_get_time();
ccomp_timer_start();
perform_calls(&calls);
ccomp_test_time_t t = {
.ccomp = ccomp_timer_stop(),
.wall = esp_timer_get_time() - start
};
portEXIT_CRITICAL(&m);
free(calls.funcs);
return t;
}
static ccomp_test_time_t ccomp_test_ref_time(void)
{
ccomp_test_call_t calls;
ccomp_test_func_t alts[] = {iram_func};
prepare_calls(0, alts, 1, 10000, &calls);
int64_t start = esp_timer_get_time();
ccomp_timer_start();
perform_calls(&calls);
ccomp_test_time_t t = {
.ccomp = ccomp_timer_stop(),
.wall = esp_timer_get_time() - start
};
free(calls.funcs);
return t;
}
TEST_CASE("instruction cache hit rate sweep test", "[test_utils][ccomp_timer]")
{
ccomp_test_time_t t_ref;
ccomp_test_time_t t_hr;
// Perform accesses on RAM. The time recorded here serves as
// reference.
t_ref = ccomp_test_ref_time();
ESP_LOGI(TAG, "Reference Time(us): %lld", (long long)t_ref.ccomp);
// Measure time at particular hit rates
for (int i = 0; i <= 100; i += 5)
{
t_hr = perform_test_at_hit_rate(i);
float error = (llabs(t_ref.ccomp - t_hr.ccomp) / (float)t_ref.wall) * 100.0f;
ESP_LOGI(TAG, "Hit Rate(%%): %d Wall Time(us): %lld Compensated Time(us): %lld Error(%%): %f", i, (long long)t_hr.wall, (long long)t_hr.ccomp, error);
// Check if the measured time is at least within some percent of the
// reference.
TEST_ASSERT(error <= 5.0f);
}
}

View File

@ -1,4 +0,0 @@
# This config is split between targets since different component needs to be included (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32"
TEST_COMPONENTS=spi_flash
CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE=y

View File

@ -1,4 +0,0 @@
# This config is split between targets since different component needs to be included (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32c2"
TEST_COMPONENTS=spi_flash
CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE=y

View File

@ -1,4 +0,0 @@
# This config is split between targets since different component needs to be included (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32c3"
TEST_COMPONENTS=spi_flash
CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE=y

View File

@ -1,4 +0,0 @@
# This config is split between targets since different component needs to be included
CONFIG_IDF_TARGET="esp32c6"
TEST_COMPONENTS=spi_flash
CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE=y

View File

@ -1,4 +0,0 @@
# This config is split between targets since different component needs to be included
CONFIG_IDF_TARGET="esp32h2"
TEST_COMPONENTS=spi_flash
CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE=y

View File

@ -1,4 +0,0 @@
# This config is split between targets since different component needs to be included (esp32, esp32s2)
CONFIG_IDF_TARGET="esp32s2"
TEST_COMPONENTS=spi_flash
CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE=y

View File

@ -1,2 +0,0 @@
TEST_COMPONENTS=spi_flash
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32c2"
TEST_COMPONENTS=spi_flash
CONFIG_COMPILER_OPTIMIZATION_NONE=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32c3"
TEST_COMPONENTS=spi_flash
CONFIG_COMPILER_OPTIMIZATION_NONE=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32c6"
TEST_COMPONENTS=spi_flash
CONFIG_COMPILER_OPTIMIZATION_NONE=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32"
TEST_COMPONENTS=spi_flash
CONFIG_COMPILER_OPTIMIZATION_NONE=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32h2"
TEST_COMPONENTS=spi_flash
CONFIG_COMPILER_OPTIMIZATION_NONE=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32c2"
TEST_COMPONENTS=spi_flash
CONFIG_SPI_FLASH_ROM_IMPL=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32c3"
TEST_COMPONENTS=spi_flash
CONFIG_SPI_FLASH_ROM_IMPL=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32c6"
TEST_COMPONENTS=spi_flash
CONFIG_SPI_FLASH_ROM_IMPL=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32h2"
TEST_COMPONENTS=spi_flash
CONFIG_SPI_FLASH_ROM_IMPL=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32s3"
TEST_COMPONENTS=spi_flash
CONFIG_SPI_FLASH_ROM_IMPL=y

View File

@ -1,5 +0,0 @@
TEST_COMPONENTS=spi_flash
CONFIG_SPIRAM=y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=n
CONFIG_IDF_TARGET="esp32s2"

View File

@ -1,6 +0,0 @@
TEST_COMPONENTS=spi_flash
CONFIG_SPIRAM=y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=n
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
CONFIG_IDF_TARGET="esp32s2"

View File

@ -1,6 +0,0 @@
TEST_COMPONENTS=spi_flash
CONFIG_SPIRAM=y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=n
CONFIG_SPIRAM_RODATA=y
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
CONFIG_IDF_TARGET="esp32s2"

View File

@ -1,6 +0,0 @@
TEST_COMPONENTS=spi_flash
CONFIG_SPIRAM=y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=y
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
CONFIG_IDF_TARGET="esp32s2"

View File

@ -1,5 +0,0 @@
TEST_COMPONENTS=spi_flash
CONFIG_SPIRAM=y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=y
CONFIG_IDF_TARGET="esp32s3"

View File

@ -1,2 +0,0 @@
TEST_COMPONENTS=spi_flash
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y

View File

@ -1,3 +0,0 @@
CONFIG_IDF_TARGET="esp32c3"
TEST_COMPONENTS=spi_flash
CONFIG_SPI_FLASH_AUTO_SUSPEND=y