feat(crc): added async CRC driver based on GDMA peripheral

This commit is contained in:
morris
2026-02-13 09:57:25 +08:00
parent 97d9585357
commit 2712bb2339
13 changed files with 967 additions and 27 deletions
+1 -1
View File
@@ -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()
+2 -2
View File
@@ -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"
@@ -0,0 +1,157 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#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
@@ -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 */
@@ -0,0 +1,444 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdatomic.h>
#include <sys/queue.h>
#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;
}
@@ -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) {
@@ -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);
@@ -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;
}
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#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
@@ -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;
+7 -14
View File
@@ -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;
}
@@ -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()
@@ -0,0 +1,212 @@
/*
* SPDX-FileCopyrightText: 2025-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 "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), &params, -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), &params, 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), &params, 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
}