feat(openthread): support esp openthread radio spinel

This commit is contained in:
Xu Si Yu 2023-12-27 15:12:25 +08:00
parent 6f053704dc
commit ea8799eef7
12 changed files with 1454 additions and 12 deletions

View File

@ -4,7 +4,7 @@ if(${idf_target} STREQUAL "linux")
return() # This component is not supported by the POSIX/Linux simulator
endif()
if(CONFIG_OPENTHREAD_ENABLED OR CONFIG_IDF_DOC_BUILD)
if(CONFIG_OPENTHREAD_ENABLED OR CONFIG_IDF_DOC_BUILD OR CONFIG_OPENTHREAD_SPINEL_ONLY)
set(public_include_dirs
"include"
@ -149,6 +149,11 @@ if(CONFIG_OPENTHREAD_ENABLED)
-Wno-maybe-uninitialized)
endif()
if(CONFIG_OPENTHREAD_NCP_VENDOR_HOOK)
list(APPEND src_dirs
"src/ncp")
endif()
if(NOT CONFIG_OPENTHREAD_DNS64_CLIENT)
list(APPEND exclude_srcs
"src/esp_openthread_dns64.c")
@ -171,6 +176,48 @@ if(CONFIG_OPENTHREAD_ENABLED)
elseif(CONFIG_OPENTHREAD_RADIO)
set(device_type "OPENTHREAD_RADIO=1")
endif()
elseif(CONFIG_OPENTHREAD_SPINEL_ONLY)
set(src_dirs
"src/spinel"
"src/port"
"openthread/src/lib/spinel"
"openthread/src/lib/hdlc"
"openthread/src/lib/platform"
"openthread/src/core/api"
"openthread/src/core/common"
"openthread/src/core/mac")
set(private_include_dirs
"private_include"
"openthread/src"
"openthread/src/core"
"openthread/src/lib"
"openthread/src/lib/hdlc"
"openthread/src/lib/spinel")
file(GLOB_RECURSE exclude_srcs_list
"src/port/*"
"openthread/src/core/api/*.cpp"
"openthread/src/core/common/*"
"openthread/src/core/mac/*")
list(REMOVE_ITEM exclude_srcs_list
"${CMAKE_CURRENT_SOURCE_DIR}/src/port/esp_openthread_alarm.c"
"${CMAKE_CURRENT_SOURCE_DIR}/src/port/esp_openthread_logging.c"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/api/error_api.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/api/logging_api.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/common/error.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/common/error.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/common/log.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/common/log.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/common/logging.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/common/string.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/common/string.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/mac/mac_frame.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/openthread/src/core/mac/mac_frame.hpp")
list(APPEND exclude_srcs ${exclude_srcs_list})
endif()
@ -201,13 +248,15 @@ idf_component_register(SRC_DIRS "${src_dirs}"
PRIV_REQUIRES console esp_event esp_partition esp_timer
ieee802154 mbedtls nvs_flash)
if(CONFIG_OPENTHREAD_ENABLED)
if(CONFIG_OPENTHREAD_ENABLED OR CONFIG_OPENTHREAD_SPINEL_ONLY)
if(CONFIG_OPENTHREAD_RADIO)
set(CONFIG_FILE_TYPE "radio")
elseif(CONFIG_OPENTHREAD_FTD)
set(CONFIG_FILE_TYPE "ftd")
elseif(CONFIG_OPENTHREAD_MTD)
set(CONFIG_FILE_TYPE "mtd")
elseif(CONFIG_OPENTHREAD_SPINEL_ONLY)
set(CONFIG_FILE_TYPE "spinel")
endif()
target_compile_definitions(

View File

@ -168,6 +168,13 @@ menu "OpenThread"
Select this to enable SPI connection to host.
endchoice
config OPENTHREAD_NCP_VENDOR_HOOK
bool "Enable vendor command for RCP"
depends on OPENTHREAD_RADIO
default n
help
Select this to enable OpenThread NCP vendor commands.
config OPENTHREAD_CLI
bool "Enable Openthread Command-Line Interface"
depends on OPENTHREAD_ENABLED
@ -362,4 +369,10 @@ menu "OpenThread"
Select this option to enable the radio statistics feature, you can use radio command to print some radio
Statistics informations.
config OPENTHREAD_SPINEL_ONLY
bool "Enable OpenThread External Radio Spinel feature"
default n
help
Select this option to enable the OpenThread Radio Spinel for external protocol stack, such as Zigbee.
endmenu

View File

@ -0,0 +1,331 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/select.h>
#include "esp_ieee802154_types.h"
#include "driver/uart.h"
#include "soc/gpio_num.h"
#define ESP_SPINEL_LOG_TAG "ESP_RADIO_SPINEL"
#define SPINEL_PROP_VENDOR_ESP_SET_COORDINATOR (SPINEL_PROP_VENDOR_ESP__BEGIN + 1) /* Vendor command for coordinator.*/
#define SPINEL_PROP_VENDOR_ESP_SET_PENDINGMODE (SPINEL_PROP_VENDOR_ESP__BEGIN + 2) /* Vendor command for pending mode.*/
typedef enum {
ESP_RADIO_SPINEL_ZIGBEE = 0x0, /* The index of Zigbee.*/
ESP_RADIO_SPINEL_OPENTHREAD = 0x1, /* The index of OpenThread.*/
ESP_RADIO_SPINEL_MAX,
} esp_radio_spinel_idx_t; /* The index of 802.15.4 related protocol stack for ESP radio spinel.*/
typedef struct {
fd_set read_fds; /* The read file descriptors.*/
fd_set write_fds; /* The write file descriptors.*/
fd_set error_fds; /* The error file descriptors.*/
int max_fd; /* The max file descriptor.*/
struct timeval timeout; /* The timeout.*/
} esp_radio_spinel_mainloop_context_t;
typedef struct {
uart_port_t port; /*!< UART port number */
uart_config_t uart_config; /*!< UART configuration, see uart_config_t docs */
gpio_num_t rx_pin; /*!< UART RX pin */
gpio_num_t tx_pin; /*!< UART TX pin */
} esp_radio_spinel_uart_config_t; /*This structure represents a context for ESP radio spinel. */
typedef void (*esp_radio_spinel_rcp_failure_handler)(void); /* The handler for rcp failure.*/
typedef esp_err_t (*esp_radio_spinel_uart_init_handler)(const esp_radio_spinel_uart_config_t *uart_config_t, int *uart_fd); /* The handler for UART initialization.*/
typedef esp_err_t (*esp_radio_spinel_uart_deinit_handler)(const esp_radio_spinel_uart_config_t *uart_config_t, int *uart_fd); /* The handler for UART deinitialization.*/
typedef struct
{
void (*receive_done)(const uint8_t *frame, esp_ieee802154_frame_info_t *frame_info); /* Callback for Receive Done.*/
void (*transmit_done)(const uint8_t *frame, const uint8_t *ack, esp_ieee802154_frame_info_t *ack_frame_info); /* Callback for Transmit Done.*/
void (*transmit_failed)(esp_ieee802154_tx_error_t error); /* Callback for Transmit Failed.*/
void (*energy_scan_done)(int8_t max_rssi); /* Callback for Energy Scan Done.*/
void (*transmit_started)(const uint8_t *frame); /* Callback for Transmit Started.*/
void (*switchover_done)(bool success); /* Callback for Switchover Done.*/
#if OPENTHREAD_CONFIG_DIAG_ENABLE
void (*diag_receive_done)(const uint8_t *frame, esp_ieee802154_frame_info_t *frame_info); /* Callback for Receive Done (diag).*/
void (*diag_transmit_done)(const uint8_t *frame, esp_ieee802154_frame_info_t *frame_info); /* Callback for Transmit Done (diag).*/
void (*diag_transmit_failed)(esp_ieee802154_tx_error_t error); /* Callback for Transmit Failed (diag).*/
#endif // OPENTHREAD_CONFIG_DIAG_ENABLE
} esp_radio_spinel_callbacks_t; /* ESP Radio Spinel Callbacks.*/
/**
* @brief Set callkbacks of ESP radio spinel.
*
* @note This function should be called before `esp_radio_spinel_init`.
*
* @param[in] aCallbacks The callbacks struct.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_callbacks(const esp_radio_spinel_callbacks_t aCallbacks, esp_radio_spinel_idx_t idx);
/**
* @brief Enable the UART interface for ESP radio spinel
*
* @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
* @param[in] idx The index of 802.15.4 related protocol stack.
*
* @return
* - ESP_OK on success
* - ESP_FAIL on failures
*
*/
esp_err_t esp_radio_spinel_uart_interface_enable(const esp_radio_spinel_uart_config_t *radio_uart_config,
esp_radio_spinel_uart_init_handler aUartInitHandler,
esp_radio_spinel_uart_deinit_handler aUartDeinitHandler,
esp_radio_spinel_idx_t idx);
/**
* @brief Initialize ESP radio spinel.
*
* @note This function should be called after `esp_radio_spinel_set_callbacks` and `esp_radio_spinel_uart_interface_enable`.
*
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_init(esp_radio_spinel_idx_t idx);
/**
* @brief Enavle ESP radio spinel.
*
* @note This function should be called after `esp_radio_spinel_init`.
*
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_enable(esp_radio_spinel_idx_t idx);
/**
* @brief Set the pending mode.
*
* @param[in] pending_mode The pending mode.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_pending_mode(esp_ieee802154_pending_mode_t pending_mode, esp_radio_spinel_idx_t idx);
/**
* @brief Get the EUI-64.
*
* @param[in] eui64 A pointer to the EUI-64.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_get_eui64(uint8_t *eui64, esp_radio_spinel_idx_t idx);
/**
* @brief Set the panid.
*
* @param[in] panid The panid.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_panid(uint16_t panid, esp_radio_spinel_idx_t idx);
/**
* @brief Set the short address.
*
* @param[in] short_address The short address.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_short_address(uint16_t short_address, esp_radio_spinel_idx_t idx);
/**
* @brief Set the extended address.
*
* @param[in] ext_address The extended address.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_extended_address(uint8_t *ext_address, esp_radio_spinel_idx_t idx);
/**
* @brief Set the coordinator mode.
*
* @param[in] enable Enable or disable the coordinator mode.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_pan_coord(bool enable, esp_radio_spinel_idx_t idx);
/**
* @brief Enable the RCP reception.
*
* @param[in] channel The channel of reception.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_receive(uint8_t channel, esp_radio_spinel_idx_t idx);
/**
* @brief Perform the energy scan.
*
* @param[in] scan_channel The channel of energy scan (usually the range is 11~26, scan all channels if it's set to 0).
* @param[in] scan_duration The duration for energy scan.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_energy_scan(uint8_t scan_channel, uint16_t scan_duration, esp_radio_spinel_idx_t idx);
/**
* @brief Perform the transmission.
*
* @param[in] frame A pointer to the frame.
* @param[in] channel The channel to use for transmitting.
* @param[in] cca Perform clear channel assessment(if it's true) or not(if it's false)
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_transmit(uint8_t *frame, uint8_t channel, bool cca, esp_radio_spinel_idx_t idx);
/**
* @brief Clear all short addresses from the source address match table.
*
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_clear_short_entries(esp_radio_spinel_idx_t idx);
/**
* @brief Add a short address to the source address match table.
*
* @param[in] short_address The short address to be added.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
* @return
* - ESP_OK on success
* - ESP_FAIL on failures
*
*/
esp_err_t esp_radio_spinel_add_short_entry(uint16_t short_address, esp_radio_spinel_idx_t idx);
/**
* @brief Clear the pending table, remove all extended/long addresses.
*
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_clear_extened_entries(esp_radio_spinel_idx_t idx);
/**
* @brief Add an extended address to the source address match table.
*
* @param[in] ext_address The extended address to be added.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
* @return
* - ESP_OK on success
* - ESP_FAIL on failures
*
*/
esp_err_t esp_radio_spinel_add_extened_entry(uint8_t *ext_address, esp_radio_spinel_idx_t idx);
/**
* @brief Sets the status of promiscuous mode.
*
* @param[in] enable Whether to enable or disable promiscuous mode.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_promiscuous_mode(bool enable, esp_radio_spinel_idx_t idx);
/**
* @brief Update the ESP radio spinel.
*
* @param[in] mainloop_context The context for ESP radio spinel.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_radio_update(esp_radio_spinel_mainloop_context_t *mainloop_context, esp_radio_spinel_idx_t idx);
/**
* @brief Process the ESP radio spinel.
*
* @param[in] mainloop_context The context for ESP radio spinel.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_radio_process(esp_radio_spinel_mainloop_context_t *mainloop_context, esp_radio_spinel_idx_t idx);
/**
* @brief Switch the radio state to Sleep.
*
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_sleep(esp_radio_spinel_idx_t idx);
/**
* @brief Set the radio's transmit power in dBm.
*
* @param[in] power The transmit power in dBm.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_set_tx_power(int8_t power, esp_radio_spinel_idx_t idx);
/**
* @brief Get the radio's transmit power in dBm.
*
* @param[in] power A pointer to the transmit power.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_get_tx_power(int8_t *power, esp_radio_spinel_idx_t idx);
/**
* @brief Register a handler to process the RCP failure.
*
* @param[in] handler The RCP failure handler.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
*/
void esp_radio_spinel_register_rcp_failure_handler(esp_radio_spinel_rcp_failure_handler handler, esp_radio_spinel_idx_t idx);
/**
* @brief Deinitialize the RCP.
*
* @param[in] idx The index of 802.15.4 related protocol stack.
*
* @return
* - ESP_OK on success
* - ESP_FAIL on failures
*
*/
esp_err_t esp_radio_spinel_rcp_deinit(esp_radio_spinel_idx_t idx);
/**
* @brief Get the version of RCP.
*
* @param[in] running_rcp_version A pointer to the RCP version string.
* @param[in] idx The index of 802.15.4 related protocol stack.
*
* @return
* - ESP_OK on success
* - ESP_FAIL on failures
*
*/
esp_err_t esp_radio_spinel_rcp_version_get(char *running_rcp_version, esp_radio_spinel_idx_t idx);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <spinel.h>
#if CONFIG_OPENTHREAD_NCP_VENDOR_HOOK
#define SPINEL_PROP_VENDOR_ESP_SET_COORDINATOR (SPINEL_PROP_VENDOR_ESP__BEGIN + 1)
#define SPINEL_PROP_VENDOR_ESP_SET_PENDINGMODE (SPINEL_PROP_VENDOR_ESP__BEGIN + 2)
#endif

View File

@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
namespace esp {
namespace radio_spinel {
/**
* This class defines an template to adapt both UartSpinelInterface and SpiSpinelInterface.
*
*/
template <typename InterfaceType> class SpinelInterfaceAdapter {
public:
SpinelInterfaceAdapter(void) {}
~SpinelInterfaceAdapter(void) {}
InterfaceType &GetSpinelInterface(void) { return mSpinelInterface; }
private:
InterfaceType mSpinelInterface;
};
} // namespace radio_spinel
} // namespace esp

View File

@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_radio_spinel.h"
#include "lib/spinel/spinel_interface.hpp"
#include "lib/hdlc/hdlc.hpp"
#include "openthread/error.h"
namespace esp {
namespace radio_spinel {
/**
* 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_radio_spinel_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_radio_spinel_uart_config_t &radio_uart_config);
/**
* @brief This method disable the HDLC interface.
*
*/
esp_err_t Disable(void);
void RegisterUartInitHandler(esp_radio_spinel_uart_init_handler handler) { mUartInitHandler = handler; }
void RegisterUartDeinitHandler(esp_radio_spinel_uart_deinit_handler handler) { mUartDeinitHandler = handler; }
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);
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;
otRcpInterfaceMetrics mInterfaceMetrics;
// 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;
};
} // namespace radio_spinel
} // namespace esp

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -218,3 +218,15 @@
#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 1
#endif
#endif //CONFIG_OPENTHREAD_LINK_METRICS
#if CONFIG_OPENTHREAD_NCP_VENDOR_HOOK
/**
* @def OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
*
* Define as 1 to support ESP OpenThread NCP vendor commands
*
*/
#ifndef OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
#define OPENTHREAD_ENABLE_NCP_VENDOR_HOOK 1
#endif
#endif //CONFIG_OPENTHREAD_NCP_VENDOR_HOOK

