mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
feat(openthread): optimize radio spinel uart interface
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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<kMaxFrameSize> encoder_buffer;
|
||||
};
|
||||
|
||||
} // namespace openthread
|
||||
} // namespace esp
|
||||
@@ -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<UartSpinelInterface> 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;
|
||||
}
|
||||
|
||||
@@ -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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/unistd.h>
|
||||
|
||||
#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<uint8_t *>(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<uint16_t>(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<time_t>(remain / 1000000);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(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<uint16_t>(rval);
|
||||
aFrame += static_cast<uint16_t>(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<time_t>(timeout_us / US_PER_S);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(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<UartSpinelInterface *>(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
|
||||
@@ -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<UartSpinelInterface> s_spinel_interface[ot::Spinel::kSpinelHeaderMaxNumIid];
|
||||
static SpinelInterfaceAdapter<UartSpinelInterface> 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<void *>(mainloop_context));
|
||||
s_spinel_interface.GetSpinelInterface().UpdateFdSet(static_cast<void *>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/unistd.h>
|
||||
#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<uint8_t *>(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<uint16_t>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user