mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-03 03:31:41 +03:00
feat(crc): added async CRC driver based on GDMA peripheral
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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), ¶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
|
||||
}
|
||||
Reference in New Issue
Block a user