View File

@ -4,13 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* This file includes compile-time configuration constants for OpenThread.
*/
#ifndef OPENTHREAD_SPINEL_CONFIG_H_
#define OPENTHREAD_SPINEL_CONFIG_H_
#pragma once
/**
* @def OPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE
@ -30,6 +24,7 @@
*
*/
#ifndef OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT
// TZ-567: Set OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT to 3 after adding rcp failure notification mechanism
#define OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT 0
#endif
@ -42,5 +37,3 @@
#ifndef OPENTHREAD_SPINEL_CONFIG_RCP_CUSTOM_RESTORATION
#define OPENTHREAD_SPINEL_CONFIG_RCP_CUSTOM_RESTORATION 0
#endif
#endif // OPENTHREAD_SPINEL_CONFIG_H_

View File

@ -0,0 +1,111 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_ieee802154.h"
#include "esp_openthread_ncp.h"
#include "ncp_base.hpp"
#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
#if CONFIG_OPENTHREAD_RCP_UART
#include "utils/uart.h"
#endif
#if CONFIG_OPENTHREAD_RCP_UART
extern "C" {
static int NcpSend(const uint8_t *aBuf, uint16_t aBufLength)
{
IgnoreError(otPlatUartSend(aBuf, aBufLength));
return aBufLength;
}
}
#endif
extern "C" void otAppNcpInit(otInstance *aInstance)
{
#if CONFIG_OPENTHREAD_RCP_SPI
otNcpSpiInit(aInstance);
#else
IgnoreError(otPlatUartEnable());
otNcpHdlcInit(aInstance, NcpSend);
#endif
}
namespace ot {
namespace Ncp {
otError NcpBase::VendorCommandHandler(uint8_t aHeader, unsigned int aCommand)
{
otError error = OT_ERROR_NONE;
otPlatLog(OT_LOG_LEVEL_WARN, OT_LOG_REGION_NCP, "VendorCommandHandler Not Implemented");
switch (aCommand)
{
default:
error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_INVALID_COMMAND);
break;
}
return error;
}
void NcpBase::VendorHandleFrameRemovedFromNcpBuffer(Spinel::Buffer::FrameTag aFrameTag)
{
OT_UNUSED_VARIABLE(aFrameTag);
}
otError NcpBase::VendorGetPropertyHandler(spinel_prop_key_t aPropKey)
{
otError error = OT_ERROR_NONE;
switch (aPropKey)
{
default:
error = OT_ERROR_NOT_FOUND;
break;
}
return error;
}
otError NcpBase::VendorSetPropertyHandler(spinel_prop_key_t aPropKey)
{
otError error = OT_ERROR_NONE;
switch (aPropKey)
{
// TZ-566: Add mechanism to allow users to register callback functions.
case SPINEL_PROP_VENDOR_ESP_SET_COORDINATOR: {
bool coordinator = false;
mDecoder.ReadBool(coordinator);
esp_ieee802154_set_coordinator(coordinator);
break;
}
case SPINEL_PROP_VENDOR_ESP_SET_PENDINGMODE: {
int32_t pending_mode = 0;
mDecoder.ReadInt32(pending_mode);
esp_ieee802154_set_pending_mode(static_cast<esp_ieee802154_pending_mode_t>(pending_mode));
break;
}
default:
error = OT_ERROR_NOT_FOUND;
break;
}
return error;
}
} // namespace Ncp
} // namespace ot
#endif // #if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK

