From 6bc9814ffb4cda6df11f9f330dfd88871c8761b8 Mon Sep 17 00:00:00 2001 From: morris Date: Fri, 5 Jun 2026 12:07:17 +0800 Subject: [PATCH] feat(esp_driver_dma): add blocking async memcpy API Add a blocking wrapper for async memcpy so simple users can wait for one DMA copy without writing their own ISR callback and semaphore plumbing. Update functional tests and documentation to use the simpler API where async completion handling is not needed. Co-authored-by: Cursor --- .../esp_driver_dma/include/esp_async_memcpy.h | 19 ++++++++++ .../esp_driver_dma/src/esp_async_memcpy.c | 35 +++++++++++++++++++ .../test_apps/dma/main/test_async_memcpy.c | 22 ++---------- .../peripherals/async_memcpy.rst | 8 +++++ .../peripherals/async_memcpy.rst | 8 +++++ 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/components/esp_driver_dma/include/esp_async_memcpy.h b/components/esp_driver_dma/include/esp_async_memcpy.h index 392c80c5372..eba0763ac58 100644 --- a/components/esp_driver_dma/include/esp_async_memcpy.h +++ b/components/esp_driver_dma/include/esp_async_memcpy.h @@ -174,6 +174,25 @@ esp_err_t esp_async_memcpy_uninstall(async_memcpy_handle_t mcp); */ esp_err_t esp_async_memcpy(async_memcpy_handle_t mcp, void *dst, void *src, size_t n, async_memcpy_isr_cb_t cb_isr, void *cb_args); +/** + * @brief Blocking memory copy function with timeout + * + * @note This function is blocking and should not be called from interrupt context. + * @note Only `timeout_ms=-1` is supported, which means waiting indefinitely. + * + * @param[in] mcp Handle of async memcpy driver that returned from `esp_async_memcpy_install` + * @param[in] dst Destination address (copy to) + * @param[in] src Source address (copy from) + * @param[in] n Number of bytes to copy + * @param[in] timeout_ms Timeout in milliseconds. Only -1 is supported. + * @return + * - ESP_OK: Copy memory successfully + * - ESP_ERR_INVALID_ARG: Copy memory failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Function called from ISR context or driver in invalid state + * - ESP_FAIL: Copy memory failed because of other error + */ +esp_err_t esp_memcpy_blocking(async_memcpy_handle_t mcp, void *dst, void *src, size_t n, int32_t timeout_ms); + #if SOC_ETM_SUPPORTED /** * @brief Async memory copy specific events that supported by the ETM module diff --git a/components/esp_driver_dma/src/esp_async_memcpy.c b/components/esp_driver_dma/src/esp_async_memcpy.c index 47d48404633..536d6d44268 100644 --- a/components/esp_driver_dma/src/esp_async_memcpy.c +++ b/components/esp_driver_dma/src/esp_async_memcpy.c @@ -4,6 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" #include "esp_check.h" #include "esp_async_memcpy.h" #include "esp_async_memcpy_priv.h" @@ -22,6 +25,38 @@ esp_err_t esp_async_memcpy(async_memcpy_handle_t asmcp, void *dst, void *src, si return asmcp->memcpy(asmcp, dst, src, n, cb_isr, cb_args); } +typedef struct { + SemaphoreHandle_t semaphore; + StaticSemaphore_t semaphore_buffer; +} memcpy_blocking_context_t; + +static bool memcpy_blocking_callback(async_memcpy_handle_t mcp_hdl, async_memcpy_event_t *event, void *user_data) +{ + BaseType_t task_woken = pdFALSE; + memcpy_blocking_context_t *ctx = (memcpy_blocking_context_t *)user_data; + (void)mcp_hdl; + (void)event; + + xSemaphoreGiveFromISR(ctx->semaphore, &task_woken); + + return task_woken == pdTRUE; +} + +esp_err_t esp_memcpy_blocking(async_memcpy_handle_t asmcp, void *dst, void *src, size_t n, int32_t timeout_ms) +{ + ESP_RETURN_ON_FALSE(asmcp && dst && src && n, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(!xPortInIsrContext(), ESP_ERR_INVALID_STATE, TAG, "called from ISR context is not allowed"); + ESP_RETURN_ON_FALSE(timeout_ms == -1, ESP_ERR_INVALID_ARG, TAG, "only timeout_ms=-1 is supported"); + + memcpy_blocking_context_t ctx = {}; + ctx.semaphore = xSemaphoreCreateBinaryStatic(&ctx.semaphore_buffer); + + ESP_RETURN_ON_ERROR(esp_async_memcpy(asmcp, dst, src, n, memcpy_blocking_callback, &ctx), TAG, "failed to start memory copy"); + + xSemaphoreTake(ctx.semaphore, portMAX_DELAY); + return ESP_OK; +} + #if SOC_ETM_SUPPORTED esp_err_t esp_async_memcpy_new_etm_event(async_memcpy_handle_t asmcp, async_memcpy_etm_event_t event_type, esp_etm_event_handle_t *out_event) { diff --git a/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c b/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c index 810cdb4ba05..dafb8841f66 100644 --- a/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c +++ b/components/esp_driver_dma/test_apps/dma/main/test_async_memcpy.c @@ -12,7 +12,6 @@ #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" @@ -102,8 +101,7 @@ static void test_memory_copy_with_same_buffer(async_memcpy_handle_t driver, asyn 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)); + TEST_ESP_OK(esp_memcpy_blocking(driver, dbuf, sbuf, 256, -1)); 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]); @@ -151,17 +149,8 @@ TEST_CASE("memory copy the same buffer with different content", "[async mcp]") #endif // SOC_HAS(LP_AHB_GDMA) } -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, @@ -177,13 +166,11 @@ static void test_memory_copy_blocking(async_memcpy_handle_t driver) } 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))); + TEST_ESP_OK(esp_memcpy_blocking(driver, test_context.to_addr, test_context.from_addr, test_context.copy_size, -1)); 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]") @@ -225,7 +212,6 @@ TEST_CASE("memory copy by DMA (blocking)", "[async mcp]") [[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, @@ -241,13 +227,11 @@ TEST_CASE("memory copy by DMA (blocking)", "[async mcp]") 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))); + TEST_ESP_OK(esp_memcpy_blocking(driver, test_context.to_addr, test_context.from_addr, test_context.copy_size, -1)); 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]") diff --git a/docs/en/api-reference/peripherals/async_memcpy.rst b/docs/en/api-reference/peripherals/async_memcpy.rst index b40e5d3e710..41ff872af65 100644 --- a/docs/en/api-reference/peripherals/async_memcpy.rst +++ b/docs/en/api-reference/peripherals/async_memcpy.rst @@ -67,6 +67,14 @@ The prototype of the callback function is :cpp:type:`async_memcpy_isr_cb_t`. The // Do something else here xSemaphoreTake(my_semaphore, portMAX_DELAY); // Wait until the buffer copy is done +For simpler use cases where the task only needs to wait until one copy finishes, use :cpp:func:`esp_memcpy_blocking`. This API is built on top of the async request path and waits internally for the completion callback. + +.. code-block:: c + + ESP_ERROR_CHECK(esp_memcpy_blocking(driver_handle, to, from, copy_len, -1)); + +The blocking API must not be called from ISR context. Currently, it only supports ``timeout_ms = -1``, which means waiting indefinitely until the memory copy completes. + Uninstall Driver ---------------- diff --git a/docs/zh_CN/api-reference/peripherals/async_memcpy.rst b/docs/zh_CN/api-reference/peripherals/async_memcpy.rst index a713a8182ea..21c96b4ca46 100644 --- a/docs/zh_CN/api-reference/peripherals/async_memcpy.rst +++ b/docs/zh_CN/api-reference/peripherals/async_memcpy.rst @@ -67,6 +67,14 @@ DMA 允许多个内存复制请求在首个请求完成之前排队,即允许 // 其他事项 xSemaphoreTake(my_semaphore, portMAX_DELAY); // 等待 buffer 复制完成 +对于只需等待单次复制完成的简单场景,可以使用 :cpp:func:`esp_memcpy_blocking`。该 API 基于异步请求路径实现,并在内部等待完成回调。 + +.. code-block:: c + + ESP_ERROR_CHECK(esp_memcpy_blocking(driver_handle, to, from, copy_len, -1)); + +阻塞 API 不能在 ISR 上下文中调用。目前仅支持 ``timeout_ms = -1``,表示无限期等待直到内存复制完成。 + 卸载驱动 ----------------