Merge branch 'review_ot_code_using_cursor_v5.3' into 'release/v5.3'

fix(openthread): make the code more robust (v5.3)

See merge request espressif/esp-idf!48085
This commit is contained in:
Shu Chen
2026-04-30 03:06:16 +00:00
18 changed files with 197 additions and 82 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<size_t>(end - cur), "%s ", aArgs[index]);
if (end > cur + strlen(aArgs[index])) {
cur += snprintf(cur, static_cast<size_t>(end - cur), "%s ", aArgs[index]);
} else {
return OT_ERROR_INVALID_ARGS;
}
}
return s_radio.PlatDiagProcess(cmd);

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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 <openthread/platform/spi-slave.h>
#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;

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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