feat(openthread): support multipan feature for host device

This commit is contained in:
Xu Si Yu
2026-09-03 17:48:38 +08:00
parent be8aead3d6
commit 28561a3221
24 changed files with 1891 additions and 334 deletions
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#if CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
#define ESP_RADIO_SPINEL_IID_LIST_LEN 2
#define ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT
#else
#define ESP_RADIO_SPINEL_IID_LIST_LEN 1
#endif
@@ -8,8 +8,8 @@
#include "esp_log.h"
#include "esp_radio_spinel.h"
#include "esp_radio_spinel_uart_transport.hpp"
#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"
@@ -19,7 +19,11 @@ namespace esp {
namespace radio_spinel {
/**
* This class defines an UART interface to the Radio Co-processor (RCP).
* This class defines a UART spinel interface to the Radio Co-processor (RCP).
*
* HDLC and UART I/O are owned by the transport layer. This class only waits on
* the transport wait fd. Complete spinel frames are delivered by the transport
* into the RxFrameBuffer bound at Init, via the receive callback.
*
*/
class UartSpinelInterface : public ot::Spinel::SpinelInterface {
@@ -66,7 +70,7 @@ public:
* @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.
* @retval OT_ERROR_FAILED Failed to call the UART driver to send the frame.
*
*/
otError SendFrame(const uint8_t *aFrame, uint16_t aLength);
@@ -74,10 +78,10 @@ public:
/**
* Waits for receiving part or all of spinel frame within specified interval.
*
* @param[in] aTimeout The timeout value in microseconds.
* @param[in] aTimeoutUs 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.
* @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeoutUs.
*
*/
otError WaitForFrame(uint64_t aTimeoutUs);
@@ -139,80 +143,69 @@ public:
otError ResetConnection(void) { return OT_ERROR_NONE; }
/**
* @brief This method enable the HDLC interface.
* Enable the spinel UART transport.
*
* Multipan: allocates a free spinel IID. Use GetIid() after Enable succeeds.
*
* @param[in] radio_uart_config UART configuration.
* @param[in] hooks Optional UART init/deinit hooks. May be nullptr.
*
* @return
* - ESP_OK on success
* - ESP_ERR_NO_MEM if allocation has failed
* - ESP_ERROR on failure
* - ESP_ERR_INVALID_STATE if already enabled
* - ESP_ERR_NO_MEM if a multipan host slot or rx queue cannot be allocated
* - ESP_FAIL on failure
*/
esp_err_t Enable(const esp_radio_spinel_uart_config_t &radio_uart_config);
esp_err_t Enable(const esp_radio_spinel_uart_config_t &radio_uart_config,
const esp_radio_spinel_uart_transport_hooks_t *hooks = nullptr);
#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
esp_err_t Enable(const esp_openthread_uart_config_t &radio_uart_config);
/**
* Enable the spinel UART transport using OpenThread UART config.
*
* @param[in] radio_uart_config OpenThread UART configuration.
* @param[in] hooks Optional UART init/deinit hooks. May be nullptr.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if already enabled
* - ESP_ERR_NO_MEM if a multipan host slot or rx queue cannot be allocated
* - ESP_FAIL on failure
*/
esp_err_t Enable(const esp_openthread_uart_config_t &radio_uart_config,
const esp_radio_spinel_uart_transport_hooks_t *hooks = nullptr);
#endif
/**
* @brief This method disable the HDLC interface.
* Disable the spinel UART transport.
*
* @return
* - ESP_OK on success
* - ESP_FAIL on failure
*/
esp_err_t Disable(void);
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; }
/**
* Returns the allocated spinel IID.
*
* Valid after Enable() succeeds. Returns -1 if the interface is not enabled.
*
*/
int8_t GetIid(void) const { return m_iid; }
private:
enum {
/**
* Maximum wait time in Milliseconds for socket to become writable (see `SendFrame`).
*
*/
kMaxWaitTime = 2000,
};
esp_err_t InitUart(const esp_radio_spinel_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);
int TryReadSpinel(void);
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_radio_spinel_uart_config_t m_uart_config;
int m_uart_fd;
int m_wait_fd;
int8_t m_iid;
otRcpInterfaceMetrics mInterfaceMetrics;
esp_radio_spinel_rcp_failure_handler mRcpFailureHandler;
// Non-copyable, intentionally not implemented.
UartSpinelInterface(const UartSpinelInterface &);
UartSpinelInterface &operator=(const UartSpinelInterface &);
esp_radio_spinel_rcp_failure_handler mRcpFailureHandler;
esp_radio_spinel_uart_init_handler mUartInitHandler;
esp_radio_spinel_uart_deinit_handler mUartDeinitHandler;
ot::Spinel::FrameBuffer<kMaxFrameSize> encoder_buffer;
};
} // namespace radio_spinel
@@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include "esp_err.h"
#include "esp_radio_spinel.h"
#include "lib/spinel/spinel_interface.hpp"
/**
* Optional UART open/close hooks. Used by the non-multipan transport.
* The multipan transport ignores them (UART is owned by esp_radio_spinel_multipan_init()).
*/
typedef struct {
esp_radio_spinel_uart_init_handler uart_init; /* May be NULL. */
esp_radio_spinel_uart_deinit_handler uart_deinit; /* May be NULL. */
} esp_radio_spinel_uart_transport_hooks_t;
/**
* Open the UART spinel transport.
*
* Non-multipan: opens the real UART and returns that fd as wait_fd. @p iid is set to 0.
* Multipan: allocates a free IID slot and returns an eventfd as wait_fd.
*
* @param[in] config UART config. Non-multipan uses it to open the port.
* Multipan may ignore it if esp_radio_spinel_multipan_init() already ran.
* @param[in] hooks Optional UART init/deinit hooks. May be NULL.
* @param[out] iid Allocated spinel interface id.
* @param[out] wait_fd Fd to select for a readable spinel frame.
*
* @return ESP_OK on success, an error code otherwise.
*/
esp_err_t esp_radio_spinel_uart_transport_open(const esp_radio_spinel_uart_config_t *config,
const esp_radio_spinel_uart_transport_hooks_t *hooks, int8_t *iid,
int *wait_fd);
/**
* Close the transport opened for @p iid.
*/
esp_err_t esp_radio_spinel_uart_transport_close(int8_t iid);
/**
* Bind the RadioSpinel receive buffer and callback for @p iid.
*
* Called from UartSpinelInterface::Init (and Enable if Init already ran).
* Non-multipan attaches the buffer to the HDLC decoder.
* Multipan stores them on the host slot and invokes the callback from read().
*/
esp_err_t esp_radio_spinel_uart_transport_bind_rx(int8_t iid,
ot::Spinel::SpinelInterface::ReceiveFrameCallback callback,
void *context,
ot::Spinel::SpinelInterface::RxFrameBuffer *frame_buffer);
/**
* Undo bind_rx for @p iid.
*
* Non-multipan detaches the HDLC decoder from the RxFrameBuffer.
* Multipan clears the host slot's buffer and callback.
* Safe to call if nothing is bound.
*/
void esp_radio_spinel_uart_transport_unbind_rx(int8_t iid);
/**
* Drain the transport for @p iid.
*
* Non-multipan: reads UART and HDLC-decodes into the bound RxFrameBuffer.
* Complete frames are delivered via the callback registered with bind_rx.
* Multipan: dequeues pending spinel frames, copies each into the bound
* RxFrameBuffer, then invokes the same callback.
*
* @return 0 on success (including no complete frame), -1 on error.
*/
int esp_radio_spinel_uart_transport_read(int8_t iid);
/**
* Write one complete spinel frame (HDLC-encoded internally).
*
* @return @p len on success, -1 on error.
*/
ssize_t esp_radio_spinel_uart_transport_write(int8_t iid, const void *buf, size_t len);
/**
* Re-install the UART after a bus error. Client wait fds stay valid.
*/
esp_err_t esp_radio_spinel_uart_transport_recover(int8_t iid);
/**
* UART baud rate in bits/second.
*/
uint32_t esp_radio_spinel_uart_transport_get_bus_speed(int8_t iid);
@@ -469,6 +469,49 @@
#endif
#endif
#if CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
/**
* @def OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID
*
* Define broadcast IID for spinel frames dedicated to all hosts in multipan configuration.
*/
#ifdef OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID
#error `OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID` is redefined.
#endif
#define OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID SPINEL_HEADER_IID(CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT)
/**
* @def OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID
*
* Specifies the maximum number of Spinel interface IDs.
*/
#ifdef OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID
#error `OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID` is redefined.
#endif
#define OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID 2
/**
* @def OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE
*
* Enables compilation of vendor specific code for Spinel
*/
#ifdef OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE
#error `OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE` is redefined.
#endif
#define OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE 1
/**
* @def OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
*
* Define to 1 to enable multipan RCP support.
*/
#ifdef OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
#error `OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE` is redefined.
#endif
#define OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE 1
#endif // CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
/*----The following options set fixed default values but can be overridden by the user header file.----*/
#if CONFIG_OPENTHREAD_BORDER_ROUTER