diff --git a/components/esp_driver_parlio/include/driver/parlio_rx.h b/components/esp_driver_parlio/include/driver/parlio_rx.h index 022bd6578c..4f7c37716e 100644 --- a/components/esp_driver_parlio/include/driver/parlio_rx.h +++ b/components/esp_driver_parlio/include/driver/parlio_rx.h @@ -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. diff --git a/components/esp_driver_parlio/linker.lf b/components/esp_driver_parlio/linker.lf index 76b5b7f0cf..900591f105 100644 --- a/components/esp_driver_parlio/linker.lf +++ b/components/esp_driver_parlio/linker.lf @@ -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) diff --git a/components/esp_driver_parlio/src/parlio_rx.c b/components/esp_driver_parlio/src/parlio_rx.c index 1ca177cdb2..12e7cb3755 100644 --- a/components/esp_driver_parlio/src/parlio_rx.c +++ b/components/esp_driver_parlio/src/parlio_rx.c @@ -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"); diff --git a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c index e7238656bf..0dc56842f3 100644 --- a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c +++ b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c @@ -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); +}