feat(dma): graduate the dma driver from esp_hw_support to esp_driver_dma

This commit is contained in:
morris
2026-01-22 14:14:14 +08:00
parent b2507c3a82
commit 35bdd8c651
91 changed files with 139 additions and 133 deletions
@@ -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