From 2712bb2339156344cbf21f89ebf8ac661f5eb4ee Mon Sep 17 00:00:00 2001 From: morris Date: Sun, 1 Feb 2026 10:32:23 +0800 Subject: [PATCH] feat(crc): added async CRC driver based on GDMA peripheral --- components/esp_driver_dma/CMakeLists.txt | 2 +- components/esp_driver_dma/Kconfig | 4 +- .../esp_driver_dma/include/esp_async_crc.h | 157 +++++++ .../esp_driver_dma/include/esp_private/gdma.h | 3 - .../esp_driver_dma/src/async_crc_gdma.c | 444 ++++++++++++++++++ .../esp_driver_dma/src/async_memcpy_cp_dma.c | 4 +- .../esp_driver_dma/src/async_memcpy_gdma.c | 5 +- components/esp_driver_dma/src/esp_async_crc.c | 90 ++++ .../esp_driver_dma/src/esp_async_crc_priv.h | 47 ++ .../src/esp_async_memcpy_priv.h | 3 +- components/esp_driver_dma/src/gdma_crc.c | 21 +- .../test_apps/dma/main/CMakeLists.txt | 2 +- .../test_apps/dma/main/test_async_crc.c | 212 +++++++++ 13 files changed, 967 insertions(+), 27 deletions(-) create mode 100644 components/esp_driver_dma/include/esp_async_crc.h create mode 100644 components/esp_driver_dma/src/async_crc_gdma.c create mode 100644 components/esp_driver_dma/src/esp_async_crc.c create mode 100644 components/esp_driver_dma/src/esp_async_crc_priv.h create mode 100644 components/esp_driver_dma/test_apps/dma/main/test_async_crc.c diff --git a/components/esp_driver_dma/CMakeLists.txt b/components/esp_driver_dma/CMakeLists.txt index 667e50ea957..a36fa937780 100644 --- a/components/esp_driver_dma/CMakeLists.txt +++ b/components/esp_driver_dma/CMakeLists.txt @@ -16,7 +16,7 @@ if(CONFIG_SOC_GDMA_SUPPORTED) list(APPEND srcs "src/gdma_etm.c") endif() if(CONFIG_SOC_GDMA_SUPPORT_CRC) - list(APPEND srcs "src/gdma_crc.c") + list(APPEND srcs "src/gdma_crc.c" "src/esp_async_crc.c" "src/async_crc_gdma.c") endif() endif() diff --git a/components/esp_driver_dma/Kconfig b/components/esp_driver_dma/Kconfig index da4d561f41b..b34bbfcb417 100644 --- a/components/esp_driver_dma/Kconfig +++ b/components/esp_driver_dma/Kconfig @@ -37,9 +37,9 @@ menu "GDMA Configurations" bool "GDMA enable weighted arbitration (Experimental)" default n help - Whether to enable the weighted arbitration for GDMA driver. + Whether to enable weighted arbitration for GDMA driver. The default weight of each channel is 1. You need to set weight for each channel before transmissions. - If this option is enabled, the buffer should be aligned to the burst size. + If this option is enabled, buffer should be aligned to the burst size. endmenu # GDMA Configurations menu "DW_GDMA Configurations" diff --git a/components/esp_driver_dma/include/esp_async_crc.h b/components/esp_driver_dma/include/esp_async_crc.h new file mode 100644 index 00000000000..af953dbb66f --- /dev/null +++ b/components/esp_driver_dma/include/esp_async_crc.h @@ -0,0 +1,157 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "soc/soc_caps.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Async CRC driver handle + */ +typedef struct async_crc_context_t *async_crc_handle_t; + +/** + * @brief Async CRC event data + */ +typedef struct { + uint32_t crc_result; /*!< CRC calculation result */ +} async_crc_event_data_t; + +/** + * @brief Type of async CRC interrupt callback function + * + * @param crc_hdl Handle of async CRC + * @param edata Event data object, which contains related data for this event + * @param cb_args User defined arguments, passed from esp_async_crc_calc function + * @return Whether a high priority task is woken up by the callback function + * + * @note User can call OS primitives (semaphore, mutex, etc) in the callback function. + * Keep in mind, if any OS primitive wakes high priority task up, the callback should return true. + * @note This callback function is invoked in interrupt context (ISR). The following restrictions apply: + * - Do not perform blocking operations (e.g., vTaskDelay, xQueueSend with non-zero timeout) + * - Keep execution time minimal to avoid impacting system interrupt latency + * - Avoid calling non-ISR-safe FreeRTOS functions + * - Do not allocate memory or perform heavy computations + * - Use only ISR-safe APIs (xQueueSendFromISR, xSemaphoreGiveFromISR, etc.) + */ +typedef bool (*async_crc_isr_cb_t)(async_crc_handle_t crc_hdl, async_crc_event_data_t *edata, void *cb_args); + +/** + * @brief Type of async CRC configuration + */ +typedef struct { + uint32_t backlog; /*!< Maximum number of pending CRC requests that can be queued per driver instance. + Higher values use more memory but provide better throughput for bursty workloads. */ + size_t dma_burst_size; /*!< DMA transfer burst size, in bytes */ +} async_crc_config_t; + +#if SOC_HAS(AHB_GDMA) +/** + * @brief Install async CRC driver, with AHB-GDMA as the backend + * + * @param[in] config Configuration of async CRC + * @param[out] crc_hdl Returned driver handle + * @return + * - ESP_OK: Install async CRC driver successfully + * - ESP_ERR_INVALID_ARG: Install async CRC driver failed because of invalid argument + * - ESP_ERR_NO_MEM: Install async CRC driver failed because out of memory + * - ESP_FAIL: Install async CRC driver failed because of other error + */ +esp_err_t esp_async_crc_install_gdma_ahb(const async_crc_config_t *config, async_crc_handle_t *crc_hdl); +#endif // SOC_HAS(AHB_GDMA) + +#if SOC_HAS(AXI_GDMA) +/** + * @brief Install async CRC driver, with AXI-GDMA as the backend + * + * @param[in] config Configuration of async CRC + * @param[out] crc_hdl Returned driver handle + * @return + * - ESP_OK: Install async CRC driver successfully + * - ESP_ERR_INVALID_ARG: Install async CRC driver failed because of invalid argument + * - ESP_ERR_NO_MEM: Install async CRC driver failed because out of memory + * - ESP_FAIL: Install async CRC driver failed because of other error + */ +esp_err_t esp_async_crc_install_gdma_axi(const async_crc_config_t *config, async_crc_handle_t *crc_hdl); +#endif // SOC_HAS(AXI_GDMA) + +/** + * @brief Uninstall async CRC driver + * + * @param[in] crc_hdl Handle of async CRC driver that returned from install functions + * @return + * - ESP_OK: Uninstall async CRC driver successfully + * - ESP_ERR_INVALID_ARG: Uninstall async CRC driver failed because of invalid argument + * - ESP_FAIL: Uninstall async CRC driver failed because of other error + */ +esp_err_t esp_async_crc_uninstall(async_crc_handle_t crc_hdl); + +/** + * @brief CRC calculation parameters + */ +typedef struct { + uint8_t width; /*!< CRC bit width: 8, 16, or 32 bits */ + uint32_t polynomial; /*!< CRC polynomial */ + uint32_t init_value; /*!< Initial CRC value */ + uint32_t final_xor_value; /*!< Final XOR value */ + bool reverse_input; /*!< Reverse data bits before processing */ + bool reverse_output; /*!< Reverse final CRC result */ +} async_crc_params_t; + +/** + * @brief Send an asynchronous CRC calculation request + * + * @note The callback function is invoked in interrupt context, never do blocking jobs in the callback. + * + * @param[in] crc_hdl Handle of async CRC driver that returned from install functions + * @param[in] data Pointer to data buffer for CRC calculation + * @param[in] size Size of data in bytes + * @param[in] params CRC calculation parameters + * @param[in] cb_isr Callback function, which got invoked in interrupt context. Set to NULL can bypass the callback. + * @param[in] cb_args User defined argument to be passed to the callback function + * @return + * - ESP_OK: Send CRC calculation request successfully + * - ESP_ERR_INVALID_ARG: Send CRC calculation request failed because of invalid argument + * - ESP_ERR_INVALID_STATE: CRC driver is not in proper state to accept new requests + * - ESP_FAIL: Send CRC calculation request failed because of other error + */ +esp_err_t esp_async_crc_calc(async_crc_handle_t crc_hdl, const void *data, size_t size, + const async_crc_params_t *params, async_crc_isr_cb_t cb_isr, void *cb_args); + +/** + * @brief Blocking CRC calculation function with timeout + * + * @note This function is blocking and should not be called from interrupt context. + * + * @param[in] crc_hdl Handle of async CRC driver that returned from install functions + * @param[in] data Pointer to data buffer for CRC calculation + * @param[in] size Size of data in bytes + * @param[in] params CRC calculation parameters + * @param[in] timeout_ms Timeout in milliseconds: + * - `< 0`: Wait forever (no timeout) + * - `0`: Return immediately (poll once) + * - `> 0`: Wait up to specified milliseconds + * @param[out] result Pointer to store CRC calculation result + * @return + * - ESP_OK: Calculate CRC successfully + * - ESP_ERR_INVALID_ARG: Calculate CRC failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Function called from ISR context or driver in invalid state + * - ESP_ERR_TIMEOUT: Operation timed out + * - ESP_FAIL: Calculate CRC failed because of other error + */ +esp_err_t esp_crc_calc_blocking(async_crc_handle_t crc_hdl, const void *data, size_t size, + const async_crc_params_t *params, int32_t timeout_ms, uint32_t *result); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_dma/include/esp_private/gdma.h b/components/esp_driver_dma/include/esp_private/gdma.h index e62b7f85964..c0f4fbdb14b 100644 --- a/components/esp_driver_dma/include/esp_private/gdma.h +++ b/components/esp_driver_dma/include/esp_private/gdma.h @@ -17,13 +17,11 @@ extern "C" { /** * @brief Type of GDMA channel handle - * */ typedef struct gdma_channel_t *gdma_channel_handle_t; /** * @brief Collection of configuration items that used for allocating GDMA channel - * */ typedef struct { struct { @@ -79,7 +77,6 @@ typedef struct { /** * @brief Type of GDMA engine trigger * @note It's recommended to initialize this structure with `GDMA_MAKE_TRIGGER`. - * */ typedef struct { int instance_id; /*!< Peripheral instance ID. Supported IDs are listed in `hal/gdma_channel.h`, e.g. SOC_GDMA_TRIG_PERIPH_UHCI0 */ diff --git a/components/esp_driver_dma/src/async_crc_gdma.c b/components/esp_driver_dma/src/async_crc_gdma.c new file mode 100644 index 00000000000..b5aad0ac84b --- /dev/null +++ b/components/esp_driver_dma/src/async_crc_gdma.c @@ -0,0 +1,444 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "esp_cache.h" +#include "esp_async_crc_priv.h" +#include "hal/gdma_ll.h" + +ESP_LOG_ATTR_TAG(TAG, "async_crc_gdma"); + +#define CRC_DMA_DESCRIPTOR_BUFFER_MAX_SIZE 4095 + +__attribute__((always_inline)) +static inline uint32_t bit_reverse32(uint32_t val) +{ + val = ((val >> 1) & 0x55555555) | ((val << 1) & 0xAAAAAAAA); + val = ((val >> 2) & 0x33333333) | ((val << 2) & 0xCCCCCCCC); + val = ((val >> 4) & 0x0F0F0F0F) | ((val << 4) & 0xF0F0F0F0); + val = ((val >> 8) & 0x00FF00FF) | ((val << 8) & 0xFF00FF00); + val = (val >> 16) | (val << 16); + return val; +} + +/// @brief Transaction object for async CRC +typedef struct async_crc_transaction { + gdma_link_list_handle_t link_list; // DMA link list for user buffer + const void *data; // User data buffer pointer + size_t size; // Size of data buffer + async_crc_params_t params; // CRC parameters (polynomial, init value, etc.) + async_crc_isr_cb_t cb_isr; // User callback + void *cb_args; // User callback arguments + STAILQ_ENTRY(async_crc_transaction) queue_entry; // Entry for idle/ready queue +} async_crc_transaction_t; + +/// @brief Context of async CRC driver using GDMA +typedef struct { + async_crc_context_t parent; // Parent IO interface + gdma_channel_handle_t tx_channel; // GDMA TX channel handle + portMUX_TYPE spin_lock; // Spinlock for synchronization + _Atomic async_crc_fsm_t fsm; // driver state machine, changing state should be atomic + size_t tx_int_mem_alignment; // Required DMA buffer alignment for internal TX memory + size_t tx_ext_mem_alignment; // Required DMA buffer alignment for external TX memory + uint32_t gdma_bus_id; // GDMA bus id (AHB, AXI, etc.) + uint32_t num_trans_objs; // number of transaction objects + async_crc_transaction_t *transaction_pool; // transaction object pool + async_crc_transaction_t *current_transaction; // Track active transaction to avoid search + STAILQ_HEAD(, async_crc_transaction) idle_queue_head; // Head of the idle queue + STAILQ_HEAD(, async_crc_transaction) ready_queue_head; // Head of the ready queue +} async_crc_gdma_context_t; + +// Forward declarations +static esp_err_t async_crc_gdma_del(async_crc_context_t *ctx); +static esp_err_t async_crc_gdma_calc(async_crc_context_t *ctx, const void *data, size_t size, + const async_crc_params_t *params, async_crc_isr_cb_t cb_isr, void *cb_args); +static bool async_crc_gdma_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data); +static esp_err_t async_crc_prepare_transaction(async_crc_gdma_context_t *crc_gdma, async_crc_transaction_t *trans); + +// Queue helper functions +static async_crc_transaction_t *try_pop_trans_from_idle_queue(async_crc_gdma_context_t *crc_gdma); +static async_crc_transaction_t *try_pop_trans_from_ready_queue(async_crc_gdma_context_t *crc_gdma); +static void try_start_pending_transaction(async_crc_gdma_context_t *crc_gdma); + +static esp_err_t async_crc_gdma_destroy_context(async_crc_gdma_context_t *crc_gdma) +{ + // clean up transaction pool + if (crc_gdma->transaction_pool) { + for (uint32_t i = 0; i < crc_gdma->num_trans_objs; i++) { + async_crc_transaction_t* trans = &crc_gdma->transaction_pool[i]; + if (trans->link_list) { + gdma_del_link_list(trans->link_list); + } + } + free(crc_gdma->transaction_pool); + } + // Delete GDMA channel + if (crc_gdma->tx_channel) { + gdma_disconnect(crc_gdma->tx_channel); + gdma_del_channel(crc_gdma->tx_channel); + } + free(crc_gdma); + return ESP_OK; +} + +esp_err_t esp_async_crc_install_gdma_template(const async_crc_config_t *config, async_crc_handle_t *crc_hdl, + gdma_new_channel_t new_channel_func, uint32_t gdma_bus_id) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(config && crc_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + uint32_t trans_queue_len = config->backlog ? config->backlog : DEFAULT_TRANSACTION_QUEUE_LENGTH; + + // allocate memory of driver context from internal memory (because it contains atomic variable) + async_crc_gdma_context_t *crc_gdma = heap_caps_calloc(1, sizeof(async_crc_gdma_context_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_RETURN_ON_FALSE(crc_gdma, ESP_ERR_NO_MEM, TAG, "no mem for crc context"); + + // init basic members of crc_gdma context + portMUX_INITIALIZE(&crc_gdma->spin_lock); + atomic_init(&crc_gdma->fsm, CRC_FSM_IDLE); + crc_gdma->gdma_bus_id = gdma_bus_id; + crc_gdma->num_trans_objs = trans_queue_len; + + // allocate memory for transaction pool from internal memory + crc_gdma->transaction_pool = heap_caps_calloc(trans_queue_len, sizeof(async_crc_transaction_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(crc_gdma->transaction_pool, ESP_ERR_NO_MEM, err, TAG, "no mem for transaction pool"); + + // Create TX channel for CRC calculation with optimized allocation strategy + gdma_channel_alloc_config_t dma_chan_alloc_cfg = {0}; + ESP_GOTO_ON_ERROR(new_channel_func(&dma_chan_alloc_cfg, &crc_gdma->tx_channel, NULL), + err, TAG, "alloc DMA channel failed"); + gdma_reset(crc_gdma->tx_channel); + + // get a free DMA trigger ID for CRC calculation, and connect it to the allocated channel + gdma_trigger_t m2m_trigger = { + .bus_id = gdma_bus_id, + }; + uint32_t free_m2m_id_mask = 0; + gdma_get_free_m2m_trig_id_mask(crc_gdma->tx_channel, &free_m2m_id_mask); + m2m_trigger.instance_id = __builtin_ctz(free_m2m_id_mask); + ESP_GOTO_ON_ERROR(gdma_connect(crc_gdma->tx_channel, m2m_trigger), err, TAG, "connect DMA channel failed"); + + gdma_strategy_config_t strategy_cfg = { + .owner_check = true, + .auto_update_desc = true, + .eof_till_data_popped = false, + }; + gdma_apply_strategy(crc_gdma->tx_channel, &strategy_cfg); + + // Configure DMA transfer + gdma_transfer_config_t transfer_cfg = { + .max_data_burst_size = config->dma_burst_size, + .access_ext_mem = true, // allow to copy data from external memory + }; + ESP_GOTO_ON_ERROR(gdma_config_transfer(crc_gdma->tx_channel, &transfer_cfg), err, TAG, "config DMA transfer failed"); + + // Get buffer alignment required by GDMA channel + gdma_get_alignment_constraints(crc_gdma->tx_channel, &crc_gdma->tx_int_mem_alignment, &crc_gdma->tx_ext_mem_alignment); + + // Register EOF callback for completion detection + gdma_tx_event_callbacks_t cbs = { + .on_trans_eof = async_crc_gdma_eof_callback, + }; + ESP_GOTO_ON_ERROR(gdma_register_tx_event_callbacks(crc_gdma->tx_channel, &cbs, crc_gdma), err, TAG, "register TX EOF callback failed"); + + STAILQ_INIT(&crc_gdma->idle_queue_head); + STAILQ_INIT(&crc_gdma->ready_queue_head); + // Pick transactions from pool and insert to idle queue + for (int i = 0; i < trans_queue_len; i++) { + STAILQ_INSERT_TAIL(&crc_gdma->idle_queue_head, &crc_gdma->transaction_pool[i], queue_entry); + } + + // Set function pointers + crc_gdma->parent.del = async_crc_gdma_del; + crc_gdma->parent.calc = async_crc_gdma_calc; + + // Return base object + *crc_hdl = &crc_gdma->parent; + return ESP_OK; + +err: + if (crc_gdma) { + async_crc_gdma_destroy_context(crc_gdma); + } + return ret; +} + +/// @brief Check if the ready queue is empty +/// @note this function is allowed to be called in ISR +static bool is_ready_queue_empty(async_crc_gdma_context_t *crc_gdma) +{ + bool empty; + portENTER_CRITICAL_SAFE(&crc_gdma->spin_lock); + empty = STAILQ_EMPTY(&crc_gdma->ready_queue_head); + portEXIT_CRITICAL_SAFE(&crc_gdma->spin_lock); + return empty; +} + +/// @brief help function to get one transaction from the ready queue +/// @note this function is allowed to be called in ISR +static async_crc_transaction_t *try_pop_trans_from_ready_queue(async_crc_gdma_context_t *crc_gdma) +{ + async_crc_transaction_t *trans = NULL; + portENTER_CRITICAL_SAFE(&crc_gdma->spin_lock); + trans = STAILQ_FIRST(&crc_gdma->ready_queue_head); + if (trans) { + STAILQ_REMOVE_HEAD(&crc_gdma->ready_queue_head, queue_entry); + } + portEXIT_CRITICAL_SAFE(&crc_gdma->spin_lock); + return trans; +} + +/// @brief help function to get one transaction from the idle queue +/// @note this function is allowed to be called in ISR +static async_crc_transaction_t *try_pop_trans_from_idle_queue(async_crc_gdma_context_t *crc_gdma) +{ + async_crc_transaction_t *trans = NULL; + portENTER_CRITICAL_SAFE(&crc_gdma->spin_lock); + trans = STAILQ_FIRST(&crc_gdma->idle_queue_head); + if (trans) { + STAILQ_REMOVE_HEAD(&crc_gdma->idle_queue_head, queue_entry); + } + portEXIT_CRITICAL_SAFE(&crc_gdma->spin_lock); + return trans; +} + +/// @brief help function to start a pending transaction +/// @note this function is allowed to be called in ISR +static void try_start_pending_transaction(async_crc_gdma_context_t *crc_gdma) +{ + async_crc_fsm_t expected_fsm = CRC_FSM_IDLE; + async_crc_transaction_t *trans = NULL; + + // Retry loop to handle race condition: + // If we acquire FSM but find queue empty, another task might have just inserted + // a transaction. We re-check the queue before giving up. + while (atomic_compare_exchange_strong(&crc_gdma->fsm, &expected_fsm, CRC_FSM_WAIT)) { + trans = try_pop_trans_from_ready_queue(crc_gdma); + if (trans) { + // Configure CRC calculator with transaction parameters + // This must be done just before starting DMA to ensure the hardware + // is not busy with a previous calculation using different params + gdma_crc_calculator_config_t crc_cfg = { + .crc_bit_width = trans->params.width, + .init_value = trans->params.init_value, + .poly_hex = trans->params.polynomial, + .reverse_data_mask = trans->params.reverse_input, + }; + // crc config validation is done in the async_crc_prepare_transaction + // so no need to check the return value here + gdma_config_crc_calculator(crc_gdma->tx_channel, &crc_cfg); + gdma_reset(crc_gdma->tx_channel); + + atomic_store(&crc_gdma->fsm, CRC_FSM_RUN); + crc_gdma->current_transaction = trans; + // Start DMA operation for CRC calculation + gdma_start(crc_gdma->tx_channel, gdma_link_get_head_addr(trans->link_list)); + return; + } + + // Queue was empty - go back to IDLE + atomic_store(&crc_gdma->fsm, CRC_FSM_IDLE); + + // Re-check if queue is still empty before giving up + // If not empty, a concurrent insert happened - retry to pick it up + if (is_ready_queue_empty(crc_gdma)) { + // Queue is truly empty, safe to exit + return; + } + + // Queue has items now, retry the loop + expected_fsm = CRC_FSM_IDLE; + } +} + +/// @brief prepare the transaction by mounting buffer and configuring DMA +/// @note This function mounts the user buffer to a DMA link list +static esp_err_t async_crc_prepare_transaction(async_crc_gdma_context_t *crc_gdma, async_crc_transaction_t *trans) +{ + // Validate CRC bit width against GDMA bus type + // in fact this validation is also done in gdma_config_crc_calculator, + // but we do it here to catch the error earlier + uint32_t max_crc_bit_width = (crc_gdma->gdma_bus_id == SOC_GDMA_BUS_AXI) ? GDMA_LL_AXI_MAX_CRC_BIT_WIDTH : GDMA_LL_AHB_MAX_CRC_BIT_WIDTH; + ESP_RETURN_ON_FALSE(trans->params.width <= max_crc_bit_width, ESP_ERR_INVALID_ARG, TAG, "invalid crc bit width %"PRIu32, trans->params.width); + + // Get buffer alignment based on memory type + size_t buffer_alignment = esp_ptr_internal(trans->data) ? crc_gdma->tx_int_mem_alignment : crc_gdma->tx_ext_mem_alignment; + + // Verify user buffer satisfies DMA alignment requirements + ESP_RETURN_ON_FALSE(((uintptr_t)trans->data % buffer_alignment) == 0, ESP_ERR_INVALID_ARG, TAG, + "Data buffer not aligned to %zu bytes", buffer_alignment); + + // Calculate number of DMA nodes needed + size_t num_dma_nodes = esp_dma_calculate_node_count(trans->size, buffer_alignment, CRC_DMA_DESCRIPTOR_BUFFER_MAX_SIZE); + + // Get descriptor alignment based on GDMA bus type + size_t item_alignment = (crc_gdma->gdma_bus_id == SOC_GDMA_BUS_AXI) ? GDMA_LL_AXI_DESC_ALIGNMENT : GDMA_LL_AHB_DESC_ALIGNMENT; + + // Create DMA link list for the buffer + gdma_link_list_config_t link_cfg = { + .item_alignment = item_alignment, + .num_items = num_dma_nodes, + .flags = { + .check_owner = true, + .items_in_ext_mem = false, + }, + }; + ESP_RETURN_ON_ERROR(gdma_new_link_list(&link_cfg, &trans->link_list), TAG, "failed to create DMA link list"); + + // Mount the user buffer to the DMA link list + gdma_buffer_mount_config_t buf_mount_config[1] = { + [0] = { + .buffer = (void *)trans->data, // DMA only reads from this buffer, so it's safe to cast away const + .buffer_alignment = buffer_alignment, + .length = trans->size, + .flags = { + .mark_eof = true, + .mark_final = GDMA_FINAL_LINK_TO_NULL, + } + } + }; + ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(trans->link_list, 0, buf_mount_config, 1, NULL), TAG, "failed to mount buffer to DMA link list"); + + // write back the source data if it's behind the cache + size_t cache_line_size = esp_cache_get_line_size_by_addr(trans->data); + if (cache_line_size > 0) { + esp_cache_msync((void*)trans->data, trans->size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); + } + + return ESP_OK; +} + +/// @brief EOF callback for GDMA CRC completion +/// @note This is called in ISR context when DMA transfer completes +static bool async_crc_gdma_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +{ + bool need_yield = false; + async_crc_gdma_context_t *crc_gdma = (async_crc_gdma_context_t *)user_data; + + // Handle normal EOF completion + if (event_data->flags.normal_eof) { + // Get current transaction that just completed + // Capture to local var before CAS in case user clears crc_gdma->current_transaction + async_crc_transaction_t *current_trans = crc_gdma->current_transaction; + + // Get CRC result from DMA peripheral + uint32_t crc_result; + gdma_crc_get_result(crc_gdma->tx_channel, &crc_result); + + // Apply output bit reversal if specified + if (current_trans->params.reverse_output) { + crc_result = bit_reverse32(crc_result); + // Mask to appropriate width + if (current_trans->params.width == 8) { + crc_result = crc_result >> 24; // For 8-bit CRC, the result is in the highest byte after bit reversal + } else if (current_trans->params.width == 16) { + crc_result = crc_result >> 16; // For 16-bit CRC, the result is in the highest 2 bytes after bit reversal + } + } + + // Apply final XOR value if specified + if (current_trans->params.final_xor_value != 0) { + crc_result ^= current_trans->params.final_xor_value; + } + + // Switch driver state from RUN to IDLE using FSM transitions + async_crc_fsm_t expected_fsm = CRC_FSM_RUN; + if (atomic_compare_exchange_strong(&crc_gdma->fsm, &expected_fsm, CRC_FSM_WAIT)) { + // Call user callback with successful completion data + if (current_trans->cb_isr) { + async_crc_event_data_t event = {.crc_result = crc_result}; + need_yield = current_trans->cb_isr(&crc_gdma->parent, &event, current_trans->cb_args); + } + current_trans->cb_isr = NULL; + + portENTER_CRITICAL_ISR(&crc_gdma->spin_lock); + // Return transaction to idle queue for reuse + STAILQ_INSERT_TAIL(&crc_gdma->idle_queue_head, current_trans, queue_entry); + crc_gdma->current_transaction = NULL; // Clear current transaction + portEXIT_CRITICAL_ISR(&crc_gdma->spin_lock); + + atomic_store(&crc_gdma->fsm, CRC_FSM_IDLE); + } + } + + // Try start the next pending transaction + try_start_pending_transaction(crc_gdma); + + return need_yield; +} + +static esp_err_t async_crc_gdma_del(async_crc_context_t *ctx) +{ + async_crc_gdma_context_t *crc_gdma = __containerof(ctx, async_crc_gdma_context_t, parent); + + // Check if driver is busy or has pending requests + // Uninstall can only proceed when driver is IDLE and no pending transactions + ESP_RETURN_ON_FALSE(atomic_load(&crc_gdma->fsm) == CRC_FSM_IDLE, ESP_ERR_INVALID_STATE, TAG, "Cannot uninstall: CRC engine is busy"); + + // Check if there are pending transactions in the ready queue + ESP_RETURN_ON_FALSE(is_ready_queue_empty(crc_gdma), ESP_ERR_INVALID_STATE, TAG, "Cannot uninstall: pending CRC requests in queue"); + + return async_crc_gdma_destroy_context(crc_gdma); +} + +static esp_err_t async_crc_gdma_calc(async_crc_context_t *ctx, const void *data, size_t size, + const async_crc_params_t *params, async_crc_isr_cb_t cb_isr, void *cb_args) +{ + ESP_RETURN_ON_FALSE(ctx && data && size && params, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + async_crc_gdma_context_t *crc_gdma = __containerof(ctx, async_crc_gdma_context_t, parent); + + // pick one transaction node from idle queue + async_crc_transaction_t *trans = try_pop_trans_from_idle_queue(crc_gdma); + ESP_RETURN_ON_FALSE(trans, ESP_ERR_INVALID_STATE, TAG, "no free node in the idle queue"); + + // clean up the transaction configuration comes from the last one + if (trans->link_list) { + gdma_del_link_list(trans->link_list); + trans->link_list = NULL; + } + + // Store transaction data + trans->data = data; + trans->size = size; + trans->params = *params; + trans->cb_isr = cb_isr; + trans->cb_args = cb_args; + + // Prepare the DMA transfer (create link list and mount buffer) + esp_err_t ret = async_crc_prepare_transaction(crc_gdma, trans); + if (ret != ESP_OK) { + goto err; + } + + // insert the trans to ready queue + portENTER_CRITICAL(&crc_gdma->spin_lock); + STAILQ_INSERT_TAIL(&crc_gdma->ready_queue_head, trans, queue_entry); + portEXIT_CRITICAL(&crc_gdma->spin_lock); + + // check driver state, if there's no running transaction, start a new one + try_start_pending_transaction(crc_gdma); + + return ESP_OK; + +err: + // return back the trans to idle queue + // Note: link_list is not freed here. It will be lazily freed on next reuse + // or during driver uninstall + if (trans) { + portENTER_CRITICAL(&crc_gdma->spin_lock); + STAILQ_INSERT_TAIL(&crc_gdma->idle_queue_head, trans, queue_entry); + portEXIT_CRITICAL(&crc_gdma->spin_lock); + } + return ret; +} diff --git a/components/esp_driver_dma/src/async_memcpy_cp_dma.c b/components/esp_driver_dma/src/async_memcpy_cp_dma.c index e8c37e51b3f..4b366cc014d 100644 --- a/components/esp_driver_dma/src/async_memcpy_cp_dma.c +++ b/components/esp_driver_dma/src/async_memcpy_cp_dma.c @@ -163,7 +163,7 @@ static void try_start_pending_transaction(async_memcpy_cpdma_context_t *mcp_dma) { async_memcpy_fsm_t expected_fsm = MCP_FSM_IDLE; async_memcpy_transaction_t *trans = NULL; - if (atomic_compare_exchange_strong(&mcp_dma->fsm, &expected_fsm, MCP_FSM_RUN_WAIT)) { + if (atomic_compare_exchange_strong(&mcp_dma->fsm, &expected_fsm, MCP_FSM_WAIT)) { trans = try_pop_trans_from_ready_queue(mcp_dma); if (trans) { atomic_store(&mcp_dma->fsm, MCP_FSM_RUN); @@ -301,7 +301,7 @@ static void mcp_default_isr_handler(void *args) // switch driver state from RUN to IDLE async_memcpy_fsm_t expected_fsm = MCP_FSM_RUN; - if (atomic_compare_exchange_strong(&mcp_dma->fsm, &expected_fsm, MCP_FSM_IDLE_WAIT)) { + if (atomic_compare_exchange_strong(&mcp_dma->fsm, &expected_fsm, MCP_FSM_WAIT)) { // invoked callback registered by user async_memcpy_isr_cb_t cb = trans->cb; if (cb) { diff --git a/components/esp_driver_dma/src/async_memcpy_gdma.c b/components/esp_driver_dma/src/async_memcpy_gdma.c index bb27289f527..ae0fc8ea77d 100644 --- a/components/esp_driver_dma/src/async_memcpy_gdma.c +++ b/components/esp_driver_dma/src/async_memcpy_gdma.c @@ -254,7 +254,7 @@ static void try_start_pending_transaction(async_memcpy_gdma_context_t *mcp_gdma) { async_memcpy_fsm_t expected_fsm = MCP_FSM_IDLE; async_memcpy_transaction_t *trans = NULL; - if (atomic_compare_exchange_strong(&mcp_gdma->fsm, &expected_fsm, MCP_FSM_RUN_WAIT)) { + if (atomic_compare_exchange_strong(&mcp_gdma->fsm, &expected_fsm, MCP_FSM_WAIT)) { trans = try_pop_trans_from_ready_queue(mcp_gdma); if (trans) { atomic_store(&mcp_gdma->fsm, MCP_FSM_RUN); @@ -452,7 +452,7 @@ static bool mcp_gdma_rx_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_ // switch driver state from RUN to IDLE async_memcpy_fsm_t expected_fsm = MCP_FSM_RUN; - if (atomic_compare_exchange_strong(&mcp_gdma->fsm, &expected_fsm, MCP_FSM_IDLE_WAIT)) { + if (atomic_compare_exchange_strong(&mcp_gdma->fsm, &expected_fsm, MCP_FSM_WAIT)) { // merge the cache aligned buffers to the original buffer esp_dma_merge_aligned_rx_buffers(rx_buf_array); @@ -469,6 +469,7 @@ static bool mcp_gdma_rx_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_ esp_os_enter_critical_isr(&mcp_gdma->spin_lock); // insert the trans object to the idle queue STAILQ_INSERT_TAIL(&mcp_gdma->idle_queue_head, trans, idle_queue_entry); + mcp_gdma->current_transaction = NULL; esp_os_exit_critical_isr(&mcp_gdma->spin_lock); atomic_store(&mcp_gdma->fsm, MCP_FSM_IDLE); diff --git a/components/esp_driver_dma/src/esp_async_crc.c b/components/esp_driver_dma/src/esp_async_crc.c new file mode 100644 index 00000000000..c8f841d4821 --- /dev/null +++ b/components/esp_driver_dma/src/esp_async_crc.c @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * 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_crc.h" +#include "esp_async_crc_priv.h" + +ESP_LOG_ATTR_TAG(TAG, "async_crc"); + +#if SOC_HAS(AHB_GDMA) +esp_err_t esp_async_crc_install_gdma_ahb(const async_crc_config_t *config, async_crc_handle_t *crc_hdl) +{ + ESP_RETURN_ON_FALSE(config && crc_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return esp_async_crc_install_gdma_template(config, crc_hdl, gdma_new_ahb_channel, SOC_GDMA_BUS_AHB); +} +#endif // SOC_HAS(AHB_GDMA) + +#if SOC_HAS(AXI_GDMA) +esp_err_t esp_async_crc_install_gdma_axi(const async_crc_config_t *config, async_crc_handle_t *crc_hdl) +{ + ESP_RETURN_ON_FALSE(config && crc_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return esp_async_crc_install_gdma_template(config, crc_hdl, gdma_new_axi_channel, SOC_GDMA_BUS_AXI); +} +#endif // SOC_HAS(AXI_GDMA) + +esp_err_t esp_async_crc_uninstall(async_crc_handle_t crc_hdl) +{ + ESP_RETURN_ON_FALSE(crc_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return crc_hdl->del(crc_hdl); +} + +esp_err_t esp_async_crc_calc(async_crc_handle_t crc_hdl, const void *data, size_t size, + const async_crc_params_t *params, async_crc_isr_cb_t cb_isr, void *cb_args) +{ + ESP_RETURN_ON_FALSE(crc_hdl && data && size && params, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return crc_hdl->calc(crc_hdl, data, size, params, cb_isr, cb_args); +} + +typedef struct { + uint32_t *result; + SemaphoreHandle_t semaphore; +} crc_blocking_context_t; + +static bool crc_blocking_callback(async_crc_handle_t crc_hdl, async_crc_event_data_t *event, void *user_data) +{ + BaseType_t task_woken = pdFALSE; + crc_blocking_context_t *ctx = (crc_blocking_context_t *)user_data; + + *(ctx->result) = event->crc_result; + + // Give the semaphore to unblock the waiting task + xSemaphoreGiveFromISR(ctx->semaphore, &task_woken); + + return (task_woken == pdTRUE); // Yield if a higher priority task was woken +} + +esp_err_t esp_crc_calc_blocking(async_crc_handle_t crc_hdl, const void *data, size_t size, + const async_crc_params_t *params, int32_t timeout_ms, uint32_t *result) +{ + ESP_RETURN_ON_FALSE(crc_hdl && data && size && params && result, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(!xPortInIsrContext(), ESP_ERR_INVALID_STATE, TAG, "called from ISR context is not allowed"); + + crc_blocking_context_t ctx = { + .result = result, + .semaphore = xSemaphoreCreateBinary() + }; + ESP_RETURN_ON_FALSE(ctx.semaphore, ESP_ERR_NO_MEM, TAG, "create semaphore failed"); + + esp_err_t ret = esp_async_crc_calc(crc_hdl, data, size, params, crc_blocking_callback, &ctx); + if (ret != ESP_OK) { + vSemaphoreDelete(ctx.semaphore); + return ret; + } + + // Wait for completion with timeout (<0 means wait forever) + TickType_t ticks = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + if (xSemaphoreTake(ctx.semaphore, ticks) != pdTRUE) { + vSemaphoreDelete(ctx.semaphore); + return ESP_ERR_TIMEOUT; + } + + vSemaphoreDelete(ctx.semaphore); + return ESP_OK; +} diff --git a/components/esp_driver_dma/src/esp_async_crc_priv.h b/components/esp_driver_dma/src/esp_async_crc_priv.h new file mode 100644 index 00000000000..f4b550d4575 --- /dev/null +++ b/components/esp_driver_dma/src/esp_async_crc_priv.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "esp_async_crc.h" +#include "esp_private/esp_dma_utils.h" +#include "esp_private/gdma.h" +#include "esp_private/gdma_link.h" + +#define DEFAULT_TRANSACTION_QUEUE_LENGTH 8 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + CRC_FSM_IDLE, // CRC engine is in idle, ready for new request + CRC_FSM_RUN, // CRC engine is processing a request + CRC_FSM_WAIT, // intermediate state, for state transitions +} async_crc_fsm_t; + +typedef struct async_crc_context_t async_crc_context_t; + +struct async_crc_context_t { + /// @brief Start a new async CRC calculation + esp_err_t (*calc)(async_crc_context_t *ctx, const void *data, size_t size, + const async_crc_params_t *params, async_crc_isr_cb_t cb_isr, void *cb_args); + /// @brief Delete async CRC driver context + esp_err_t (*del)(async_crc_context_t *ctx); +}; + +// Template function for different GDMA types +typedef esp_err_t (*gdma_new_channel_t)(const gdma_channel_alloc_config_t *config, + gdma_channel_handle_t *ret_tx_chan, gdma_channel_handle_t *ret_rx_chan); + +esp_err_t esp_async_crc_install_gdma_template(const async_crc_config_t *config, async_crc_handle_t *crc_hdl, + gdma_new_channel_t new_channel_func, uint32_t gdma_bus_id); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_dma/src/esp_async_memcpy_priv.h b/components/esp_driver_dma/src/esp_async_memcpy_priv.h index 3b85b77a9e4..b84698dab8e 100644 --- a/components/esp_driver_dma/src/esp_async_memcpy_priv.h +++ b/components/esp_driver_dma/src/esp_async_memcpy_priv.h @@ -20,9 +20,8 @@ extern "C" { #endif typedef enum { - MCP_FSM_IDLE_WAIT, /// intermediate state, for state changes from others to IDLE + MCP_FSM_WAIT, /// intermediate state, for state changes between IDLE and RUN MCP_FSM_IDLE, - MCP_FSM_RUN_WAIT, /// intermediate state, for state changes from others to RUN MCP_FSM_RUN, } async_memcpy_fsm_t; diff --git a/components/esp_driver_dma/src/gdma_crc.c b/components/esp_driver_dma/src/gdma_crc.c index 649241ca535..d128f873c65 100644 --- a/components/esp_driver_dma/src/gdma_crc.c +++ b/components/esp_driver_dma/src/gdma_crc.c @@ -10,23 +10,16 @@ ESP_LOG_ATTR_TAG(TAG, "gdma"); esp_err_t gdma_config_crc_calculator(gdma_channel_handle_t dma_chan, const gdma_crc_calculator_config_t *config) { - ESP_RETURN_ON_FALSE(dma_chan && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + if (!dma_chan || !config) { + return ESP_ERR_INVALID_ARG; + } gdma_pair_t *pair = dma_chan->pair; gdma_group_t *group = pair->group; gdma_hal_context_t *hal = &group->hal; - switch (group->bus_id) { -#if SOC_HAS(AHB_GDMA) - case SOC_GDMA_BUS_AHB: - ESP_RETURN_ON_FALSE(config->crc_bit_width <= GDMA_LL_AHB_MAX_CRC_BIT_WIDTH, ESP_ERR_INVALID_ARG, TAG, "invalid crc bit width"); - break; -#endif // SOC_HAS(AHB_GDMA) -#if SOC_HAS(AXI_GDMA) - case SOC_GDMA_BUS_AXI: - ESP_RETURN_ON_FALSE(config->crc_bit_width <= GDMA_LL_AXI_MAX_CRC_BIT_WIDTH, ESP_ERR_INVALID_ARG, TAG, "invalid crc bit width"); - break; -#endif // SOC_HAS(AXI_GDMA) - default: - ESP_LOGE(TAG, "invalid bus id: %d", group->bus_id); + + // validate the crc bit width against GDMA bus type + uint32_t max_crc_bit_width = (group->bus_id == SOC_GDMA_BUS_AXI) ? GDMA_LL_AXI_MAX_CRC_BIT_WIDTH : GDMA_LL_AHB_MAX_CRC_BIT_WIDTH; + if (config->crc_bit_width > max_crc_bit_width) { return ESP_ERR_INVALID_ARG; } diff --git a/components/esp_driver_dma/test_apps/dma/main/CMakeLists.txt b/components/esp_driver_dma/test_apps/dma/main/CMakeLists.txt index a2c1e51c943..f3c9e24bd1a 100644 --- a/components/esp_driver_dma/test_apps/dma/main/CMakeLists.txt +++ b/components/esp_driver_dma/test_apps/dma/main/CMakeLists.txt @@ -12,7 +12,7 @@ if(CONFIG_SOC_GDMA_SUPPORTED) endif() if(CONFIG_SOC_GDMA_SUPPORT_CRC) - list(APPEND srcs "test_gdma_crc.c") + list(APPEND srcs "test_gdma_crc.c" "test_async_crc.c") endif() endif() diff --git a/components/esp_driver_dma/test_apps/dma/main/test_async_crc.c b/components/esp_driver_dma/test_apps/dma/main/test_async_crc.c new file mode 100644 index 00000000000..a6f17779d02 --- /dev/null +++ b/components/esp_driver_dma/test_apps/dma/main/test_async_crc.c @@ -0,0 +1,212 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#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 "esp_async_crc.h" +#include "esp_crc.h" + +#define TEST_ASYNC_CRC_BENCH_COUNTS 16 + +typedef struct { + uint32_t init_value; + uint32_t crc_bit_width; + uint32_t poly_hex; + bool reverse_data_mask; + bool reverse_result; + uint32_t final_xor; + uint32_t expected_result; +} test_crc_case_t; + +static void test_async_crc_various_poly(async_crc_handle_t driver) +{ + static const char test_input_string[] __attribute__((aligned(16))) = "GDMACRC Share::Connect::Innovate"; + // CRC online: https://www.lddgo.net/en/encrypt/crc + static test_crc_case_t crc_test_cases[] = { + { + .crc_bit_width = 8, + .init_value = 0x00, + .poly_hex = 0x07, + .expected_result = 0xB8, + }, + { + .crc_bit_width = 8, + .init_value = 0x10, + .poly_hex = 0x23, + .reverse_data_mask = true, + .reverse_result = true, + .final_xor = 0x1F, + .expected_result = 0xB7, + }, + { + .crc_bit_width = 16, + .init_value = 0xFFFF, + .poly_hex = 0x1021, + .expected_result = 0xA9B2, + }, + { + .crc_bit_width = 16, + .init_value = 0x1234, + .poly_hex = 0x8005, + .reverse_data_mask = true, + .reverse_result = true, + .final_xor = 0xABCD, + .expected_result = 0x9C6B, + } + }; + uint32_t result = 0; + for (int i = 0; i < sizeof(crc_test_cases) / sizeof(crc_test_cases[0]); i++) { + async_crc_params_t params = { + .width = crc_test_cases[i].crc_bit_width, + .polynomial = crc_test_cases[i].poly_hex, + .init_value = crc_test_cases[i].init_value, + .final_xor_value = crc_test_cases[i].final_xor, + .reverse_input = crc_test_cases[i].reverse_data_mask, + .reverse_output = crc_test_cases[i].reverse_result, + }; + + TEST_ESP_OK(esp_crc_calc_blocking(driver, test_input_string, strlen(test_input_string), ¶ms, -1, &result)); + printf("CRC Result: 0x%"PRIx32", Expected: 0x%"PRIx32"\r\n", result, crc_test_cases[i].expected_result); + TEST_ASSERT_EQUAL(crc_test_cases[i].expected_result, result); + } +} + +TEST_CASE("async_crc calculation with various CRC polynomials", "[async_crc]") +{ + async_crc_config_t config = { + .backlog = 1, + .dma_burst_size = 16, + }; + // also test multiple instances + async_crc_handle_t driver1 = NULL; + async_crc_handle_t driver2 = NULL; +#if SOC_HAS(AHB_GDMA) + printf("Testing async CRC calculation with various polynomials by AHB GDMA\r\n"); + TEST_ESP_OK(esp_async_crc_install_gdma_ahb(&config, &driver1)); + TEST_ESP_OK(esp_async_crc_install_gdma_ahb(&config, &driver2)); + test_async_crc_various_poly(driver1); + test_async_crc_various_poly(driver2); + TEST_ESP_OK(esp_async_crc_uninstall(driver1)); + TEST_ESP_OK(esp_async_crc_uninstall(driver2)); +#endif + +#if SOC_HAS(AXI_GDMA) + printf("Testing async CRC calculation with various polynomials by AXI GDMA\r\n"); + TEST_ESP_OK(esp_async_crc_install_gdma_axi(&config, &driver1)); + TEST_ESP_OK(esp_async_crc_install_gdma_axi(&config, &driver2)); + test_async_crc_various_poly(driver1); + test_async_crc_various_poly(driver2); + TEST_ESP_OK(esp_async_crc_uninstall(driver1)); + TEST_ESP_OK(esp_async_crc_uninstall(driver2)); +#endif +} + +typedef struct { + uint32_t crc_result; + SemaphoreHandle_t sem; +} crc_async_user_context_t; + +static bool test_async_crc_result_cb(async_crc_handle_t crc_hdl, async_crc_event_data_t *edata, void *cb_args) +{ + crc_async_user_context_t* user = (crc_async_user_context_t*)cb_args; + BaseType_t high_task_wakeup = pdFALSE; + user->crc_result = edata->crc_result; + xSemaphoreGiveFromISR(user->sem, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +static void test_async_crc_calc_with_callback(async_crc_handle_t driver) +{ + static const char test_input_string[] __attribute__((aligned(16))) = "GDMACRC Share::Connect::Innovate"; + SemaphoreHandle_t sem = xSemaphoreCreateBinary(); + crc_async_user_context_t user_ctx = { + .sem = sem, + }; + + async_crc_params_t params = { + .width = 16, + .polynomial = 0x2025, + .init_value = 0x00, + .final_xor_value = 0x00, + }; + + TEST_ESP_OK(esp_async_crc_calc(driver, test_input_string, strlen(test_input_string), ¶ms, test_async_crc_result_cb, &user_ctx)); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(sem, pdMS_TO_TICKS(100))); + printf("CRC Result: 0x%"PRIx32", Expected: 0x9D1B\r\n", user_ctx.crc_result); + TEST_ASSERT_EQUAL(0x9D1B, user_ctx.crc_result); + vSemaphoreDelete(sem); +} + +TEST_CASE("async_crc calculation with callback", "[async_crc]") +{ + async_crc_config_t config = { + .backlog = 1, + .dma_burst_size = 16, + }; + async_crc_handle_t driver = NULL; +#if SOC_HAS(AHB_GDMA) + printf("Testing async CRC calculation with callback by AHB GDMA\r\n"); + TEST_ESP_OK(esp_async_crc_install_gdma_ahb(&config, &driver)); + test_async_crc_calc_with_callback(driver); + TEST_ESP_OK(esp_async_crc_uninstall(driver)); +#endif + +#if SOC_HAS(AXI_GDMA) + printf("Testing async CRC calculation with callback by AXI GDMA\r\n"); + TEST_ESP_OK(esp_async_crc_install_gdma_axi(&config, &driver)); + test_async_crc_calc_with_callback(driver); + TEST_ESP_OK(esp_async_crc_uninstall(driver)); +#endif +} + +static void test_async_crc_multiple_requests(async_crc_handle_t driver) +{ + static const char test_input_string[] __attribute__((aligned(16))) = "GDMACRC Share::Connect::Innovate"; + SemaphoreHandle_t sem = xSemaphoreCreateCounting(TEST_ASYNC_CRC_BENCH_COUNTS, 0); + crc_async_user_context_t user_ctx = { + .sem = sem, + }; + async_crc_params_t params = { + .width = 32, + .polynomial = 0x04C11DB7, + .init_value = 0xFFFFFFFF, + .final_xor_value = 0x00, + }; + + for (int i = 0; i < TEST_ASYNC_CRC_BENCH_COUNTS; i++) { + TEST_ESP_OK(esp_async_crc_calc(driver, test_input_string, strlen(test_input_string), ¶ms, test_async_crc_result_cb, &user_ctx)); + } + + for (int i = 0; i < TEST_ASYNC_CRC_BENCH_COUNTS; i++) { + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(sem, pdMS_TO_TICKS(100))); + } + printf("CRC Result of request 0x%"PRIx32"\r\n", user_ctx.crc_result); + TEST_ASSERT_EQUAL(0x692F6C7E, user_ctx.crc_result); + vSemaphoreDelete(sem); +}; + +TEST_CASE("async_crc multiple requests", "[async_crc]") +{ + async_crc_config_t config = { + .backlog = TEST_ASYNC_CRC_BENCH_COUNTS, + .dma_burst_size = 32, + }; + async_crc_handle_t driver = NULL; +#if SOC_HAS(AHB_GDMA) + printf("Testing async CRC performance benchmark by AHB GDMA\r\n"); + TEST_ESP_OK(esp_async_crc_install_gdma_ahb(&config, &driver)); + test_async_crc_multiple_requests(driver); + TEST_ESP_OK(esp_async_crc_uninstall(driver)); +#endif +}