From ea8799eef78d0bb4c4a25622640e61b6dc108fc5 Mon Sep 17 00:00:00 2001 From: Xu Si Yu Date: Wed, 27 Dec 2023 15:12:25 +0800 Subject: [PATCH] feat(openthread): support esp openthread radio spinel --- components/openthread/CMakeLists.txt | 53 ++- components/openthread/Kconfig | 13 + .../openthread/include/esp_radio_spinel.h | 331 ++++++++++++++++ .../private_include/esp_openthread_ncp.h | 15 + .../esp_radio_spinel_adapter.hpp | 29 ++ .../esp_radio_spinel_uart_interface.hpp | 204 ++++++++++ .../openthread-core-esp32x-radio-config.h | 14 +- ...=> openthread-core-esp32x-spinel-config.h} | 11 +- .../openthread/src/ncp/esp_openthread_ncp.cpp | 111 ++++++ .../src/ncp/esp_openthread_ncp_hdlc.cpp | 33 ++ .../src/spinel/esp_radio_spinel.cpp | 352 ++++++++++++++++++ .../esp_radio_spinel_uart_interface.cpp | 300 +++++++++++++++ 12 files changed, 1454 insertions(+), 12 deletions(-) create mode 100644 components/openthread/include/esp_radio_spinel.h create mode 100644 components/openthread/private_include/esp_openthread_ncp.h create mode 100644 components/openthread/private_include/esp_radio_spinel_adapter.hpp create mode 100644 components/openthread/private_include/esp_radio_spinel_uart_interface.hpp rename components/openthread/private_include/{openthread-spinel-config.h => openthread-core-esp32x-spinel-config.h} (82%) create mode 100644 components/openthread/src/ncp/esp_openthread_ncp.cpp create mode 100644 components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp create mode 100644 components/openthread/src/spinel/esp_radio_spinel.cpp create mode 100644 components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp diff --git a/components/openthread/CMakeLists.txt b/components/openthread/CMakeLists.txt index 8dfd62708f..eb3d3dc339 100644 --- a/components/openthread/CMakeLists.txt +++ b/components/openthread/CMakeLists.txt @@ -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( diff --git a/components/openthread/Kconfig b/components/openthread/Kconfig index c298ca40e5..fc2fa8115c 100644 --- a/components/openthread/Kconfig +++ b/components/openthread/Kconfig @@ -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 diff --git a/components/openthread/include/esp_radio_spinel.h b/components/openthread/include/esp_radio_spinel.h new file mode 100644 index 0000000000..7a3fda8837 --- /dev/null +++ b/components/openthread/include/esp_radio_spinel.h @@ -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 +#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 diff --git a/components/openthread/private_include/esp_openthread_ncp.h b/components/openthread/private_include/esp_openthread_ncp.h new file mode 100644 index 0000000000..39f9e9532d --- /dev/null +++ b/components/openthread/private_include/esp_openthread_ncp.h @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#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 diff --git a/components/openthread/private_include/esp_radio_spinel_adapter.hpp b/components/openthread/private_include/esp_radio_spinel_adapter.hpp new file mode 100644 index 0000000000..50ba3c540f --- /dev/null +++ b/components/openthread/private_include/esp_radio_spinel_adapter.hpp @@ -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 class SpinelInterfaceAdapter { +public: + SpinelInterfaceAdapter(void) {} + + ~SpinelInterfaceAdapter(void) {} + + InterfaceType &GetSpinelInterface(void) { return mSpinelInterface; } + +private: + InterfaceType mSpinelInterface; +}; + +} // namespace radio_spinel +} // namespace esp diff --git a/components/openthread/private_include/esp_radio_spinel_uart_interface.hpp b/components/openthread/private_include/esp_radio_spinel_uart_interface.hpp new file mode 100644 index 0000000000..b053d48519 --- /dev/null +++ b/components/openthread/private_include/esp_radio_spinel_uart_interface.hpp @@ -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 diff --git a/components/openthread/private_include/openthread-core-esp32x-radio-config.h b/components/openthread/private_include/openthread-core-esp32x-radio-config.h index 6a292f2d6e..2f34b61ef1 100644 --- a/components/openthread/private_include/openthread-core-esp32x-radio-config.h +++ b/components/openthread/private_include/openthread-core-esp32x-radio-config.h @@ -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 diff --git a/components/openthread/private_include/openthread-spinel-config.h b/components/openthread/private_include/openthread-core-esp32x-spinel-config.h similarity index 82% rename from components/openthread/private_include/openthread-spinel-config.h rename to components/openthread/private_include/openthread-core-esp32x-spinel-config.h index 2911ee3df6..65badda57e 100644 --- a/components/openthread/private_include/openthread-spinel-config.h +++ b/components/openthread/private_include/openthread-core-esp32x-spinel-config.h @@ -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_ diff --git a/components/openthread/src/ncp/esp_openthread_ncp.cpp b/components/openthread/src/ncp/esp_openthread_ncp.cpp new file mode 100644 index 0000000000..7e5127f869 --- /dev/null +++ b/components/openthread/src/ncp/esp_openthread_ncp.cpp @@ -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(pending_mode)); + break; + } + + default: + error = OT_ERROR_NOT_FOUND; + break; + } + + return error; +} + +} // namespace Ncp +} // namespace ot + +#endif // #if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK diff --git a/components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp b/components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp new file mode 100644 index 0000000000..0e8a3dbb88 --- /dev/null +++ b/components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp @@ -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(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 diff --git a/components/openthread/src/spinel/esp_radio_spinel.cpp b/components/openthread/src/spinel/esp_radio_spinel.cpp new file mode 100644 index 0000000000..25374d862d --- /dev/null +++ b/components/openthread/src/spinel/esp_radio_spinel.cpp @@ -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 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(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(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(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; +} diff --git a/components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp b/components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp new file mode 100644 index 0000000000..e633e78266 --- /dev/null +++ b/components/openthread/src/spinel/esp_radio_spinel_uart_interface.cpp @@ -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 +#include +#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(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 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(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(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