View File

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common/new.hpp"
#include "ncp_hdlc.hpp"
#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
namespace ot {
namespace Ncp {
static OT_DEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpHdlc), uint64_t);
extern "C" void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSendCallback)
{
NcpHdlc *ncpHdlc = nullptr;
Instance *instance = static_cast<Instance *>(aInstance);
ncpHdlc = new (&sNcpRaw) NcpHdlc(instance, aSendCallback);
if (ncpHdlc == nullptr || ncpHdlc != NcpBase::GetNcpInstance())
{
OT_ASSERT(false);
}
}
} // namespace Ncp
} // namespace ot
#endif // #if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK

View File

@ -0,0 +1,352 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "esp_log.h"
#include "platform/exit_code.h"
#include "radio_spinel.hpp"
#include "esp_radio_spinel.h"
#include "esp_radio_spinel_adapter.hpp"
#include "esp_radio_spinel_uart_interface.hpp"
using ot::Spinel::RadioSpinel;
using ot::Spinel::RadioSpinelCallbacks;
using esp::radio_spinel::SpinelInterfaceAdapter;
using esp::radio_spinel::UartSpinelInterface;
static SpinelInterfaceAdapter<UartSpinelInterface> s_spinel_interface[ot::Spinel::kSpinelHeaderMaxNumIid];
static RadioSpinel s_radio[ot::Spinel::kSpinelHeaderMaxNumIid];
static esp_radio_spinel_callbacks_t s_esp_radio_spinel_callbacks[ot::Spinel::kSpinelHeaderMaxNumIid];
otRadioFrame s_transmit_frame;
static esp_radio_spinel_idx_t get_index_from_instance(otInstance *instance)
{
// TZ-563: Implement the function to get the esp radio spinel idx from otInstance for multipan rcp
return ESP_RADIO_SPINEL_ZIGBEE;
}
static otInstance* get_instance_from_index(esp_radio_spinel_idx_t idx)
{
// TZ-563: Implement the function to get otInstance pointer from esp radio spinel idx
return nullptr;
}
void ReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
{
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
assert(s_esp_radio_spinel_callbacks[idx].receive_done);
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
esp_ieee802154_frame_info_t frame_info;
if (frame) {
frame[0] = aFrame->mLength;
memcpy((void *)(frame + 1), aFrame->mPsdu, frame[0]);
frame_info.rssi = aFrame->mInfo.mRxInfo.mRssi;
frame_info.timestamp = aFrame->mInfo.mRxInfo.mTimestamp;
frame_info.pending = aFrame->mInfo.mRxInfo.mAckedWithFramePending;
s_esp_radio_spinel_callbacks[idx].receive_done(frame, &frame_info);
free(frame);
} else {
ESP_LOGE(ESP_SPINEL_LOG_TAG, "Fail to alloc memory for frame");
}
}
void TransmitDone(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError)
{
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
assert(s_esp_radio_spinel_callbacks[idx].transmit_done && s_esp_radio_spinel_callbacks[idx].transmit_failed);
if (aError == OT_ERROR_NONE) {
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
uint8_t *ack = nullptr;
if (frame) {
esp_ieee802154_frame_info_t ack_info;
frame[0] = aFrame->mLength;
memcpy((void *)(frame + 1), aFrame->mPsdu, frame[0]);
if (aAckFrame) {
ack = (uint8_t *)malloc(aAckFrame->mLength + 1);
if (ack) {
ack[0] = aAckFrame->mLength;
memcpy((void *)(ack + 1), aAckFrame->mPsdu, ack[0]);
} else {
ESP_LOGE(ESP_SPINEL_LOG_TAG, "Fail to alloc memory for ack");
}
}
s_esp_radio_spinel_callbacks[idx].transmit_done(frame, ack, &ack_info);
free(frame);
free(ack);
} else {
ESP_LOGE(ESP_SPINEL_LOG_TAG, "Fail to alloc memory for frame");
}
} else {
switch (aError) {
case OT_ERROR_CHANNEL_ACCESS_FAILURE:
s_esp_radio_spinel_callbacks[idx].transmit_failed(ESP_IEEE802154_TX_ERR_CCA_BUSY);
break;
case OT_ERROR_NO_ACK:
s_esp_radio_spinel_callbacks[idx].transmit_failed(ESP_IEEE802154_TX_ERR_NO_ACK);
break;
default:
s_esp_radio_spinel_callbacks[idx].transmit_failed(ESP_IEEE802154_TX_ERR_ABORT);
break;
}
}
}
void EnergyScanDone(otInstance *aInstance, int8_t aMaxRssi)
{
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
assert(s_esp_radio_spinel_callbacks[idx].energy_scan_done);
s_esp_radio_spinel_callbacks[idx].energy_scan_done(aMaxRssi);
}
void TxStarted(otInstance *aInstance, otRadioFrame *aFrame)
{
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
assert(s_esp_radio_spinel_callbacks[idx].transmit_started);
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
if (frame) {
frame[0] = aFrame->mLength;
memcpy((void *)(frame + 1), aFrame->mPsdu, frame[0]);
s_esp_radio_spinel_callbacks[idx].transmit_started(frame);
free(frame);
} else {
ESP_LOGE(ESP_SPINEL_LOG_TAG, "Fail to alloc memory for frame");
}
}
void SwitchoverDone(otInstance *aInstance, bool aSuccess)
{
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
assert(s_esp_radio_spinel_callbacks[idx].switchover_done);
s_esp_radio_spinel_callbacks[idx].switchover_done(aSuccess);
}
#if OPENTHREAD_CONFIG_DIAG_ENABLE
void DiagReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
{
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
assert(s_esp_radio_spinel_callbacks[idx].diag_receive_done);
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
esp_ieee802154_frame_info_t frame_info;
if (frame) {
frame[0] = aFrame->mLength;
memcpy((void *)(frame + 1), aFrame->mPsdu, frame[0]);
frame_info.rssi = aFrame->mInfo.mRxInfo.mRssi;
frame_info.timestamp = aFrame->mInfo.mRxInfo.mTimestamp;
frame_info.pending = aFrame->mInfo.mRxInfo.mAckedWithFramePending;
s_esp_radio_spinel_callbacks[idx].diag_receive_done(frame, &frame_info);
free(frame);
} else {
ESP_LOGE(ESP_SPINEL_LOG_TAG, "Fail to alloc memory for frame");
}
}
void DiagTransmitDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
{
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
assert(s_esp_radio_spinel_callbacks[idx].diag_transmit_done && s_esp_radio_spinel_callbacks[idx].diag_transmit_failed);
if (aError == OT_ERROR_NONE) {
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
if (frame) {
esp_ieee802154_frame_info_t ack_info;
frame[0] = aFrame->mLength;
memcpy((void *)(frame + 1), aFrame->mPsdu, frame[0]);
s_esp_radio_spinel_callbacks[idx].diag_transmit_done(frame, &ack_info);
free(frame);
} else {
ESP_LOGE(ESP_SPINEL_LOG_TAG, "Fail to alloc memory for frame");
}
} else {
switch (aError) {
case OT_ERROR_CHANNEL_ACCESS_FAILURE:
s_esp_radio_spinel_callbacks[idx].diag_transmit_failed(ESP_IEEE802154_TX_ERR_CCA_BUSY);
break;
case OT_ERROR_NO_ACK:
s_esp_radio_spinel_callbacks[idx].diag_transmit_failed(ESP_IEEE802154_TX_ERR_NO_ACK);
break;
default:
s_esp_radio_spinel_callbacks[idx].diag_transmit_failed(ESP_IEEE802154_TX_ERR_CCA_BUSY);
break;
}
}
}
#endif // OPENTHREAD_CONFIG_DIAG_ENABLE
void esp_radio_spinel_set_callbacks(const esp_radio_spinel_callbacks_t aCallbacks, esp_radio_spinel_idx_t idx)
{
s_esp_radio_spinel_callbacks[idx] = aCallbacks;
RadioSpinelCallbacks Callbacks;
Callbacks.mReceiveDone = ReceiveDone;
Callbacks.mTransmitDone = TransmitDone;
Callbacks.mEnergyScanDone = EnergyScanDone;
Callbacks.mTxStarted = TxStarted;
Callbacks.mSwitchoverDone = SwitchoverDone;
#if OPENTHREAD_CONFIG_DIAG_ENABLE
Callbacks.mDiagReceiveDone = DiagReceiveDone;
Callbacks.mDiagTransmitDone = DiagTransmitDone;
#endif // OPENTHREAD_CONFIG_DIAG_ENABLE
s_radio[idx].SetCallbacks(Callbacks);
}
esp_err_t esp_radio_spinel_uart_interface_enable(const esp_radio_spinel_uart_config_t *radio_uart_config,
esp_radio_spinel_uart_init_handler aUartInitHandler,
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");
ESP_LOGI(ESP_SPINEL_LOG_TAG, "Spinel UART interface has been successfully enabled");
return ESP_OK;
}
void esp_radio_spinel_init(esp_radio_spinel_idx_t idx)
{
spinel_iid_t iidList[ot::Spinel::kSpinelHeaderMaxNumIid];
// Multipan is not currently supported
iidList[0] = 0;
s_radio[idx].Init(s_spinel_interface[idx].GetSpinelInterface(), /*reset_radio=*/true, /*skip_rcp_compatibility_check=*/false, iidList, ot::Spinel::kSpinelHeaderMaxNumIid);
}
void esp_radio_spinel_enable(esp_radio_spinel_idx_t idx)
{
otInstance *instance = get_instance_from_index(idx);
s_radio[idx].Enable(instance);
}
void esp_radio_spinel_set_pending_mode(esp_ieee802154_pending_mode_t pending_mode, esp_radio_spinel_idx_t idx)
{
s_radio[idx].Set(SPINEL_PROP_VENDOR_ESP_SET_PENDINGMODE, SPINEL_DATATYPE_INT32_S, static_cast<int32_t>(pending_mode));
}
void esp_radio_spinel_get_eui64(uint8_t *eui64, esp_radio_spinel_idx_t idx)
{
SuccessOrDie(s_radio[idx].GetIeeeEui64(eui64));
}
void esp_radio_spinel_set_panid(uint16_t panid, esp_radio_spinel_idx_t idx)
{
SuccessOrDie(s_radio[idx].SetPanId(panid));
}
void esp_radio_spinel_set_short_address(uint16_t short_address, esp_radio_spinel_idx_t idx)
{
SuccessOrDie(s_radio[idx].SetShortAddress(short_address));
}
void esp_radio_spinel_set_extended_address(uint8_t *ext_address, esp_radio_spinel_idx_t idx)
{
otExtAddress aExtAddress;
memcpy(aExtAddress.m8, (void *)ext_address, OT_EXT_ADDRESS_SIZE);
SuccessOrDie(s_radio[idx].SetExtendedAddress(aExtAddress));
}
void esp_radio_spinel_set_pan_coord(bool enable, esp_radio_spinel_idx_t idx)
{
s_radio[idx].Set(SPINEL_PROP_VENDOR_ESP_SET_COORDINATOR, SPINEL_DATATYPE_BOOL_S, enable);
}
void esp_radio_spinel_receive(uint8_t channel, esp_radio_spinel_idx_t idx)
{
s_radio[idx].Receive(channel);
}
void esp_radio_spinel_energy_scan(uint8_t scan_channel, uint16_t scan_duration, esp_radio_spinel_idx_t idx)
{
s_radio[idx].EnergyScan(scan_channel, scan_duration);
}
void esp_radio_spinel_transmit(uint8_t *frame, uint8_t channel, bool cca, esp_radio_spinel_idx_t idx)
{
s_transmit_frame.mLength = frame[0];
s_transmit_frame.mPsdu = frame + 1;
s_transmit_frame.mInfo.mTxInfo.mCsmaCaEnabled = cca;
s_transmit_frame.mChannel = channel;
s_transmit_frame.mInfo.mTxInfo.mRxChannelAfterTxDone = channel;
SuccessOrDie(s_radio[idx].Transmit(s_transmit_frame));
}
void esp_radio_spinel_clear_short_entries(esp_radio_spinel_idx_t idx)
{
SuccessOrDie(s_radio[idx].ClearSrcMatchShortEntries());
}
esp_err_t esp_radio_spinel_add_short_entry(uint16_t short_address, esp_radio_spinel_idx_t idx)
{
return (s_radio[idx].AddSrcMatchShortEntry(short_address) == OT_ERROR_NONE) ? ESP_OK : ESP_FAIL;
}
void esp_radio_spinel_clear_extened_entries(esp_radio_spinel_idx_t idx)
{
SuccessOrDie(s_radio[idx].ClearSrcMatchExtEntries());
}
esp_err_t esp_radio_spinel_add_extened_entry(uint8_t *ext_address, esp_radio_spinel_idx_t idx)
{
otExtAddress aExtAddress;
memcpy(aExtAddress.m8, (void *)ext_address, OT_EXT_ADDRESS_SIZE);
return (s_radio[idx].AddSrcMatchExtEntry(aExtAddress) == OT_ERROR_NONE) ? ESP_OK : ESP_FAIL;
}
void esp_radio_spinel_set_promiscuous_mode(bool enable, esp_radio_spinel_idx_t idx)
{
SuccessOrDie(s_radio[idx].SetPromiscuous(enable));
}
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));
}
void esp_radio_spinel_radio_process(esp_radio_spinel_mainloop_context_t *mainloop_context, esp_radio_spinel_idx_t idx)
{
s_radio[idx].Process(static_cast<void *>(mainloop_context));
}
void esp_radio_spinel_sleep(esp_radio_spinel_idx_t idx)
{
s_radio[idx].Sleep();
}
void esp_radio_spinel_set_tx_power(int8_t power, esp_radio_spinel_idx_t idx)
{
s_radio[idx].SetTransmitPower(power);
}
void esp_radio_spinel_get_tx_power(int8_t *power, esp_radio_spinel_idx_t idx)
{
int8_t aPower;
s_radio[idx].GetTransmitPower(aPower);
*power = aPower;
}
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);
}
esp_err_t esp_radio_spinel_rcp_deinit(esp_radio_spinel_idx_t idx)
{
if (s_radio[idx].IsEnabled()) {
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");
return ESP_OK;
}
esp_err_t esp_radio_spinel_rcp_version_get(char *running_rcp_version, esp_radio_spinel_idx_t idx)
{
const char *rcp_version = s_radio[idx].GetVersion();
ESP_RETURN_ON_FALSE(rcp_version != nullptr, ESP_FAIL, ESP_SPINEL_LOG_TAG, "Fail to get rcp version");
strcpy(running_rcp_version, rcp_version);
return ESP_OK;
}

View File

@ -0,0 +1,300 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_radio_spinel_uart_interface.hpp"
#include <errno.h>
#include <sys/unistd.h>
#include "esp_check.h"
#include "esp_openthread_common_macro.h"
#include "openthread/platform/time.h"
#include "hdlc.hpp"
namespace esp {
namespace radio_spinel {
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_radio_spinel_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);
if (error == ESP_OK) {
ESP_LOGI(ESP_SPINEL_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;
ot::Spinel::FrameBuffer<kMaxFrameSize> encoder_buffer;
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(ESP_SPINEL_LOG_TAG, "send radio frame failed");
} else {
ESP_LOGD(ESP_SPINEL_LOG_TAG, "sent radio frame");
}
return error;
}
void UartSpinelInterface::Process(const void *aMainloopContext)
{
if (FD_ISSET(m_uart_fd, &((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->read_fds)) {
ESP_LOGD(ESP_SPINEL_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(ESP_SPINEL_LOG_TAG, "received hdlc radio frame");
m_receiver_frame_callback(m_receiver_frame_context);
} else {
ESP_LOGE(ESP_SPINEL_LOG_TAG, "dropping radio frame: %s", otThreadErrorToString(error));
m_receive_frame_buffer->DiscardFrame();
}
}
esp_err_t UartSpinelInterface::InitUart(const esp_radio_spinel_uart_config_t &radio_uart_config)
{
if (mUartInitHandler) {
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;
}
}
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;
}
}
esp_err_t UartSpinelInterface::TryRecoverUart(void)
{
ESP_RETURN_ON_ERROR(DeinitUart(), ESP_SPINEL_LOG_TAG, "DeInitUart failed");
ESP_RETURN_ON_ERROR(InitUart(m_uart_config), ESP_SPINEL_LOG_TAG, "InitUart failed");
return ESP_OK;
}
otError UartSpinelInterface::HardwareReset(void)
{
if (mRcpFailureHandler) {
mRcpFailureHandler();
TryRecoverUart();
}
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_radio_spinel_mainloop_context_t *)aMainloopContext)->read_fds);
if (m_uart_fd > ((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->max_fd) {
((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->max_fd = m_uart_fd;
}
}
uint32_t UartSpinelInterface::GetBusSpeed(void) const
{
return m_uart_config.uart_config.baud_rate;
}
} // namespace radio_spinel
} // namespace esp