mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(dma): graduate the dma driver from esp_hw_support to esp_driver_dma
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
|
||||
project(dma_test)
|
||||
|
||||
message(STATUS "Checking dma registers are not read-write by half-word")
|
||||
include($ENV{IDF_PATH}/tools/ci/check_register_rw_half_word.cmake)
|
||||
check_register_rw_half_word(SOC_MODULES "*gdma" "pcr" "hp_sys_clkrst"
|
||||
HAL_MODULES "*gdma")
|
||||
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
@@ -0,0 +1,31 @@
|
||||
set(srcs "test_app_main.c")
|
||||
|
||||
if(CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED)
|
||||
list(APPEND srcs "test_async_memcpy.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_GDMA_SUPPORTED)
|
||||
list(APPEND srcs "test_gdma.c" "gdma_test_utils.c")
|
||||
|
||||
if(CONFIG_SOC_ETM_SUPPORTED AND CONFIG_SOC_GDMA_SUPPORT_ETM)
|
||||
list(APPEND srcs "test_gdma_etm.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_GDMA_SUPPORT_CRC)
|
||||
list(APPEND srcs "test_gdma_crc.c")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_DW_GDMA_SUPPORTED)
|
||||
list(APPEND srcs "test_dw_gdma.c")
|
||||
endif()
|
||||
|
||||
# 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}
|
||||
PRIV_REQUIRES unity esp_mm esp_driver_gpio esp_psram esp_driver_dma
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
idf_component_get_property(lib_name esp_hal_dma COMPONENT_LIB)
|
||||
# Test GDMA retention correctness with software retention feature
|
||||
target_compile_definitions(${lib_name} PRIVATE "CI_TEST_SW_RETENTION=1")
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "gdma_test_utils.h"
|
||||
#include "esp_private/sleep_retention.h"
|
||||
#include "hal/gdma_ll.h"
|
||||
|
||||
void test_gdma_trigger_retention_backup(gdma_channel_handle_t chan, ...)
|
||||
{
|
||||
#if SOC_PAU_SUPPORTED && SOC_GDMA_SUPPORT_SLEEP_RETENTION
|
||||
// trigger a software retention to test GDMA retention correctnesss
|
||||
// 1. backup gdma register context
|
||||
sleep_retention_do_extra_retention(true);
|
||||
|
||||
// 2. reset gdma registers to default value
|
||||
gdma_channel_handle_t chan_itor = chan;
|
||||
va_list args;
|
||||
int group_id = -1;
|
||||
va_start(args, chan);
|
||||
while (chan_itor) {
|
||||
gdma_get_group_channel_id(chan_itor, &group_id, NULL);
|
||||
_gdma_ll_reset_register(group_id);
|
||||
chan_itor = va_arg(args, gdma_channel_handle_t);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
// 3. restore gdma register context
|
||||
sleep_retention_do_extra_retention(false);
|
||||
#endif
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_private/gdma.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Trigger a "fake" sleep retention process.
|
||||
*
|
||||
* @note Call this help function after the gdma set up is completed. Then check the gdma functionality is still working.
|
||||
*
|
||||
* @param chan GDMA channel handle to be reset
|
||||
* @param ... Other GDMA channel handle if any
|
||||
*/
|
||||
void test_gdma_trigger_retention_backup(gdma_channel_handle_t chan, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
ccomp_timer: "^1.0.0"
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 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 pulse_cnt driver, the threshold is left for that case
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (-400)
|
||||
|
||||
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(" ____ __ __ _ _____ _\r\n");
|
||||
printf("| _ \\| \\/ | / \\ |_ _|__ ___| |_\r\n");
|
||||
printf("| | | | |\\/| | / _ \\ | |/ _ \\/ __| __|\r\n");
|
||||
printf("| |_| | | | |/ ___ \\ | | __/\\__ \\ |_\r\n");
|
||||
printf("|____/|_| |_/_/ \\_\\ |_|\\___||___/\\__|\r\n");
|
||||
unity_run_menu();
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/param.h>
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "ccomp_timer.h"
|
||||
#include "esp_async_memcpy.h"
|
||||
#include "hal/efuse_hal.h"
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
#include "hal/gdma_ll.h"
|
||||
#endif
|
||||
|
||||
#define IDF_LOG_PERFORMANCE(item, value_fmt, value, ...) \
|
||||
printf("[Performance][%s]: " value_fmt "\n", item, value, ##__VA_ARGS__)
|
||||
|
||||
typedef struct {
|
||||
uint32_t seed;
|
||||
size_t buffer_size;
|
||||
size_t copy_size;
|
||||
uint8_t *src_buf;
|
||||
uint8_t *dst_buf;
|
||||
uint8_t *from_addr;
|
||||
uint8_t *to_addr;
|
||||
uint32_t align; // alignment required by DMA engine
|
||||
uint32_t src_offset;
|
||||
uint32_t dst_offset;
|
||||
bool src_in_psram;
|
||||
bool dst_in_psram;
|
||||
bool src_dst_same;
|
||||
} memcpy_testbench_context_t;
|
||||
|
||||
static void async_memcpy_setup_testbench(memcpy_testbench_context_t *test_context)
|
||||
{
|
||||
srand(test_context->seed);
|
||||
size_t buffer_size = test_context->buffer_size;
|
||||
size_t copy_size = buffer_size;
|
||||
uint8_t *src_buf = NULL;
|
||||
uint8_t *dst_buf = NULL;
|
||||
uint8_t *from_addr = NULL;
|
||||
uint8_t *to_addr = NULL;
|
||||
|
||||
uint32_t mem_caps = test_context->src_in_psram ? MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT : MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT ;
|
||||
src_buf = heap_caps_aligned_calloc(test_context->align, 1, buffer_size, mem_caps);
|
||||
TEST_ASSERT_NOT_NULL(src_buf);
|
||||
if (test_context->src_dst_same) {
|
||||
dst_buf = src_buf;
|
||||
} else {
|
||||
mem_caps = test_context->dst_in_psram ? MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT : MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT ;
|
||||
dst_buf = heap_caps_aligned_calloc(test_context->align, 1, buffer_size, mem_caps);
|
||||
TEST_ASSERT_NOT_NULL(dst_buf);
|
||||
}
|
||||
|
||||
// adding extra offset
|
||||
from_addr = src_buf + test_context->src_offset;
|
||||
to_addr = dst_buf + test_context->dst_offset;
|
||||
copy_size -= MAX(test_context->src_offset, test_context->dst_offset);
|
||||
|
||||
printf("copy @%p --> @%p, %zu Bytes\r\n", from_addr, to_addr, copy_size);
|
||||
for (int i = 0; i < copy_size; i++) {
|
||||
from_addr[i] = rand() % 256;
|
||||
}
|
||||
|
||||
// save context
|
||||
test_context->copy_size = copy_size;
|
||||
test_context->src_buf = src_buf;
|
||||
test_context->dst_buf = dst_buf;
|
||||
test_context->from_addr = from_addr;
|
||||
test_context->to_addr = to_addr;
|
||||
}
|
||||
|
||||
static void async_memcpy_verify_and_clear_testbench(uint32_t copy_size, uint8_t *src_buf, uint8_t *dst_buf, uint8_t *from_addr, uint8_t *to_addr)
|
||||
{
|
||||
// check if source date has been copied to destination and source data not broken
|
||||
for (int i = 0; i < copy_size; i++) {
|
||||
if (from_addr[i] != to_addr[i]) {
|
||||
printf("location[%d]:s=%d,d=%d\r\n", i, from_addr[i], to_addr[i]);
|
||||
TEST_FAIL_MESSAGE("destination data doesn't match source data");
|
||||
}
|
||||
}
|
||||
free(src_buf);
|
||||
free(dst_buf);
|
||||
}
|
||||
|
||||
static void test_memory_copy_with_same_buffer(async_memcpy_handle_t driver, async_memcpy_config_t *config)
|
||||
{
|
||||
uint8_t *sbuf = heap_caps_aligned_calloc(config->dma_burst_size, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
uint8_t *dbuf = heap_caps_aligned_calloc(config->dma_burst_size, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(sbuf);
|
||||
TEST_ASSERT_NOT_NULL(dbuf);
|
||||
|
||||
for (int j = 0; j < 20; j++) {
|
||||
TEST_ESP_OK(esp_async_memcpy(driver, dbuf, sbuf, 256, NULL, NULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (sbuf[i] != dbuf[i]) {
|
||||
printf("location[%d]:s=%d,d=%d\r\n", i, sbuf[i], dbuf[i]);
|
||||
TEST_FAIL_MESSAGE("destination data doesn't match source data");
|
||||
} else {
|
||||
sbuf[i] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(sbuf);
|
||||
free(dbuf);
|
||||
}
|
||||
|
||||
TEST_CASE("memory copy the same buffer with different content", "[async mcp]")
|
||||
{
|
||||
async_memcpy_config_t config = ASYNC_MEMCPY_DEFAULT_CONFIG();
|
||||
async_memcpy_handle_t driver = NULL;
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
printf("Testing memcpy by AHB GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&config, &driver));
|
||||
test_memory_copy_with_same_buffer(driver, &config);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
printf("Testing memcpy by AXI GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_axi(&config, &driver));
|
||||
test_memory_copy_with_same_buffer(driver, &config);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
|
||||
#if SOC_CP_DMA_SUPPORTED
|
||||
printf("Testing memcpy by CP DMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_cpdma(&config, &driver));
|
||||
test_memory_copy_with_same_buffer(driver, &config);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_CP_DMA_SUPPORTED
|
||||
}
|
||||
|
||||
static bool test_async_memcpy_cb_v1(async_memcpy_handle_t mcp_hdl, async_memcpy_event_t *event, void *cb_args)
|
||||
{
|
||||
SemaphoreHandle_t sem = (SemaphoreHandle_t)cb_args;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
xSemaphoreGiveFromISR(sem, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static void test_memory_copy_blocking(async_memcpy_handle_t driver)
|
||||
{
|
||||
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
|
||||
const uint32_t test_buffer_size[] = {256, 512, 1024, 2048, 4096, 5008};
|
||||
memcpy_testbench_context_t test_context = {
|
||||
.align = 16,
|
||||
};
|
||||
for (int i = 0; i < sizeof(test_buffer_size) / sizeof(test_buffer_size[0]); i++) {
|
||||
// Test different align edge
|
||||
for (int off = 0; off < 4; off++) {
|
||||
test_context.buffer_size = test_buffer_size[i];
|
||||
test_context.seed = i;
|
||||
if (!efuse_hal_flash_encryption_enabled()) {
|
||||
test_context.src_offset = off;
|
||||
test_context.dst_offset = off;
|
||||
}
|
||||
async_memcpy_setup_testbench(&test_context);
|
||||
|
||||
TEST_ESP_OK(esp_async_memcpy(driver, test_context.to_addr, test_context.from_addr, test_context.copy_size, test_async_memcpy_cb_v1, sem));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(sem, pdMS_TO_TICKS(10)));
|
||||
async_memcpy_verify_and_clear_testbench(test_context.copy_size, test_context.src_buf, test_context.dst_buf,
|
||||
test_context.from_addr, test_context.to_addr);
|
||||
}
|
||||
}
|
||||
vSemaphoreDelete(sem);
|
||||
}
|
||||
|
||||
TEST_CASE("memory copy by DMA (blocking)", "[async mcp]")
|
||||
{
|
||||
async_memcpy_config_t config = {
|
||||
.backlog = 1,
|
||||
.dma_burst_size = 0,
|
||||
};
|
||||
async_memcpy_handle_t driver = NULL;
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
printf("Testing memcpy by AHB GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&config, &driver));
|
||||
test_memory_copy_blocking(driver);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
printf("Testing memcpy by AXI GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_axi(&config, &driver));
|
||||
test_memory_copy_blocking(driver);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
|
||||
#if SOC_CP_DMA_SUPPORTED
|
||||
printf("Testing memcpy by CP DMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_cpdma(&config, &driver));
|
||||
test_memory_copy_blocking(driver);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_CP_DMA_SUPPORTED
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void test_memcpy_with_dest_addr_unaligned(async_memcpy_handle_t driver, bool src_in_psram, bool dst_in_psram)
|
||||
{
|
||||
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
|
||||
const uint32_t test_buffer_size[] = {256, 512, 1024, 2048, 4096, 5012};
|
||||
memcpy_testbench_context_t test_context = {
|
||||
.align = 4,
|
||||
.src_in_psram = src_in_psram,
|
||||
.dst_in_psram = dst_in_psram,
|
||||
};
|
||||
for (int i = 0; i < sizeof(test_buffer_size) / sizeof(test_buffer_size[0]); i++) {
|
||||
// Test different alignment
|
||||
for (int off = 0; off < 4; off++) {
|
||||
test_context.buffer_size = test_buffer_size[i];
|
||||
test_context.seed = i;
|
||||
test_context.src_offset = off;
|
||||
test_context.dst_offset = off + 1;
|
||||
async_memcpy_setup_testbench(&test_context);
|
||||
|
||||
TEST_ESP_OK(esp_async_memcpy(driver, test_context.to_addr, test_context.from_addr, test_context.copy_size, test_async_memcpy_cb_v1, sem));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(sem, pdMS_TO_TICKS(10)));
|
||||
async_memcpy_verify_and_clear_testbench(test_context.copy_size, test_context.src_buf, test_context.dst_buf,
|
||||
test_context.from_addr, test_context.to_addr);
|
||||
}
|
||||
}
|
||||
vSemaphoreDelete(sem);
|
||||
}
|
||||
|
||||
TEST_CASE("memory copy with dest address unaligned", "[async mcp]")
|
||||
{
|
||||
[[maybe_unused]] async_memcpy_config_t driver_config = {
|
||||
.backlog = 4,
|
||||
.dma_burst_size = 32,
|
||||
};
|
||||
[[maybe_unused]] async_memcpy_handle_t driver = NULL;
|
||||
|
||||
if (efuse_hal_flash_encryption_enabled()) {
|
||||
TEST_PASS_MESSAGE("Flash encryption is enabled, skip this test");
|
||||
}
|
||||
|
||||
#if SOC_CP_DMA_SUPPORTED
|
||||
printf("Testing memcpy by CP DMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_cpdma(&driver_config, &driver));
|
||||
test_memcpy_with_dest_addr_unaligned(driver, false, false);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_CP_DMA_SUPPORTED
|
||||
|
||||
#if SOC_HAS(AHB_GDMA) && !GDMA_LL_AHB_RX_BURST_NEEDS_ALIGNMENT && !CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION
|
||||
printf("Testing memcpy by AHB GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&driver_config, &driver));
|
||||
test_memcpy_with_dest_addr_unaligned(driver, false, false);
|
||||
#if GDMA_LL_GET(AHB_PSRAM_CAPABLE) && SOC_HAS(SPIRAM)
|
||||
test_memcpy_with_dest_addr_unaligned(driver, true, true);
|
||||
#endif // GDMA_LL_GET(AHB_PSRAM_CAPABLE) && SOC_HAS(SPIRAM)
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA) && !CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION
|
||||
printf("Testing memcpy by AXI GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_axi(&driver_config, &driver));
|
||||
test_memcpy_with_dest_addr_unaligned(driver, false, false);
|
||||
#if GDMA_LL_GET(AXI_PSRAM_CAPABLE) && SOC_HAS(SPIRAM)
|
||||
test_memcpy_with_dest_addr_unaligned(driver, true, true);
|
||||
#endif // GDMA_LL_GET(AXI_PSRAM_CAPABLE) && SOC_HAS(SPIRAM)
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
}
|
||||
|
||||
#define TEST_ASYNC_MEMCPY_BENCH_COUNTS 16
|
||||
|
||||
typedef struct {
|
||||
int perf_count;
|
||||
SemaphoreHandle_t sem;
|
||||
} mcp_perf_user_context_t;
|
||||
|
||||
static IRAM_ATTR bool test_async_memcpy_perf_cb(async_memcpy_handle_t mcp_hdl, async_memcpy_event_t *event, void *cb_args)
|
||||
{
|
||||
mcp_perf_user_context_t* user = (mcp_perf_user_context_t*)cb_args;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
user->perf_count++;
|
||||
if (user->perf_count == TEST_ASYNC_MEMCPY_BENCH_COUNTS) {
|
||||
xSemaphoreGiveFromISR(user->sem, &high_task_wakeup);
|
||||
}
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static void test_memcpy_performance(async_memcpy_handle_t driver, uint32_t buffer_size, bool src_in_psram, bool dst_in_psram)
|
||||
{
|
||||
int64_t elapse_us = 0;
|
||||
float throughput = 0.0;
|
||||
|
||||
memcpy_testbench_context_t test_context = {
|
||||
.align = 32, // set alignment same as the burst size, to achieve the best performance
|
||||
.buffer_size = buffer_size,
|
||||
.src_in_psram = src_in_psram,
|
||||
.dst_in_psram = dst_in_psram,
|
||||
};
|
||||
async_memcpy_setup_testbench(&test_context);
|
||||
|
||||
// get CPU memcpy performance
|
||||
ccomp_timer_start();
|
||||
for (int i = 0; i < TEST_ASYNC_MEMCPY_BENCH_COUNTS; i++) {
|
||||
memcpy(test_context.to_addr, test_context.from_addr, test_context.buffer_size);
|
||||
}
|
||||
elapse_us = ccomp_timer_stop();
|
||||
throughput = (float)test_context.buffer_size * 1e6 * TEST_ASYNC_MEMCPY_BENCH_COUNTS / 1024 / 1024 / elapse_us;
|
||||
IDF_LOG_PERFORMANCE("CPU_COPY", "%.2f MB/s, dir: %s->%s", throughput, src_in_psram ? "PSRAM" : "SRAM", dst_in_psram ? "PSRAM" : "SRAM");
|
||||
|
||||
// get DMA memcpy performance
|
||||
ccomp_timer_start();
|
||||
mcp_perf_user_context_t user_context = {
|
||||
.perf_count = 0,
|
||||
.sem = xSemaphoreCreateBinary()
|
||||
};
|
||||
for (int i = 0; i < TEST_ASYNC_MEMCPY_BENCH_COUNTS; i++) {
|
||||
TEST_ESP_OK(esp_async_memcpy(driver, test_context.to_addr, test_context.from_addr, test_context.copy_size, test_async_memcpy_perf_cb, &user_context));
|
||||
}
|
||||
// wait for done semaphore
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(user_context.sem, pdMS_TO_TICKS(1000)));
|
||||
elapse_us = ccomp_timer_stop();
|
||||
async_memcpy_verify_and_clear_testbench(test_context.copy_size, test_context.src_buf, test_context.dst_buf, test_context.from_addr, test_context.to_addr);
|
||||
throughput = (float)buffer_size * 1e6 * TEST_ASYNC_MEMCPY_BENCH_COUNTS / 1024 / 1024 / elapse_us;
|
||||
IDF_LOG_PERFORMANCE("DMA_COPY", "%.2f MB/s, dir: %s->%s", throughput, src_in_psram ? "PSRAM" : "SRAM", dst_in_psram ? "PSRAM" : "SRAM");
|
||||
|
||||
vSemaphoreDelete(user_context.sem);
|
||||
}
|
||||
|
||||
TEST_CASE("memory copy performance 40KB: SRAM->SRAM", "[async mcp]")
|
||||
{
|
||||
async_memcpy_config_t driver_config = {
|
||||
.backlog = TEST_ASYNC_MEMCPY_BENCH_COUNTS,
|
||||
.dma_burst_size = 32,
|
||||
};
|
||||
async_memcpy_handle_t driver = NULL;
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
printf("Testing memcpy by AHB GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&driver_config, &driver));
|
||||
test_memcpy_performance(driver, 40 * 1024, false, false);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
printf("Testing memcpy by AXI GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_axi(&driver_config, &driver));
|
||||
test_memcpy_performance(driver, 40 * 1024, false, false);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
|
||||
#if SOC_CP_DMA_SUPPORTED
|
||||
printf("Testing memcpy by CP DMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_cpdma(&driver_config, &driver));
|
||||
test_memcpy_performance(driver, 40 * 1024, false, false);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif // SOC_CP_DMA_SUPPORTED
|
||||
}
|
||||
|
||||
#if SOC_SPIRAM_SUPPORTED
|
||||
TEST_CASE("memory copy performance 40KB: PSRAM->PSRAM", "[async mcp]")
|
||||
{
|
||||
[[maybe_unused]] async_memcpy_config_t driver_config = {
|
||||
.backlog = TEST_ASYNC_MEMCPY_BENCH_COUNTS,
|
||||
.dma_burst_size = 32,
|
||||
};
|
||||
[[maybe_unused]] async_memcpy_handle_t driver = NULL;
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
#if GDMA_LL_GET(AHB_PSRAM_CAPABLE)
|
||||
printf("Testing memcpy by AHB GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&driver_config, &driver));
|
||||
test_memcpy_performance(driver, 40 * 1024, true, true);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
#if GDMA_LL_GET(AXI_PSRAM_CAPABLE)
|
||||
printf("Testing memcpy by AXI GDMA\r\n");
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_axi(&driver_config, &driver));
|
||||
test_memcpy_performance(driver, 40 * 1024, true, true);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
|
||||
#endif
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION
|
||||
typedef struct {
|
||||
SemaphoreHandle_t sem;
|
||||
int64_t elapse_us;
|
||||
} test_weighted_arb_context_t;
|
||||
static IRAM_ATTR bool test_weighted_arb_isr_cb(async_memcpy_handle_t mcp_hdl, async_memcpy_event_t *event, void *cb_args)
|
||||
{
|
||||
test_weighted_arb_context_t *ctx = (test_weighted_arb_context_t *)cb_args;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
ctx->elapse_us = ccomp_timer_get_time();
|
||||
xSemaphoreGiveFromISR(ctx->sem, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static void memcpy_weighted_arb_test(async_memcpy_handle_t driver[2], size_t burst_size, uint32_t buffer_size, bool buffer_in_psram)
|
||||
{
|
||||
SemaphoreHandle_t sem[2] = {xSemaphoreCreateBinary(), xSemaphoreCreateBinary()};
|
||||
int64_t elapse_us[2] = {0};
|
||||
float throughput[2] = {0.0};
|
||||
|
||||
memcpy_testbench_context_t test_context = {
|
||||
.align = burst_size,
|
||||
.buffer_size = buffer_size,
|
||||
.src_dst_same = !buffer_in_psram, // if buffer is in PSRAM, no memory size limitation
|
||||
.src_in_psram = buffer_in_psram,
|
||||
.dst_in_psram = buffer_in_psram,
|
||||
};
|
||||
async_memcpy_setup_testbench(&test_context);
|
||||
test_weighted_arb_context_t ctx[2] = {
|
||||
[0] = {
|
||||
.sem = sem[0],
|
||||
},
|
||||
[1] = {
|
||||
.sem = sem[1],
|
||||
}
|
||||
};
|
||||
|
||||
ccomp_timer_start();
|
||||
TEST_ESP_OK(esp_async_memcpy(driver[0], test_context.to_addr, test_context.from_addr, test_context.copy_size, test_weighted_arb_isr_cb, &ctx[0]));
|
||||
TEST_ESP_OK(esp_async_memcpy(driver[1], test_context.to_addr, test_context.from_addr, test_context.copy_size, test_weighted_arb_isr_cb, &ctx[1]));
|
||||
|
||||
// get channel_1 spent time
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(sem[1], pdMS_TO_TICKS(1000)));
|
||||
elapse_us[1] = ctx[1].elapse_us;
|
||||
|
||||
// wait for channel_0 done, keep channel_1 busy to do arbitration
|
||||
while (xSemaphoreTake(sem[0], 0) == pdFALSE) {
|
||||
TEST_ESP_OK(esp_async_memcpy(driver[1], test_context.to_addr, test_context.from_addr, test_context.copy_size, test_weighted_arb_isr_cb, &ctx[1]));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(sem[1], pdMS_TO_TICKS(1000)));
|
||||
}
|
||||
// get channel_0 spent time
|
||||
elapse_us[0] = ctx[0].elapse_us;
|
||||
|
||||
ccomp_timer_stop();
|
||||
throughput[0] = (float)test_context.buffer_size * 1e6 / 1024 / 1024 / elapse_us[0];
|
||||
IDF_LOG_PERFORMANCE("DMA0_COPY", "%.2f MB/s, size: %zu Bytes", throughput[0], test_context.buffer_size);
|
||||
|
||||
throughput[1] = (float)test_context.buffer_size * 1e6 / 1024 / 1024 / elapse_us[1];
|
||||
IDF_LOG_PERFORMANCE("DMA1_COPY", "%.2f MB/s, size: %zu Bytes", throughput[1], test_context.buffer_size);
|
||||
|
||||
// the bandwidth of channel_1 should be at least 10 times of channel_0
|
||||
TEST_ASSERT_EQUAL(throughput[1] / throughput[0] > 10, true);
|
||||
|
||||
async_memcpy_verify_and_clear_testbench(test_context.copy_size, test_context.src_buf, buffer_in_psram ? test_context.dst_buf : NULL,
|
||||
test_context.from_addr, test_context.to_addr);
|
||||
|
||||
vSemaphoreDelete(sem[0]);
|
||||
vSemaphoreDelete(sem[1]);
|
||||
}
|
||||
|
||||
TEST_CASE("GDMA M2M Weighted Arbitration Test SRAM->SRAM", "[GDMA][M2M][async mcp]")
|
||||
{
|
||||
async_memcpy_config_t driver_config = {
|
||||
.backlog = TEST_ASYNC_MEMCPY_BENCH_COUNTS,
|
||||
.dma_burst_size = 32,
|
||||
};
|
||||
|
||||
async_memcpy_handle_t driver[2] = {NULL};
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
driver_config.weight = 1;
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&driver_config, &driver[0]));
|
||||
driver_config.weight = 15;
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&driver_config, &driver[1]));
|
||||
memcpy_weighted_arb_test(driver, driver_config.dma_burst_size, 200 * 1024, false);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver[0]));
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver[1]));
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
}
|
||||
|
||||
#if SOC_SPIRAM_SUPPORTED
|
||||
TEST_CASE("GDMA M2M Weighted Arbitration Test PSRAM->PSRAM", "[GDMA][M2M][async mcp]")
|
||||
{
|
||||
[[maybe_unused]] async_memcpy_config_t driver_config = {
|
||||
.backlog = TEST_ASYNC_MEMCPY_BENCH_COUNTS,
|
||||
.dma_burst_size = 32, // PSRAM bandwidth may be not enough if burst size is 64
|
||||
};
|
||||
|
||||
[[maybe_unused]] async_memcpy_handle_t driver[2] = {NULL};
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
#if GDMA_LL_GET(AHB_PSRAM_CAPABLE)
|
||||
driver_config.weight = 1;
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&driver_config, &driver[0]));
|
||||
driver_config.weight = 15;
|
||||
TEST_ESP_OK(esp_async_memcpy_install_gdma_ahb(&driver_config, &driver[1]));
|
||||
memcpy_weighted_arb_test(driver, driver_config.dma_burst_size, 200 * 1024, true);
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver[0]));
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(driver[1]));
|
||||
#endif
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
}
|
||||
#endif // SOC_SPIRAM_SUPPORTED
|
||||
|
||||
#endif // CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION
|
||||
@@ -0,0 +1,612 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "esp_private/dw_gdma.h"
|
||||
#include "hal/dw_gdma_ll.h"
|
||||
#include "hal/efuse_hal.h"
|
||||
#include "esp_cache.h"
|
||||
#include "esp_private/esp_cache_private.h"
|
||||
|
||||
TEST_CASE("DW_GDMA channel allocation", "[DW_GDMA]")
|
||||
{
|
||||
printf("install DMA channels exhaustively\r\n");
|
||||
dw_gdma_channel_static_config_t static_config = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.num_outstanding_requests = 1,
|
||||
};
|
||||
dw_gdma_channel_alloc_config_t alloc_config = {
|
||||
.src = static_config,
|
||||
.dst = static_config,
|
||||
};
|
||||
dw_gdma_channel_handle_t chans[DW_GDMA_LL_GROUPS][DW_GDMA_LL_CHANNELS_PER_GROUP];
|
||||
for (int i = 0; i < DW_GDMA_LL_GROUPS; i++) {
|
||||
for (int j = 0; j < DW_GDMA_LL_CHANNELS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(dw_gdma_new_channel(&alloc_config, &chans[i][j]));
|
||||
}
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, dw_gdma_new_channel(&alloc_config, &chans[0][0]));
|
||||
|
||||
printf("delete DMA channels\r\n");
|
||||
for (int i = 0; i < DW_GDMA_LL_GROUPS; i++) {
|
||||
for (int j = 0; j < DW_GDMA_LL_CHANNELS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(dw_gdma_del_channel(chans[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool test_dw_gdma_conti_mode_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
SemaphoreHandle_t done_sem = (SemaphoreHandle_t)user_data;
|
||||
xSemaphoreGiveFromISR(done_sem, &task_woken);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("DW_GDMA M2M Test: Contiguous Mode", "[DW_GDMA]")
|
||||
{
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
|
||||
printf("prepare the source and destination buffers\r\n");
|
||||
size_t sram_alignment = 0;
|
||||
TEST_ESP_OK(esp_cache_get_alignment(0, &sram_alignment));
|
||||
size_t alignment = MAX(sram_alignment, 8);
|
||||
uint8_t *src_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
uint8_t *dst_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(src_buf);
|
||||
TEST_ASSERT_NOT_NULL(dst_buf);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
src_buf[i] = i;
|
||||
}
|
||||
if (sram_alignment) {
|
||||
// do write-back for the source data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync((void *)src_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
|
||||
printf("allocate a channel for memory copy\r\n");
|
||||
dw_gdma_channel_static_config_t static_config = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.num_outstanding_requests = 1,
|
||||
};
|
||||
dw_gdma_channel_alloc_config_t alloc_config = {
|
||||
.src = static_config,
|
||||
.dst = static_config,
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_SELF, // DMA as the flow controller
|
||||
.chan_priority = 1,
|
||||
};
|
||||
dw_gdma_channel_handle_t m2m_chan = NULL;
|
||||
TEST_ESP_OK(dw_gdma_new_channel(&alloc_config, &m2m_chan));
|
||||
|
||||
printf("register event handler\r\n");
|
||||
dw_gdma_event_callbacks_t cbs = {
|
||||
.on_full_trans_done = test_dw_gdma_conti_mode_trans_done_cb,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_register_event_callbacks(m2m_chan, &cbs, done_sem));
|
||||
|
||||
printf("set up memory copy transaction\r\n");
|
||||
dw_gdma_block_transfer_config_t transfer_config = {
|
||||
.src = {
|
||||
.addr = (uint32_t)src_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.dst = {
|
||||
.addr = (uint32_t)dst_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.size = 256,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_config_transfer(m2m_chan, &transfer_config));
|
||||
|
||||
printf("start the DMA engine\r\n");
|
||||
TEST_ESP_OK(dw_gdma_channel_enable_ctrl(m2m_chan, true));
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(100)));
|
||||
// DMA should stop after the first block transfer is done
|
||||
TEST_ASSERT_EQUAL(pdFALSE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(100)));
|
||||
|
||||
printf("check the memory copy result\r\n");
|
||||
if (sram_alignment) {
|
||||
// the destination data are not reflected to the cache, so do an invalidate to ask the cache load new data
|
||||
TEST_ESP_OK(esp_cache_msync((void *)dst_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(i, dst_buf[i]);
|
||||
}
|
||||
|
||||
TEST_ESP_OK(dw_gdma_del_channel(m2m_chan));
|
||||
free(src_buf);
|
||||
free(dst_buf);
|
||||
vSemaphoreDelete(done_sem);
|
||||
}
|
||||
|
||||
static bool test_dw_gdma_reload_mode_block_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
SemaphoreHandle_t done_sem = (SemaphoreHandle_t)user_data;
|
||||
xSemaphoreGiveFromISR(done_sem, &task_woken);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("DW_GDMA M2M Test: Reload Mode", "[DW_GDMA]")
|
||||
{
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
|
||||
printf("prepare the source and destination buffers\r\n");
|
||||
size_t sram_alignment = 0;
|
||||
TEST_ESP_OK(esp_cache_get_alignment(0, &sram_alignment));
|
||||
size_t alignment = MAX(sram_alignment, 8);
|
||||
uint8_t *src_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
uint8_t *dst_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(src_buf);
|
||||
TEST_ASSERT_NOT_NULL(dst_buf);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
src_buf[i] = i;
|
||||
}
|
||||
if (sram_alignment) {
|
||||
// do write-back for the source data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync((void *)src_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
|
||||
printf("allocate a channel for memory copy\r\n");
|
||||
dw_gdma_channel_static_config_t static_config = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_RELOAD,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.num_outstanding_requests = 1,
|
||||
};
|
||||
dw_gdma_channel_alloc_config_t alloc_config = {
|
||||
.src = static_config,
|
||||
.dst = static_config,
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_SELF, // DMA as the flow controller
|
||||
.chan_priority = 1,
|
||||
};
|
||||
dw_gdma_channel_handle_t m2m_chan = NULL;
|
||||
TEST_ESP_OK(dw_gdma_new_channel(&alloc_config, &m2m_chan));
|
||||
|
||||
printf("register event handler\r\n");
|
||||
dw_gdma_event_callbacks_t cbs = {
|
||||
.on_block_trans_done = test_dw_gdma_reload_mode_block_done_cb,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_register_event_callbacks(m2m_chan, &cbs, done_sem));
|
||||
|
||||
printf("set up memory copy transaction\r\n");
|
||||
dw_gdma_block_transfer_config_t transfer_config = {
|
||||
.src = {
|
||||
.addr = (uint32_t)src_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.dst = {
|
||||
.addr = (uint32_t)dst_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.size = 256,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_config_transfer(m2m_chan, &transfer_config));
|
||||
|
||||
dw_gdma_block_markers_t markers = {
|
||||
.en_trans_done_intr = true, // enable block trans done interrupt
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_set_block_markers(m2m_chan, markers));
|
||||
|
||||
printf("start the DMA engine\r\n");
|
||||
TEST_ESP_OK(dw_gdma_channel_enable_ctrl(m2m_chan, true));
|
||||
|
||||
// because of the auto-reload, we can keep receiving the block trans done event
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(100)));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(100)));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(100)));
|
||||
|
||||
printf("check the memory copy result\r\n");
|
||||
if (sram_alignment) {
|
||||
// the destination data are not reflected to the cache, so do an invalidate to ask the cache load new data
|
||||
TEST_ESP_OK(esp_cache_msync((void *)dst_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(i, dst_buf[i]);
|
||||
}
|
||||
|
||||
// stop the DMA channel
|
||||
TEST_ESP_OK(dw_gdma_channel_enable_ctrl(m2m_chan, false));
|
||||
TEST_ESP_OK(dw_gdma_del_channel(m2m_chan));
|
||||
free(src_buf);
|
||||
free(dst_buf);
|
||||
vSemaphoreDelete(done_sem);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SemaphoreHandle_t done_sem;
|
||||
uint8_t count;
|
||||
} test_gdma_shadow_mode_user_data_t;
|
||||
|
||||
static bool test_dw_gdma_shadow_mode_block_invalid_cb(dw_gdma_channel_handle_t chan, const dw_gdma_break_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
test_gdma_shadow_mode_user_data_t *udata = (test_gdma_shadow_mode_user_data_t *)user_data;
|
||||
udata->count++;
|
||||
|
||||
dw_gdma_block_markers_t markers = {
|
||||
.is_last = true, // mark the block as the last one
|
||||
.is_valid = true, // mark the block as valid so that the DMA can continue the transfer
|
||||
};
|
||||
dw_gdma_channel_set_block_markers(chan, markers);
|
||||
// after the block is marked as valid again, tell the DMA to continue the transfer
|
||||
dw_gdma_channel_continue(chan);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
static bool test_dw_gdma_shadow_mode_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
test_gdma_shadow_mode_user_data_t *udata = (test_gdma_shadow_mode_user_data_t *)user_data;
|
||||
SemaphoreHandle_t done_sem = udata->done_sem;
|
||||
xSemaphoreGiveFromISR(done_sem, &task_woken);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("DW_GDMA M2M Test: Shadow Mode", "[DW_GDMA]")
|
||||
{
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
|
||||
printf("prepare the source and destination buffers\r\n");
|
||||
size_t sram_alignment = 0;
|
||||
TEST_ESP_OK(esp_cache_get_alignment(0, &sram_alignment));
|
||||
size_t alignment = MAX(sram_alignment, 8);
|
||||
uint8_t *src_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
uint8_t *dst_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(src_buf);
|
||||
TEST_ASSERT_NOT_NULL(dst_buf);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
src_buf[i] = i;
|
||||
}
|
||||
if (sram_alignment) {
|
||||
// do write-back for the source data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync((void *)src_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
|
||||
printf("allocate a channel for memory copy\r\n");
|
||||
dw_gdma_channel_static_config_t static_config = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_SHADOW,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.num_outstanding_requests = 1,
|
||||
};
|
||||
dw_gdma_channel_alloc_config_t alloc_config = {
|
||||
.src = static_config,
|
||||
.dst = static_config,
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_SELF, // DMA as the flow controller
|
||||
.chan_priority = 1,
|
||||
};
|
||||
dw_gdma_channel_handle_t m2m_chan = NULL;
|
||||
TEST_ESP_OK(dw_gdma_new_channel(&alloc_config, &m2m_chan));
|
||||
|
||||
printf("set up memory copy transaction\r\n");
|
||||
dw_gdma_block_transfer_config_t transfer_config = {
|
||||
.src = {
|
||||
.addr = (uint32_t)src_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.dst = {
|
||||
.addr = (uint32_t)dst_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.size = 256,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_config_transfer(m2m_chan, &transfer_config));
|
||||
dw_gdma_block_markers_t markers = {
|
||||
.is_valid = true, // mark the block as valid so that the DMA can start the transfer
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_set_block_markers(m2m_chan, markers));
|
||||
|
||||
printf("register event handler\r\n");
|
||||
dw_gdma_event_callbacks_t cbs = {
|
||||
.on_invalid_block = test_dw_gdma_shadow_mode_block_invalid_cb,
|
||||
.on_full_trans_done = test_dw_gdma_shadow_mode_trans_done_cb,
|
||||
};
|
||||
test_gdma_shadow_mode_user_data_t user_data = {
|
||||
.done_sem = done_sem,
|
||||
.count = 0,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_register_event_callbacks(m2m_chan, &cbs, &user_data));
|
||||
|
||||
printf("start the DMA engine\r\n");
|
||||
TEST_ESP_OK(dw_gdma_channel_enable_ctrl(m2m_chan, true));
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(1000)));
|
||||
// should only go into the block invalid callback for once
|
||||
TEST_ASSERT_EQUAL_UINT8(1, user_data.count);
|
||||
|
||||
printf("check the memory copy result\r\n");
|
||||
if (sram_alignment) {
|
||||
// the destination data are not reflected to the cache, so do an invalidate to ask the cache load new data
|
||||
TEST_ESP_OK(esp_cache_msync((void *)dst_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(i, dst_buf[i]);
|
||||
}
|
||||
|
||||
TEST_ESP_OK(dw_gdma_del_channel(m2m_chan));
|
||||
free(src_buf);
|
||||
free(dst_buf);
|
||||
vSemaphoreDelete(done_sem);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SemaphoreHandle_t done_sem;
|
||||
void *dst_buffer_addr;
|
||||
size_t dst_buffer_size;
|
||||
uint8_t count;
|
||||
} test_gdma_list_mode_user_data_t;
|
||||
|
||||
static bool test_dw_gdma_list_mode_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
test_gdma_list_mode_user_data_t *udata = (test_gdma_list_mode_user_data_t *)user_data;
|
||||
SemaphoreHandle_t done_sem = udata->done_sem;
|
||||
xSemaphoreGiveFromISR(done_sem, &task_woken);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
static bool test_dw_gdma_list_mode_invalid_block_cb(dw_gdma_channel_handle_t chan, const dw_gdma_break_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
test_gdma_list_mode_user_data_t *udata = (test_gdma_list_mode_user_data_t *)user_data;
|
||||
dw_gdma_lli_handle_t lli = event_data->invalid_lli;
|
||||
udata->count++;
|
||||
// clear the destination buffer
|
||||
memset(udata->dst_buffer_addr, 0, udata->dst_buffer_size);
|
||||
esp_cache_msync(udata->dst_buffer_addr, udata->dst_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
dw_gdma_block_markers_t markers = {
|
||||
.is_last = true, // mark the next block as the last one
|
||||
.is_valid = true, // mark the block as valid so that the DMA can continue the transfer
|
||||
};
|
||||
dw_gdma_lli_set_block_markers(lli, markers);
|
||||
// after the item is marked as valid again, tell the DMA to continue the transfer
|
||||
dw_gdma_channel_continue(chan);
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("DW_GDMA M2M Test: Link-List Mode", "[DW_GDMA]")
|
||||
{
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
|
||||
printf("prepare the source and destination buffers\r\n");
|
||||
size_t sram_alignment = 0;
|
||||
TEST_ESP_OK(esp_cache_get_alignment(0, &sram_alignment));
|
||||
size_t alignment = MAX(sram_alignment, 8);
|
||||
uint8_t *src_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
uint8_t *dst_buf = heap_caps_aligned_calloc(alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(src_buf);
|
||||
TEST_ASSERT_NOT_NULL(dst_buf);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
src_buf[i] = i;
|
||||
}
|
||||
if (sram_alignment) {
|
||||
// do write-back for the source data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync((void *)src_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
|
||||
printf("allocate a channel for memory copy\r\n");
|
||||
dw_gdma_channel_static_config_t static_config = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_LIST,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.num_outstanding_requests = 1,
|
||||
};
|
||||
dw_gdma_channel_alloc_config_t alloc_config = {
|
||||
.src = static_config,
|
||||
.dst = static_config,
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_SELF, // DMA as the flow controller
|
||||
.chan_priority = 1,
|
||||
};
|
||||
dw_gdma_channel_handle_t m2m_chan = NULL;
|
||||
TEST_ESP_OK(dw_gdma_new_channel(&alloc_config, &m2m_chan));
|
||||
|
||||
printf("create singly DMA link list\r\n");
|
||||
dw_gdma_link_list_config_t link_list_config = {
|
||||
.num_items = 2,
|
||||
.link_type = DW_GDMA_LINKED_LIST_TYPE_SINGLY,
|
||||
};
|
||||
dw_gdma_link_list_handle_t link_list = NULL;
|
||||
TEST_ESP_OK(dw_gdma_new_link_list(&link_list_config, &link_list));
|
||||
|
||||
printf("set up memory copy transaction\r\n");
|
||||
dw_gdma_block_transfer_config_t transfer_config = {
|
||||
.src = {
|
||||
.addr = (uint32_t)src_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.dst = {
|
||||
.addr = (uint32_t)dst_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 0,
|
||||
},
|
||||
.size = 128,
|
||||
};
|
||||
dw_gdma_block_markers_t markers = {
|
||||
.is_valid = true, // mark the block as valid so that the DMA can start the transfer
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_lli_config_transfer(dw_gdma_link_list_get_item(link_list, 0), &transfer_config));
|
||||
TEST_ESP_OK(dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers));
|
||||
transfer_config.src.addr = (uint32_t)(src_buf + 128);
|
||||
transfer_config.dst.addr = (uint32_t)(dst_buf + 128);
|
||||
markers.is_last = true;
|
||||
TEST_ESP_OK(dw_gdma_lli_config_transfer(dw_gdma_link_list_get_item(link_list, 1), &transfer_config));
|
||||
TEST_ESP_OK(dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 1), markers));
|
||||
|
||||
printf("register event handler\r\n");
|
||||
dw_gdma_event_callbacks_t cbs = {
|
||||
.on_full_trans_done = test_dw_gdma_list_mode_trans_done_cb,
|
||||
.on_invalid_block = test_dw_gdma_list_mode_invalid_block_cb,
|
||||
};
|
||||
test_gdma_list_mode_user_data_t user_data = {
|
||||
.done_sem = done_sem,
|
||||
.count = 0,
|
||||
.dst_buffer_addr = dst_buf,
|
||||
.dst_buffer_size = 256,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_register_event_callbacks(m2m_chan, &cbs, &user_data));
|
||||
|
||||
printf("use the link list\r\n");
|
||||
TEST_ESP_OK(dw_gdma_channel_use_link_list(m2m_chan, link_list));
|
||||
printf("start the DMA engine\r\n");
|
||||
TEST_ESP_OK(dw_gdma_channel_enable_ctrl(m2m_chan, true));
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(1000)));
|
||||
|
||||
printf("check the memory copy result\r\n");
|
||||
if (sram_alignment) {
|
||||
// the destination data are not reflected to the cache, so do an invalidate to ask the cache load new data
|
||||
TEST_ESP_OK(esp_cache_msync((void *)dst_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(i, dst_buf[i]);
|
||||
}
|
||||
|
||||
// delete the singly link list, and create a circular link list instead
|
||||
TEST_ESP_OK(dw_gdma_del_link_list(link_list));
|
||||
printf("create circular DMA link list\r\n");
|
||||
link_list_config.link_type = DW_GDMA_LINKED_LIST_TYPE_CIRCULAR;
|
||||
link_list_config.num_items = 1;
|
||||
TEST_ESP_OK(dw_gdma_new_link_list(&link_list_config, &link_list));
|
||||
// set the transfer parameters for the link list item
|
||||
transfer_config.size = 256;
|
||||
transfer_config.src.addr = (uint32_t)src_buf;
|
||||
transfer_config.dst.addr = (uint32_t)dst_buf;
|
||||
TEST_ESP_OK(dw_gdma_lli_config_transfer(dw_gdma_link_list_get_item(link_list, 0), &transfer_config));
|
||||
markers.is_valid = true;
|
||||
markers.is_last = false;
|
||||
TEST_ESP_OK(dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers));
|
||||
|
||||
printf("use the link list\r\n");
|
||||
TEST_ESP_OK(dw_gdma_channel_use_link_list(m2m_chan, link_list));
|
||||
TEST_ESP_OK(dw_gdma_channel_enable_ctrl(m2m_chan, true));
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(done_sem, pdMS_TO_TICKS(1000)));
|
||||
// should only go into the block invalid callback for once
|
||||
TEST_ASSERT_EQUAL_UINT8(1, user_data.count);
|
||||
|
||||
printf("check the memory copy result\r\n");
|
||||
if (sram_alignment) {
|
||||
// the destination data are not reflected to the cache, so do an invalidate to ask the cache load new data
|
||||
TEST_ESP_OK(esp_cache_msync((void *)dst_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(i, dst_buf[i]);
|
||||
}
|
||||
|
||||
TEST_ESP_OK(dw_gdma_del_link_list(link_list));
|
||||
TEST_ESP_OK(dw_gdma_del_channel(m2m_chan));
|
||||
free(src_buf);
|
||||
free(dst_buf);
|
||||
vSemaphoreDelete(done_sem);
|
||||
}
|
||||
|
||||
TEST_CASE("DW_GDMA M2M Test: memory set with fixed address", "[DW_GDMA]")
|
||||
{
|
||||
printf("prepare the source and destination buffers\r\n");
|
||||
// memset: source in psram and destination in sram
|
||||
size_t ext_mem_alignment = 0;
|
||||
size_t int_mem_alignment = 0;
|
||||
TEST_ESP_OK(esp_cache_get_alignment(MALLOC_CAP_SPIRAM, &ext_mem_alignment));
|
||||
TEST_ESP_OK(esp_cache_get_alignment(0, &int_mem_alignment));
|
||||
if (efuse_hal_flash_encryption_enabled()) {
|
||||
TEST_PASS_MESSAGE("Flash encryption is enabled, skip this test");
|
||||
}
|
||||
uint8_t *src_buf = heap_caps_aligned_calloc(ext_mem_alignment, 1, 256, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
uint8_t *dst_buf = heap_caps_aligned_calloc(int_mem_alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(src_buf);
|
||||
TEST_ASSERT_NOT_NULL(dst_buf);
|
||||
// prepare the source buffer, only the first byte has a non-zero value
|
||||
for (int i = 0; i < 256; i++) {
|
||||
src_buf[i] = 0;
|
||||
}
|
||||
src_buf[0] = 66;
|
||||
if (ext_mem_alignment) {
|
||||
// do write-back for the source data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync((void *)src_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
|
||||
printf("allocate a channel for memory set\r\n");
|
||||
dw_gdma_channel_static_config_t static_config = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.num_outstanding_requests = 1,
|
||||
};
|
||||
dw_gdma_channel_alloc_config_t alloc_config = {
|
||||
.src = static_config,
|
||||
.dst = static_config,
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_SELF, // DMA as the flow controller
|
||||
.chan_priority = 1,
|
||||
};
|
||||
dw_gdma_channel_handle_t m2m_chan = NULL;
|
||||
TEST_ESP_OK(dw_gdma_new_channel(&alloc_config, &m2m_chan));
|
||||
|
||||
printf("set up memory set transaction\r\n");
|
||||
dw_gdma_block_transfer_config_t transfer_config = {
|
||||
.src = {
|
||||
.addr = (uint32_t)src_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_FIXED,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 1, // Note for ESP32P4, if the buffer is in PSRAM and the burst mode is fixed, we can't set the burst length larger than 1
|
||||
},
|
||||
.dst = {
|
||||
.addr = (uint32_t)dst_buf,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.width = DW_GDMA_TRANS_WIDTH_8,
|
||||
.burst_items = 4,
|
||||
.burst_len = 1,
|
||||
},
|
||||
.size = 256,
|
||||
};
|
||||
TEST_ESP_OK(dw_gdma_channel_config_transfer(m2m_chan, &transfer_config));
|
||||
|
||||
printf("start the DMA engine\r\n");
|
||||
TEST_ESP_OK(dw_gdma_channel_enable_ctrl(m2m_chan, true));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
printf("check the memory set result\r\n");
|
||||
if (int_mem_alignment) {
|
||||
// the destination data are not reflected to the cache, so do an invalidate to ask the cache load new data
|
||||
TEST_ESP_OK(esp_cache_msync((void *)dst_buf, 256, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(66, dst_buf[i]);
|
||||
}
|
||||
|
||||
TEST_ESP_OK(dw_gdma_del_channel(m2m_chan));
|
||||
free(src_buf);
|
||||
free(dst_buf);
|
||||
}
|
||||
@@ -0,0 +1,681 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/param.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "unity.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_private/gdma.h"
|
||||
#include "esp_private/gdma_link.h"
|
||||
#include "esp_private/esp_dma_utils.h"
|
||||
#include "hal/dma_types.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/gdma_ll.h"
|
||||
#include "hal/cache_ll.h"
|
||||
#include "hal/cache_hal.h"
|
||||
#include "hal/efuse_hal.h"
|
||||
#include "esp_cache.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "gdma_test_utils.h"
|
||||
|
||||
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||
#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1))
|
||||
|
||||
TEST_CASE("GDMA channel allocation", "[GDMA]")
|
||||
{
|
||||
gdma_channel_alloc_config_t channel_config = {};
|
||||
gdma_channel_handle_t tx_channels[GDMA_LL_GET(PAIRS_PER_INST)] = {};
|
||||
gdma_channel_handle_t rx_channels[GDMA_LL_GET(PAIRS_PER_INST)] = {};
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
// install TX channels
|
||||
for (int i = 0; i < GDMA_LL_AHB_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&channel_config, &tx_channels[i], NULL));
|
||||
};
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gdma_new_ahb_channel(&channel_config, &tx_channels[0], NULL));
|
||||
|
||||
// Free interrupts before installing RX interrupts to ensure enough free interrupts
|
||||
for (int i = 0; i < GDMA_LL_AHB_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_del_channel(tx_channels[i]));
|
||||
}
|
||||
|
||||
// install RX channels
|
||||
for (int i = 0; i < GDMA_LL_AHB_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&channel_config, NULL, &rx_channels[i]));
|
||||
}
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gdma_new_ahb_channel(&channel_config, NULL, &rx_channels[0]));
|
||||
|
||||
for (int i = 0; i < GDMA_LL_AHB_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_del_channel(rx_channels[i]));
|
||||
}
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
// install single and paired TX/RX channels
|
||||
#if GDMA_LL_AHB_PAIRS_PER_GROUP >= 2
|
||||
// single tx channel
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&channel_config, &tx_channels[0], NULL));
|
||||
|
||||
// create tx and rx channel pair
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&channel_config, &tx_channels[1], &rx_channels[1]));
|
||||
// create single rx channel
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&channel_config, NULL, &rx_channels[0]));
|
||||
|
||||
gdma_trigger_t fake_ahb_trigger1 = {
|
||||
.bus_id = SOC_GDMA_BUS_AHB,
|
||||
.instance_id = 0,
|
||||
};
|
||||
gdma_trigger_t fake_ahb_trigger2 = {
|
||||
.bus_id = SOC_GDMA_BUS_AHB,
|
||||
.instance_id = 1,
|
||||
};
|
||||
TEST_ESP_OK(gdma_connect(tx_channels[0], fake_ahb_trigger1));
|
||||
// can't connect multiple channels to the same peripheral
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, gdma_connect(tx_channels[1], fake_ahb_trigger1));
|
||||
TEST_ESP_OK(gdma_connect(tx_channels[1], fake_ahb_trigger2));
|
||||
|
||||
// but rx and tx can connect to the same peripheral
|
||||
TEST_ESP_OK(gdma_connect(rx_channels[0], fake_ahb_trigger1));
|
||||
TEST_ESP_OK(gdma_connect(rx_channels[1], fake_ahb_trigger2));
|
||||
for (int i = 0; i < 2; i++) {
|
||||
TEST_ESP_OK(gdma_disconnect(tx_channels[i]));
|
||||
TEST_ESP_OK(gdma_disconnect(rx_channels[i]));
|
||||
TEST_ESP_OK(gdma_del_channel(tx_channels[i]));
|
||||
TEST_ESP_OK(gdma_del_channel(rx_channels[i]));
|
||||
}
|
||||
#endif // GDMA_LL_AHB_PAIRS_PER_GROUP >= 2
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
// install TX channels
|
||||
for (int i = 0; i < GDMA_LL_AXI_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&channel_config, &tx_channels[i], NULL));
|
||||
};
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gdma_new_axi_channel(&channel_config, &tx_channels[0], NULL));
|
||||
|
||||
// Free interrupts before installing RX interrupts to ensure enough free interrupts
|
||||
for (int i = 0; i < GDMA_LL_AXI_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_del_channel(tx_channels[i]));
|
||||
}
|
||||
|
||||
// install RX channels
|
||||
for (int i = 0; i < GDMA_LL_AXI_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&channel_config, NULL, &rx_channels[i]));
|
||||
}
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gdma_new_axi_channel(&channel_config, NULL, &rx_channels[0]));
|
||||
|
||||
for (int i = 0; i < GDMA_LL_AXI_PAIRS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(gdma_del_channel(rx_channels[i]));
|
||||
}
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
|
||||
// install single and paired TX/RX channels
|
||||
#if GDMA_LL_AXI_PAIRS_PER_GROUP >= 2
|
||||
// single tx channel
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&channel_config, &tx_channels[0], NULL));
|
||||
|
||||
// create tx and rx channel pair
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&channel_config, &tx_channels[1], &rx_channels[1]));
|
||||
// create single rx channel
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&channel_config, NULL, &rx_channels[0]));
|
||||
|
||||
gdma_trigger_t fake_axi_trigger1 = {
|
||||
.bus_id = SOC_GDMA_BUS_AXI,
|
||||
.instance_id = 0,
|
||||
};
|
||||
gdma_trigger_t fake_axi_trigger2 = {
|
||||
.bus_id = SOC_GDMA_BUS_AXI,
|
||||
.instance_id = 1,
|
||||
};
|
||||
TEST_ESP_OK(gdma_connect(tx_channels[0], fake_axi_trigger1));
|
||||
// can't connect multiple channels to the same peripheral
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, gdma_connect(tx_channels[1], fake_axi_trigger1));
|
||||
TEST_ESP_OK(gdma_connect(tx_channels[1], fake_axi_trigger2));
|
||||
|
||||
// but rx and tx can connect to the same peripheral
|
||||
TEST_ESP_OK(gdma_connect(rx_channels[0], fake_axi_trigger1));
|
||||
TEST_ESP_OK(gdma_connect(rx_channels[1], fake_axi_trigger2));
|
||||
for (int i = 0; i < 2; i++) {
|
||||
TEST_ESP_OK(gdma_disconnect(tx_channels[i]));
|
||||
TEST_ESP_OK(gdma_disconnect(rx_channels[i]));
|
||||
TEST_ESP_OK(gdma_del_channel(tx_channels[i]));
|
||||
TEST_ESP_OK(gdma_del_channel(rx_channels[i]));
|
||||
}
|
||||
#endif // GDMA_LL_AXI_PAIRS_PER_GROUP >= 2
|
||||
}
|
||||
|
||||
static void test_gdma_config_link_list(gdma_channel_handle_t tx_chan, gdma_channel_handle_t rx_chan,
|
||||
gdma_link_list_handle_t *tx_link_list, gdma_link_list_handle_t *rx_link_list,
|
||||
size_t burst_size, bool dma_link_in_ext_mem)
|
||||
{
|
||||
|
||||
gdma_strategy_config_t strategy = {
|
||||
.auto_update_desc = true,
|
||||
.owner_check = true,
|
||||
};
|
||||
TEST_ESP_OK(gdma_apply_strategy(tx_chan, &strategy));
|
||||
TEST_ESP_OK(gdma_apply_strategy(rx_chan, &strategy));
|
||||
|
||||
gdma_transfer_config_t transfer_cfg = {
|
||||
.max_data_burst_size = burst_size,
|
||||
#if SOC_DMA_CAN_ACCESS_FLASH
|
||||
.access_ext_mem = true,
|
||||
#endif
|
||||
};
|
||||
TEST_ESP_OK(gdma_config_transfer(tx_chan, &transfer_cfg));
|
||||
TEST_ESP_OK(gdma_config_transfer(rx_chan, &transfer_cfg));
|
||||
|
||||
gdma_trigger_t m2m_trigger = GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0);
|
||||
// get a free DMA trigger ID for memory copy
|
||||
uint32_t free_m2m_id_mask = 0;
|
||||
gdma_get_free_m2m_trig_id_mask(tx_chan, &free_m2m_id_mask);
|
||||
m2m_trigger.instance_id = __builtin_ctz(free_m2m_id_mask);
|
||||
TEST_ESP_OK(gdma_connect(tx_chan, m2m_trigger));
|
||||
TEST_ESP_OK(gdma_connect(rx_chan, m2m_trigger));
|
||||
|
||||
// create DMA link list for TX channel (a singly link with 3 nodes)
|
||||
gdma_link_list_config_t tx_link_list_config = {
|
||||
.item_alignment = 8, // 8-byte alignment required by the AXI-GDMA
|
||||
.num_items = 3,
|
||||
.flags = {
|
||||
.items_in_ext_mem = dma_link_in_ext_mem,
|
||||
.check_owner = true,
|
||||
}
|
||||
};
|
||||
TEST_ESP_OK(gdma_new_link_list(&tx_link_list_config, tx_link_list));
|
||||
// create DMA link list for RX channel
|
||||
gdma_link_list_config_t rx_link_list_config = {
|
||||
.item_alignment = 8, // 8-byte alignment required by the AXI-GDMA
|
||||
.num_items = 5,
|
||||
.flags = {
|
||||
.items_in_ext_mem = dma_link_in_ext_mem,
|
||||
.check_owner = true,
|
||||
},
|
||||
};
|
||||
TEST_ESP_OK(gdma_new_link_list(&rx_link_list_config, rx_link_list));
|
||||
}
|
||||
|
||||
static bool test_gdma_m2m_rx_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
SemaphoreHandle_t done_sem = (SemaphoreHandle_t)user_data;
|
||||
xSemaphoreGiveFromISR(done_sem, &task_woken);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
static void test_gdma_m2m_transaction(gdma_channel_handle_t tx_chan, gdma_channel_handle_t rx_chan, bool dma_link_in_ext_mem, bool trig_retention_backup)
|
||||
{
|
||||
size_t sram_alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
|
||||
gdma_rx_event_callbacks_t rx_cbs = {
|
||||
.on_recv_eof = test_gdma_m2m_rx_eof_callback,
|
||||
};
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
TEST_ESP_OK(gdma_register_rx_event_callbacks(rx_chan, &rx_cbs, done_sem));
|
||||
|
||||
if (efuse_hal_flash_encryption_enabled()) {
|
||||
dma_link_in_ext_mem = false;
|
||||
}
|
||||
|
||||
gdma_link_list_handle_t tx_link_list = NULL;
|
||||
gdma_link_list_handle_t rx_link_list = NULL;
|
||||
test_gdma_config_link_list(tx_chan, rx_chan, &tx_link_list, &rx_link_list, 16, dma_link_in_ext_mem);
|
||||
|
||||
size_t int_mem_alignment = 0;
|
||||
size_t ext_mem_alignment = 0;
|
||||
TEST_ESP_OK(gdma_get_alignment_constraints(tx_chan, &int_mem_alignment, &ext_mem_alignment));
|
||||
|
||||
// allocate the source buffer from SRAM
|
||||
uint8_t *src_data = heap_caps_aligned_calloc(int_mem_alignment, 1, 128, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(src_data);
|
||||
|
||||
// allocate the destination buffer from SRAM
|
||||
uint8_t *dst_data = heap_caps_aligned_calloc(int_mem_alignment, 1, 256, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(dst_data);
|
||||
|
||||
// prepare the source data
|
||||
for (int i = 0; i < 128; i++) {
|
||||
src_data[i] = i;
|
||||
}
|
||||
if (sram_alignment) {
|
||||
// do write-back for the source data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync(src_data, 128, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
// test DMA can read data from main flash
|
||||
#if SOC_DMA_CAN_ACCESS_FLASH
|
||||
static const char src_string[] __attribute__((aligned(GDMA_LL_GET(ACCESS_ENCRYPTION_MEM_ALIGNMENT)))) = "GDMA can read MSPI Flash data!!!";
|
||||
size_t src_string_len = strlen(src_string);
|
||||
TEST_ASSERT_TRUE(esp_ptr_in_drom(src_string));
|
||||
|
||||
// Only gonna copy length = src_string_len, set the character after to be 0xFF as a canary
|
||||
dst_data[128 + src_string_len] = 0xFF;
|
||||
if (sram_alignment) {
|
||||
// do write-back for the dst data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync(dst_data, 256, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
#endif
|
||||
|
||||
gdma_buffer_mount_config_t tx_buf_mount_config[] = {
|
||||
[0] = {
|
||||
.buffer = src_data,
|
||||
.buffer_alignment = int_mem_alignment,
|
||||
.length = 64,
|
||||
},
|
||||
[1] = {
|
||||
.buffer = src_data + 64,
|
||||
.buffer_alignment = int_mem_alignment,
|
||||
.length = 64,
|
||||
#if !SOC_DMA_CAN_ACCESS_FLASH
|
||||
.flags = {
|
||||
.mark_eof = true,
|
||||
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
|
||||
}
|
||||
#endif
|
||||
},
|
||||
#if SOC_DMA_CAN_ACCESS_FLASH
|
||||
[2] = {
|
||||
.buffer = (void *)src_string,
|
||||
.buffer_alignment = ext_mem_alignment,
|
||||
.length = src_string_len,
|
||||
.flags = {
|
||||
.mark_eof = true,
|
||||
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
|
||||
}
|
||||
},
|
||||
#endif
|
||||
};
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(tx_link_list, 0, tx_buf_mount_config, sizeof(tx_buf_mount_config) / sizeof(gdma_buffer_mount_config_t), NULL));
|
||||
|
||||
gdma_buffer_mount_config_t rx_buf_mount_config = {
|
||||
.buffer = dst_data,
|
||||
.buffer_alignment = sram_alignment, // RX buffer should be aligned to the cache line size, because we will do cache invalidate later
|
||||
.length = 256,
|
||||
};
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(rx_link_list, 0, &rx_buf_mount_config, 1, NULL));
|
||||
|
||||
if (trig_retention_backup) {
|
||||
test_gdma_trigger_retention_backup(tx_chan, rx_chan);
|
||||
}
|
||||
|
||||
TEST_ESP_OK(gdma_start(rx_chan, gdma_link_get_head_addr(rx_link_list)));
|
||||
TEST_ESP_OK(gdma_start(tx_chan, gdma_link_get_head_addr(tx_link_list)));
|
||||
|
||||
xSemaphoreTake(done_sem, 1000 / portTICK_PERIOD_MS);
|
||||
|
||||
if (sram_alignment) {
|
||||
// the destination data are not reflected to the cache, so do an invalidate to ask the cache load new data
|
||||
TEST_ESP_OK(esp_cache_msync((void *)dst_data, 256, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
|
||||
}
|
||||
|
||||
// The owner bit should been written back by the DMA
|
||||
gdma_lli_owner_t owner = GDMA_LLI_OWNER_DMA;
|
||||
TEST_ESP_OK(gdma_link_get_owner(tx_link_list, 0, &owner));
|
||||
TEST_ASSERT_EQUAL(GDMA_LLI_OWNER_CPU, owner);
|
||||
TEST_ESP_OK(gdma_link_get_owner(rx_link_list, 0, &owner));
|
||||
TEST_ASSERT_EQUAL(GDMA_LLI_OWNER_CPU, owner);
|
||||
|
||||
// validate the destination data
|
||||
for (int i = 0; i < 128; i++) {
|
||||
TEST_ASSERT_EQUAL(i, dst_data[i]);
|
||||
}
|
||||
#if SOC_DMA_CAN_ACCESS_FLASH
|
||||
TEST_ASSERT_TRUE(dst_data[128 + src_string_len] == 0xFF);
|
||||
dst_data[128 + src_string_len] = '\0';
|
||||
TEST_ASSERT_TRUE(strcmp(src_string, (const char *)((uint32_t)dst_data + 128)) == 0);
|
||||
#endif
|
||||
free(src_data);
|
||||
free(dst_data);
|
||||
TEST_ESP_OK(gdma_del_link_list(tx_link_list));
|
||||
TEST_ESP_OK(gdma_del_link_list(rx_link_list));
|
||||
vSemaphoreDelete(done_sem);
|
||||
}
|
||||
|
||||
static void test_gdma_m2m_mode(bool trig_retention_backup)
|
||||
{
|
||||
gdma_channel_handle_t tx_chan = NULL;
|
||||
gdma_channel_handle_t rx_chan = NULL;
|
||||
gdma_channel_alloc_config_t chan_alloc_config = {};
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&chan_alloc_config, &tx_chan, &rx_chan));
|
||||
|
||||
test_gdma_m2m_transaction(tx_chan, rx_chan, false, trig_retention_backup);
|
||||
|
||||
TEST_ESP_OK(gdma_del_channel(tx_chan));
|
||||
TEST_ESP_OK(gdma_del_channel(rx_chan));
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&chan_alloc_config, &tx_chan, &rx_chan));
|
||||
|
||||
bool lli_in_ext_mem = false;
|
||||
#if SOC_SPIRAM_SUPPORTED
|
||||
// the AXI GDMA allows to put the DMA link list in the external memory
|
||||
lli_in_ext_mem = true;
|
||||
#endif
|
||||
test_gdma_m2m_transaction(tx_chan, rx_chan, lli_in_ext_mem, trig_retention_backup);
|
||||
|
||||
TEST_ESP_OK(gdma_del_channel(tx_chan));
|
||||
TEST_ESP_OK(gdma_del_channel(rx_chan));
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
}
|
||||
|
||||
TEST_CASE("GDMA M2M Mode", "[GDMA][M2M]")
|
||||
{
|
||||
test_gdma_m2m_mode(false);
|
||||
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP && SOC_GDMA_SUPPORT_SLEEP_RETENTION
|
||||
// test again with retention
|
||||
test_gdma_m2m_mode(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SemaphoreHandle_t done_sem;
|
||||
dma_buffer_split_array_t *align_array;
|
||||
} test_gdma_context_t;
|
||||
|
||||
static bool test_gdma_m2m_unaligned_rx_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
test_gdma_context_t *user_ctx = (test_gdma_context_t*)user_data;
|
||||
TEST_ESP_OK(esp_dma_merge_aligned_rx_buffers(user_ctx->align_array));
|
||||
xSemaphoreGiveFromISR(user_ctx->done_sem, &task_woken);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
static void test_gdma_m2m_unaligned_buffer_test(uint8_t *dst_data, uint8_t *src_data, size_t data_length, size_t offset_len)
|
||||
{
|
||||
TEST_ASSERT_NOT_NULL(src_data);
|
||||
TEST_ASSERT_NOT_NULL(dst_data);
|
||||
memset(src_data, 0, data_length + offset_len);
|
||||
memset(dst_data, 0, data_length + offset_len);
|
||||
gdma_channel_handle_t tx_chan = NULL;
|
||||
gdma_channel_handle_t rx_chan = NULL;
|
||||
gdma_channel_alloc_config_t chan_alloc_config = {};
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&chan_alloc_config, &tx_chan, &rx_chan));
|
||||
size_t sram_alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
|
||||
|
||||
gdma_link_list_handle_t tx_link_list = NULL;
|
||||
gdma_link_list_handle_t rx_link_list = NULL;
|
||||
test_gdma_config_link_list(tx_chan, rx_chan, &tx_link_list, &rx_link_list, 0, false);
|
||||
|
||||
size_t rx_mem_alignment = 0;
|
||||
TEST_ESP_OK(gdma_get_alignment_constraints(rx_chan, &rx_mem_alignment, NULL));
|
||||
|
||||
// prepare the source data
|
||||
for (int i = 0; i < data_length; i++) {
|
||||
src_data[i] = i;
|
||||
}
|
||||
if (sram_alignment) {
|
||||
// do write-back for the source data because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync(src_data, ALIGN_UP(data_length, sram_alignment), ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
}
|
||||
|
||||
gdma_buffer_mount_config_t tx_buf_mount_config[] = {
|
||||
[0] = {
|
||||
.buffer = src_data,
|
||||
.buffer_alignment = 1,
|
||||
.length = data_length,
|
||||
.flags = {
|
||||
.mark_eof = true,
|
||||
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
|
||||
}
|
||||
}
|
||||
};
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(tx_link_list, 0, tx_buf_mount_config, sizeof(tx_buf_mount_config) / sizeof(gdma_buffer_mount_config_t), NULL));
|
||||
|
||||
dma_buffer_split_array_t align_array = {0};
|
||||
gdma_buffer_mount_config_t rx_aligned_buf_mount_config[3] = {0};
|
||||
uint8_t* stash_buffer = NULL;
|
||||
TEST_ESP_OK(esp_dma_split_rx_buffer_to_cache_aligned(dst_data + offset_len, data_length, &align_array, &stash_buffer));
|
||||
for (int i = 0; i < 3; i++) {
|
||||
rx_aligned_buf_mount_config[i].buffer = align_array.aligned_buffer[i].aligned_buffer;
|
||||
rx_aligned_buf_mount_config[i].buffer_alignment = MAX(sram_alignment, rx_mem_alignment);
|
||||
rx_aligned_buf_mount_config[i].length = align_array.aligned_buffer[i].length;
|
||||
}
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(rx_link_list, 0, rx_aligned_buf_mount_config, 3, NULL));
|
||||
|
||||
gdma_rx_event_callbacks_t rx_cbs = {
|
||||
.on_recv_eof = test_gdma_m2m_unaligned_rx_eof_callback,
|
||||
};
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
test_gdma_context_t user_ctx = {
|
||||
.done_sem = done_sem,
|
||||
.align_array = &align_array,
|
||||
};
|
||||
TEST_ESP_OK(gdma_register_rx_event_callbacks(rx_chan, &rx_cbs, &user_ctx));
|
||||
|
||||
TEST_ESP_OK(gdma_start(rx_chan, gdma_link_get_head_addr(rx_link_list)));
|
||||
TEST_ESP_OK(gdma_start(tx_chan, gdma_link_get_head_addr(tx_link_list)));
|
||||
|
||||
xSemaphoreTake(done_sem, 1000 / portTICK_PERIOD_MS);
|
||||
|
||||
// validate the destination data
|
||||
for (int i = 0; i < data_length; i++) {
|
||||
TEST_ASSERT_EQUAL(i % 256, dst_data[i + offset_len]);
|
||||
}
|
||||
|
||||
TEST_ESP_OK(gdma_del_link_list(tx_link_list));
|
||||
TEST_ESP_OK(gdma_del_link_list(rx_link_list));
|
||||
TEST_ESP_OK(gdma_del_channel(tx_chan));
|
||||
TEST_ESP_OK(gdma_del_channel(rx_chan));
|
||||
vSemaphoreDelete(done_sem);
|
||||
free(stash_buffer);
|
||||
}
|
||||
|
||||
TEST_CASE("GDMA M2M Unaligned RX Buffer Test", "[GDMA][M2M]")
|
||||
{
|
||||
if (efuse_hal_flash_encryption_enabled()) {
|
||||
TEST_PASS_MESSAGE("Flash encryption is enabled, skip this test");
|
||||
}
|
||||
|
||||
uint8_t *sbuf = heap_caps_aligned_calloc(64, 1, 10240, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
uint8_t *dbuf = heap_caps_aligned_calloc(64, 1, 10240, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
|
||||
// case buffer len less than buffer alignment
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 60, 0);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 60, 4);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 60, 2);
|
||||
|
||||
// case buffer head aligned
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 246, 0);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 8182, 0);
|
||||
|
||||
// case buffer tail aligned
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 246, 10);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 8182, 10);
|
||||
|
||||
// case buffer unaligned
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 100, 10);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 10, 60);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 256, 10);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 8192, 10);
|
||||
|
||||
// case buffer full aligned
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 256, 0);
|
||||
test_gdma_m2m_unaligned_buffer_test(dbuf, sbuf, 8192, 0);
|
||||
|
||||
free(sbuf);
|
||||
free(dbuf);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void test_gdma_memcpy_from_to_psram(gdma_channel_handle_t tx_chan, gdma_channel_handle_t rx_chan)
|
||||
{
|
||||
#define COPY_SIZE (40*1024)
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
gdma_rx_event_callbacks_t rx_cbs = {
|
||||
.on_recv_eof = test_gdma_m2m_rx_eof_callback,
|
||||
};
|
||||
TEST_ESP_OK(gdma_register_rx_event_callbacks(rx_chan, &rx_cbs, done_sem));
|
||||
|
||||
gdma_strategy_config_t strategy = {
|
||||
.auto_update_desc = true,
|
||||
.owner_check = true,
|
||||
.eof_till_data_popped = true,
|
||||
};
|
||||
TEST_ESP_OK(gdma_apply_strategy(tx_chan, &strategy));
|
||||
TEST_ESP_OK(gdma_apply_strategy(rx_chan, &strategy));
|
||||
|
||||
gdma_transfer_config_t transfer_cfg = {
|
||||
.max_data_burst_size = 32,
|
||||
.access_ext_mem = true, // allow to do memory copy from/to external memory
|
||||
};
|
||||
TEST_ESP_OK(gdma_config_transfer(tx_chan, &transfer_cfg));
|
||||
TEST_ESP_OK(gdma_config_transfer(rx_chan, &transfer_cfg));
|
||||
|
||||
gdma_trigger_t m2m_trigger = GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0);
|
||||
// get a free DMA trigger ID for memory copy
|
||||
uint32_t free_m2m_id_mask = 0;
|
||||
gdma_get_free_m2m_trig_id_mask(tx_chan, &free_m2m_id_mask);
|
||||
m2m_trigger.instance_id = __builtin_ctz(free_m2m_id_mask);
|
||||
TEST_ESP_OK(gdma_connect(tx_chan, m2m_trigger));
|
||||
TEST_ESP_OK(gdma_connect(rx_chan, m2m_trigger));
|
||||
|
||||
gdma_link_list_handle_t tx_link_list = NULL;
|
||||
gdma_link_list_handle_t rx_link_list = NULL;
|
||||
// create DMA link list for TX channel
|
||||
gdma_link_list_config_t tx_link_list_config = {
|
||||
.item_alignment = 8, // 8-byte alignment required by the AXI-GDMA
|
||||
.num_items = 20,
|
||||
.flags = {
|
||||
.items_in_ext_mem = false,
|
||||
}
|
||||
};
|
||||
TEST_ESP_OK(gdma_new_link_list(&tx_link_list_config, &tx_link_list));
|
||||
// create DMA link list for RX channel
|
||||
gdma_link_list_config_t rx_link_list_config = {
|
||||
.item_alignment = 8, // 8-byte alignment required by the AXI-GDMA
|
||||
.num_items = 20,
|
||||
.flags = {
|
||||
.items_in_ext_mem = false,
|
||||
},
|
||||
};
|
||||
TEST_ESP_OK(gdma_new_link_list(&rx_link_list_config, &rx_link_list));
|
||||
|
||||
// allocate the source buffer from SRAM
|
||||
uint8_t *src_data = heap_caps_aligned_calloc(32, 1, COPY_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(src_data);
|
||||
TEST_ASSERT_TRUE(esp_ptr_internal(src_data));
|
||||
// prepare the source data
|
||||
for (int i = 0; i < COPY_SIZE; i++) {
|
||||
src_data[i] = i;
|
||||
}
|
||||
size_t sram_cache_line_sz = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
|
||||
size_t psram_cache_line_sz = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
|
||||
// do cache sync if necessary
|
||||
if (sram_cache_line_sz) {
|
||||
TEST_ESP_OK(esp_cache_msync(src_data, COPY_SIZE, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE));
|
||||
}
|
||||
|
||||
// allocate the destination buffer from PSRAM
|
||||
uint8_t *dst_data = heap_caps_aligned_calloc(32, 1, COPY_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(dst_data);
|
||||
TEST_ASSERT_TRUE(esp_ptr_external_ram(dst_data));
|
||||
if (psram_cache_line_sz) {
|
||||
TEST_ESP_OK(esp_cache_msync(dst_data, COPY_SIZE, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE));
|
||||
}
|
||||
|
||||
gdma_buffer_mount_config_t tx_buf_mount_config = {
|
||||
.buffer = src_data,
|
||||
.buffer_alignment = 32,
|
||||
.length = COPY_SIZE,
|
||||
.flags = {
|
||||
.mark_eof = true,
|
||||
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
|
||||
}
|
||||
};
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(tx_link_list, 0, &tx_buf_mount_config, 1, NULL));
|
||||
|
||||
gdma_buffer_mount_config_t rx_buf_mount_config = {
|
||||
.buffer = dst_data,
|
||||
.buffer_alignment = 32,
|
||||
.length = COPY_SIZE,
|
||||
.flags = {
|
||||
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
|
||||
}
|
||||
};
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(rx_link_list, 0, &rx_buf_mount_config, 1, NULL));
|
||||
|
||||
TEST_ESP_OK(gdma_start(rx_chan, gdma_link_get_head_addr(rx_link_list)));
|
||||
TEST_ESP_OK(gdma_start(tx_chan, gdma_link_get_head_addr(tx_link_list)));
|
||||
|
||||
xSemaphoreTake(done_sem, pdMS_TO_TICKS(1000));
|
||||
|
||||
/// let the DMA to copy the data back to the source buffer again
|
||||
/// clear the "src_data" because now we want to use it as the destination buffer
|
||||
memset(src_data, 0, COPY_SIZE);
|
||||
// do cache sync if necessary
|
||||
if (sram_cache_line_sz) {
|
||||
TEST_ESP_OK(esp_cache_msync(src_data, COPY_SIZE, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE));
|
||||
}
|
||||
|
||||
tx_buf_mount_config.buffer = dst_data;
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(tx_link_list, 0, &tx_buf_mount_config, 1, NULL));
|
||||
rx_buf_mount_config.buffer = src_data;
|
||||
TEST_ESP_OK(gdma_link_mount_buffers(rx_link_list, 0, &rx_buf_mount_config, 1, NULL));
|
||||
|
||||
TEST_ESP_OK(gdma_start(rx_chan, gdma_link_get_head_addr(rx_link_list)));
|
||||
TEST_ESP_OK(gdma_start(tx_chan, gdma_link_get_head_addr(tx_link_list)));
|
||||
|
||||
xSemaphoreTake(done_sem, pdMS_TO_TICKS(1000));
|
||||
|
||||
bool compare_result = true;
|
||||
for (int i = 0; i < COPY_SIZE; i++) {
|
||||
if (src_data[i] != i % 256) {
|
||||
printf("miss match! src_data[%d]=%d, should be %d\n", i, src_data[i], i % 256);
|
||||
compare_result = false;
|
||||
}
|
||||
if (dst_data[i] != i % 256) {
|
||||
printf("miss match! dst_data[%d]=%d, should be %d\n", i, dst_data[i], i % 256);
|
||||
compare_result = false;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_TRUE(compare_result);
|
||||
|
||||
free(src_data);
|
||||
free(dst_data);
|
||||
TEST_ESP_OK(gdma_del_link_list(tx_link_list));
|
||||
TEST_ESP_OK(gdma_del_link_list(rx_link_list));
|
||||
vSemaphoreDelete(done_sem);
|
||||
#undef COPY_SIZE
|
||||
}
|
||||
|
||||
#if SOC_SPIRAM_SUPPORTED
|
||||
TEST_CASE("GDMA memory copy SRAM->PSRAM->SRAM", "[GDMA][M2M]")
|
||||
{
|
||||
[[maybe_unused]] gdma_channel_handle_t tx_chan = NULL;
|
||||
[[maybe_unused]] gdma_channel_handle_t rx_chan = NULL;
|
||||
[[maybe_unused]] gdma_channel_alloc_config_t chan_alloc_config = {};
|
||||
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
#if GDMA_LL_GET(AHB_PSRAM_CAPABLE)
|
||||
printf("Testing AHB-GDMA memory copy SRAM->PSRAM->SRAM\n");
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&chan_alloc_config, &tx_chan, &rx_chan));
|
||||
|
||||
test_gdma_memcpy_from_to_psram(tx_chan, rx_chan);
|
||||
|
||||
TEST_ESP_OK(gdma_del_channel(tx_chan));
|
||||
TEST_ESP_OK(gdma_del_channel(rx_chan));
|
||||
#endif
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
#if GDMA_LL_GET(AXI_PSRAM_CAPABLE)
|
||||
printf("Testing AXI-GDMA memory copy SRAM->PSRAM->SRAM\n");
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&chan_alloc_config, &tx_chan, &rx_chan));
|
||||
|
||||
test_gdma_memcpy_from_to_psram(tx_chan, rx_chan);
|
||||
|
||||
TEST_ESP_OK(gdma_del_channel(tx_chan));
|
||||
TEST_ESP_OK(gdma_del_channel(rx_chan));
|
||||
#endif
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
}
|
||||
#endif // SOC_SPIRAM_SUPPORTED
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "unity.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "esp_private/gdma.h"
|
||||
#include "hal/dma_types.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/cache_hal.h"
|
||||
#include "hal/cache_ll.h"
|
||||
#include "hal/gdma_ll.h"
|
||||
#include "esp_cache.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t init_value;
|
||||
uint32_t crc_bit_width;
|
||||
uint32_t poly_hex;
|
||||
bool reverse_data_mask;
|
||||
uint32_t expected_result;
|
||||
} test_crc_case_t;
|
||||
static test_crc_case_t crc_test_cases[] = {
|
||||
// CRC8, x^8+x^2+x+1
|
||||
[0] = {
|
||||
.crc_bit_width = 8,
|
||||
.init_value = 0x00,
|
||||
.poly_hex = 0x07,
|
||||
.expected_result = 0xB8,
|
||||
},
|
||||
[1] = {
|
||||
.crc_bit_width = 8,
|
||||
.init_value = 0x00,
|
||||
.poly_hex = 0x07,
|
||||
.reverse_data_mask = true, // refin = true
|
||||
.expected_result = 0xF0,
|
||||
},
|
||||
// CRC16, x^16+x^12+x^5+1
|
||||
[2] = {
|
||||
.crc_bit_width = 16,
|
||||
.init_value = 0xFFFF,
|
||||
.poly_hex = 0x1021,
|
||||
.expected_result = 0xA9B2,
|
||||
},
|
||||
// CRC32, x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
|
||||
[3] = {
|
||||
.crc_bit_width = 32,
|
||||
.init_value = 0xFFFFFFFF,
|
||||
.poly_hex = 0x04C11DB7,
|
||||
.expected_result = 0x692F6C7E,
|
||||
}
|
||||
};
|
||||
|
||||
// CRC online: https://www.lddgo.net/en/encrypt/crc
|
||||
static void test_gdma_crc_calculation(gdma_channel_handle_t tx_chan, int test_num_crc_algorithm)
|
||||
{
|
||||
// Note, burst size should be at least 16 when accessing encrypted external memory
|
||||
gdma_transfer_config_t transfer_cfg = {
|
||||
.max_data_burst_size = 16,
|
||||
.access_ext_mem = true,
|
||||
};
|
||||
TEST_ESP_OK(gdma_config_transfer(tx_chan, &transfer_cfg));
|
||||
|
||||
uint32_t crc_result = 0;
|
||||
|
||||
static const char test_input_string[] __attribute__((aligned(GDMA_LL_GET(ACCESS_ENCRYPTION_MEM_ALIGNMENT)))) = "GDMACRC Share::Connect::Innovate";
|
||||
size_t input_data_size = strlen(test_input_string);
|
||||
|
||||
TEST_ASSERT_EQUAL((uintptr_t)test_input_string % GDMA_LL_GET(ACCESS_ENCRYPTION_MEM_ALIGNMENT), 0);
|
||||
// this test case also test the GDMA can fetch data from MSPI Flash
|
||||
TEST_ASSERT_TRUE(esp_ptr_in_drom(test_input_string));
|
||||
printf("Calculate CRC value for string: \"%s\"\r\n", test_input_string);
|
||||
|
||||
gdma_trigger_t m2m_trigger = GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0);
|
||||
// get a free DMA trigger ID
|
||||
uint32_t free_m2m_id_mask = 0;
|
||||
gdma_get_free_m2m_trig_id_mask(tx_chan, &free_m2m_id_mask);
|
||||
m2m_trigger.instance_id = __builtin_ctz(free_m2m_id_mask);
|
||||
TEST_ESP_OK(gdma_connect(tx_chan, m2m_trigger));
|
||||
|
||||
size_t sram_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
|
||||
size_t alignment = MAX(sram_cache_line_size, 8);
|
||||
dma_descriptor_align8_t *tx_descs = heap_caps_aligned_calloc(alignment, 1, sizeof(dma_descriptor_align8_t),
|
||||
MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(tx_descs);
|
||||
|
||||
tx_descs->buffer = (void *)test_input_string;
|
||||
tx_descs->dw0.size = input_data_size + 1; // +1 for '\0'
|
||||
tx_descs->dw0.length = input_data_size;
|
||||
tx_descs->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
||||
tx_descs->dw0.suc_eof = 1;
|
||||
tx_descs->next = NULL;
|
||||
|
||||
if (sram_cache_line_size) {
|
||||
// do write-back for the buffer because it's in the cache
|
||||
TEST_ESP_OK(esp_cache_msync((void *)tx_descs, sizeof(dma_descriptor_align8_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED));
|
||||
}
|
||||
|
||||
for (int i = 0; i < test_num_crc_algorithm; i++) {
|
||||
gdma_crc_calculator_config_t crc_config = {
|
||||
.crc_bit_width = crc_test_cases[i].crc_bit_width,
|
||||
.init_value = crc_test_cases[i].init_value,
|
||||
.poly_hex = crc_test_cases[i].poly_hex,
|
||||
.reverse_data_mask = crc_test_cases[i].reverse_data_mask,
|
||||
};
|
||||
TEST_ESP_OK(gdma_config_crc_calculator(tx_chan, &crc_config));
|
||||
TEST_ESP_OK(gdma_reset(tx_chan));
|
||||
TEST_ESP_OK(gdma_start(tx_chan, (intptr_t)tx_descs));
|
||||
// simply wait for the transfer done
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
TEST_ESP_OK(gdma_crc_get_result(tx_chan, &crc_result));
|
||||
printf("CRC Result: 0x%"PRIx32"\r\n", crc_result);
|
||||
TEST_ASSERT_EQUAL(crc_test_cases[i].expected_result, crc_result);
|
||||
}
|
||||
|
||||
free(tx_descs);
|
||||
}
|
||||
|
||||
TEST_CASE("GDMA CRC Calculation", "[GDMA][CRC]")
|
||||
{
|
||||
gdma_channel_handle_t tx_chan = NULL;
|
||||
gdma_channel_alloc_config_t tx_chan_alloc_config = {
|
||||
};
|
||||
#if SOC_HAS(AHB_GDMA)
|
||||
printf("Test CRC calculation for AHB GDMA\r\n");
|
||||
TEST_ESP_OK(gdma_new_ahb_channel(&tx_chan_alloc_config, &tx_chan, NULL));
|
||||
test_gdma_crc_calculation(tx_chan, 4);
|
||||
TEST_ESP_OK(gdma_del_channel(tx_chan));
|
||||
#endif // SOC_HAS(AHB_GDMA)
|
||||
|
||||
#if SOC_HAS(AXI_GDMA)
|
||||
printf("Test CRC calculation for AXI GDMA\r\n");
|
||||
TEST_ESP_OK(gdma_new_axi_channel(&tx_chan_alloc_config, &tx_chan, NULL));
|
||||
test_gdma_crc_calculation(tx_chan, 3);
|
||||
TEST_ESP_OK(gdma_del_channel(tx_chan));
|
||||
#endif // SOC_HAS(AXI_GDMA)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "unity.h"
|
||||
#include "unity_test_utils.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_etm.h"
|
||||
#include "driver/gpio_etm.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_async_memcpy.h"
|
||||
|
||||
TEST_CASE("async_memcpy_eof_event", "[GDMA][ETM]")
|
||||
{
|
||||
const uint32_t output_gpio = 1;
|
||||
// async_memcpy done ---> ETM channel A ---> GPIO toggle
|
||||
printf("allocate etm channel\r\n");
|
||||
esp_etm_channel_config_t etm_config = {};
|
||||
esp_etm_channel_handle_t etm_channel_a;
|
||||
TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a));
|
||||
|
||||
printf("allocate GPIO etm task\r\n");
|
||||
esp_etm_task_handle_t gpio_task = NULL;
|
||||
gpio_etm_task_config_t gpio_task_config = {
|
||||
.action = GPIO_ETM_TASK_ACTION_TOG,
|
||||
};
|
||||
TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task));
|
||||
// set gpio number for the gpio etm primitives
|
||||
TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio));
|
||||
|
||||
printf("initialize gpio\r\n");
|
||||
gpio_config_t task_gpio_config = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT_OUTPUT,
|
||||
.pin_bit_mask = 1ULL << output_gpio,
|
||||
};
|
||||
TEST_ESP_OK(gpio_config(&task_gpio_config));
|
||||
|
||||
// put the GPIO into initial state
|
||||
TEST_ESP_OK(gpio_set_level(output_gpio, 1));
|
||||
|
||||
printf("install async memcpy context\r\n");
|
||||
async_memcpy_handle_t mcp_ctx = NULL;
|
||||
async_memcpy_config_t config = ASYNC_MEMCPY_DEFAULT_CONFIG();
|
||||
TEST_ESP_OK(esp_async_memcpy_install(&config, &mcp_ctx));
|
||||
|
||||
printf("get async memcpy etm event handle\r\n");
|
||||
esp_etm_event_handle_t mcp_event = NULL;
|
||||
TEST_ESP_OK(esp_async_memcpy_new_etm_event(mcp_ctx, ASYNC_MEMCPY_ETM_EVENT_COPY_DONE, &mcp_event));
|
||||
|
||||
printf("connect event and task to the channel\r\n");
|
||||
TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, mcp_event, gpio_task));
|
||||
printf("enable etm channel\r\n");
|
||||
TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a));
|
||||
|
||||
TEST_ESP_OK(esp_etm_dump(stdout));
|
||||
|
||||
const uint32_t buffer_size = 1024;
|
||||
uint8_t *src_buf = heap_caps_aligned_alloc(64, buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
TEST_ASSERT_NOT_NULL(src_buf);
|
||||
uint8_t *dst_buf = heap_caps_aligned_alloc(64, buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
TEST_ASSERT_NOT_NULL(dst_buf);
|
||||
|
||||
printf("start memcpy\r\n");
|
||||
for (int j = 0; j < 19; j++) {
|
||||
TEST_ESP_OK(esp_async_memcpy(mcp_ctx, dst_buf, src_buf, buffer_size, NULL, NULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
// simply wait for the last memcpy to finish
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
// check the final GPIO level
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(output_gpio));
|
||||
|
||||
// delete etm primitives
|
||||
TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio));
|
||||
TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a));
|
||||
TEST_ESP_OK(esp_etm_del_task(gpio_task));
|
||||
TEST_ESP_OK(esp_etm_del_event(mcp_event));
|
||||
TEST_ESP_OK(esp_etm_del_channel(etm_channel_a));
|
||||
TEST_ESP_OK(esp_async_memcpy_uninstall(mcp_ctx));
|
||||
free(src_buf);
|
||||
free(dst_buf);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'release',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
['esp32s2', 'esp32c2', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_dma(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(reset=True)
|
||||
|
||||
|
||||
@pytest.mark.octal_psram
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'release',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32s3'], indirect=['target'])
|
||||
def test_dma_psram(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(reset=True)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'weighted_arbitration',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', soc_filtered_targets('SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION == 1'), indirect=['target'])
|
||||
def test_dma_weighted_arbitration(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(reset=True)
|
||||
|
||||
|
||||
@pytest.mark.flash_encryption
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'ext_mem_encryption',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32p4', 'esp32c5'], indirect=['target'])
|
||||
def test_dma_ext_mem_encryption(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(reset=True)
|
||||
|
||||
|
||||
@pytest.mark.flash_encryption_f4r8
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'ext_mem_encryption',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32s3'], indirect=['target'])
|
||||
def test_dma_ext_mem_encryption_s3_f4r8(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(reset=True)
|
||||
@@ -0,0 +1,9 @@
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x9000
|
||||
CONFIG_SECURE_FLASH_ENC_ENABLED=y
|
||||
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y
|
||||
CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y
|
||||
CONFIG_SECURE_BOOT_ALLOW_JTAG=y
|
||||
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=y
|
||||
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=y
|
||||
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_CACHE=y
|
||||
CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=y
|
||||
@@ -0,0 +1,9 @@
|
||||
# set compiler optimization level
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
|
||||
# we can silent the assertion to save the binary footprint
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
||||
|
||||
# enable the option to test retention correctness
|
||||
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION=y
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_HEX=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_OCT=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
Reference in New Issue
Block a user