feat(parlio_rx): support to receive in isr context

This commit is contained in:
laokaiyao
2025-07-02 16:51:19 +08:00
parent 5812b19e91
commit f0c45b7115
4 changed files with 376 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -161,7 +161,8 @@ typedef struct {
interrupt, if the data length is set to `0`, that mean the EOF will only triggers
when the end pulse detected, please ensure there is an end pulse for a frame and
`parlio_rx_pulse_delimiter_config_t::has_end_pulse` flag is set */
uint32_t timeout_ticks; /*!< The number of APB clock ticks to trigger timeout interrupt. Set 0 to disable the receive timeout interrupt */
uint32_t timeout_ticks; /*!< The number of APB clock ticks to trigger timeout interrupt. Set 0 to disable the receive timeout interrupt
The timeout counter starts when soft delimiter is stopped but the data is still not enough for EOF. */
} parlio_rx_soft_delimiter_config_t;
/**
@@ -270,6 +271,29 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
size_t payload_size,
const parlio_receive_config_t* recv_cfg);
/**
* @brief Receive data by Parallel IO RX unit in ISR context (e.g. inside a callback function)
* @note This function should only be called in ISR context, and the callback function should not block.
* @note The payload buffer should be accessible in ISR context.
* The payload buffer will be sent to the tail of the transaction queue.
*
* @param[in] rx_unit Parallel IO RX unit handle that created by `parlio_new_rx_unit`
* @param[in] payload The payload buffer pointer
* @param[in] payload_size The size of the payload buffer, in bytes.
* @param[in] recv_cfg The configuration of this receive transaction
* @param[out] hp_task_woken Whether the high priority task is woken (Optional, set NULL if not needed)
* @return
* - ESP_OK: success to queue the transaction
* - ESP_FAIL: failed to queue the transaction since the queue is full
* - ESP_ERR_INVALID_ARG: invalid arguments, some conditions are not met
* - ESP_ERR_INVALID_STATE: invalid state
*/
esp_err_t parlio_rx_unit_receive_from_isr(parlio_rx_unit_handle_t rx_unit,
void *payload,
size_t payload_size,
const parlio_receive_config_t* recv_cfg,
bool *hp_task_woken);
/**
* @brief Wait for all pending RX transactions done
* @note This function will block until all receiving transactions done or timeout.

View File

@@ -14,6 +14,7 @@ entries:
parlio_rx: parlio_rx_default_desc_done_callback (noflash)
parlio_rx: parlio_rx_mount_transaction_buffer (noflash)
parlio_rx: parlio_rx_set_delimiter_config (noflash)
parlio_rx: parlio_rx_unit_receive_from_isr (noflash)
[mapping:parlio_driver_gdma_link]
archive: libesp_hw_support.a
@@ -25,3 +26,9 @@ entries:
if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION = y:
gdma_link: gdma_link_get_buffer (noflash)
[mapping:parlio_driver_soc_periph]
archive: libsoc.a
entries:
if PARLIO_RX_ISR_HANDLER_IN_IRAM = y:
parlio_periph: parlio_periph_signals (noflash)

View File

@@ -73,7 +73,7 @@ typedef struct parlio_rx_unit_t {
size_t desc_size; /*!< DMA descriptors total size */
parlio_dma_desc_t **dma_descs; /*!< DMA descriptor array pointer */
parlio_dma_desc_t *curr_desc; /*!< The pointer of the current descriptor */
void *usr_recv_buf; /*!< The pointe to the user's receiving buffer */
void *usr_recv_buf; /*!< The point to the user's receiving buffer */
/* Infinite transaction specific */
void *dma_buf; /*!< Additional internal DMA buffer only for infinite transactions */
@@ -115,7 +115,8 @@ typedef struct parlio_rx_delimiter_t {
} flags;
} parlio_rx_delimiter_t;
#define PRALIO_RX_MOUNT_SIZE_CALC(total_size, div, align) ((((total_size) / (align)) / (div)) * (align))
#define PARLIO_RX_MOUNT_SIZE_CALC(total_size, div, align) ((((total_size) / (align)) / (div)) * (align))
#define PARLIO_RX_CHECK_ISR(condition, err) if (!(condition)) { return err; }
static portMUX_TYPE s_rx_spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
@@ -150,11 +151,11 @@ size_t parlio_rx_mount_transaction_buffer(parlio_rx_unit_handle_t rx_unit, parli
size_t rest_size = trans->size - offset;
if (rest_size >= 2 * PARLIO_MAX_ALIGNED_DMA_BUF_SIZE) {
mount_size = PRALIO_RX_MOUNT_SIZE_CALC(trans->size, desc_num, alignment);
mount_size = PARLIO_RX_MOUNT_SIZE_CALC(trans->size, desc_num, alignment);
} else if (rest_size <= PARLIO_MAX_ALIGNED_DMA_BUF_SIZE) {
mount_size = (desc_num == 2) && (i == 0) ? PRALIO_RX_MOUNT_SIZE_CALC(rest_size, 2, alignment) : rest_size;
mount_size = (desc_num == 2) && (i == 0) ? PARLIO_RX_MOUNT_SIZE_CALC(rest_size, 2, alignment) : rest_size;
} else {
mount_size = PRALIO_RX_MOUNT_SIZE_CALC(rest_size, 2, alignment);
mount_size = PARLIO_RX_MOUNT_SIZE_CALC(rest_size, 2, alignment);
}
p_desc[i]->buffer = (void *)((uint8_t *)trans->payload + offset);
p_desc[i]->dw0.size = mount_size;
@@ -716,6 +717,10 @@ esp_err_t parlio_rx_unit_enable(parlio_rx_unit_handle_t rx_unit, bool reset_queu
} else if (xQueueReceive(rx_unit->trans_que, &trans, 0) == pdTRUE) {
// The semaphore always supposed to be taken successfully
assert(xSemaphoreTake(rx_unit->trans_sem, 0) == pdTRUE);
if (trans.flags.indirect_mount && trans.flags.infinite && rx_unit->dma_buf == NULL) {
rx_unit->dma_buf = heap_caps_aligned_calloc(rx_unit->base.group->dma_align, 1, trans.size, PARLIO_DMA_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(rx_unit->dma_buf, ESP_ERR_NO_MEM, err, TAG, "No memory for the internal DMA buffer");
}
if (rx_unit->cfg.flags.free_clk) {
PARLIO_CLOCK_SRC_ATOMIC() {
parlio_ll_rx_enable_clock(hal->regs, false);
@@ -994,6 +999,62 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
return ret;
}
esp_err_t parlio_rx_unit_receive_from_isr(parlio_rx_unit_handle_t rx_unit,
void *payload,
size_t payload_size,
const parlio_receive_config_t* recv_cfg,
bool *hp_task_woken)
{
PARLIO_RX_CHECK_ISR(rx_unit && payload && recv_cfg, ESP_ERR_INVALID_ARG);
PARLIO_RX_CHECK_ISR(recv_cfg->delimiter, ESP_ERR_INVALID_ARG);
PARLIO_RX_CHECK_ISR(payload_size <= rx_unit->max_recv_size, ESP_ERR_INVALID_ARG);
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
uint32_t alignment = rx_unit->base.group->dma_align;
PARLIO_RX_CHECK_ISR(payload_size % alignment == 0, ESP_ERR_INVALID_ARG);
if (recv_cfg->flags.partial_rx_en) {
PARLIO_RX_CHECK_ISR(payload_size >= 2 * alignment, ESP_ERR_INVALID_ARG);
}
#endif
#if CONFIG_PARLIO_RX_ISR_CACHE_SAFE
PARLIO_RX_CHECK_ISR(esp_ptr_internal(payload), ESP_ERR_INVALID_ARG);
#else
PARLIO_RX_CHECK_ISR(recv_cfg->flags.indirect_mount || esp_ptr_internal(payload), ESP_ERR_INVALID_ARG);
#endif
if (recv_cfg->delimiter->eof_data_len) {
PARLIO_RX_CHECK_ISR(payload_size >= recv_cfg->delimiter->eof_data_len, ESP_ERR_INVALID_ARG);
}
if (recv_cfg->delimiter->mode != PARLIO_RX_SOFT_MODE) {
PARLIO_RX_CHECK_ISR(rx_unit->cfg.valid_gpio_num >= 0, ESP_ERR_INVALID_ARG);
/* Check if the valid_sig_line_id is equal or greater than data width, otherwise valid_sig_line_id is conflict with data signal.
* Specifically, level or pulse delimiter requires one data line as valid signal, so these two delimiters can't support PARLIO_RX_UNIT_MAX_DATA_WIDTH */
PARLIO_RX_CHECK_ISR(recv_cfg->delimiter->valid_sig_line_id >= rx_unit->cfg.data_width, ESP_ERR_INVALID_ARG);
/* Assign the signal here to ensure iram safe */
recv_cfg->delimiter->valid_sig = parlio_periph_signals.groups[rx_unit->base.group->group_id].
rx_units[rx_unit->base.unit_id].
data_sigs[recv_cfg->delimiter->valid_sig_line_id];
}
void *p_buffer = payload;
if (recv_cfg->flags.partial_rx_en && recv_cfg->flags.indirect_mount) {
/* The internal DMA buffer should be allocated before calling this function */
PARLIO_RX_CHECK_ISR(rx_unit->dma_buf, ESP_ERR_INVALID_STATE);
p_buffer = rx_unit->dma_buf;
}
/* Create the transaction */
parlio_rx_transaction_t transaction = {
.delimiter = recv_cfg->delimiter,
.payload = p_buffer,
.size = payload_size,
.recv_bytes = 0,
.flags.infinite = recv_cfg->flags.partial_rx_en,
.flags.indirect_mount = recv_cfg->flags.indirect_mount,
};
rx_unit->usr_recv_buf = payload;
return xQueueSendFromISR(rx_unit->trans_que, &transaction, hp_task_woken) == pdTRUE ? ESP_OK : ESP_FAIL;
}
esp_err_t parlio_rx_unit_wait_all_done(parlio_rx_unit_handle_t rx_unit, int timeout_ms)
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -591,3 +591,279 @@ TEST_CASE("parallel_rx_unit_receive_timeout_test", "[parlio_rx]")
TEST_ESP_OK(gpio_reset_pin(TEST_VALID_GPIO));
free(payload);
}
typedef struct {
uint32_t partial_recv_cnt;
uint32_t recv_done_cnt;
uint32_t timeout_cnt;
uint32_t isr_send_cnt;
uint32_t isr_send_success_cnt;
parlio_rx_unit_handle_t rx_unit;
parlio_rx_delimiter_handle_t delimiter;
uint8_t *isr_payload;
size_t isr_payload_size;
bool enable_isr_send;
} test_isr_data_t;
TEST_PARLIO_CALLBACK_ATTR
static bool test_parlio_rx_isr_done_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_data)
{
bool hp_task_woken = false;
test_isr_data_t *test_data = (test_isr_data_t *)user_data;
test_data->recv_done_cnt++;
// Call parlio_rx_unit_receive_from_isr in ISR context to queue new receive transactions
if (test_data->enable_isr_send && test_data->isr_send_cnt < 3) {
test_data->isr_send_cnt++;
parlio_receive_config_t recv_config = {
.delimiter = test_data->delimiter,
.flags.partial_rx_en = false,
};
esp_err_t ret = parlio_rx_unit_receive_from_isr(test_data->rx_unit,
test_data->isr_payload,
test_data->isr_payload_size,
&recv_config,
&hp_task_woken);
if (ret == ESP_OK) {
test_data->isr_send_success_cnt++;
}
}
return hp_task_woken;
}
TEST_PARLIO_CALLBACK_ATTR
static bool test_parlio_rx_isr_partial_recv_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_data)
{
test_isr_data_t *test_data = (test_isr_data_t *)user_data;
test_data->partial_recv_cnt++;
return false;
}
TEST_CASE("parallel_rx_unit_receive_isr_test", "[parlio_rx]")
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_delimiter_handle_t deli = NULL;
// Create RX unit
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
config.flags.free_clk = 1;
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
// Create soft delimiter
parlio_rx_soft_delimiter_config_t sft_deli_cfg = {
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.eof_data_len = 1024, // Use smaller data length for testing convenience
.timeout_ticks = 0,
};
TEST_ESP_OK(parlio_new_rx_soft_delimiter(&sft_deli_cfg, &deli));
// Prepare test data structure
test_isr_data_t test_data = {
.partial_recv_cnt = 0,
.recv_done_cnt = 0,
.timeout_cnt = 0,
.isr_send_cnt = 0,
.isr_send_success_cnt = 0,
.rx_unit = rx_unit,
.delimiter = deli,
.enable_isr_send = false,
};
// Allocate DMA compatible buffers
uint32_t alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
alignment = alignment < 4 ? 4 : alignment;
size_t payload_size = ALIGN_UP(1024, alignment);
uint8_t *payload1 = heap_caps_aligned_calloc(alignment, 1, payload_size, TEST_PARLIO_DMA_MEM_ALLOC_CAPS);
uint8_t *payload2 = heap_caps_aligned_calloc(alignment, 1, payload_size, TEST_PARLIO_DMA_MEM_ALLOC_CAPS);
TEST_ASSERT(payload1 && payload2);
test_data.isr_payload = payload2;
test_data.isr_payload_size = payload_size;
// Register callback functions
parlio_rx_event_callbacks_t cbs = {
.on_partial_receive = test_parlio_rx_isr_partial_recv_callback,
.on_receive_done = test_parlio_rx_isr_done_callback,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, &test_data));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
printf("Testing basic ISR receive functionality...\n");
// Start soft delimiter
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
// Send first transaction
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.partial_rx_en = false,
};
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload1, payload_size, &recv_config));
// Wait for first transaction to complete
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
// Verify first transaction completed
TEST_ASSERT_EQUAL_UINT32(1, test_data.recv_done_cnt);
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(1, test_data.partial_recv_cnt);
// Reset counters and enable ISR sending
memset(&test_data, 0, sizeof(test_isr_data_t));
test_data.rx_unit = rx_unit;
test_data.delimiter = deli;
test_data.isr_payload = payload2;
test_data.isr_payload_size = payload_size;
test_data.enable_isr_send = true;
printf("Testing queuing new transactions in ISR callback...\n");
// Send initial transaction, which will trigger ISR sending in callback
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload1, payload_size, &recv_config));
// Wait for all transactions to complete (including transactions queued in ISR)
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 10000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
printf("ISR send_cnt: %"PRIu32", success_cnt: %"PRIu32"\n",
test_data.isr_send_cnt, test_data.isr_send_success_cnt);
printf("recv_done_cnt: %"PRIu32", partial_recv_cnt: %"PRIu32"\n",
test_data.recv_done_cnt, test_data.partial_recv_cnt);
// Verify ISR sending functionality
TEST_ASSERT_GREATER_THAN_UINT32(0, test_data.isr_send_cnt);
TEST_ASSERT_EQUAL_UINT32(test_data.isr_send_cnt, test_data.isr_send_success_cnt);
// Verify total receive done count = 1 (initial) + ISR success send count
TEST_ASSERT_EQUAL_UINT32(1 + test_data.isr_send_success_cnt, test_data.recv_done_cnt);
// Verify partial receive count should be at least equal to receive done count
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(test_data.recv_done_cnt, test_data.partial_recv_cnt);
printf("Test completed: ISR receive functionality works\n");
// Clean up resources
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
free(payload1);
free(payload2);
}
TEST_PARLIO_CALLBACK_ATTR
static bool test_parlio_rx_isr_partial_recv_callback2(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_data)
{
bool hp_task_woken = false;
test_isr_data_t *test_data = (test_isr_data_t *)user_data;
if (test_data->isr_send_success_cnt == 0) {
parlio_receive_config_t recv_config = {
.delimiter = test_data->delimiter,
.flags.partial_rx_en = true,
.flags.indirect_mount = true,
};
esp_err_t ret = parlio_rx_unit_receive_from_isr(test_data->rx_unit,
test_data->isr_payload,
test_data->isr_payload_size,
&recv_config,
&hp_task_woken);
if (ret == ESP_OK) {
test_data->isr_send_success_cnt++;
}
}
test_data->partial_recv_cnt++;
return hp_task_woken;
}
TEST_CASE("parallel_rx_unit_infinite_transaction_switch_test", "[parlio_rx]")
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_delimiter_handle_t deli = NULL;
// Create RX unit
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
config.flags.free_clk = 1;
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
// Create soft delimiter
parlio_rx_soft_delimiter_config_t sft_deli_cfg = {
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.eof_data_len = 1024, // Use smaller data length for testing convenience
.timeout_ticks = 0,
};
TEST_ESP_OK(parlio_new_rx_soft_delimiter(&sft_deli_cfg, &deli));
// Prepare test data structure
test_isr_data_t test_data = {
.partial_recv_cnt = 0,
.recv_done_cnt = 0,
.timeout_cnt = 0,
.isr_send_cnt = 0,
.isr_send_success_cnt = 0,
.rx_unit = rx_unit,
.delimiter = deli,
.enable_isr_send = false,
};
// Allocate DMA compatible buffers
uint32_t alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
alignment = alignment < 4 ? 4 : alignment;
size_t payload_size = ALIGN_UP(1024, alignment);
uint8_t *payload1 = heap_caps_aligned_calloc(alignment, 1, payload_size, TEST_PARLIO_DMA_MEM_ALLOC_CAPS);
uint8_t *payload2 = heap_caps_aligned_calloc(alignment, 1, payload_size, TEST_PARLIO_DMA_MEM_ALLOC_CAPS);
TEST_ASSERT(payload1 && payload2);
test_data.isr_payload = payload2;
test_data.isr_payload_size = payload_size;
// Register callback functions
parlio_rx_event_callbacks_t cbs = {
.on_partial_receive = test_parlio_rx_isr_partial_recv_callback2,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, &test_data));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
printf("Testing basic ISR receive functionality...\n");
// Start soft delimiter
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
// Send first transaction
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.partial_rx_en = true,
.flags.indirect_mount = true,
};
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload1, payload_size, &recv_config));
// Wait for first transaction to complete
vTaskDelay(pdMS_TO_TICKS(100));
// Verify first transaction completed
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(1, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(1, test_data.isr_send_success_cnt);
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
test_data.partial_recv_cnt = 0;
test_data.isr_send_success_cnt = 0;
// Do not reset queue, so the last transaction can be resumed after enable
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, false));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(1, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(1, test_data.isr_send_success_cnt);
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
free(payload1);
free(payload2);
}