From 2d1cbe418cef0f95dd80d4a6028e7f58ed437226 Mon Sep 17 00:00:00 2001 From: Xu Si Yu Date: Wed, 4 Mar 2026 15:24:41 +0800 Subject: [PATCH] feat(openthread): optimize radio spinel uart interface --- components/openthread/Kconfig | 7 + .../openthread/include/esp_radio_spinel.h | 17 +- .../esp_radio_spinel_uart_interface.hpp | 16 +- .../esp_uart_spinel_interface.hpp | 203 ----------- .../src/port/esp_openthread_radio_spinel.cpp | 9 +- .../src/port/esp_uart_spinel_interface.cpp | 320 ------------------ .../src/spinel/esp_radio_spinel.cpp | 23 +- .../esp_radio_spinel_uart_interface.cpp | 94 ++++- components/openthread/srcs_ftd_mtd.cmake | 13 +- 9 files changed, 150 insertions(+), 552 deletions(-) delete mode 100644 components/openthread/private_include/esp_uart_spinel_interface.hpp delete mode 100644 components/openthread/src/port/esp_uart_spinel_interface.cpp diff --git a/components/openthread/Kconfig b/components/openthread/Kconfig index aa1de81246..12a5066d16 100644 --- a/components/openthread/Kconfig +++ b/components/openthread/Kconfig @@ -604,6 +604,13 @@ menu "OpenThread" help The maximum number of backoffs the CSMA-CA algorithm will attempt before declaring a channel access failure. + + config OPENTHREAD_SPINEL_UART_DRIVER_BUFFER_SIZE + int "UART driver buffer size for radio spinel interface" + depends on OPENTHREAD_SPINEL_ONLY || OPENTHREAD_RADIO_SPINEL_UART + default 1024 + help + RX buffer size passed to uart_driver_install() for the radio spinel UART interface. endmenu menuconfig OPENTHREAD_DEBUG diff --git a/components/openthread/include/esp_radio_spinel.h b/components/openthread/include/esp_radio_spinel.h index 18638b3275..2fda58174b 100644 --- a/components/openthread/include/esp_radio_spinel.h +++ b/components/openthread/include/esp_radio_spinel.h @@ -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 */ @@ -72,12 +72,23 @@ void esp_radio_spinel_set_callbacks(const esp_radio_spinel_callbacks_t aCallback * @note This function should be called before `esp_radio_spinel_init`. * * @param[in] radio_uart_config The config of UART for radio spinel. - * @param[in] aUartInitHandler The function for UART initialization - * @param[in] aUartDeinitHandler The function for UART deinitialization + * Must not be nullptr. + * + * @param[in] aUartInitHandler Optional UART initialization handler. + * If provided (not nullptr), this handler will be used + * to initialize the UART. Otherwise, a default + * internal implementation will be used. + * + * @param[in] aUartDeinitHandler Optional UART deinitialization handler. + * If provided (not nullptr), this handler will be used + * to deinitialize the UART. Otherwise, a default + * internal implementation will be used. + * * @param[in] idx The index of 802.15.4 related protocol stack. * * @return * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if `radio_uart_config` is nullptr * - ESP_FAIL on failures * */ diff --git a/components/openthread/private_include/esp_radio_spinel_uart_interface.hpp b/components/openthread/private_include/esp_radio_spinel_uart_interface.hpp index 70f7440b01..67be800011 100644 --- a/components/openthread/private_include/esp_radio_spinel_uart_interface.hpp +++ b/components/openthread/private_include/esp_radio_spinel_uart_interface.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,9 @@ #include "lib/spinel/spinel_interface.hpp" #include "lib/hdlc/hdlc.hpp" #include "openthread/error.h" +#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART +#include "esp_openthread_types.h" +#endif namespace esp { namespace radio_spinel { @@ -143,6 +146,9 @@ public: * - ESP_ERROR on failure */ esp_err_t Enable(const esp_radio_spinel_uart_config_t &radio_uart_config); +#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART + esp_err_t Enable(const esp_openthread_uart_config_t &radio_uart_config); +#endif /** * @brief This method disable the HDLC interface. @@ -150,7 +156,13 @@ public: */ esp_err_t Disable(void); - void RegisterUartInitHandler(esp_radio_spinel_uart_init_handler handler) { mUartInitHandler = handler; } + void RegisterUartInitHandler(esp_radio_spinel_uart_init_handler handler) + { + if (mUartInitHandler != NULL) { + ESP_LOGW(ESP_SPINEL_LOG_TAG, "UartInitHandler already registered, will overwrite (prev=%p, new=%p)", mUartInitHandler, handler); + } + mUartInitHandler = handler; + } void RegisterUartDeinitHandler(esp_radio_spinel_uart_deinit_handler handler) { mUartDeinitHandler = handler; } diff --git a/components/openthread/private_include/esp_uart_spinel_interface.hpp b/components/openthread/private_include/esp_uart_spinel_interface.hpp deleted file mode 100644 index 5cd1de7137..0000000000 --- a/components/openthread/private_include/esp_uart_spinel_interface.hpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include "esp_err.h" -#include "esp_openthread.h" -#include "esp_openthread_types.h" -#include "hal/uart_types.h" -#include "lib/spinel/spinel_interface.hpp" -#include "lib/hdlc/hdlc.hpp" -#include "openthread/error.h" - -namespace esp { -namespace openthread { - -/** - * This class defines an UART interface to the Radio Co-processor (RCP). - * - */ -class UartSpinelInterface : public ot::Spinel::SpinelInterface { -public: - /** - * @brief This constructor of object. - */ - UartSpinelInterface(void); - - /** - * @brief This destructor of the object. - * - */ - ~UartSpinelInterface(void); - - /** - * Initializes the interface to the Radio Co-processor (RCP). - * - * @note This method should be called before reading and sending spinel frames to the interface. - * - * @param[in] aCallback Callback on frame received - * @param[in] aCallbackContext Callback context - * @param[in] aFrameBuffer A reference to a `RxFrameBuffer` object. - * - * @retval OT_ERROR_NONE The interface is initialized successfully - * @retval OT_ERROR_ALREADY The interface is already initialized. - * @retval OT_ERROR_FAILED Failed to initialize the interface. - * - */ - otError Init(ReceiveFrameCallback aCallback, void *aCallbackContext, RxFrameBuffer &aFrameBuffer); - - /** - * Deinitializes the interface to the RCP. - * - */ - void Deinit(void); - - /** - * Encodes and sends a spinel frame to Radio Co-processor (RCP) over the socket. - * - * @param[in] aFrame A pointer to buffer containing the spinel frame to send. - * @param[in] aLength The length (number of bytes) in the frame. - * - * @retval OT_ERROR_NONE Successfully encoded and sent the spinel frame. - * @retval OT_ERROR_BUSY Failed due to another operation is on going. - * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode the frame. - * @retval OT_ERROR_FAILED Failed to call the SPI driver to send the frame. - * - */ - otError SendFrame(const uint8_t *aFrame, uint16_t aLength); - - /** - * Waits for receiving part or all of spinel frame within specified interval. - * - * @param[in] aTimeout The timeout value in microseconds. - * - * @retval OT_ERROR_NONE Part or all of spinel frame is received. - * @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeout. - * - */ - otError WaitForFrame(uint64_t aTimeoutUs); - - /** - * Updates the file descriptor sets with file descriptors used by the radio driver. - * - * @param[in,out] aMainloopContext A pointer to the mainloop context. - * - */ - void UpdateFdSet(void *aMainloopContext); - - /** - * Performs radio driver processing. - * - * @param[in] aMainloopContext A pointer to the mainloop context. - * - */ - void Process(const void *aMainloopContext); - - /** - * Returns the bus speed between the host and the radio. - * - * @returns Bus speed in bits/second. - * - */ - uint32_t GetBusSpeed(void) const; - - /** - * Hardware resets the RCP. - * - * @retval OT_ERROR_NONE Successfully reset the RCP. - * @retval OT_ERROR_NOT_IMPLEMENT The hardware reset is not implemented. - * - */ - otError HardwareReset(void); - - /** - * Returns the RCP interface metrics. - * - * @returns The RCP interface metrics. - * - */ - const otRcpInterfaceMetrics *GetRcpInterfaceMetrics(void) const { return &mInterfaceMetrics; } - - /** - * This methods registers the callback for RCP failure. - * - * @param[in] handler The RCP failure handler. - * - */ - void RegisterRcpFailureHandler(esp_openthread_rcp_failure_handler handler) { mRcpFailureHandler = handler; } - - /** - * This method is called when RCP is reset to recreate the connection with it. - * Intentionally empty. - * - */ - otError ResetConnection(void) { return OT_ERROR_NONE; } - - /** - * @brief This method enable the HDLC interface. - * - * @return - * - ESP_OK on success - * - ESP_ERR_NO_MEM if allocation has failed - * - ESP_ERROR on failure - */ - esp_err_t Enable(const esp_openthread_uart_config_t &radio_uart_config); - - /** - * @brief This method disable the HDLC interface. - * - */ - esp_err_t Disable(void); - -private: - - enum { - /** - * Maximum wait time in Milliseconds for socket to become writable (see `SendFrame`). - * - */ - kMaxWaitTime = 2000, - }; - - esp_err_t InitUart(const esp_openthread_uart_config_t &radio_uart_config); - - esp_err_t DeinitUart(void); - - int TryReadAndDecode(void); - - otError WaitForWritable(void); - - otError Write(const uint8_t *frame, uint16_t length); - - esp_err_t TryRecoverUart(void); - - static void HandleHdlcFrame(void *context, otError error); - void HandleHdlcFrame(otError error); - - ReceiveFrameCallback m_receiver_frame_callback; - void *m_receiver_frame_context; - RxFrameBuffer *m_receive_frame_buffer; - - ot::Hdlc::Decoder m_hdlc_decoder; - uint8_t *m_uart_rx_buffer; - - esp_openthread_uart_config_t m_uart_config; - int m_uart_fd; - - otRcpInterfaceMetrics mInterfaceMetrics; - - // Non-copyable, intentionally not implemented. - UartSpinelInterface(const UartSpinelInterface &); - UartSpinelInterface &operator=(const UartSpinelInterface &); - - esp_openthread_rcp_failure_handler mRcpFailureHandler; - - ot::Spinel::FrameBuffer encoder_buffer; -}; - -} // namespace openthread -} // namespace esp diff --git a/components/openthread/src/port/esp_openthread_radio_spinel.cpp b/components/openthread/src/port/esp_openthread_radio_spinel.cpp index c5ff44923d..b7892da9af 100644 --- a/components/openthread/src/port/esp_openthread_radio_spinel.cpp +++ b/components/openthread/src/port/esp_openthread_radio_spinel.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 */ @@ -18,7 +18,7 @@ #include "esp_spinel_interface.hpp" #include "esp_spinel_ncp_vendor_macro.h" #include "esp_spi_spinel_interface.hpp" -#include "esp_uart_spinel_interface.hpp" +#include "esp_radio_spinel_uart_interface.hpp" #include "openthread-core-config.h" #include "lib/spinel/radio_spinel.hpp" #include "lib/spinel/spinel.h" @@ -36,7 +36,7 @@ using esp::openthread::SpinelInterfaceAdapter; using ot::Spinel::SpinelDriver; #if CONFIG_OPENTHREAD_RADIO_SPINEL_UART // CONFIG_OPENTHREAD_RADIO_SPINEL_UART -using esp::openthread::UartSpinelInterface; +using esp::radio_spinel::UartSpinelInterface; static SpinelInterfaceAdapter s_spinel_interface; #else // CONFIG_OPENTHREAD_RADIO_SPINEL_SPI using esp::openthread::SpiSpinelInterface; @@ -150,7 +150,8 @@ esp_err_t esp_openthread_rcp_deinit(void) ESP_RETURN_ON_FALSE(s_radio.Sleep() == OT_ERROR_NONE, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, "Radio fails to sleep"); ESP_RETURN_ON_FALSE(s_radio.Disable() == OT_ERROR_NONE, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, "Fail to disable radio"); } - ESP_RETURN_ON_FALSE(s_spinel_interface.GetSpinelInterface().Disable() == OT_ERROR_NONE, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, "Fail to deinitialize UART"); + ESP_RETURN_ON_ERROR(s_spinel_interface.GetSpinelInterface().Disable(), OT_PLAT_LOG_TAG, + "Fail to deinitialize UART"); esp_openthread_platform_workflow_unregister(radiospinel_workflow); return ESP_OK; } diff --git a/components/openthread/src/port/esp_uart_spinel_interface.cpp b/components/openthread/src/port/esp_uart_spinel_interface.cpp deleted file mode 100644 index 728d9f1821..0000000000 --- a/components/openthread/src/port/esp_uart_spinel_interface.cpp +++ /dev/null @@ -1,320 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "esp_uart_spinel_interface.hpp" - -#include -#include -#include -#include - -#include "esp_check.h" -#include "esp_err.h" -#include "esp_log.h" -#include "esp_openthread_common_macro.h" -#include "esp_openthread_types.h" -#include "esp_openthread_uart.h" -#include "driver/uart_vfs.h" -#include "common/code_utils.hpp" -#include "common/logging.hpp" -#include "driver/uart.h" -#include "lib/platform/exit_code.h" -#include "openthread/platform/time.h" - -namespace esp { -namespace openthread { - -UartSpinelInterface::UartSpinelInterface(void) - : m_receiver_frame_callback(nullptr) - , m_receiver_frame_context(nullptr) - , m_receive_frame_buffer(nullptr) - , m_uart_fd(-1) - , mRcpFailureHandler(nullptr) -{ -} - -UartSpinelInterface::~UartSpinelInterface(void) -{ - Deinit(); -} - -otError UartSpinelInterface::Init(ReceiveFrameCallback aCallback, void *aCallbackContext, RxFrameBuffer &aFrameBuffer) -{ - otError error = OT_ERROR_NONE; - - m_receiver_frame_callback = aCallback; - m_receiver_frame_context = aCallbackContext; - m_receive_frame_buffer = &aFrameBuffer; - m_hdlc_decoder.Init(aFrameBuffer, HandleHdlcFrame, this); - - return error; -} - -void UartSpinelInterface::Deinit(void) -{ - m_receiver_frame_callback = nullptr; - m_receiver_frame_context = nullptr; - m_receive_frame_buffer = nullptr; -} - -esp_err_t UartSpinelInterface::Enable(const esp_openthread_uart_config_t &radio_uart_config) -{ - esp_err_t error = ESP_OK; - m_uart_rx_buffer = static_cast(heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_8BIT)); - if (m_uart_rx_buffer == NULL) { - return ESP_ERR_NO_MEM; - } - - error = InitUart(radio_uart_config); - ESP_LOGI(OT_PLAT_LOG_TAG, "spinel UART interface initialization completed"); - return error; -} - -esp_err_t UartSpinelInterface::Disable(void) -{ - if (m_uart_rx_buffer) { - heap_caps_free(m_uart_rx_buffer); - } - m_uart_rx_buffer = NULL; - - return DeinitUart(); -} - -otError UartSpinelInterface::SendFrame(const uint8_t *frame, uint16_t length) -{ - otError error = OT_ERROR_NONE; - encoder_buffer.Clear(); - ot::Hdlc::Encoder hdlc_encoder(encoder_buffer); - - SuccessOrExit(error = hdlc_encoder.BeginFrame()); - SuccessOrExit(error = hdlc_encoder.Encode(frame, length)); - SuccessOrExit(error = hdlc_encoder.EndFrame()); - - SuccessOrExit(error = Write(encoder_buffer.GetFrame(), encoder_buffer.GetLength())); - -exit: - if (error != OT_ERROR_NONE) { - ESP_LOGE(OT_PLAT_LOG_TAG, "send radio frame failed"); - } else { - ESP_LOGD(OT_PLAT_LOG_TAG, "sent radio frame"); - } - - return error; -} - -void UartSpinelInterface::Process(const void *aMainloopContext) -{ - if (FD_ISSET(m_uart_fd, &((esp_openthread_mainloop_context_t *)aMainloopContext)->read_fds)) { - ESP_LOGD(OT_PLAT_LOG_TAG, "radio uart read event"); - TryReadAndDecode(); - } -} - -int UartSpinelInterface::TryReadAndDecode(void) -{ - uint8_t buffer[UART_HW_FIFO_LEN(m_uart_config.port)]; - ssize_t rval; - - do { - rval = read(m_uart_fd, buffer, sizeof(buffer)); - if (rval > 0) { - m_hdlc_decoder.Decode(buffer, static_cast(rval)); - } - } while (rval > 0); - - if ((rval < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK)) { - ESP_ERROR_CHECK(TryRecoverUart()); - } - - return rval; -} - -otError UartSpinelInterface::WaitForWritable(void) -{ - otError error = OT_ERROR_NONE; - struct timeval timeout = {kMaxWaitTime / MS_PER_S, (kMaxWaitTime % MS_PER_S) * US_PER_MS}; - uint64_t now = otPlatTimeGet(); - uint64_t end = now + kMaxWaitTime * US_PER_MS; - fd_set write_fds; - fd_set error_fds; - int rval; - - while (true) { - FD_ZERO(&write_fds); - FD_ZERO(&error_fds); - FD_SET(m_uart_fd, &write_fds); - FD_SET(m_uart_fd, &error_fds); - - rval = select(m_uart_fd + 1, NULL, &write_fds, &error_fds, &timeout); - - if (rval > 0) { - if (FD_ISSET(m_uart_fd, &write_fds)) { - ExitNow(); - } else if (FD_ISSET(m_uart_fd, &error_fds)) { - ExitNow(error = OT_ERROR_FAILED); - } - } else if ((rval < 0) && (errno != EINTR)) { - ESP_ERROR_CHECK(TryRecoverUart()); - ExitNow(error = OT_ERROR_FAILED); - } - - now = otPlatTimeGet(); - - if (end > now) { - uint64_t remain = end - now; - - timeout.tv_sec = static_cast(remain / 1000000); - timeout.tv_usec = static_cast(remain % 1000000); - } else { - break; - } - } - - error = OT_ERROR_FAILED; - -exit: - return error; -} - -otError UartSpinelInterface::Write(const uint8_t *aFrame, uint16_t length) -{ - otError error = OT_ERROR_NONE; - - while (length) { - ssize_t rval; - - rval = write(m_uart_fd, aFrame, length); - - if (rval > 0) { - assert(rval <= length); - length -= static_cast(rval); - aFrame += static_cast(rval); - continue; - } else if (rval < 0) { - ESP_ERROR_CHECK(TryRecoverUart()); - ExitNow(error = OT_ERROR_FAILED); - } - - SuccessOrExit(error = WaitForWritable()); - } - -exit: - return error; -} - -otError UartSpinelInterface::WaitForFrame(uint64_t timeout_us) -{ - otError error = OT_ERROR_NONE; - struct timeval timeout; - fd_set read_fds; - fd_set error_fds; - int rval; - - FD_ZERO(&read_fds); - FD_ZERO(&error_fds); - FD_SET(m_uart_fd, &read_fds); - FD_SET(m_uart_fd, &error_fds); - - timeout.tv_sec = static_cast(timeout_us / US_PER_S); - timeout.tv_usec = static_cast(timeout_us % US_PER_S); - - rval = select(m_uart_fd + 1, &read_fds, NULL, &error_fds, &timeout); - - if (rval > 0) { - if (FD_ISSET(m_uart_fd, &read_fds)) { - TryReadAndDecode(); - } else if (FD_ISSET(m_uart_fd, &error_fds)) { - ESP_ERROR_CHECK(TryRecoverUart()); - ExitNow(error = OT_ERROR_FAILED); - } - } else if (rval == 0) { - ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT); - } else { - ESP_ERROR_CHECK(TryRecoverUart()); - ExitNow(error = OT_ERROR_FAILED); - } - -exit: - return error; -} - -void UartSpinelInterface::HandleHdlcFrame(void *context, otError error) -{ - static_cast(context)->HandleHdlcFrame(error); -} - -void UartSpinelInterface::HandleHdlcFrame(otError error) -{ - if (error == OT_ERROR_NONE) { - ESP_LOGD(OT_PLAT_LOG_TAG, "received hdlc radio frame"); - m_receiver_frame_callback(m_receiver_frame_context); - } else { - ESP_LOGE(OT_PLAT_LOG_TAG, "dropping radio frame: %s", otThreadErrorToString(error)); - m_receive_frame_buffer->DiscardFrame(); - } -} - -esp_err_t UartSpinelInterface::InitUart(const esp_openthread_uart_config_t &radio_uart_config) -{ - char uart_path[16]; - - m_uart_config = radio_uart_config; - ESP_RETURN_ON_ERROR(esp_openthread_uart_init_port(&radio_uart_config), OT_PLAT_LOG_TAG, - "esp_openthread_uart_init_port failed"); - // We have a driver now installed so set up the read/write functions to use driver also. - uart_vfs_dev_port_set_tx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF); - uart_vfs_dev_port_set_rx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF); - - snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", radio_uart_config.port); - m_uart_fd = open(uart_path, O_RDWR | O_NONBLOCK); - - return m_uart_fd >= 0 ? ESP_OK : ESP_FAIL; -} - -esp_err_t UartSpinelInterface::DeinitUart(void) -{ - if (m_uart_fd != -1) { - close(m_uart_fd); - m_uart_fd = -1; - return uart_driver_delete(m_uart_config.port); - } else { - return ESP_ERR_INVALID_STATE; - } -} - -esp_err_t UartSpinelInterface::TryRecoverUart(void) -{ - ESP_RETURN_ON_ERROR(DeinitUart(), OT_PLAT_LOG_TAG, "DeInitUart failed"); - ESP_RETURN_ON_ERROR(InitUart(m_uart_config), OT_PLAT_LOG_TAG, "InitUart failed"); - return ESP_OK; -} - -otError UartSpinelInterface::HardwareReset(void) -{ - if (mRcpFailureHandler) { - TryRecoverUart(); - mRcpFailureHandler(); - } - return OT_ERROR_NONE; -} - -void UartSpinelInterface::UpdateFdSet(void *aMainloopContext) -{ - // Register only READ events for radio UART and always wait - // for a radio WRITE to complete. - FD_SET(m_uart_fd, &((esp_openthread_mainloop_context_t *)aMainloopContext)->read_fds); - if (m_uart_fd > ((esp_openthread_mainloop_context_t *)aMainloopContext)->max_fd) { - ((esp_openthread_mainloop_context_t *)aMainloopContext)->max_fd = m_uart_fd; - } -} - -uint32_t UartSpinelInterface::GetBusSpeed(void) const -{ - return m_uart_config.uart_config.baud_rate; -} - -} // namespace openthread -} // namespace esp diff --git a/components/openthread/src/spinel/esp_radio_spinel.cpp b/components/openthread/src/spinel/esp_radio_spinel.cpp index 8d561d2704..3c35b0cbd8 100644 --- a/components/openthread/src/spinel/esp_radio_spinel.cpp +++ b/components/openthread/src/spinel/esp_radio_spinel.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,7 +29,7 @@ using esp::radio_spinel::SpinelInterfaceAdapter; using esp::radio_spinel::UartSpinelInterface; using ot::Spinel::SpinelDriver; -static SpinelInterfaceAdapter s_spinel_interface[ot::Spinel::kSpinelHeaderMaxNumIid]; +static SpinelInterfaceAdapter s_spinel_interface; static RadioSpinel s_radio[ot::Spinel::kSpinelHeaderMaxNumIid]; static esp_radio_spinel_callbacks_t s_esp_radio_spinel_callbacks[ot::Spinel::kSpinelHeaderMaxNumIid]; static SpinelDriver s_spinel_driver[ot::Spinel::kSpinelHeaderMaxNumIid]; @@ -205,11 +205,11 @@ esp_err_t esp_radio_spinel_uart_interface_enable(const esp_radio_spinel_uart_con esp_radio_spinel_uart_deinit_handler aUartDeinitHandler, esp_radio_spinel_idx_t idx) { - ESP_RETURN_ON_FALSE(aUartInitHandler != nullptr, ESP_FAIL, ESP_SPINEL_LOG_TAG, "UartInitHandler can not be set to NULL"); - ESP_RETURN_ON_FALSE(aUartDeinitHandler != nullptr, ESP_FAIL, ESP_SPINEL_LOG_TAG, "UartDeinitHandler can not be set to NULL"); - s_spinel_interface[idx].GetSpinelInterface().RegisterUartInitHandler(aUartInitHandler); - s_spinel_interface[idx].GetSpinelInterface().RegisterUartDeinitHandler(aUartDeinitHandler); - ESP_RETURN_ON_FALSE(s_spinel_interface[idx].GetSpinelInterface().Enable(*radio_uart_config) == OT_ERROR_NONE, ESP_FAIL, ESP_SPINEL_LOG_TAG, "Spinel UART interface failed to enable"); + s_spinel_interface.GetSpinelInterface().RegisterUartInitHandler(aUartInitHandler); + s_spinel_interface.GetSpinelInterface().RegisterUartDeinitHandler(aUartDeinitHandler); + ESP_RETURN_ON_FALSE(radio_uart_config != nullptr, ESP_ERR_INVALID_ARG, ESP_SPINEL_LOG_TAG, "radio_uart_config can not be NULL"); + ESP_RETURN_ON_ERROR(s_spinel_interface.GetSpinelInterface().Enable(*radio_uart_config), ESP_SPINEL_LOG_TAG, + "Spinel UART interface failed to enable"); ESP_LOGI(ESP_SPINEL_LOG_TAG, "Spinel UART interface has been successfully enabled"); return ESP_OK; } @@ -222,7 +222,7 @@ void esp_radio_spinel_init(esp_radio_spinel_idx_t idx) // Multipan is not currently supported iidList[0] = 0; s_spinel_driver[idx].SetCoprocessorResetFailureCallback(radio_spinel_coprocessor_reset_failure_callback, instance); - s_spinel_driver[idx].Init(s_spinel_interface[idx].GetSpinelInterface(), true, iidList, ot::Spinel::kSpinelHeaderMaxNumIid); + s_spinel_driver[idx].Init(s_spinel_interface.GetSpinelInterface(), true, iidList, ot::Spinel::kSpinelHeaderMaxNumIid); s_radio[idx].SetCompatibilityErrorCallback(radio_spinel_compatibility_error_callback, instance); s_radio[idx].Init(/*skip_rcp_compatibility_check=*/false, /*reset_radio=*/true, &s_spinel_driver[idx], s_radio_caps, false); s_radio[idx].SetVendorRestorePropertiesCallback(esp_radio_spinel_restore_vendor_properities, instance); @@ -324,7 +324,7 @@ esp_err_t esp_radio_spinel_set_pan_coord(bool enable, esp_radio_spinel_idx_t idx void esp_radio_spinel_radio_update(esp_radio_spinel_mainloop_context_t *mainloop_context, esp_radio_spinel_idx_t idx) { - s_spinel_interface[idx].GetSpinelInterface().UpdateFdSet(static_cast(mainloop_context)); + s_spinel_interface.GetSpinelInterface().UpdateFdSet(static_cast(mainloop_context)); } void esp_radio_spinel_radio_process(esp_radio_spinel_mainloop_context_t *mainloop_context, esp_radio_spinel_idx_t idx) @@ -354,7 +354,7 @@ esp_err_t esp_radio_spinel_get_tx_power(int8_t *power, esp_radio_spinel_idx_t id void esp_radio_spinel_register_rcp_failure_handler(esp_radio_spinel_rcp_failure_handler handler, esp_radio_spinel_idx_t idx) { - s_spinel_interface[idx].GetSpinelInterface().RegisterRcpFailureHandler(handler); + s_spinel_interface.GetSpinelInterface().RegisterRcpFailureHandler(handler); } esp_err_t esp_radio_spinel_rcp_deinit(esp_radio_spinel_idx_t idx) @@ -363,7 +363,8 @@ esp_err_t esp_radio_spinel_rcp_deinit(esp_radio_spinel_idx_t idx) ESP_RETURN_ON_FALSE(s_radio[idx].Sleep() == OT_ERROR_NONE, ESP_ERR_INVALID_STATE, ESP_SPINEL_LOG_TAG, "Radio fails to sleep"); ESP_RETURN_ON_FALSE(s_radio[idx].Disable() == OT_ERROR_NONE, ESP_ERR_INVALID_STATE, ESP_SPINEL_LOG_TAG, "Fail to disable radio"); } - ESP_RETURN_ON_FALSE(s_spinel_interface[idx].GetSpinelInterface().Disable() == OT_ERROR_NONE, ESP_ERR_INVALID_STATE, ESP_SPINEL_LOG_TAG, "Fail to deinitialize UART"); + ESP_RETURN_ON_ERROR(s_spinel_interface.GetSpinelInterface().Disable(), ESP_SPINEL_LOG_TAG, + "Fail to deinitialize UART"); return ESP_OK; } diff --git a/components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp b/components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp index 9c57e54bdf..8a69b98c5b 100644 --- a/components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp +++ b/components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp @@ -1,32 +1,65 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "esp_radio_spinel_uart_interface.hpp" #include +#include #include #include "esp_check.h" #include "esp_openthread_common_macro.h" #include "openthread/platform/time.h" #include "hdlc.hpp" #include "common/code_utils.hpp" +#include "esp_vfs_dev.h" +#include "driver/uart.h" +#include "driver/uart_vfs.h" +#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART +#include "esp_openthread_types.h" +#endif namespace esp { namespace radio_spinel { +static esp_err_t RadioSpinelUartInitPort(const esp_radio_spinel_uart_config_t *config) +{ + char uart_path[16]; + snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", config->port); + bool is_uart_registered = (access(uart_path, F_OK) == 0); + if (!is_uart_registered) { + // Register UART VFS devices before opening /dev/uart/x if not already present. + uart_vfs_dev_register(); + } + + ESP_RETURN_ON_ERROR(uart_param_config(config->port, &config->uart_config), ESP_SPINEL_LOG_TAG, + "uart_param_config failed"); + ESP_RETURN_ON_ERROR( + uart_set_pin(config->port, config->tx_pin, config->rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), + ESP_SPINEL_LOG_TAG, "uart_set_pin failed"); + ESP_RETURN_ON_ERROR(uart_driver_install(config->port, CONFIG_OPENTHREAD_SPINEL_UART_DRIVER_BUFFER_SIZE, 0, 0, NULL, 0), + ESP_SPINEL_LOG_TAG, "uart_driver_install failed"); + uart_vfs_dev_use_driver(config->port); + return ESP_OK; +} + UartSpinelInterface::UartSpinelInterface(void) : m_receiver_frame_callback(nullptr) , m_receiver_frame_context(nullptr) , m_receive_frame_buffer(nullptr) + , m_uart_rx_buffer(nullptr) , m_uart_fd(-1) , mRcpFailureHandler(nullptr) + , mUartInitHandler(nullptr) + , mUartDeinitHandler(nullptr) { } UartSpinelInterface::~UartSpinelInterface(void) { + // Ensure UART resources are released even if caller forgets Disable(). + Disable(); Deinit(); } @@ -52,6 +85,11 @@ void UartSpinelInterface::Deinit(void) esp_err_t UartSpinelInterface::Enable(const esp_radio_spinel_uart_config_t &radio_uart_config) { esp_err_t error = ESP_OK; + + if (m_uart_fd != -1 || m_uart_rx_buffer != NULL) { + return ESP_ERR_INVALID_STATE; + } + m_uart_rx_buffer = static_cast(heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_8BIT)); if (m_uart_rx_buffer == NULL) { return ESP_ERR_NO_MEM; @@ -60,10 +98,27 @@ esp_err_t UartSpinelInterface::Enable(const esp_radio_spinel_uart_config_t &radi error = InitUart(radio_uart_config); if (error == ESP_OK) { ESP_LOGI(ESP_SPINEL_LOG_TAG, "spinel UART interface initialization completed"); + } else { + heap_caps_free(m_uart_rx_buffer); + m_uart_rx_buffer = NULL; } return error; } +#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART +// NOTE: This overload bridges config types; keep field-wise copy and avoid storing references/pointers to the input to prevent potential lifetime-related memory risks. +esp_err_t UartSpinelInterface::Enable(const esp_openthread_uart_config_t &radio_uart_config) +{ + esp_radio_spinel_uart_config_t spinel_uart_config = { + .port = radio_uart_config.port, + .uart_config = radio_uart_config.uart_config, + .rx_pin = radio_uart_config.rx_pin, + .tx_pin = radio_uart_config.tx_pin, + }; + return Enable(spinel_uart_config); +} +#endif + esp_err_t UartSpinelInterface::Disable(void) { if (m_uart_rx_buffer) { @@ -71,6 +126,10 @@ esp_err_t UartSpinelInterface::Disable(void) } m_uart_rx_buffer = NULL; + if (m_uart_fd == -1) { + return ESP_OK; + } + return DeinitUart(); } @@ -108,8 +167,9 @@ int UartSpinelInterface::TryReadAndDecode(void) { uint8_t buffer[UART_HW_FIFO_LEN(m_uart_config.port)]; ssize_t rval; + do { - rval = read(m_uart_fd, buffer, sizeof(buffer)); + rval = read(m_uart_fd, buffer, sizeof(buffer)); if (rval > 0) { m_hdlc_decoder.Decode(buffer, static_cast(rval)); } @@ -252,8 +312,25 @@ esp_err_t UartSpinelInterface::InitUart(const esp_radio_spinel_uart_config_t &ra m_uart_config = radio_uart_config; return mUartInitHandler(&m_uart_config, &m_uart_fd); } else { - ESP_LOGE(ESP_SPINEL_LOG_TAG, "None mUartInitHandler"); - return ESP_FAIL; + char uart_path[16]; + esp_err_t err = ESP_OK; + + m_uart_config = radio_uart_config; + ESP_RETURN_ON_ERROR(RadioSpinelUartInitPort(&radio_uart_config), ESP_SPINEL_LOG_TAG, + "RadioSpinelUartInitPort failed"); + // We have a driver now installed so set up the read/write functions to use driver also. + uart_vfs_dev_port_set_tx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF); + uart_vfs_dev_port_set_rx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF); + + snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", radio_uart_config.port); + m_uart_fd = open(uart_path, O_RDWR | O_NONBLOCK); + + if (m_uart_fd < 0) { + err = uart_driver_delete(m_uart_config.port); + ESP_RETURN_ON_ERROR(err, ESP_SPINEL_LOG_TAG, "uart_driver_delete failed after open"); + return ESP_FAIL; + } + return ESP_OK; } } @@ -262,8 +339,13 @@ esp_err_t UartSpinelInterface::DeinitUart(void) if (mUartDeinitHandler) { return mUartDeinitHandler(&m_uart_config, &m_uart_fd); } else { - ESP_LOGE(ESP_SPINEL_LOG_TAG, "None mUartDeinitHandler"); - return ESP_FAIL; + if (m_uart_fd != -1) { + close(m_uart_fd); + m_uart_fd = -1; + return uart_driver_delete(m_uart_config.port); + } else { + return ESP_ERR_INVALID_STATE; + } } } diff --git a/components/openthread/srcs_ftd_mtd.cmake b/components/openthread/srcs_ftd_mtd.cmake index e8cee70187..4d6c2fab63 100644 --- a/components/openthread/srcs_ftd_mtd.cmake +++ b/components/openthread/srcs_ftd_mtd.cmake @@ -7,6 +7,7 @@ set(src_dirs "src" "src/port" + "src/spinel" "openthread/examples/platforms/utils" "openthread/src/core/api" "openthread/src/core/common" @@ -48,16 +49,22 @@ if(CONFIG_OPENTHREAD_RADIO_NATIVE) list(APPEND exclude_srcs "src/port/esp_openthread_radio_spinel.cpp" "src/port/esp_spi_spinel_interface.cpp" - "src/port/esp_uart_spinel_interface.cpp") + "src/spinel/esp_radio_spinel.cpp" + "src/spinel/esp_radio_spinel_uart_interface.cpp") elseif(CONFIG_OPENTHREAD_RADIO_SPINEL_UART OR CONFIG_OPENTHREAD_RADIO_SPINEL_SPI) list(APPEND exclude_srcs "src/port/esp_openthread_radio.c" - "src/port/esp_openthread_sleep.c") + "src/port/esp_openthread_sleep.c" + "src/spinel/esp_radio_spinel.cpp") + if(CONFIG_OPENTHREAD_RADIO_SPINEL_SPI) + list(APPEND exclude_srcs "src/spinel/esp_radio_spinel_uart_interface.cpp") + endif() elseif(CONFIG_OPENTHREAD_RADIO_154_NONE) list(APPEND exclude_srcs "src/port/esp_openthread_radio_spinel.cpp" "src/port/esp_spi_spinel_interface.cpp" - "src/port/esp_uart_spinel_interface.cpp" + "src/spinel/esp_radio_spinel.cpp" + "src/spinel/esp_radio_spinel_uart_interface.cpp" "src/port/esp_openthread_radio.c" "src/port/esp_openthread_sleep.c") endif()