diff --git a/components/openthread/private_include/esp_spi_spinel_interface.hpp b/components/openthread/private_include/esp_spi_spinel_interface.hpp index e5b0976e1fd..47a54628fd7 100644 --- a/components/openthread/private_include/esp_spi_spinel_interface.hpp +++ b/components/openthread/private_include/esp_spi_spinel_interface.hpp @@ -168,6 +168,7 @@ private: esp_openthread_spi_host_config_t m_spi_config; uint8_t m_tx_buffer[kSPIFrameSize]; + uint8_t *m_rx_dma_buf; ///< DMA-aligned RX buffer; avoids cache-coherency issues with unaligned frame buffer int m_event_fd; volatile uint16_t m_pending_data_len; diff --git a/components/openthread/src/esp_openthread.cpp b/components/openthread/src/esp_openthread.cpp index 81ae923834a..2eb9d39a785 100644 --- a/components/openthread/src/esp_openthread.cpp +++ b/components/openthread/src/esp_openthread.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -80,19 +80,20 @@ esp_err_t esp_openthread_init(const esp_openthread_platform_config_t *config) ESP_RETURN_ON_ERROR(esp_openthread_platform_init(config), OT_PLAT_LOG_TAG, "Failed to initialize OpenThread platform driver"); esp_openthread_lock_acquire(portMAX_DELAY); - ESP_RETURN_ON_FALSE(otInstanceInitSingle() != NULL, ESP_FAIL, OT_PLAT_LOG_TAG, - "Failed to initialize OpenThread instance"); + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(otInstanceInitSingle() != NULL, ESP_FAIL, exit, OT_PLAT_LOG_TAG, + "Failed to initialize OpenThread instance"); #if CONFIG_OPENTHREAD_DNS64_CLIENT - ESP_RETURN_ON_ERROR(esp_openthread_dns64_client_init(), OT_PLAT_LOG_TAG, - "Failed to initialize OpenThread dns64 client"); + ESP_GOTO_ON_ERROR(esp_openthread_dns64_client_init(), exit, OT_PLAT_LOG_TAG, + "Failed to initialize OpenThread dns64 client"); #endif #if !CONFIG_OPENTHREAD_RADIO - ESP_RETURN_ON_ERROR(esp_openthread_state_event_init(esp_openthread_get_instance()), OT_PLAT_LOG_TAG, - "Failed to initialize OpenThread state event"); + ESP_GOTO_ON_ERROR(esp_openthread_state_event_init(esp_openthread_get_instance()), exit, OT_PLAT_LOG_TAG, + "Failed to initialize OpenThread state event"); #endif +exit: esp_openthread_lock_release(); - - return ESP_OK; + return ret; } esp_err_t esp_openthread_auto_start(otOperationalDatasetTlvs *datasetTlvs) @@ -140,7 +141,7 @@ esp_err_t esp_openthread_auto_start(otOperationalDatasetTlvs *datasetTlvs) memcpy(dataset.mMeshLocalPrefix.m8, prefix.mPrefix.mFields.m8, sizeof(dataset.mMeshLocalPrefix.m8)); dataset.mComponents.mIsMeshLocalPrefixPresent = true; } else { - ESP_LOGE("Failed to parse mesh local prefix", CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX); + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to parse mesh local prefix: %s", CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX); } // Network Key diff --git a/components/openthread/src/esp_openthread_cli.c b/components/openthread/src/esp_openthread_cli.c index 8a831022644..1a206046725 100644 --- a/components/openthread/src/esp_openthread_cli.c +++ b/components/openthread/src/esp_openthread_cli.c @@ -31,8 +31,12 @@ static int cli_output_callback(void *context, const char *format, va_list args) { char prompt_check[3]; int ret = 0; + va_list args_copy; + + va_copy(args_copy, args); + vsnprintf(prompt_check, sizeof(prompt_check), format, args_copy); + va_end(args_copy); - vsnprintf(prompt_check, sizeof(prompt_check), format, args); if (!strncmp(prompt_check, "> ", sizeof(prompt_check)) && s_cli_task) { xTaskNotifyGive(s_cli_task); } else { @@ -60,7 +64,11 @@ esp_err_t esp_openthread_cli_input(const char *line) ESP_RETURN_ON_FALSE(line_copy != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "Failed to copy OpenThread CLI line input"); - return esp_openthread_task_queue_post(line_handle_task, line_copy); + esp_err_t ret = esp_openthread_task_queue_post(line_handle_task, line_copy); + if (ret != ESP_OK) { + free(line_copy); + } + return ret; } static int ot_cli_console_callback(int argc, char **argv) @@ -102,6 +110,11 @@ static void ot_cli_loop(void *context) console_config.hint_color = -1; ret = esp_console_init(&console_config); + if (ret != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to initialize console: %s", esp_err_to_name(ret)); + vTaskDelete(NULL); + return; + } linenoiseSetMultiLine(true); linenoiseHistorySetMaxLen(100); @@ -141,9 +154,8 @@ static void ot_cli_loop(void *context) } } -void esp_openthread_cli_create_task() +void esp_openthread_cli_create_task(void) { - xTaskCreate(ot_cli_loop, "ot_cli", 4096, xTaskGetCurrentTaskHandle(), 4, &s_cli_task); - - return; + BaseType_t ret = xTaskCreate(ot_cli_loop, "ot_cli", 4096, xTaskGetCurrentTaskHandle(), 4, &s_cli_task); + ESP_RETURN_ON_FALSE(ret == pdPASS, , OT_PLAT_LOG_TAG, "Failed to create OpenThread CLI task"); } diff --git a/components/openthread/src/esp_openthread_dns64.c b/components/openthread/src/esp_openthread_dns64.c index 442ec4308f0..2659b1a4510 100644 --- a/components/openthread/src/esp_openthread_dns64.c +++ b/components/openthread/src/esp_openthread_dns64.c @@ -107,6 +107,7 @@ static void dns_found_handler(const char *name, const ip_addr_t *ipaddr, void *c if (resolve_entry && resolve_entry->found) { if (!ipaddr) { resolve_entry->found(name, NULL, resolve_entry->callback_arg); + resolve_entry->is_using = false; } else if (lwip_strnicmp(name, resolve_entry->name, sizeof(resolve_entry->name)) == 0) { ip_addr_t ipaddr_copy = *ipaddr; ip6_addr_t nat64_prefix; @@ -117,8 +118,8 @@ static void dns_found_handler(const char *name, const ip_addr_t *ipaddr, void *c ipaddr_copy.u_addr.ip6.zone = IP6_NO_ZONE; } resolve_entry->found(name, &ipaddr_copy, resolve_entry->callback_arg); + resolve_entry->is_using = false; } - resolve_entry->is_using = false; } } diff --git a/components/openthread/src/esp_openthread_lock.c b/components/openthread/src/esp_openthread_lock.c index 7e35da6c21b..322c00c1f73 100644 --- a/components/openthread/src/esp_openthread_lock.c +++ b/components/openthread/src/esp_openthread_lock.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -20,9 +20,14 @@ bool esp_openthread_lock_acquire(TickType_t block_ticks) { ESP_RETURN_ON_FALSE(s_openthread_mutex && s_openthread_task_mutex, false, OT_PLAT_LOG_TAG, "Failed to acquire the lock because the mutex is not ready"); - BaseType_t ret = xSemaphoreTakeRecursive(s_openthread_mutex, block_ticks) && - xSemaphoreTakeRecursive(s_openthread_task_mutex, block_ticks); - return (ret == pdTRUE); + if (xSemaphoreTakeRecursive(s_openthread_mutex, block_ticks) != pdTRUE) { + return false; + } + if (xSemaphoreTakeRecursive(s_openthread_task_mutex, block_ticks) != pdTRUE) { + xSemaphoreGiveRecursive(s_openthread_mutex); + return false; + } + return true; } void esp_openthread_lock_release(void) @@ -61,6 +66,7 @@ esp_err_t esp_openthread_lock_init(void) s_openthread_mutex = xSemaphoreCreateRecursiveMutex(); s_openthread_task_mutex = xSemaphoreCreateRecursiveMutex(); if (s_openthread_mutex == NULL || s_openthread_task_mutex == NULL) { + esp_openthread_lock_deinit(); return ESP_ERR_NO_MEM; } return ESP_OK; diff --git a/components/openthread/src/esp_openthread_netif_glue.c b/components/openthread/src/esp_openthread_netif_glue.c index 3af3cfd850a..fc371d48490 100644 --- a/components/openthread/src/esp_openthread_netif_glue.c +++ b/components/openthread/src/esp_openthread_netif_glue.c @@ -241,10 +241,8 @@ void esp_openthread_register_meshcop_e_handler(esp_event_handler_t handler, bool { if (for_publish) { meshcop_e_publish_handler = handler; - } else if (!for_publish) { - meshcop_e_remove_handler = handler; } else { - ESP_ERROR_CHECK(ESP_FAIL); + meshcop_e_remove_handler = handler; } } @@ -343,6 +341,8 @@ void *esp_openthread_netif_glue_init(const esp_openthread_platform_config_t *con s_openthread_netif_glue.event_fd = eventfd(0, 0); if (s_openthread_netif_glue.event_fd < 0) { ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to create event fd for Thread netif"); + vQueueDelete(s_packet_queue); + s_packet_queue = NULL; ExitNow(error = ESP_FAIL); } s_openthread_netif_glue.base.post_attach = openthread_netif_post_attach; diff --git a/components/openthread/src/esp_openthread_task_queue.c b/components/openthread/src/esp_openthread_task_queue.c index 9d9bf87e6f3..d656b77dde2 100644 --- a/components/openthread/src/esp_openthread_task_queue.c +++ b/components/openthread/src/esp_openthread_task_queue.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,8 +34,12 @@ esp_err_t esp_openthread_task_queue_init(const esp_openthread_platform_config_t ESP_RETURN_ON_FALSE(s_task_queue_event_fd >= 0, ESP_FAIL, OT_PLAT_LOG_TAG, "Failed to create OpenThread task queue event fd"); s_task_queue = xQueueCreate(config->port_config.task_queue_size, sizeof(task_storage_t)); - ESP_RETURN_ON_FALSE(s_task_queue != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, - "Failed to create OpenThread task queue"); + if (s_task_queue == NULL) { + close(s_task_queue_event_fd); + s_task_queue_event_fd = -1; + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to create OpenThread task queue"); + return ESP_ERR_NO_MEM; + } return esp_openthread_platform_workflow_register(&esp_openthread_task_queue_update, &esp_openthread_task_queue_process, task_queue_workflow); } diff --git a/components/openthread/src/port/esp_openthread_messagepool.c b/components/openthread/src/port/esp_openthread_messagepool.c index 7996af33408..37046033494 100644 --- a/components/openthread/src/port/esp_openthread_messagepool.c +++ b/components/openthread/src/port/esp_openthread_messagepool.c @@ -18,11 +18,14 @@ #define OT_MSGPOOL_NUM_BUFFERS_NO_PSRAM 65 static int s_buffer_pool_head = -1; +static uint16_t s_buffer_pool_size = 0; static otMessageBuffer **s_buffer_pool_pointer = NULL; static otMessageBuffer *s_buffer_pool = NULL; void otPlatMessagePoolInit(otInstance *aInstance, uint16_t aMinNumFreeBuffers, size_t aBufferSize) { + assert(aBufferSize % sizeof(otMessageBuffer) == 0); + uint16_t num_buffers = aMinNumFreeBuffers; if (!esp_psram_is_initialized() && num_buffers > OT_MSGPOOL_NUM_BUFFERS_NO_PSRAM) { @@ -41,6 +44,7 @@ void otPlatMessagePoolInit(otInstance *aInstance, uint16_t aMinNumFreeBuffers, s s_buffer_pool_pointer[i] = buffer_pool + i * aBufferSize / sizeof(otMessageBuffer); } s_buffer_pool_head = num_buffers - 1; + s_buffer_pool_size = num_buffers; s_buffer_pool = buffer_pool; ESP_LOGI(OT_PLAT_LOG_TAG, "Create message buffer pool successfully, size %d", num_buffers * aBufferSize); } @@ -57,6 +61,7 @@ otMessageBuffer *otPlatMessagePoolNew(otInstance *aInstance) void otPlatMessagePoolFree(otInstance *aInstance, otMessageBuffer *aBuffer) { + assert(s_buffer_pool_head + 1 < s_buffer_pool_size); s_buffer_pool_head++; s_buffer_pool_pointer[s_buffer_pool_head] = aBuffer; } @@ -77,4 +82,5 @@ void otPlatMessagePoolDeinit(otInstance *aInstance) s_buffer_pool = NULL; } s_buffer_pool_head = -1; + s_buffer_pool_size = 0; } diff --git a/components/openthread/src/port/esp_openthread_radio.c b/components/openthread/src/port/esp_openthread_radio.c index fee5c36093a..4485b0bb06f 100644 --- a/components/openthread/src/port/esp_openthread_radio.c +++ b/components/openthread/src/port/esp_openthread_radio.c @@ -80,7 +80,7 @@ static uint32_t s_csl_sample_time; #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 static uint32_t s_mac_frame_counter; static uint8_t s_key_id; -static struct otMacKeyMaterial s_pervious_key; +static struct otMacKeyMaterial s_previous_key; static struct otMacKeyMaterial s_current_key; static struct otMacKeyMaterial s_next_key; static bool s_with_security_enh_ack = false; @@ -140,7 +140,7 @@ esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *conf void esp_openthread_radio_deinit(void) { - if (s_radio_event_fd > 0) { + if (s_radio_event_fd != -1) { close(s_radio_event_fd); s_radio_event_fd = -1; } @@ -492,7 +492,7 @@ void otPlatRadioSetMacKey(otInstance *aInstance, uint8_t aKeyIdMode, uint8_t aKe assert(aPrevKey != NULL && aCurrKey != NULL && aNextKey != NULL); s_key_id = aKeyId; - s_pervious_key = *aPrevKey; + s_previous_key = *aPrevKey; s_current_key = *aCurrKey; s_next_key = *aNextKey; } @@ -619,7 +619,7 @@ static esp_err_t IRAM_ATTR enh_ack_set_security_addr_and_key(otRadioFrame *ack_f if (key_id == s_key_id) { key = &s_current_key; } else if (key_id == s_key_id - 1) { - key = &s_pervious_key; + key = &s_previous_key; } else if (key_id == s_key_id + 1) { key = &s_next_key; } else { @@ -692,6 +692,7 @@ void IRAM_ATTR esp_ieee802154_receive_done(uint8_t *data, esp_ieee802154_frame_i if (atomic_load(&s_recv_queue.used) == CONFIG_IEEE802154_RX_BUFFER_SIZE) { ESP_EARLY_LOGE(OT_PLAT_LOG_TAG, "radio receive buffer full!"); + esp_ieee802154_receive_handle_done(data); return; } diff --git a/components/openthread/src/port/esp_openthread_radio_spinel.cpp b/components/openthread/src/port/esp_openthread_radio_spinel.cpp index b7892da9afa..f9695b23871 100644 --- a/components/openthread/src/port/esp_openthread_radio_spinel.cpp +++ b/components/openthread/src/port/esp_openthread_radio_spinel.cpp @@ -414,7 +414,11 @@ otError otPlatDiagProcess(otInstance *aInstance, uint8_t aArgsLength, char *aArg char *end = cmd + sizeof(cmd); for (int index = 0; index < aArgsLength; index++) { - cur += snprintf(cur, static_cast(end - cur), "%s ", aArgs[index]); + if (end > cur + strlen(aArgs[index])) { + cur += snprintf(cur, static_cast(end - cur), "%s ", aArgs[index]); + } else { + return OT_ERROR_INVALID_ARGS; + } } return s_radio.PlatDiagProcess(cmd); diff --git a/components/openthread/src/port/esp_openthread_settings.c b/components/openthread/src/port/esp_openthread_settings.c index e2cc930d267..c9f62f25aed 100644 --- a/components/openthread/src/port/esp_openthread_settings.c +++ b/components/openthread/src/port/esp_openthread_settings.c @@ -223,4 +223,5 @@ otError otPlatSettingsDelete(otInstance *aInstance, uint16_t aKey, int aIndex) void otPlatSettingsWipe(otInstance *aInstance) { nvs_erase_all(s_ot_nvs_handle); + nvs_commit(s_ot_nvs_handle); } diff --git a/components/openthread/src/port/esp_openthread_sleep.c b/components/openthread/src/port/esp_openthread_sleep.c index 8ca7504a6f6..8570ed5aa65 100644 --- a/components/openthread/src/port/esp_openthread_sleep.c +++ b/components/openthread/src/port/esp_openthread_sleep.c @@ -36,6 +36,7 @@ esp_err_t esp_openthread_sleep_init(void) void esp_openthread_sleep_process(void) { + assert(s_pm_lock != NULL); if (s_ot_sleep == false && esp_ieee802154_get_state() == ESP_IEEE802154_RADIO_SLEEP) { esp_pm_lock_release(s_pm_lock); s_ot_sleep = true; diff --git a/components/openthread/src/port/esp_openthread_spi_slave.c b/components/openthread/src/port/esp_openthread_spi_slave.c index 754300e5c9c..9daf496f5f8 100644 --- a/components/openthread/src/port/esp_openthread_spi_slave.c +++ b/components/openthread/src/port/esp_openthread_spi_slave.c @@ -1,17 +1,8 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ - -/* SPI Slave example, receiver (uses SPI Slave driver to communicate with sender) - - This example code is in the Public Domain (or CC0 licensed, at your option.) - - Unless required by applicable law or agreed to in writing, this - software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. -*/ #include #include "esp_attr.h" @@ -42,6 +33,15 @@ typedef struct { uint16_t input_buf_len; } pending_transaction_t; +// DMA bounce buffer for RX — always sized to max(input, output) so MISO is +// driven for the full output even when NcpSpi passes a small input buffer. +#define SPI_SLAVE_RX_DMA_BUF_SIZE OPENTHREAD_CONFIG_NCP_SPI_BUFFER_SIZE +static DRAM_ATTR uint8_t *s_rx_dma_buf = NULL; + +// Guards the BUSY path: only return OT_ERROR_BUSY when a transaction is truly +// queued in the driver, so post_trans_cb is guaranteed to fire and re-queue. +static volatile DRAM_ATTR bool s_transaction_in_flight = false; + static otPlatSpiSlaveTransactionProcessCallback s_process_callback = NULL; static otPlatSpiSlaveTransactionCompleteCallback s_complete_callback = NULL; @@ -59,54 +59,78 @@ static void IRAM_ATTR handle_spi_setup_done(spi_slave_transaction_t *trans) static void IRAM_ATTR handle_spi_transaction_done(spi_slave_transaction_t *trans) { gpio_set_level(s_spi_config->intr_pin, 1); + s_transaction_in_flight = false; pending_transaction_t *pending_transaction = (pending_transaction_t *)(trans->user); trans->trans_len /= CHAR_BIT; + // Cap trans_len: HW reports actual clock count which may exceed slave buffer. + uint16_t max_buf_len = pending_transaction->output_buf_len > pending_transaction->input_buf_len + ? pending_transaction->output_buf_len : pending_transaction->input_buf_len; + if (trans->trans_len > max_buf_len) { + trans->trans_len = max_buf_len; + } + + // Copy RX bounce buffer back to the actual NcpSpi input buffer. + if (s_input_buf && s_rx_dma_buf && s_rx_dma_buf != s_input_buf) { + memcpy(s_input_buf, s_rx_dma_buf, pending_transaction->input_buf_len); + } + if (s_complete_callback && s_complete_callback(s_context, (void*)trans->tx_buffer, pending_transaction->output_buf_len, - trans->rx_buffer, pending_transaction->input_buf_len, trans->trans_len)) { + s_input_buf, pending_transaction->input_buf_len, trans->trans_len)) { esp_openthread_task_queue_post(s_process_callback, s_context); } - trans = NULL; } esp_err_t esp_openthread_host_rcp_spi_init(const esp_openthread_platform_config_t *config) { + esp_err_t ret = ESP_OK; + s_spi_config = heap_caps_malloc(sizeof(esp_openthread_spi_slave_config_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - ESP_RETURN_ON_FALSE(s_spi_config != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, - "failed to allocate memory for SPI transaction on internal heap"); + ESP_GOTO_ON_FALSE(s_spi_config != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG, + "failed to allocate memory for SPI transaction on internal heap"); memcpy(s_spi_config, &(config->host_config.spi_slave_config), sizeof(esp_openthread_spi_slave_config_t)); gpio_config_t io_conf = { .intr_type = GPIO_INTR_DISABLE, .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = (1 << s_spi_config->intr_pin), + .pin_bit_mask = (1ULL << s_spi_config->intr_pin), }; - ESP_RETURN_ON_ERROR(gpio_config(&io_conf), OT_PLAT_LOG_TAG, "fail to configure SPI gpio"); + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, OT_PLAT_LOG_TAG, "fail to configure SPI gpio"); + // Deassert INT: GPIO latch resets to LOW after chip reset, which would + // cause a spurious NEGEDGE before the slave is ready. + gpio_set_level(s_spi_config->intr_pin, 1); gpio_set_pull_mode(s_spi_config->bus_config.mosi_io_num, GPIO_PULLUP_ONLY); gpio_set_pull_mode(s_spi_config->bus_config.sclk_io_num, GPIO_PULLUP_ONLY); gpio_set_pull_mode(s_spi_config->slave_config.spics_io_num, GPIO_PULLUP_ONLY); s_spi_transaction = heap_caps_malloc(sizeof(spi_slave_transaction_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(s_spi_transaction != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG, "failed to allocate memory for SPI transaction on internal heap"); s_pending_transaction = heap_caps_malloc(sizeof(pending_transaction_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - if (s_spi_transaction == NULL || s_pending_transaction == NULL) { - heap_caps_free(s_spi_config); - heap_caps_free(s_spi_transaction); - heap_caps_free(s_pending_transaction); - ESP_LOGE(OT_PLAT_LOG_TAG, "failed to allocate memory for SPI transaction on internal heap"); - return ESP_ERR_NO_MEM; - } + ESP_GOTO_ON_FALSE(s_pending_transaction != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG, "failed to allocate memory for pending transaction on internal heap"); + s_rx_dma_buf = heap_caps_malloc(SPI_SLAVE_RX_DMA_BUF_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + ESP_GOTO_ON_FALSE(s_rx_dma_buf != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG, "failed to allocate memory for RX DMA buffer on internal heap"); s_spi_transaction->user = (void *)s_pending_transaction; - /* Initialize SPI slave interface */ s_spi_config->slave_config.post_setup_cb = handle_spi_setup_done; s_spi_config->slave_config.post_trans_cb = handle_spi_transaction_done; - ESP_RETURN_ON_ERROR(spi_slave_initialize(s_spi_config->host_device, &s_spi_config->bus_config, - &s_spi_config->slave_config, SPI_DMA_CH_AUTO), - OT_PLAT_LOG_TAG, "fail to initialize SPI slave"); + ESP_GOTO_ON_ERROR(spi_slave_initialize(s_spi_config->host_device, &s_spi_config->bus_config, + &s_spi_config->slave_config, SPI_DMA_CH_AUTO), + err, OT_PLAT_LOG_TAG, "fail to initialize SPI slave"); return ESP_OK; + +err: + heap_caps_free(s_spi_config); + s_spi_config = NULL; + heap_caps_free(s_spi_transaction); + s_spi_transaction = NULL; + heap_caps_free(s_pending_transaction); + s_pending_transaction = NULL; + heap_caps_free(s_rx_dma_buf); + s_rx_dma_buf = NULL; + return ret; } void esp_openthread_spi_slave_deinit(void) @@ -117,9 +141,11 @@ void esp_openthread_spi_slave_deinit(void) heap_caps_free(s_spi_config); heap_caps_free(s_spi_transaction); heap_caps_free(s_pending_transaction); + heap_caps_free(s_rx_dma_buf); s_spi_config = NULL; s_spi_transaction = NULL; s_pending_transaction = NULL; + s_rx_dma_buf = NULL; return; } @@ -147,14 +173,21 @@ otError IRAM_ATTR otPlatSpiSlavePrepareTransaction(uint8_t *aOutputBuf, uint16_t s_input_len = aInputBufLen; } - trans_length = s_output_len > s_input_len ? s_output_len : s_input_len; - trans_length *= CHAR_BIT; - if ((gpio_get_level(s_spi_config->slave_config.spics_io_num) == 0)) { + // Use max(input, output) so MISO is driven for the full output frame; + // s_rx_dma_buf absorbs extra RX bytes to avoid overflowing the NcpSpi buffer. + uint16_t trans_data_len = (s_input_len > s_output_len) ? s_input_len : s_output_len; + trans_length = trans_data_len * CHAR_BIT; + + // In task context, return BUSY only when a transaction is already in flight + // AND CS is asserted — ensures post_trans_cb will fire to re-queue. + // In ISR context (post_trans_cb) we always queue unconditionally. + if (xPortCanYield() && s_transaction_in_flight && + (gpio_get_level(s_spi_config->slave_config.spics_io_num) == 0)) { ESP_EARLY_LOGE(SPI_SLAVE_TAG, "SPI busy"); return OT_ERROR_BUSY; } s_spi_transaction->length = trans_length; - s_spi_transaction->rx_buffer = s_input_buf; + s_spi_transaction->rx_buffer = s_rx_dma_buf; s_spi_transaction->tx_buffer = s_output_buf; pending_transaction_t *pending_transaction = (pending_transaction_t *)s_spi_transaction->user; @@ -171,6 +204,7 @@ otError IRAM_ATTR otPlatSpiSlavePrepareTransaction(uint8_t *aOutputBuf, uint16_t } if (trans_state == ESP_OK) { + s_transaction_in_flight = true; return OT_ERROR_NONE; } else { return OT_ERROR_FAILED; diff --git a/components/openthread/src/port/esp_openthread_trel.c b/components/openthread/src/port/esp_openthread_trel.c index 8eca69ce4e6..9b50504385d 100644 --- a/components/openthread/src/port/esp_openthread_trel.c +++ b/components/openthread/src/port/esp_openthread_trel.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -191,6 +191,8 @@ esp_err_t esp_openthread_trel_process(otInstance *aInstance, const esp_openthrea } if (should_handle) { otPlatTrelHandleReceived(aInstance, data_buf, length, source_addr); + s_trel_counters.mRxPackets++; + s_trel_counters.mRxBytes += length; } pbuf_free(recv_buf); free(data_buf_to_free); @@ -255,7 +257,13 @@ void otPlatTrelSend(otInstance *aInstance, esp_openthread_task_switching_lock_release(); err = esp_netif_tcpip_exec(trel_send_task, &task); esp_openthread_task_switching_lock_acquire(portMAX_DELAY); - ESP_RETURN_ON_FALSE(err == ESP_OK, , OT_PLAT_LOG_TAG, "Failed to send TREL message"); + if (err == ESP_OK) { + s_trel_counters.mTxPackets++; + s_trel_counters.mTxBytes += aUdpPayloadLen; + } else { + s_trel_counters.mTxFailure++; + ESP_LOGW(OT_PLAT_LOG_TAG, "Failed to send TREL message"); + } } void otPlatTrelNotifyPeerSocketAddressDifference(otInstance *aInstance, @@ -284,8 +292,10 @@ void otPlatTrelRegisterService(otInstance *aInstance, uint16_t aPort, const uint s_is_service_registered = true; uint16_t index = 0; while (index < aTxtLength) { - const uint8_t *item_header = aTxtData + index + 1; uint8_t item_len = aTxtData[index]; + ESP_GOTO_ON_FALSE(index + 1 + item_len <= aTxtLength, ESP_FAIL, exit, OT_PLAT_LOG_TAG, + "Malformed _trel._udp TXT record: item exceeds data length"); + const uint8_t *item_header = aTxtData + index + 1; char key[UINT8_MAX + 1]; for (uint16_t i = 0; i < item_len; i++) { @@ -348,6 +358,7 @@ void otPlatTrelDisable(otInstance *aInstance) free_all_buffer(); close(s_trel_event_fd); s_trel_event_fd = -1; + s_trel_netif = NULL; } const otPlatTrelCounters *otPlatTrelGetCounters(otInstance *aInstance) diff --git a/components/openthread/src/port/esp_openthread_uart.c b/components/openthread/src/port/esp_openthread_uart.c index 1e19af66e5b..685e2ee5586 100644 --- a/components/openthread/src/port/esp_openthread_uart.c +++ b/components/openthread/src/port/esp_openthread_uart.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,7 +30,8 @@ #endif static int s_uart_port; -static int s_uart_fd; +static int s_uart_fd = -1; +static bool s_uart_driver_installed = false; static uint8_t s_uart_buffer[ESP_OPENTHREAD_UART_BUFFER_SIZE]; static const char *uart_workflow = "uart"; @@ -89,6 +90,7 @@ esp_err_t esp_openthread_uart_init_port(const esp_openthread_uart_config_t *conf OT_PLAT_LOG_TAG, "uart_set_pin failed"); ESP_RETURN_ON_ERROR(uart_driver_install(config->port, ESP_OPENTHREAD_UART_BUFFER_SIZE, 0, 0, NULL, 0), OT_PLAT_LOG_TAG, "uart_driver_install failed"); + s_uart_driver_installed = true; uart_vfs_dev_use_driver(config->port); return ESP_OK; } @@ -173,7 +175,10 @@ void esp_openthread_uart_deinit() close(s_uart_fd); s_uart_fd = -1; } - uart_driver_delete(s_uart_port); + if (s_uart_driver_installed) { + uart_driver_delete(s_uart_port); + s_uart_driver_installed = false; + } esp_openthread_platform_workflow_unregister(uart_workflow); } diff --git a/components/openthread/src/port/esp_openthread_udp.c b/components/openthread/src/port/esp_openthread_udp.c index 614bac12b2a..b298ce31216 100644 --- a/components/openthread/src/port/esp_openthread_udp.c +++ b/components/openthread/src/port/esp_openthread_udp.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -135,8 +135,12 @@ static void udp_recv_task(void *ctx) ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to copy OpenThread message when receiving OpenThread plat UDP")); task->socket->mHandler(task->socket->mContext, message, &message_info); otMessageFree(message); + message = NULL; exit: + if (message != NULL) { + otMessageFree(message); + } free(task); if (data_buf_to_free) { free(data_buf_to_free); @@ -156,6 +160,8 @@ static void handle_udp_recv(void *ctx, struct udp_pcb *pcb, struct pbuf *p, cons if (task == NULL) { ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate recv task when receiving OpenThread plat UDP"); + pbuf_free(p); + return; } task->socket = (otUdpSocket *)ctx; task->recv_buf = p; diff --git a/components/openthread/src/port/esp_spi_spinel_interface.cpp b/components/openthread/src/port/esp_spi_spinel_interface.cpp index 3a409491697..16be363c78a 100644 --- a/components/openthread/src/port/esp_spi_spinel_interface.cpp +++ b/components/openthread/src/port/esp_spi_spinel_interface.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include "openthread/error.h" #include "esp_check.h" +#include "esp_heap_caps.h" #include "esp_openthread_common_macro.h" #include "esp_rom_sys.h" #include "esp_vfs.h" @@ -17,6 +18,7 @@ #include "driver/gpio.h" #include "driver/spi_master.h" #include "hal/gpio_types.h" +#include "soc/soc_caps.h" #include "ncp/ncp_spi.hpp" using ot::Spinel::SpiFrame; @@ -26,7 +28,8 @@ namespace esp { namespace openthread { SpiSpinelInterface::SpiSpinelInterface(void) - : m_event_fd(-1) + : m_rx_dma_buf(nullptr) + , m_event_fd(-1) , m_receiver_frame_callback(nullptr) , m_receiver_frame_context(nullptr) , m_receive_frame_buffer(nullptr) @@ -73,7 +76,7 @@ esp_err_t SpiSpinelInterface::Enable(const esp_openthread_spi_host_config_t &spi io_conf.mode = GPIO_MODE_INPUT; io_conf.pull_up_en = GPIO_PULLUP_ENABLE; ESP_RETURN_ON_ERROR(gpio_config(&io_conf), OT_PLAT_LOG_TAG, "fail to config spi gpio"); - gpio_install_isr_service(0); // The gpio isr service may has been installed. + gpio_install_isr_service(0); ESP_RETURN_ON_ERROR(gpio_isr_handler_add(spi_config.intr_pin, GpioIntrHandler, this), OT_PLAT_LOG_TAG, "fail to add gpio isr handler"); m_has_pending_device_frame = false; @@ -82,6 +85,9 @@ esp_err_t SpiSpinelInterface::Enable(const esp_openthread_spi_host_config_t &spi ESP_RETURN_ON_FALSE(m_event_fd >= 0, ESP_FAIL, OT_PLAT_LOG_TAG, "fail to get event fd"); + m_rx_dma_buf = (uint8_t *)heap_caps_malloc(kSPIFrameSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + ESP_RETURN_ON_FALSE(m_rx_dma_buf != nullptr, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "fail to alloc SPI RX DMA buffer"); + ESP_LOGI(OT_PLAT_LOG_TAG, "spinel SPI interface initialization completed"); return ESP_OK; @@ -105,6 +111,8 @@ esp_err_t SpiSpinelInterface::Disable(void) ESP_RETURN_ON_ERROR(spi_bus_free(m_spi_config.host_device), OT_PLAT_LOG_TAG, "fail to free spi bus"); gpio_uninstall_isr_service(); } + heap_caps_free(m_rx_dma_buf); + m_rx_dma_buf = nullptr; return ESP_OK; } @@ -115,8 +123,7 @@ otError SpiSpinelInterface::SendFrame(const uint8_t *frame, uint16_t length) "send frame is too long"); memcpy(&m_tx_buffer[kSPIFrameHeaderSize], frame, length); - uint16_t rx_data_size = - length < kSmallPacketSize ? kSmallPacketSize : length; // We'll use tx_size to receive small packets piggybacked + uint16_t rx_data_size = length < kSmallPacketSize ? kSmallPacketSize : length; if (ConductSPITransaction(false, length, rx_data_size) == ESP_OK) { return OT_ERROR_NONE; } else { @@ -152,9 +159,12 @@ esp_err_t SpiSpinelInterface::ConductSPITransaction(bool reset, uint16_t tx_data transaction.length = data_size * CHAR_BIT; transaction.rxlength = (rx_data_size + kSPIFrameHeaderSize) * CHAR_BIT; transaction.tx_buffer = m_tx_buffer; - transaction.rx_buffer = rx_buffer; + // Use aligned DMA buffer: MultiFrameBuffer's internal array is not + // cache-line-aligned, causing stale D-cache reads after DMA completes. + transaction.rx_buffer = m_rx_dma_buf; ESP_RETURN_ON_ERROR(spi_device_polling_transmit(m_device, &transaction), OT_PLAT_LOG_TAG, "SPI transaction failed"); + memcpy(rx_buffer, m_rx_dma_buf, data_size); SpiFrame rx_frame(rx_buffer); if (!rx_frame.IsValid() || rx_frame.GetHeaderAcceptLen() > kSPIFrameSize || @@ -162,17 +172,14 @@ esp_err_t SpiSpinelInterface::ConductSPITransaction(bool reset, uint16_t tx_data vTaskDelay(pdMS_TO_TICKS(15)); ESP_RETURN_ON_ERROR(spi_device_polling_transmit(m_device, &transaction), OT_PLAT_LOG_TAG, "fail to retry SPI invalid transaction"); + memcpy(rx_buffer, m_rx_dma_buf, data_size); } - if (rx_frame.IsResetFlagSet()) { - ESP_LOGW(OT_PLAT_LOG_TAG, "RCP Reset"); - m_receive_frame_buffer->DiscardFrame(); - return ESP_OK; - } if (rx_frame.GetHeaderDataLen() == 0 && rx_frame.GetHeaderAcceptLen() == 0) { vTaskDelay(pdMS_TO_TICKS(15)); ESP_RETURN_ON_ERROR(spi_device_polling_transmit(m_device, &transaction), OT_PLAT_LOG_TAG, "fail to retry SPI empty transaction"); + memcpy(rx_buffer, m_rx_dma_buf, data_size); } if (rx_frame.GetHeaderDataLen() > 0 && rx_frame.GetHeaderDataLen() < tx_frame.GetHeaderAcceptLen()) { @@ -267,7 +274,20 @@ otError SpiSpinelInterface::HardwareReset(void) { if (mRcpFailureHandler) { mRcpFailureHandler(); - ConductSPITransaction(true, 0, 0); // clear + ConductSPITransaction(true, 0, 0); + // Drain stale GPIO interrupt events accumulated before reset, otherwise + // WaitForFrame() would fire immediately on a stale event and fail. + uint64_t event; + struct timeval zero_timeout = {0, 0}; + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(m_event_fd, &read_fds); + if (select(m_event_fd + 1, &read_fds, NULL, NULL, &zero_timeout) > 0 && + FD_ISSET(m_event_fd, &read_fds)) { + read(m_event_fd, &event, sizeof(event)); + } + m_pending_data_len = 0; + } return OT_ERROR_NONE; } diff --git a/examples/openthread/ot_br/main/esp_ot_br.c b/examples/openthread/ot_br/main/esp_ot_br.c index c9bb84eff0d..4de707e5a57 100644 --- a/examples/openthread/ot_br/main/esp_ot_br.c +++ b/examples/openthread/ot_br/main/esp_ot_br.c @@ -93,6 +93,7 @@ void app_main(void) ESP_ERROR_CHECK(mdns_hostname_set("esp-ot-br")); #if CONFIG_OPENTHREAD_SUPPORT_HW_RESET_RCP esp_openthread_register_rcp_failure_handler(rcp_failure_hardware_reset_handler); + esp_openthread_set_coprocessor_reset_failure_callback(rcp_failure_hardware_reset_handler); #endif #if CONFIG_OPENTHREAD_CLI