diff --git a/.codespellrc b/.codespellrc index 515e8859e46..ef27af48047 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,4 +1,4 @@ [codespell] skip = build,*.yuv,components/fatfs/src/*,alice.txt,*.rgb,components/wpa_supplicant/*,components/esp_wifi/*,*.pem,*/COPYING*,docs/sphinx-known-warnings.txt -ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn,registr,oen,parms +ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn,registr,oen,parms,aCount write-changes = true diff --git a/components/esp_hal_ieee802154/esp32c5/include/hal/ieee802154_ll.h b/components/esp_hal_ieee802154/esp32c5/include/hal/ieee802154_ll.h index 4f94a5224b2..840b7de86b9 100644 --- a/components/esp_hal_ieee802154/esp32c5/include/hal/ieee802154_ll.h +++ b/components/esp_hal_ieee802154/esp32c5/include/hal/ieee802154_ll.h @@ -9,3 +9,4 @@ #include "hal/ieee802154_common_ll.h" #define IEEE802154_RSSI_COMPENSATION_VALUE 0 +#define IEEE802154_RX_SENSITIVITY -104 diff --git a/components/esp_hal_ieee802154/esp32c6/include/hal/ieee802154_ll.h b/components/esp_hal_ieee802154/esp32c6/include/hal/ieee802154_ll.h index 4f94a5224b2..840b7de86b9 100644 --- a/components/esp_hal_ieee802154/esp32c6/include/hal/ieee802154_ll.h +++ b/components/esp_hal_ieee802154/esp32c6/include/hal/ieee802154_ll.h @@ -9,3 +9,4 @@ #include "hal/ieee802154_common_ll.h" #define IEEE802154_RSSI_COMPENSATION_VALUE 0 +#define IEEE802154_RX_SENSITIVITY -104 diff --git a/components/esp_hal_ieee802154/esp32h2/include/hal/ieee802154_ll.h b/components/esp_hal_ieee802154/esp32h2/include/hal/ieee802154_ll.h index 6fc4a17ef6d..0d77c446e4f 100644 --- a/components/esp_hal_ieee802154/esp32h2/include/hal/ieee802154_ll.h +++ b/components/esp_hal_ieee802154/esp32h2/include/hal/ieee802154_ll.h @@ -12,9 +12,10 @@ extern "C" { #endif -extern uint32_t bt_bb_get_rssi_comp(void); - #define IEEE802154_RSSI_COMPENSATION_VALUE bt_bb_get_rssi_comp() +#define IEEE802154_RX_SENSITIVITY -102 + +extern uint32_t bt_bb_get_rssi_comp(void); #ifdef __cplusplus } diff --git a/components/ieee802154/CMakeLists.txt b/components/ieee802154/CMakeLists.txt index 5f410b3e9bf..96b81e30cb5 100644 --- a/components/ieee802154/CMakeLists.txt +++ b/components/ieee802154/CMakeLists.txt @@ -31,6 +31,10 @@ if(CONFIG_IEEE802154_DEBUG) list(APPEND srcs "driver/esp_ieee802154_debug.c") endif() +if(CONFIG_IEEE802154_MULTI_PAN_ENABLE) + list(APPEND srcs "esp_ieee802154_multipan.c") +endif() + idf_component_register( SRCS "${srcs}" INCLUDE_DIRS "${include}" diff --git a/components/ieee802154/driver/esp_ieee802154_ack.c b/components/ieee802154/driver/esp_ieee802154_ack.c index ce15b445974..8e1aa2c8fe1 100644 --- a/components/ieee802154/driver/esp_ieee802154_ack.c +++ b/components/ieee802154/driver/esp_ieee802154_ack.c @@ -5,7 +5,6 @@ */ #include -#include "hal/ieee802154_ll.h" #include "esp_attr.h" #include "esp_err.h" #include "esp_ieee802154_ack.h" diff --git a/components/ieee802154/driver/esp_ieee802154_debug.c b/components/ieee802154/driver/esp_ieee802154_debug.c index 5979c0483fb..baa9dba1317 100644 --- a/components/ieee802154/driver/esp_ieee802154_debug.c +++ b/components/ieee802154/driver/esp_ieee802154_debug.c @@ -6,7 +6,6 @@ #include #include -#include "hal/ieee802154_ll.h" #include "esp_ieee802154_util.h" #include "esp_log.h" diff --git a/components/ieee802154/driver/esp_ieee802154_dev.c b/components/ieee802154/driver/esp_ieee802154_dev.c index 1594a660a64..7136454a430 100644 --- a/components/ieee802154/driver/esp_ieee802154_dev.c +++ b/components/ieee802154/driver/esp_ieee802154_dev.c @@ -24,10 +24,13 @@ #include "esp_ieee802154_sec.h" #include "esp_ieee802154_util.h" #include "esp_ieee802154_timer.h" -#include "hal/ieee802154_ll.h" #include "esp_attr.h" #include "esp_phy_init.h" +#if CONFIG_IEEE802154_MULTI_PAN_ENABLE +#include "esp_ieee802154_multipan.h" +#endif + #if CONFIG_PM_ENABLE #include "esp_pm.h" #include "esp_private/esp_clk.h" @@ -124,6 +127,16 @@ esp_err_t ieee802154_receive_handle_done(const uint8_t *data) return ESP_OK; } +static void ieee802154_rx_buffer_clear(void) +{ + memset(s_rx_frame, 0, sizeof(s_rx_frame)); + memset(s_rx_frame_info, 0, sizeof(s_rx_frame_info)); + s_rx_index = 0; + s_recent_rx_frame_info_index = 0; + s_needs_next_operation = false; + s_pending_rx_stop = false; +} + static IRAM_ATTR void event_end_process(void) { ieee802154_etm_channel_clear(IEEE802154_ETM_CHANNEL0); @@ -915,8 +928,7 @@ esp_err_t ieee802154_mac_init(void) ieee802154_txon_delay_set(); - memset(s_rx_frame, 0, sizeof(s_rx_frame)); - + ieee802154_rx_buffer_clear(); ieee802154_set_state(IEEE802154_STATE_IDLE); // TODO: Add flags for IEEE802154 ISR allocating. TZ-102 diff --git a/components/ieee802154/driver/esp_ieee802154_pib.c b/components/ieee802154/driver/esp_ieee802154_pib.c index b8a95369db4..e4f4d88f607 100644 --- a/components/ieee802154/driver/esp_ieee802154_pib.c +++ b/components/ieee802154/driver/esp_ieee802154_pib.c @@ -7,7 +7,6 @@ #include #include #include "sdkconfig.h" -#include "hal/ieee802154_ll.h" #include "esp_check.h" #include "esp_ieee802154_pib.h" #include "esp_ieee802154_util.h" diff --git a/components/ieee802154/driver/esp_ieee802154_sec.c b/components/ieee802154/driver/esp_ieee802154_sec.c index c628666b0a7..37a06918380 100644 --- a/components/ieee802154/driver/esp_ieee802154_sec.c +++ b/components/ieee802154/driver/esp_ieee802154_sec.c @@ -7,7 +7,6 @@ #include "sdkconfig.h" #include #include "esp_err.h" -#include "hal/ieee802154_ll.h" #include "esp_ieee802154_frame.h" #include "esp_ieee802154_sec.h" #include "esp_ieee802154_util.h" diff --git a/components/ieee802154/driver/esp_ieee802154_timer.c b/components/ieee802154/driver/esp_ieee802154_timer.c index f4c6eb2b79c..8a373f9ef04 100644 --- a/components/ieee802154/driver/esp_ieee802154_timer.c +++ b/components/ieee802154/driver/esp_ieee802154_timer.c @@ -5,7 +5,6 @@ */ #include "soc/soc.h" -#include "hal/ieee802154_ll.h" #include "esp_check.h" #include "esp_ieee802154_timer.h" #include "esp_ieee802154_util.h" diff --git a/components/ieee802154/driver/esp_ieee802154_util.c b/components/ieee802154/driver/esp_ieee802154_util.c index a37cf4b6d85..f77e693a41a 100644 --- a/components/ieee802154/driver/esp_ieee802154_util.c +++ b/components/ieee802154/driver/esp_ieee802154_util.c @@ -7,7 +7,6 @@ #include "esp_private/esp_modem_clock.h" #include "soc/soc.h" #include "soc/periph_defs.h" -#include "hal/ieee802154_ll.h" #include "esp_check.h" #include "esp_coex_i154.h" #include "esp_ieee802154_util.h" diff --git a/components/ieee802154/esp_ieee802154.c b/components/ieee802154/esp_ieee802154.c index 1947c73a524..3ed014fb34c 100644 --- a/components/ieee802154/esp_ieee802154.c +++ b/components/ieee802154/esp_ieee802154.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -20,7 +20,6 @@ #include "esp_ieee802154_util.h" #include "esp_log.h" #include "esp_coex_i154.h" -#include "hal/ieee802154_ll.h" #include "hal/ieee802154_common_ll.h" esp_err_t esp_ieee802154_event_callback_list_register(esp_ieee802154_event_cb_list_t cb_list) @@ -49,6 +48,11 @@ esp_err_t esp_ieee802154_disable(void) return ieee802154_mac_deinit(); } +int8_t esp_ieee802154_get_receive_sensitivity(void) +{ + return IEEE802154_RX_SENSITIVITY; +} + uint8_t esp_ieee802154_get_channel(void) { return ieee802154_pib_get_channel(); @@ -166,96 +170,6 @@ esp_err_t esp_ieee802154_set_coordinator(bool enable) return ESP_OK; } -#if CONFIG_IEEE802154_MULTI_PAN_ENABLE - -uint16_t esp_ieee802154_get_multipan_panid(esp_ieee802154_multipan_index_t index) -{ - assert(index < CONFIG_IEEE802154_INTERFACE_NUM); - return ieee802154_ll_get_multipan_panid(index); -} - -esp_err_t esp_ieee802154_set_multipan_panid(esp_ieee802154_multipan_index_t index, uint16_t panid) -{ - assert(index < CONFIG_IEEE802154_INTERFACE_NUM); - ieee802154_ll_set_multipan_panid(index, panid); - return ESP_OK; -} - -uint16_t esp_ieee802154_get_multipan_short_address(esp_ieee802154_multipan_index_t index) -{ - assert(index < CONFIG_IEEE802154_INTERFACE_NUM); - return ieee802154_ll_get_multipan_short_addr(index); -} - -esp_err_t esp_ieee802154_set_multipan_short_address(esp_ieee802154_multipan_index_t index, uint16_t short_address) -{ - assert(index < CONFIG_IEEE802154_INTERFACE_NUM); - ieee802154_ll_set_multipan_short_addr(index, short_address); - return ESP_OK; -} - -esp_err_t esp_ieee802154_get_multipan_extended_address(esp_ieee802154_multipan_index_t index, uint8_t *ext_addr) -{ - assert(index < CONFIG_IEEE802154_INTERFACE_NUM); - assert(ext_addr != NULL); - ieee802154_ll_get_multipan_ext_addr(index, ext_addr); - return ESP_OK; -} - -esp_err_t esp_ieee802154_set_multipan_extended_address(esp_ieee802154_multipan_index_t index, const uint8_t *ext_addr) -{ - assert(index < CONFIG_IEEE802154_INTERFACE_NUM); - assert(ext_addr != NULL); - ieee802154_ll_set_multipan_ext_addr(index, ext_addr); - return ESP_OK; -} - -uint8_t esp_ieee802154_get_multipan_enable(void) -{ - return ieee802154_ll_get_multipan_enable_mask(); -} - -esp_err_t esp_ieee802154_set_multipan_enable(uint8_t mask) -{ - assert(mask < (1 << CONFIG_IEEE802154_INTERFACE_NUM)); - ieee802154_ll_set_multipan_enable_mask(mask); - return ESP_OK; -} - -esp_ieee802154_pending_mode_t esp_ieee802154_multipan_get_pending_mode(esp_ieee802154_multipan_index_t index) -{ - assert(index < CONFIG_IEEE802154_INTERFACE_NUM); - return ieee802154_pib_get_pending_mode(index); -} - -esp_err_t esp_ieee802154_multipan_set_pending_mode(esp_ieee802154_multipan_index_t inf_index, esp_ieee802154_pending_mode_t pending_mode) -{ - assert(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); - ieee802154_pib_set_pending_mode(inf_index, pending_mode); - return ESP_OK; -} - -esp_err_t esp_ieee802154_multipan_add_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short) -{ - assert(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); - return ieee802154_add_pending_addr(inf_index, addr, is_short); -} - -esp_err_t esp_ieee802154_multipan_clear_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short) -{ - assert(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); - return ieee802154_clear_pending_addr(inf_index, addr, is_short); -} - -esp_err_t esp_ieee802154_multipan_reset_pending_table(esp_ieee802154_multipan_index_t inf_index, bool is_short) -{ - assert(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); - ieee802154_reset_pending_table(inf_index, is_short); - return ESP_OK; -} - -#endif // CONFIG_IEEE802154_MULTI_PAN_ENABLE - esp_err_t esp_ieee802154_set_ack_timeout(uint32_t timeout) { // Divide by 16 and round it up. diff --git a/components/ieee802154/esp_ieee802154_multipan.c b/components/ieee802154/esp_ieee802154_multipan.c new file mode 100644 index 00000000000..5a144cbd457 --- /dev/null +++ b/components/ieee802154/esp_ieee802154_multipan.c @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include + +#include "esp_err.h" +#include "esp_ieee802154_ack.h" +#include "esp_ieee802154_pib.h" +#include "esp_ieee802154_util.h" + +static uint8_t s_rx_when_idle_mask = 0; + +uint16_t esp_ieee802154_get_multipan_panid(esp_ieee802154_multipan_index_t index) +{ + IEEE802154_ASSERT(index < CONFIG_IEEE802154_INTERFACE_NUM); + return ieee802154_ll_get_multipan_panid(index); +} + +esp_err_t esp_ieee802154_set_multipan_panid(esp_ieee802154_multipan_index_t index, uint16_t panid) +{ + IEEE802154_ASSERT(index < CONFIG_IEEE802154_INTERFACE_NUM); + ieee802154_ll_set_multipan_panid(index, panid); + return ESP_OK; +} + +uint16_t esp_ieee802154_get_multipan_short_address(esp_ieee802154_multipan_index_t index) +{ + IEEE802154_ASSERT(index < CONFIG_IEEE802154_INTERFACE_NUM); + return ieee802154_ll_get_multipan_short_addr(index); +} + +esp_err_t esp_ieee802154_set_multipan_short_address(esp_ieee802154_multipan_index_t index, uint16_t short_address) +{ + IEEE802154_ASSERT(index < CONFIG_IEEE802154_INTERFACE_NUM); + ieee802154_ll_set_multipan_short_addr(index, short_address); + return ESP_OK; +} + +esp_err_t esp_ieee802154_get_multipan_extended_address(esp_ieee802154_multipan_index_t index, uint8_t *ext_addr) +{ + IEEE802154_ASSERT(index < CONFIG_IEEE802154_INTERFACE_NUM); + IEEE802154_ASSERT(ext_addr != NULL); + ieee802154_ll_get_multipan_ext_addr(index, ext_addr); + return ESP_OK; +} + +esp_err_t esp_ieee802154_set_multipan_extended_address(esp_ieee802154_multipan_index_t index, const uint8_t *ext_addr) +{ + IEEE802154_ASSERT(index < CONFIG_IEEE802154_INTERFACE_NUM); + IEEE802154_ASSERT(ext_addr != NULL); + ieee802154_ll_set_multipan_ext_addr(index, ext_addr); + return ESP_OK; +} + +uint8_t esp_ieee802154_get_multipan_enable(void) +{ + return ieee802154_ll_get_multipan_enable_mask(); +} + +esp_err_t esp_ieee802154_set_multipan_enable(uint8_t mask) +{ + IEEE802154_ASSERT(mask < (1 << CONFIG_IEEE802154_INTERFACE_NUM)); + ieee802154_ll_set_multipan_enable_mask(mask); + return ESP_OK; +} + +esp_ieee802154_pending_mode_t esp_ieee802154_multipan_get_pending_mode(esp_ieee802154_multipan_index_t index) +{ + IEEE802154_ASSERT(index < CONFIG_IEEE802154_INTERFACE_NUM); + return ieee802154_pib_get_pending_mode(index); +} + +esp_err_t esp_ieee802154_multipan_set_pending_mode(esp_ieee802154_multipan_index_t inf_index, esp_ieee802154_pending_mode_t pending_mode) +{ + IEEE802154_ASSERT(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); + ieee802154_pib_set_pending_mode(inf_index, pending_mode); + return ESP_OK; +} + +esp_err_t esp_ieee802154_multipan_add_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short) +{ + IEEE802154_ASSERT(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); + return ieee802154_add_pending_addr(inf_index, addr, is_short); +} + +esp_err_t esp_ieee802154_multipan_clear_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short) +{ + IEEE802154_ASSERT(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); + return ieee802154_clear_pending_addr(inf_index, addr, is_short); +} + +esp_err_t esp_ieee802154_multipan_reset_pending_table(esp_ieee802154_multipan_index_t inf_index, bool is_short) +{ + IEEE802154_ASSERT(inf_index < CONFIG_IEEE802154_INTERFACE_NUM); + ieee802154_reset_pending_table(inf_index, is_short); + return ESP_OK; +} + +esp_err_t esp_ieee802154_multipan_sleep(int8_t idx) +{ + IEEE802154_ASSERT(idx >= 0 && idx < CONFIG_IEEE802154_INTERFACE_NUM); + uint8_t enable_mask = ieee802154_ll_get_multipan_enable_mask(); + enable_mask &= ~(1 << idx); + IEEE802154_ASSERT(enable_mask < (1 << CONFIG_IEEE802154_INTERFACE_NUM)); + ieee802154_ll_set_multipan_enable_mask(enable_mask); + if (enable_mask == 0) { + return ieee802154_sleep(); + } + return ESP_OK; +} + +esp_err_t esp_ieee802154_multipan_receive(int8_t idx) +{ + IEEE802154_ASSERT(idx >= 0 && idx < CONFIG_IEEE802154_INTERFACE_NUM); + uint8_t enable_mask = ieee802154_ll_get_multipan_enable_mask(); + enable_mask |= (1 << idx); + IEEE802154_ASSERT(enable_mask < (1 << CONFIG_IEEE802154_INTERFACE_NUM)); + ieee802154_ll_set_multipan_enable_mask(enable_mask); + return ieee802154_receive(); +} + +esp_err_t esp_ieee802154_multipan_set_rx_when_idle(int8_t idx, bool enable) +{ + IEEE802154_ASSERT(idx >= 0 && idx < CONFIG_IEEE802154_INTERFACE_NUM); + if (enable) { + s_rx_when_idle_mask |= (1 << idx); + ieee802154_pib_set_rx_when_idle(true); + } else { + s_rx_when_idle_mask &= ~(1 << idx); + if (s_rx_when_idle_mask == 0) { + ieee802154_pib_set_rx_when_idle(false); + } + } + return ESP_OK; +} + +// Note: multipan receive_at is not supported yet. diff --git a/components/ieee802154/include/esp_ieee802154.h b/components/ieee802154/include/esp_ieee802154.h index 56c45dbd16e..8e6be4cdbfd 100644 --- a/components/ieee802154/include/esp_ieee802154.h +++ b/components/ieee802154/include/esp_ieee802154.h @@ -39,6 +39,14 @@ esp_err_t esp_ieee802154_enable(void); */ esp_err_t esp_ieee802154_disable(void); +/** + * @brief Get the receive sensitivity. + * + * @return The receive sensitivity in dBm. + * + */ +int8_t esp_ieee802154_get_receive_sensitivity(void); + /** * @brief Get the operational channel. * @@ -278,98 +286,6 @@ esp_err_t esp_ieee802154_get_extended_address(uint8_t *ext_addr); */ esp_err_t esp_ieee802154_set_extended_address(const uint8_t *ext_addr); -/** - * @brief Get the device PAN ID for specific interface. - * - * @param[in] index The interface index. - * - * @return The device PAN ID. - * - */ -uint16_t esp_ieee802154_get_multipan_panid(esp_ieee802154_multipan_index_t index); - -/** - * @brief Set the device PAN ID for specific interface. - * - * @param[in] index The interface index. - * @param[in] panid The device PAN ID. - * - * @return - * - ESP_OK on success. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_set_multipan_panid(esp_ieee802154_multipan_index_t index, uint16_t panid); - -/** - * @brief Get the device short address for specific interface. - * - * @param[in] index The interface index. - * - * @return The device short address. - * - */ -uint16_t esp_ieee802154_get_multipan_short_address(esp_ieee802154_multipan_index_t index); - -/** - * @brief Set the device short address for specific interface. - * - * @param[in] index The interface index. - * @param[in] short_address The device short address. - * - * @return - * - ESP_OK on success. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_set_multipan_short_address(esp_ieee802154_multipan_index_t index, uint16_t short_address); - -/** - * @brief Get the device extended address for specific interface. - * - * @param[in] index The interface index. - * @param[out] ext_addr The pointer to the device extended address. Must not be NULL. - * - * @return - * - ESP_OK on success. - * - ESP_ERR_INVALID_ARG if ext_addr is NULL. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_get_multipan_extended_address(esp_ieee802154_multipan_index_t index, uint8_t *ext_addr); - -/** - * @brief Set the device extended address for specific interface. - * - * @param[in] index The interface index. - * @param[in] ext_addr The pointer to the device extended address. Must not be NULL. - * - * @return - * - ESP_OK on success. - * - ESP_ERR_INVALID_ARG if addr is NULL. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_set_multipan_extended_address(esp_ieee802154_multipan_index_t index, const uint8_t *ext_addr); - -/** - * @brief Get the device current multipan interface enable mask. - * - * @return Current multipan interface enable mask. - * - */ -uint8_t esp_ieee802154_get_multipan_enable(void); - -/** - * @brief Enable specific interface for the device. - * - * As an example, call `esp_ieee802154_set_multipan_enable(BIT(ESP_IEEE802154_MULTIPAN_0) | BIT(ESP_IEEE802154_MULTIPAN_1));` - * to enable multipan interface 0 and 1. - * - * @param[in] mask The multipan interface bit mask. - * - * @return - * - ESP_OK on success. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_set_multipan_enable(uint8_t mask); - /** * @brief Get the device coordinator. * @@ -769,80 +685,6 @@ esp_err_t esp_ieee802154_event_callback_list_register(esp_ieee802154_event_cb_li */ esp_err_t esp_ieee802154_event_callback_list_unregister(void); -/** - * @brief Add a pending address to the multipan table for the specified interface. - * - * @note This API should be called only when the IEEE 802.15.4 subsystem is enabled. - * - * @param inf_index Index of the interface. - * @param addr Pointer to the address to add. - * @param is_short True if the address is a short address, false if extended. - * - * @return - * - ESP_OK on success. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_multipan_add_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short); - -/** - * @brief Remove a pending address from the multipan table for the specified interface. - * - * @note This API should be called only when the IEEE 802.15.4 subsystem is enabled. - * - * @param inf_index Index of the interface. - * @param addr Pointer to the address to remove. - * @param is_short True if the address is a short address, false if extended. - * - * @return - * - ESP_OK on success. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_multipan_clear_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short); - -/** - * @brief Reset the pending address table for the specified interface. - * - * @note This API clears all pending addresses of the specified type (short or extended) for the interface. - * - * @param inf_index Index of the interface. - * @param is_short True to reset short addresses, false to reset extended addresses. - * - * @return - * - ESP_OK on success. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_multipan_reset_pending_table(esp_ieee802154_multipan_index_t inf_index, bool is_short); - -/** - * @brief Get the auto frame pending mode of the specified multipan interface. - * - * @note This API returns the logical pending mode configured for a given multipan - * interface. The value is used by the ACK generation logic together with the - * per-interface pending address table. - * - * @param inf_index Index of the multipan interface. - * - * @return - * - The auto frame pending mode, refer to esp_ieee802154_pending_mode_t. - */ -esp_ieee802154_pending_mode_t esp_ieee802154_multipan_get_pending_mode(esp_ieee802154_multipan_index_t inf_index); - -/** - * @brief Set the auto frame pending mode of the specified multipan interface. - * - * @note This API configures the logical pending mode for a given multipan interface. - * The configured mode is used by the ACK generation logic together with the - * per-interface pending address table. - * - * @param inf_index Index of the multipan interface. - * @param pending_mode Pending mode to set for this interface. - * - * @return - * - ESP_OK on success. - * - ESP_FAIL on failure. - */ -esp_err_t esp_ieee802154_multipan_set_pending_mode(esp_ieee802154_multipan_index_t inf_index, esp_ieee802154_pending_mode_t pending_mode); - #ifdef __cplusplus } #endif diff --git a/components/ieee802154/include/esp_ieee802154_multipan.h b/components/ieee802154/include/esp_ieee802154_multipan.h new file mode 100644 index 00000000000..58766f8585a --- /dev/null +++ b/components/ieee802154/include/esp_ieee802154_multipan.h @@ -0,0 +1,224 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "esp_ieee802154_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the device PAN ID for specific interface. + * + * @param[in] index The interface index. + * + * @return The device PAN ID. + * + */ +uint16_t esp_ieee802154_get_multipan_panid(esp_ieee802154_multipan_index_t index); + +/** + * @brief Set the device PAN ID for specific interface. + * + * @param[in] index The interface index. + * @param[in] panid The device PAN ID. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_set_multipan_panid(esp_ieee802154_multipan_index_t index, uint16_t panid); + +/** + * @brief Get the device short address for specific interface. + * + * @param[in] index The interface index. + * + * @return The device short address. + * + */ +uint16_t esp_ieee802154_get_multipan_short_address(esp_ieee802154_multipan_index_t index); + +/** + * @brief Set the device short address for specific interface. + * + * @param[in] index The interface index. + * @param[in] short_address The device short address. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_set_multipan_short_address(esp_ieee802154_multipan_index_t index, uint16_t short_address); + +/** + * @brief Get the device extended address for specific interface. + * + * @param[in] index The interface index. + * @param[out] ext_addr The pointer to the device extended address. Must not be NULL. + * + * @return + * - ESP_OK on success. + * - ESP_ERR_INVALID_ARG if ext_addr is NULL. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_get_multipan_extended_address(esp_ieee802154_multipan_index_t index, uint8_t *ext_addr); + +/** + * @brief Set the device extended address for specific interface. + * + * @param[in] index The interface index. + * @param[in] ext_addr The pointer to the device extended address. Must not be NULL. + * + * @return + * - ESP_OK on success. + * - ESP_ERR_INVALID_ARG if addr is NULL. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_set_multipan_extended_address(esp_ieee802154_multipan_index_t index, const uint8_t *ext_addr); + +/** + * @brief Get the device current multipan interface enable mask. + * + * @return Current multipan interface enable mask. + * + */ +uint8_t esp_ieee802154_get_multipan_enable(void); + +/** + * @brief Enable specific interface for the device. + * + * As an example, call `esp_ieee802154_set_multipan_enable(BIT(ESP_IEEE802154_MULTIPAN_0) | BIT(ESP_IEEE802154_MULTIPAN_1));` + * to enable multipan interface 0 and 1. + * + * @param[in] mask The multipan interface bit mask. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_set_multipan_enable(uint8_t mask); + +/** + * @brief Add a pending address to the multipan table for the specified interface. + * + * @note This API should be called only when the IEEE 802.15.4 subsystem is enabled. + * + * @param inf_index Index of the interface. + * @param addr Pointer to the address to add. + * @param is_short True if the address is a short address, false if extended. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_multipan_add_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short); + +/** + * @brief Remove a pending address from the multipan table for the specified interface. + * + * @note This API should be called only when the IEEE 802.15.4 subsystem is enabled. + * + * @param inf_index Index of the interface. + * @param addr Pointer to the address to remove. + * @param is_short True if the address is a short address, false if extended. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_multipan_clear_pending_addr(esp_ieee802154_multipan_index_t inf_index, const uint8_t *addr, bool is_short); + +/** + * @brief Reset the pending address table for the specified interface. + * + * @note This API clears all pending addresses of the specified type (short or extended) for the interface. + * + * @param inf_index Index of the interface. + * @param is_short True to reset short addresses, false to reset extended addresses. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_multipan_reset_pending_table(esp_ieee802154_multipan_index_t inf_index, bool is_short); + +/** + * @brief Get the auto frame pending mode of the specified multipan interface. + * + * @note This API returns the logical pending mode configured for a given multipan + * interface. The value is used by the ACK generation logic together with the + * per-interface pending address table. + * + * @param inf_index Index of the multipan interface. + * + * @return + * - Current pending mode of type refer to esp_ieee802154_pending_mode_t + */ +esp_ieee802154_pending_mode_t esp_ieee802154_multipan_get_pending_mode(esp_ieee802154_multipan_index_t inf_index); + +/** + * @brief Set the auto frame pending mode of the specified multipan interface. + * + * @note This API configures the logical pending mode for a given multipan interface. + * The configured mode is used by the ACK generation logic together with the + * per-interface pending address table. + * + * @param inf_index Index of the multipan interface. + * @param pending_mode Pending mode to set for this interface. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_multipan_set_pending_mode(esp_ieee802154_multipan_index_t inf_index, esp_ieee802154_pending_mode_t pending_mode); + +/** + * @brief Set the IEEE 802.15.4 Radio to sleep state for the specified multipan interface. + * + * @param[in] idx Index of the multipan interface. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_multipan_sleep(int8_t idx); + +/** + * @brief Set the IEEE 802.15.4 Radio to receive state for the specified multipan interface. + * + * @param[in] idx Index of the multipan interface. + * + * @return + * - ESP_OK on success. + * - ESP_FAIL on failure. + */ +esp_err_t esp_ieee802154_multipan_receive(int8_t idx); + +/** + * @brief Set the RxOnWhenIdle mode for the specified multipan interface. + * + * Each interface owns one bit in an internal mask. The hardware RxOnWhenIdle + * setting is enabled when any interface requests it, and is disabled only + * when all interface bits are cleared. + * + * @param[in] idx Index of the multipan interface. + * @param[in] enable True to enable RxOnWhenIdle for this interface. + * + * @return + * - ESP_OK on success. + */ +esp_err_t esp_ieee802154_multipan_set_rx_when_idle(int8_t idx, bool enable); + +#ifdef __cplusplus +} +#endif diff --git a/components/ieee802154/linker.lf b/components/ieee802154/linker.lf index dbe150462c8..1561738fcf0 100644 --- a/components/ieee802154/linker.lf +++ b/components/ieee802154/linker.lf @@ -79,9 +79,9 @@ entries: esp_ieee802154_util: ieee802154_channel_to_freq (noflash) if IEEE802154_MULTI_PAN_ENABLE = y: - esp_ieee802154: esp_ieee802154_get_multipan_extended_address (noflash) - esp_ieee802154: esp_ieee802154_get_multipan_panid (noflash) - esp_ieee802154: esp_ieee802154_get_multipan_short_address (noflash) + esp_ieee802154_multipan: esp_ieee802154_get_multipan_extended_address (noflash) + esp_ieee802154_multipan: esp_ieee802154_get_multipan_panid (noflash) + esp_ieee802154_multipan: esp_ieee802154_get_multipan_short_address (noflash) esp_ieee802154_dev: is_broadcast_addr (noflash) esp_ieee802154_dev: is_broadcast_panid (noflash) esp_ieee802154_dev: update_mpf_index (noflash) diff --git a/components/openthread/Kconfig b/components/openthread/Kconfig index 8e0d219e663..9e6844ddf28 100644 --- a/components/openthread/Kconfig +++ b/components/openthread/Kconfig @@ -323,7 +323,7 @@ menu "OpenThread" bool "Enable link metrics feature" default n select IEEE802154_TIMING_OPTIMIZATION if IEEE802154_ENABLED - select OPENTHREAD_TIMING_OPTIMIZATION + select OPENTHREAD_TIMING_OPTIMIZATION if OPENTHREAD_RADIO_NATIVE help Select this option to enable link metrics feature @@ -427,17 +427,16 @@ menu "OpenThread" endmenu menu "Thread Memory Allocation" - depends on (SPIRAM_USE_CAPS_ALLOC || SPIRAM_USE_MALLOC) config OPENTHREAD_PLATFORM_MALLOC_CAP_SPIRAM + depends on (SPIRAM_USE_CAPS_ALLOC || SPIRAM_USE_MALLOC) bool 'Allocate memory from PSRAM' default y help Select this option to allocate buffer from PSRAM for Thread config OPENTHREAD_PLATFORM_MSGPOOL_MANAGEMENT - bool 'Allocate message pool buffer from PSRAM' - depends on OPENTHREAD_PLATFORM_MALLOC_CAP_SPIRAM - default y + bool 'Allocate message pool buffer from platform' + default n help If enabled, the message pool is managed by platform defined logic. endmenu @@ -471,8 +470,8 @@ menu "OpenThread" config OPENTHREAD_NUM_MESSAGE_BUFFERS int "The number of openthread message buffers" - default 65 if !OPENTHREAD_PLATFORM_MSGPOOL_MANAGEMENT - default 1024 if OPENTHREAD_PLATFORM_MSGPOOL_MANAGEMENT + default 65 if !OPENTHREAD_PLATFORM_MALLOC_CAP_SPIRAM + default 1024 if OPENTHREAD_PLATFORM_MALLOC_CAP_SPIRAM config OPENTHREAD_XTAL_ACCURACY int "The accuracy of the XTAL" @@ -517,6 +516,22 @@ menu "OpenThread" OpenThread esp_netif, attaches the esp_netif glue, and enables lwIP-specific DNS/netif handling. + config OPENTHREAD_MULTIPAN_RCP_ENABLE + bool "Enable Multipan RCP" + depends on (OPENTHREAD_RADIO && IEEE802154_MULTI_PAN_ENABLE) + default n + help + Enable MULTI-PAN RCP support. This also enables multiple OpenThread + instances. Currently only radio devices are supported. + + config OPENTHREAD_MULTIPLE_INTERFACES_COUNT + int "The number of multiple interfaces" + depends on OPENTHREAD_MULTIPAN_RCP_ENABLE + default 2 + range 2 3 + help + Number of multiple interfaces. + endmenu menu "Thread Log" diff --git a/components/openthread/openthread b/components/openthread/openthread index b678a4f63b6..43cc05a9bcf 160000 --- a/components/openthread/openthread +++ b/components/openthread/openthread @@ -1 +1 @@ -Subproject commit b678a4f63b6f9397d1a0fa8f31e5b8e0271a4d00 +Subproject commit 43cc05a9bcf780bd758bad53d897e2d88cf8cb75 diff --git a/components/openthread/private_include/esp_openthread_common_macro.h b/components/openthread/private_include/esp_openthread_common_macro.h index 8595bc7684b..4893e7fb3cc 100644 --- a/components/openthread/private_include/esp_openthread_common_macro.h +++ b/components/openthread/private_include/esp_openthread_common_macro.h @@ -42,3 +42,9 @@ #else #define ESP_OPENTHREAD_ASSERT(a) assert(a) #endif + +#if CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE +#define OT_INSTANCE_COUNT CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT +#else +#define OT_INSTANCE_COUNT 1 +#endif diff --git a/components/openthread/private_include/esp_openthread_instance.h b/components/openthread/private_include/esp_openthread_instance.h new file mode 100644 index 00000000000..c7ce7684b63 --- /dev/null +++ b/components/openthread/private_include/esp_openthread_instance.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "openthread/instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize OpenThread instance(s) for the selected build (single or multiple). + * + * @return true on success, false otherwise. + */ +bool esp_openthread_instances_init(void); + +/** + * @brief Finalize OpenThread instance(s) for the selected build (single or multiple). + * + * @param[in] instance Instance provided by the caller (may be unused in multi-instance builds). + */ +void esp_openthread_instances_deinit(otInstance *instance); + +/** + * @brief Check whether tasklets are pending. + * + * @param[in] instance Instance provided by the caller (may be unused in multi-instance builds). + */ +bool esp_openthread_tasklets_are_pending(otInstance *instance); + +/** + * @brief Process pending tasklets. + * + * @param[in] instance Instance provided by the caller (may be unused in multi-instance builds). + */ +void esp_openthread_tasklets_process(otInstance *instance); + +/** + * @brief Initialize the NCP/RCP application for the selected instance mode. + * + * @param[in] instance Instance provided by the caller (may be unused in multi-instance builds). + * + * @note Only used when CONFIG_OPENTHREAD_RADIO is enabled. + */ +void esp_openthread_ncp_app_init(otInstance *instance); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_openthread_ncp.h b/components/openthread/private_include/esp_openthread_ncp.h index 2d529dd7704..05026cd766a 100644 --- a/components/openthread/private_include/esp_openthread_ncp.h +++ b/components/openthread/private_include/esp_openthread_ncp.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,8 +10,21 @@ extern "C" { #endif +/** + * @brief This function initializes the NCP for a single instance + * + * @param[in] aInstance The OpenThread instance. + */ void otAppNcpInit(otInstance *aInstance); +/** + * @brief This function initializes the NCP for multiple instances + * + * @param[in] aInstances The array of OpenThread instances. + * @param[in] aCount The number of instances. + */ +void otAppNcpInitMulti(otInstance **aInstances, uint8_t aCount); + #if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE esp_err_t esp_console_redirect_to_otlog(void); #endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE diff --git a/components/openthread/private_include/esp_openthread_platform.h b/components/openthread/private_include/esp_openthread_platform.h index 582599f5d7f..faa78ec1d65 100644 --- a/components/openthread/private_include/esp_openthread_platform.h +++ b/components/openthread/private_include/esp_openthread_platform.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -155,6 +155,37 @@ void esp_openthread_set_storage_name(const char *name); */ uint32_t esp_openthread_get_alloc_caps(void); +/** + * @brief This function gets the index of the instance + * + * @param[in] instance The OpenThread instance. + * + * @note Invalid input is logged and asserted. + * + * @return + * - The index of the instance + */ +int8_t esp_openthread_get_idx_from_instance(otInstance *instance); + +/** + * @brief This function gets the instance from the index + * + * @param[in] idx The index of the instance. + * + * @note Invalid index is logged and asserted. A valid index always has an instance. + * + * @return + * - The instance + */ +otInstance *esp_openthread_get_instance_from_idx(int8_t idx); + +/** + * @brief This function sets the active instance + * + * @param[in] instance The OpenThread instance. + */ +void esp_openthread_set_active_instance(otInstance *instance); + #ifdef __cplusplus } // end of extern "C" #endif 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 3941c4cf35d..b7fbff9501a 100644 --- a/components/openthread/private_include/openthread-core-esp32x-radio-config.h +++ b/components/openthread/private_include/openthread-core-esp32x-radio-config.h @@ -165,6 +165,62 @@ #define OPENTHREAD_CONFIG_TIME_SYNC_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE + * + * Define to 1 to enable multiple instance support. + */ +#ifdef OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#error `OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE` is redefined. +#endif +#if CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE +#define OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE 1 +#else +#define OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + * + * Define to 1 to enable multipan RCP support. + */ +#ifdef OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +#error `OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE` is redefined. +#endif +#if CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE +#define OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE 1 +#else +#define OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + * + * Define to 1 to enable multiple static instance support. + */ +#ifdef OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE +#error `OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE` is redefined. +#endif +#if CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE +#define OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE 1 +#else +#define OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID + * + * Define broadcast IID for spinel frames dedicated to all hosts in multipan configuration. + * Host interfaces use IID 0 .. COUNT-1; broadcast takes the next slot. + * + */ +#ifdef OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID +#error `OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID` is redefined. +#endif +#if CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE +#define OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID SPINEL_HEADER_IID(CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT) +#endif + /*----The following options set fixed default values but can be overridden by the user header file.----*/ #if CONFIG_OPENTHREAD_LINK_METRICS diff --git a/components/openthread/private_include/openthread-core-esp32x-spinel-config.h b/components/openthread/private_include/openthread-core-esp32x-spinel-config.h index 4f848146ccb..92d38757029 100644 --- a/components/openthread/private_include/openthread-core-esp32x-spinel-config.h +++ b/components/openthread/private_include/openthread-core-esp32x-spinel-config.h @@ -90,11 +90,11 @@ #endif /** - * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_RETX_SECURITY_ENABLE + * @def OPENTHREAD_CONFIG_CRYPTO_LIB * * Workaround for SPINEL_ONLY build configuration. - * See esp-idf MR !50530 for details. + * See esp-idf MR !49222 for details. */ -#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_RETX_SECURITY_ENABLE -#define OPENTHREAD_CONFIG_MAC_SOFTWARE_RETX_SECURITY_ENABLE 0 +#ifndef OPENTHREAD_CONFIG_CRYPTO_LIB +#define OPENTHREAD_CONFIG_CRYPTO_LIB OPENTHREAD_CONFIG_CRYPTO_LIB_PSA #endif diff --git a/components/openthread/sbom_openthread.yml b/components/openthread/sbom_openthread.yml index 6d40f289e71..52b620afc19 100644 --- a/components/openthread/sbom_openthread.yml +++ b/components/openthread/sbom_openthread.yml @@ -5,7 +5,7 @@ supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' originator: 'Organization: Google LLC' description: OpenThread released by Google is an open-source implementation of the Thread networking url: https://github.com/espressif/openthread -hash: b678a4f63b6f9397d1a0fa8f31e5b8e0271a4d00 +hash: 43cc05a9bcf780bd758bad53d897e2d88cf8cb75 cve-exclude-list: - cve: CVE-2026-8369 reason: We use Espressif’s NAT64 implementation and hence this CVE from the upstream NAT64 implementation is not applicable. diff --git a/components/openthread/src/esp_openthread.cpp b/components/openthread/src/esp_openthread.cpp index 399f7a090e4..2fea58c0750 100644 --- a/components/openthread/src/esp_openthread.cpp +++ b/components/openthread/src/esp_openthread.cpp @@ -12,8 +12,8 @@ #include "esp_openthread_common_macro.h" #include "esp_openthread_cli.h" #include "esp_openthread_dns64.h" +#include "esp_openthread_instance.h" #include "esp_openthread_lock.h" -#include "esp_openthread_ncp.h" #include "esp_openthread_netif_glue.h" #include "esp_openthread_platform.h" #include "esp_openthread_sleep.h" @@ -26,7 +26,6 @@ #include "openthread/instance.h" #include "openthread/logging.h" #include "openthread/netdata.h" -#include "openthread/tasklet.h" #include "openthread/thread.h" #if CONFIG_OPENTHREAD_FTD @@ -81,7 +80,7 @@ esp_err_t esp_openthread_init(const esp_openthread_platform_config_t *config) "Failed to initialize OpenThread platform driver"); esp_openthread_lock_acquire(portMAX_DELAY); esp_err_t ret = ESP_OK; - ESP_GOTO_ON_FALSE(otInstanceInitSingle() != NULL, ESP_FAIL, exit, OT_PLAT_LOG_TAG, + ESP_GOTO_ON_FALSE(esp_openthread_instances_init(), ESP_FAIL, exit, OT_PLAT_LOG_TAG, "Failed to initialize OpenThread instance"); #if CONFIG_OPENTHREAD_DNS64_CLIENT ESP_GOTO_ON_ERROR(esp_openthread_dns64_client_init(), exit, OT_PLAT_LOG_TAG, @@ -203,7 +202,7 @@ esp_err_t esp_openthread_launch_mainloop(void) esp_openthread_lock_acquire(portMAX_DELAY); esp_openthread_platform_update(&mainloop); - if (otTaskletsArePending(instance)) { + if (esp_openthread_tasklets_are_pending(instance)) { mainloop.timeout.tv_sec = 0; mainloop.timeout.tv_usec = 0; } @@ -222,9 +221,7 @@ esp_err_t esp_openthread_launch_mainloop(void) if (result >= 0) { esp_openthread_lock_acquire(portMAX_DELAY); error = esp_openthread_platform_process(instance, &mainloop); - while (otTaskletsArePending(instance)) { - otTaskletsProcess(instance); - } + esp_openthread_tasklets_process(instance); esp_openthread_lock_release(); if (error != ESP_OK) { ESP_LOGE(OT_PLAT_LOG_TAG, "esp_openthread_platform_process failed"); @@ -244,7 +241,9 @@ esp_err_t esp_openthread_launch_mainloop(void) esp_err_t esp_openthread_deinit(void) { - otInstanceFinalize(esp_openthread_get_instance()); + esp_openthread_lock_acquire(portMAX_DELAY); + esp_openthread_instances_deinit(esp_openthread_get_instance()); + esp_openthread_lock_release(); return esp_openthread_platform_deinit(); } @@ -271,7 +270,7 @@ static void ot_task_worker(void *aContext) #endif // CONFIG_OPENTHREAD_CLI #if CONFIG_OPENTHREAD_RADIO - otAppNcpInit(esp_openthread_get_instance()); + esp_openthread_ncp_app_init(esp_openthread_get_instance()); #endif xSemaphoreGive(s_ot_syn_semaphore); diff --git a/components/openthread/src/esp_openthread_instance.cpp b/components/openthread/src/esp_openthread_instance.cpp new file mode 100644 index 00000000000..c52daf509c7 --- /dev/null +++ b/components/openthread/src/esp_openthread_instance.cpp @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "core/instance/instance.hpp" +#include "esp_openthread.h" +#include "esp_openthread_instance.h" +#include "esp_openthread_ncp.h" +#include "openthread/instance.h" +#include "openthread/tasklet.h" + +bool esp_openthread_instances_init(void) +{ + return otInstanceInitSingle() != NULL; +} + +void esp_openthread_instances_deinit(otInstance *instance) +{ + otInstanceFinalize(instance); +} + +bool esp_openthread_tasklets_are_pending(otInstance *instance) +{ + return otTaskletsArePending(instance); +} + +void esp_openthread_tasklets_process(otInstance *instance) +{ + while (otTaskletsArePending(instance)) { + otTaskletsProcess(instance); + } +} + +#if CONFIG_OPENTHREAD_RADIO +void esp_openthread_ncp_app_init(otInstance *instance) +{ + otAppNcpInit(instance); +} +#endif + +otInstance *esp_openthread_get_instance(void) +{ + return (otInstance *)&ot::Instance::Get(); +} diff --git a/components/openthread/src/esp_openthread_platform.cpp b/components/openthread/src/esp_openthread_platform.cpp index a2431f82d04..991ab07267d 100644 --- a/components/openthread/src/esp_openthread_platform.cpp +++ b/components/openthread/src/esp_openthread_platform.cpp @@ -20,7 +20,6 @@ #include "esp_partition.h" #include "common/code_utils.hpp" #include "common/logging.hpp" -#include "core/instance/instance.hpp" #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "openthread/cli.h" @@ -166,11 +165,6 @@ exit: return ret; } -otInstance *esp_openthread_get_instance(void) -{ - return (otInstance *)&ot::Instance::Get(); -} - esp_err_t esp_openthread_platform_deinit(void) { ESP_RETURN_ON_FALSE(s_openthread_platform_initialized, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, diff --git a/components/openthread/src/multiple_instances/esp_openthread_instance.cpp b/components/openthread/src/multiple_instances/esp_openthread_instance.cpp new file mode 100644 index 00000000000..133eec44cf1 --- /dev/null +++ b/components/openthread/src/multiple_instances/esp_openthread_instance.cpp @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include +#include "esp_log.h" +#include "esp_openthread.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_instance.h" +#include "esp_openthread_ncp.h" +#include "esp_openthread_platform.h" +#include "openthread/instance.h" +#include "openthread/tasklet.h" + +static otInstance *s_active_instance = NULL; +static otInstance *s_instances[OT_INSTANCE_COUNT] = {NULL}; + +bool esp_openthread_instances_init(void) +{ + for (int i = 0; i < OT_INSTANCE_COUNT; i++) { + s_instances[i] = otInstanceInitMultiple(i); + assert(s_instances[i]); + } + esp_openthread_set_active_instance(s_instances[0]); + return true; +} + +void esp_openthread_instances_deinit(otInstance *instance) +{ + (void)instance; + for (int i = 0; i < OT_INSTANCE_COUNT; i++) { + otInstanceFinalize(s_instances[i]); + } +} + +bool esp_openthread_tasklets_are_pending(otInstance *instance) +{ + (void)instance; + + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + if (otTaskletsArePending(esp_openthread_get_instance_from_idx(idx))) { + return true; + } + } + return false; +} + +void esp_openthread_tasklets_process(otInstance *instance) +{ + (void)instance; + + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + otInstance *inst = esp_openthread_get_instance_from_idx(idx); + while (otTaskletsArePending(inst)) { + otTaskletsProcess(inst); + } + } +} + +#if CONFIG_OPENTHREAD_RADIO +void esp_openthread_ncp_app_init(otInstance *instance) +{ + (void)instance; + + otInstance *instances[OT_INSTANCE_COUNT] = {nullptr}; + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + instances[idx] = esp_openthread_get_instance_from_idx(idx); + } + otAppNcpInitMulti(instances, OT_INSTANCE_COUNT); +} +#endif + +otInstance *esp_openthread_get_instance(void) +{ + return s_active_instance; +} + +int8_t esp_openthread_get_idx_from_instance(otInstance *instance) +{ + if (instance == nullptr) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Instance is NULL"); + assert(instance != nullptr); + } + + uint8_t idx = otInstanceGetIndex(instance); + if (idx >= OT_INSTANCE_COUNT) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Index %u is out of range", idx); + assert(idx < OT_INSTANCE_COUNT); + } + + return static_cast(idx); +} + +otInstance *esp_openthread_get_instance_from_idx(int8_t idx) +{ + if (idx < 0 || idx >= OT_INSTANCE_COUNT) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Index %d is out of range", idx); + assert(idx >= 0 && idx < OT_INSTANCE_COUNT); + } + + otInstance *instance = otInstanceGetInstance(static_cast(idx)); + if (instance == nullptr) { + ESP_LOGE(OT_PLAT_LOG_TAG, "No instance for index %d", idx); + assert(instance != nullptr); + } + + return instance; +} + +void esp_openthread_set_active_instance(otInstance *instance) +{ + s_active_instance = instance; +} diff --git a/components/openthread/src/multiple_instances/port/esp_openthread_alarm.c b/components/openthread/src/multiple_instances/port/esp_openthread_alarm.c new file mode 100644 index 00000000000..82f3f4ba562 --- /dev/null +++ b/components/openthread/src/multiple_instances/port/esp_openthread_alarm.c @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_alarm.h" + +#include +#include +#include + +#include "esp_log.h" +#include "esp_openthread.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_platform.h" +#include "esp_timer.h" +#include "openthread-core-config.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "openthread/platform/alarm-micro.h" +#include "openthread/platform/alarm-milli.h" +#include "openthread/platform/diag.h" +#include "openthread/platform/radio.h" +#include "openthread/platform/time.h" + +typedef struct { + uint32_t alarm_ms; + bool is_ms_running; + uint32_t alarm_us; + bool is_us_running; + otInstance *instance; +} esp_openthread_alarm_t; + +static esp_openthread_alarm_t s_alarms[OT_INSTANCE_COUNT]; +static const char *alarm_workflow = "alarm"; + +static inline bool is_expired(uint32_t target, uint32_t now) +{ + return (((now - target) & (1 << 31)) == 0); +} + +static inline uint32_t calculate_duration(uint32_t target, uint32_t now) +{ + return is_expired(target, now) ? 0 : target - now; +} + +uint64_t otPlatTimeGet(void) +{ + return (uint64_t)esp_timer_get_time(); +} + +void otPlatAlarmMilliStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + + s_alarms[idx].alarm_ms = aT0 + aDt; + s_alarms[idx].is_ms_running = true; + + ESP_LOGD(OT_PLAT_LOG_TAG, "Millisecond timer alarm start running, t0=%"PRIu32", dt=%"PRIu32"", aT0, aDt); +} + +void otPlatAlarmMilliStop(otInstance *aInstance) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + + s_alarms[idx].is_ms_running = false; +} + +uint32_t inline otPlatAlarmMilliGetNow(void) +{ + return otPlatTimeGet() / US_PER_MS; +} + +void otPlatAlarmMicroStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + + s_alarms[idx].alarm_us = aT0 + aDt; + s_alarms[idx].is_us_running = true; + + ESP_LOGD(OT_PLAT_LOG_TAG, "Microsecond timer alarm start running, t0=%"PRIu32", dt=%"PRIu32"", aT0, aDt); +} + +void otPlatAlarmMicroStop(otInstance *aInstance) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + + s_alarms[idx].is_us_running = false; +} + +uint32_t inline otPlatAlarmMicroGetNow(void) +{ + return otPlatTimeGet(); +} + +esp_err_t esp_openthread_alarm_init(void) +{ + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + memset((void *)(&s_alarms[idx]), 0, sizeof(esp_openthread_alarm_t)); + } + return esp_openthread_platform_workflow_register(&esp_openthread_alarm_update, &esp_openthread_alarm_process, + alarm_workflow); +} + +void esp_openthread_alarm_deinit(void) +{ + esp_openthread_platform_workflow_unregister(alarm_workflow); +} + +void esp_openthread_alarm_update(esp_openthread_mainloop_context_t *mainloop) +{ + struct timeval *timeout = &mainloop->timeout; + int64_t remain_min_time_us = INT64_MAX; + int64_t remaining_us = 0; + + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + if (s_alarms[idx].is_ms_running) { + remaining_us = calculate_duration(s_alarms[idx].alarm_ms, otPlatAlarmMilliGetNow()) * US_PER_MS; + if (remain_min_time_us > remaining_us) { + remain_min_time_us = remaining_us; + } + } + if (s_alarms[idx].is_us_running) { + remaining_us = calculate_duration(s_alarms[idx].alarm_us, otPlatAlarmMicroGetNow()); + if (remain_min_time_us > remaining_us) { + remain_min_time_us = remaining_us; + } + } + } + if (remain_min_time_us != INT64_MAX && remain_min_time_us > 0) { + timeout->tv_sec = remain_min_time_us / US_PER_S; + timeout->tv_usec = remain_min_time_us % US_PER_S; + } else { + timeout->tv_sec = 0; + timeout->tv_usec = 0; + } +} + +esp_err_t esp_openthread_alarm_process(otInstance *aInstance, const esp_openthread_mainloop_context_t *mainloop) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(mainloop); + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + otInstance *instance = esp_openthread_get_instance_from_idx(idx); + if (s_alarms[idx].is_ms_running && is_expired(s_alarms[idx].alarm_ms, otPlatAlarmMilliGetNow())) { + s_alarms[idx].is_ms_running = false; + +#if OPENTHREAD_CONFIG_DIAG_ENABLE + if (otPlatDiagModeGet()) { + otPlatDiagAlarmFired(instance); + } else +#endif + { + otPlatAlarmMilliFired(instance); + } + + ESP_LOGD(OT_PLAT_LOG_TAG, "Millisecond timer alarm fired"); + } + if (s_alarms[idx].is_us_running && is_expired(s_alarms[idx].alarm_us, otPlatAlarmMicroGetNow())) { + s_alarms[idx].is_us_running = false; + otPlatAlarmMicroFired(instance); + ESP_LOGD(OT_PLAT_LOG_TAG, "Microsecond timer alarm fired"); + } + } + return ESP_OK; +} diff --git a/components/openthread/src/multiple_instances/port/esp_openthread_radio.c b/components/openthread/src/multiple_instances/port/esp_openthread_radio.c new file mode 100644 index 00000000000..45c38754edd --- /dev/null +++ b/components/openthread/src/multiple_instances/port/esp_openthread_radio.c @@ -0,0 +1,1064 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_radio.h" + +#include "esp_err.h" +#include "sdkconfig.h" +#include "esp_check.h" +#include "esp_ieee802154.h" +#include "esp_ieee802154_multipan.h" +#include "esp_ieee802154_types.h" +#include "esp_mac.h" +#include "esp_openthread.h" +#include "esp_openthread_common.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_platform.h" +#include "esp_openthread_types.h" +#include "esp_random.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "esp_vfs.h" +#include "esp_vfs_eventfd.h" +#include "rom/ets_sys.h" + +#include "openthread-core-config.h" +#include "openthread/error.h" +#include "openthread/link.h" +#include "openthread/platform/diag.h" +#include "openthread/platform/multipan.h" +#include "openthread/platform/radio.h" +#include "openthread/platform/time.h" +#include "utils/link_metrics.h" +#include "utils/mac_frame.h" +#include "psa/crypto.h" + +#if (CONFIG_ESP_COEX_SW_COEXIST_ENABLE || CONFIG_EXTERNAL_COEX_ENABLE) +#include "esp_coex_i154.h" +#endif + +#define ESP_OPENTHREAD_XTAL_ACCURACY CONFIG_OPENTHREAD_XTAL_ACCURACY +#define ESP_OPENTHREAD_CSL_ACCURACY CONFIG_OPENTHREAD_CSL_ACCURACY +#define ESP_OPENTHREAD_CSL_UNCERTAIN CONFIG_OPENTHREAD_CSL_UNCERTAIN + +#define EVENT_TX_DONE (1 << 0) +#define EVENT_TX_FAILED (1 << 1) +#define EVENT_RX_DONE (1 << 2) +#define EVENT_ENERGY_DETECT_DONE (1 << 3) +#define EVENT_SLEEP (1 << 4) + +typedef struct { + uint8_t length; + uint8_t psdu[OT_RADIO_FRAME_MAX_SIZE]; +} esp_openthread_radio_tx_psdu; + +typedef struct esp_openthread_radio_transmit_context { + int8_t index; + otRadioFrame *aFrame; +} esp_openthread_radio_transmit_context_t; + +typedef struct esp_openthread_radio_receive_context { + int8_t index; + otRadioFrame frame; +} esp_openthread_radio_receive_context_t; + +static otRadioFrame s_transmit_frame[OT_INSTANCE_COUNT]; +static esp_openthread_radio_tx_psdu s_transmit_psdu[OT_INSTANCE_COUNT]; +static uint8_t *s_enhack; +static esp_openthread_radio_receive_context_t s_receive_frame[CONFIG_IEEE802154_RX_BUFFER_SIZE]; +static otRadioFrame s_ack_frame; +static int s_ed_power; +static esp_ieee802154_tx_error_t s_tx_error; +static int s_radio_event_fd = -1; +static const char *s_radio_workflow = "radio"; +static uint8_t s_txrx_events; + +#if CONFIG_OPENTHREAD_DIAG +static bool s_diag_mode = false; +#endif + +#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT +static otRadioIeInfo s_transmit_ie_info[OT_INSTANCE_COUNT]; +#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +static uint32_t s_csl_period; +static uint32_t s_csl_sample_time; +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 +typedef struct { + uint32_t mac_frame_counter; + uint8_t key_id; + struct otMacKeyMaterial previous_key; + struct otMacKeyMaterial current_key; + struct otMacKeyMaterial next_key; +#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE + uint8_t previous_key_bytes[16]; + uint8_t current_key_bytes[16]; + uint8_t next_key_bytes[16]; +#endif + bool with_security_enh_ack; + uint32_t ack_frame_counter; + uint8_t ack_key_id; + uint8_t security_key[16]; + uint8_t security_addr[8]; +} esp_openthread_radio_security_ctx_t; + +static esp_openthread_radio_security_ctx_t s_sec_ctx[OT_INSTANCE_COUNT]; +#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + +/* Head is the in-flight TX; remaining entries are pending. + * Only touched in OpenThread task context (ISR only reads head/index). */ +typedef struct { + uint8_t head; + uint8_t tail; + uint8_t used; +} esp_openthread_tx_queue_info_t; + +static esp_openthread_radio_transmit_context_t s_tx_queue[OT_INSTANCE_COUNT]; +static esp_openthread_tx_queue_info_t s_tx_queue_info = {.head = 0, .tail = 0, .used = 0}; + +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 +static void ot_set_security_key_from_key_material(int8_t idx, struct otMacKeyMaterial a_key_material) +{ + if (idx < 0 || idx >= OT_INSTANCE_COUNT) { + return; + } +#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE + /* Key bytes are pre-exported in task context (otPlatRadioSetMacKey). + * Identify which cached buffer to use by comparing the key reference. + * Jira TZ-2472 */ + if (a_key_material.mKeyMaterial.mKeyRef == s_sec_ctx[idx].previous_key.mKeyMaterial.mKeyRef) { + memcpy(s_sec_ctx[idx].security_key, s_sec_ctx[idx].previous_key_bytes, 16); + } else if (a_key_material.mKeyMaterial.mKeyRef == s_sec_ctx[idx].next_key.mKeyMaterial.mKeyRef) { + memcpy(s_sec_ctx[idx].security_key, s_sec_ctx[idx].next_key_bytes, 16); + } else { + memcpy(s_sec_ctx[idx].security_key, s_sec_ctx[idx].current_key_bytes, 16); + } +#else + memcpy(s_sec_ctx[idx].security_key, a_key_material.mKeyMaterial.mKey.m8, + sizeof(a_key_material.mKeyMaterial.mKey.m8)); +#endif +} +#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + +static esp_openthread_circular_queue_info_t s_recv_queue = {.head = 0, .tail = 0, .used = 0}; + +static void radio_tx_queue_pop_head(void) +{ + assert(s_tx_queue_info.used > 0); + s_tx_queue_info.head = (s_tx_queue_info.head + 1) % OT_INSTANCE_COUNT; + s_tx_queue_info.used--; +} + +static void radio_start_transmit(otInstance *aInstance, otRadioFrame *aFrame, int8_t idx) +{ + esp_ieee802154_set_channel(aFrame->mChannel); + + aFrame->mPsdu[-1] = aFrame->mLength; // length locates one byte before the psdu (esp_openthread_radio_tx_psdu); + + if (otMacFrameIsSecurityEnabled(aFrame) && !aFrame->mInfo.mTxInfo.mIsSecurityProcessed) { +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (!s_transmit_frame[idx].mInfo.mTxInfo.mIsARetx || s_csl_period > 0) { +#else + if (!s_transmit_frame[idx].mInfo.mTxInfo.mIsARetx) { +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + otMacFrameSetFrameCounter(aFrame, s_sec_ctx[idx].mac_frame_counter++); + } + if (otMacFrameIsKeyIdMode1(aFrame)) { + s_transmit_frame[idx].mInfo.mTxInfo.mAesKey = &s_sec_ctx[idx].current_key; + if (!s_transmit_frame[idx].mInfo.mTxInfo.mIsARetx) { + otMacFrameSetKeyId(aFrame, s_sec_ctx[idx].key_id); + } + esp_ieee802154_get_multipan_extended_address((esp_ieee802154_multipan_index_t)idx, s_sec_ctx[idx].security_addr); + } + ot_set_security_key_from_key_material(idx, s_sec_ctx[idx].current_key); + esp_ieee802154_set_transmit_security(&aFrame->mPsdu[-1], s_sec_ctx[idx].security_key, + s_sec_ctx[idx].security_addr); + } + + if (aFrame->mInfo.mTxInfo.mTxDelay != 0) { + esp_ieee802154_transmit_at(&aFrame->mPsdu[-1], aFrame->mInfo.mTxInfo.mCsmaCaEnabled, + (aFrame->mInfo.mTxInfo.mTxDelayBaseTime + aFrame->mInfo.mTxInfo.mTxDelay)); + } else { + esp_ieee802154_transmit(&aFrame->mPsdu[-1], aFrame->mInfo.mTxInfo.mCsmaCaEnabled); + } + + otPlatRadioTxStarted(aInstance, aFrame); +} + +static void set_event(uint8_t event) +{ + uint64_t event_write = event; + s_txrx_events |= event; + int ret = write(s_radio_event_fd, &event_write, sizeof(event_write)); + assert(ret == sizeof(event_write)); +} + +static inline void clr_event(uint8_t event) +{ + s_txrx_events &= ~event; +} + +static inline bool get_event(uint8_t event) +{ + return s_txrx_events & event; +} + +static void ot_radio_receive_done(uint8_t *data, esp_ieee802154_frame_info_t *frame_info); +static void ot_radio_receive_sfd_done(void); +static void ot_radio_transmit_done(const uint8_t *frame, const uint8_t *ack, + esp_ieee802154_frame_info_t *ack_frame_info); +static void ot_radio_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error); +static void ot_radio_transmit_sfd_done(uint8_t *frame); +static void ot_radio_energy_detect_done(int8_t power); +static esp_err_t ot_radio_enh_ack_generator(uint8_t *frame, esp_ieee802154_frame_info_t *frame_info, + uint8_t *enhack_frame); + +static void radio_init_software_context(void) +{ + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + s_transmit_frame[idx].mPsdu = s_transmit_psdu[idx].psdu; + } + + for (uint8_t i = 0; i < CONFIG_IEEE802154_RX_BUFFER_SIZE; i++) { + s_receive_frame[i].frame.mPsdu = NULL; + } + + s_ack_frame.mPsdu = NULL; + memset(&s_recv_queue, 0, sizeof(esp_openthread_circular_queue_info_t)); + memset(&s_tx_queue_info, 0, sizeof(esp_openthread_tx_queue_info_t)); + s_txrx_events = 0; + +#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT + for (int idx = 0; idx < OT_INSTANCE_COUNT; idx++) { + s_transmit_frame[idx].mInfo.mTxInfo.mIeInfo = &s_transmit_ie_info[idx]; + } +#endif +} + +esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *config) +{ + esp_err_t ret = ESP_OK; + const uint8_t multipan_mask = (uint8_t)((1U << CONFIG_IEEE802154_INTERFACE_NUM) - 1U); + + ESP_RETURN_ON_FALSE(s_radio_event_fd == -1, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, + "Radio was initialized already!"); + + esp_ieee802154_event_cb_list_t cb_list = { + .rx_done_cb = ot_radio_receive_done, + .rx_sfd_done_cb = ot_radio_receive_sfd_done, + .tx_done_cb = ot_radio_transmit_done, + .tx_failed_cb = ot_radio_transmit_failed, + .tx_sfd_done_cb = ot_radio_transmit_sfd_done, + .ed_done_cb = ot_radio_energy_detect_done, + .enh_ack_generator_cb = ot_radio_enh_ack_generator, + }; + ESP_RETURN_ON_ERROR(esp_ieee802154_event_callback_list_register(cb_list), OT_PLAT_LOG_TAG, + "Failed to register ieee802154 event callbacks"); + + s_radio_event_fd = eventfd(0, EFD_SUPPORT_ISR); + ESP_GOTO_ON_FALSE(s_radio_event_fd != -1, ESP_FAIL, exit_unregister, OT_PLAT_LOG_TAG, "Failed to create eventfd"); + + radio_init_software_context(); + + esp_ieee802154_enable(); + ESP_GOTO_ON_ERROR(esp_ieee802154_set_multipan_enable(multipan_mask), exit, OT_PLAT_LOG_TAG, + "Fail to set multipan mask"); + + for (int i = 0; i < CONFIG_IEEE802154_INTERFACE_NUM; i++) { + if (multipan_mask & (1U << i)) { + esp_ieee802154_multipan_set_rx_when_idle(i, true); + } + } + esp_ieee802154_set_promiscuous(false); + + ESP_GOTO_ON_ERROR(esp_openthread_platform_workflow_register(&esp_openthread_radio_update, + &esp_openthread_radio_process, + s_radio_workflow), + exit, OT_PLAT_LOG_TAG, "Fail to register radio workflow"); + + return ESP_OK; + +exit: + esp_ieee802154_disable(); + if (s_radio_event_fd != -1) { + close(s_radio_event_fd); + s_radio_event_fd = -1; + } +exit_unregister: + esp_ieee802154_event_callback_list_unregister(); + return ret; +} + +void esp_openthread_radio_deinit(void) +{ + if (s_radio_event_fd != -1) { + close(s_radio_event_fd); + s_radio_event_fd = -1; + } + + memset(&s_recv_queue, 0, sizeof(esp_openthread_circular_queue_info_t)); + memset(&s_tx_queue_info, 0, sizeof(esp_openthread_tx_queue_info_t)); + s_txrx_events = 0; + + esp_ieee802154_disable(); + esp_ieee802154_event_callback_list_unregister(); + esp_openthread_platform_workflow_unregister(s_radio_workflow); +} + +void esp_openthread_radio_update(esp_openthread_mainloop_context_t *mainloop) +{ + FD_SET(s_radio_event_fd, &mainloop->read_fds); + if (s_radio_event_fd > mainloop->max_fd) { + mainloop->max_fd = s_radio_event_fd; + } +} + +esp_err_t esp_openthread_radio_process(otInstance *aInstance, const esp_openthread_mainloop_context_t *mainloop) +{ + uint64_t event_read; + int ret = read(s_radio_event_fd, &event_read, sizeof(event_read)); + assert(ret == sizeof(event_read)); + bool start_pending_tx = false; + + if (get_event(EVENT_TX_DONE)) { + clr_event(EVENT_TX_DONE); + assert(s_tx_queue_info.used > 0); + int8_t tx_idx = s_tx_queue[s_tx_queue_info.head].index; + assert(tx_idx >= 0 && tx_idx < OT_INSTANCE_COUNT); + otInstance *tx_instance = esp_openthread_get_instance_from_idx(tx_idx); + + if (s_ack_frame.mPsdu == NULL) { + otPlatRadioTxDone(tx_instance, &s_transmit_frame[tx_idx], NULL, OT_ERROR_NONE); + } else { + otPlatRadioTxDone(tx_instance, &s_transmit_frame[tx_idx], &s_ack_frame, OT_ERROR_NONE); + esp_ieee802154_receive_handle_done(s_ack_frame.mPsdu - 1); + s_ack_frame.mPsdu = NULL; + } + radio_tx_queue_pop_head(); + start_pending_tx = true; + } + + if (get_event(EVENT_TX_FAILED)) { + clr_event(EVENT_TX_FAILED); + assert(s_tx_queue_info.used > 0); + int8_t tx_idx = s_tx_queue[s_tx_queue_info.head].index; + assert(tx_idx >= 0 && tx_idx < OT_INSTANCE_COUNT); + otInstance *tx_instance = esp_openthread_get_instance_from_idx(tx_idx); + + otError err = OT_ERROR_NONE; + + switch (s_tx_error) { + case ESP_IEEE802154_TX_ERR_CCA_BUSY: + case ESP_IEEE802154_TX_ERR_ABORT: + case ESP_IEEE802154_TX_ERR_COEXIST: + err = OT_ERROR_CHANNEL_ACCESS_FAILURE; + break; + + case ESP_IEEE802154_TX_ERR_NO_ACK: + case ESP_IEEE802154_TX_ERR_INVALID_ACK: + err = OT_ERROR_NO_ACK; + break; + + default: + ETS_ASSERT(false); + break; + } + + otPlatRadioTxDone(tx_instance, &s_transmit_frame[tx_idx], NULL, err); + radio_tx_queue_pop_head(); + start_pending_tx = true; + } + + // After in-flight TX finishes, start the next frame already in the queue + // before processing RX, to reduce TX idle time. + if (start_pending_tx && s_tx_queue_info.used > 0) { + esp_openthread_radio_transmit_context_t *packet = &s_tx_queue[s_tx_queue_info.head]; + otInstance *instance = esp_openthread_get_instance_from_idx(packet->index); + radio_start_transmit(instance, packet->aFrame, packet->index); + } + + if (get_event(EVENT_ENERGY_DETECT_DONE)) { + clr_event(EVENT_ENERGY_DETECT_DONE); + otPlatRadioEnergyScanDone(aInstance, s_ed_power); + } + + while (atomic_load(&s_recv_queue.used)) { + if (s_receive_frame[s_recv_queue.head].frame.mPsdu != NULL) { + if (s_receive_frame[s_recv_queue.head].index == ESP_IEEE802154_MULTIPAN_MAX) { + // should check all used instances and receive packets for multicast packet. + // TZ-2585 + for (int i = 0; i < OT_INSTANCE_COUNT; i++) { + otInstance *ins = esp_openthread_get_instance_from_idx(i); + otPlatRadioReceiveDone(ins, &s_receive_frame[s_recv_queue.head].frame, OT_ERROR_NONE); + } + } else { + otInstance *ins = esp_openthread_get_instance_from_idx(s_receive_frame[s_recv_queue.head].index); + otPlatRadioReceiveDone(ins, &s_receive_frame[s_recv_queue.head].frame, OT_ERROR_NONE); + } + esp_ieee802154_receive_handle_done(s_receive_frame[s_recv_queue.head].frame.mPsdu - 1); + s_receive_frame[s_recv_queue.head].frame.mPsdu = NULL; + s_recv_queue.head = (s_recv_queue.head + 1) % CONFIG_IEEE802154_RX_BUFFER_SIZE; + atomic_fetch_sub(&s_recv_queue.used, 1); + } + } + + if (get_event(EVENT_SLEEP)) { + clr_event(EVENT_SLEEP); + esp_ieee802154_sleep(); + } + + return ESP_OK; +} + +void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) +{ + uint8_t eui64[8] = {0}; + esp_read_mac(eui64, ESP_MAC_IEEE802154); + memcpy(aIeeeEui64, eui64, sizeof(eui64)); +} + +void otPlatRadioSetPanId(otInstance *aInstance, uint16_t panid) +{ + esp_ieee802154_multipan_index_t idx = (esp_ieee802154_multipan_index_t)esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_set_multipan_panid(idx, panid); +} + +void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress) +{ + esp_ieee802154_multipan_index_t idx = (esp_ieee802154_multipan_index_t)esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_set_multipan_extended_address(idx, aAddress->m8); +} + +void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t aAddress) +{ + esp_ieee802154_multipan_index_t idx = (esp_ieee802154_multipan_index_t)esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_set_multipan_short_address(idx, aAddress); +} + +void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable) +{ + esp_ieee802154_set_promiscuous(aEnable); +} + +bool otPlatRadioIsEnabled(otInstance *aInstance) +{ + return esp_ieee802154_get_state() != ESP_IEEE802154_RADIO_DISABLE; +} + +otError otPlatRadioEnable(otInstance *aInstance) +{ + // radio has been enabled in esp_openthread_radio_init() + + return OT_ERROR_NONE; +} + +otError otPlatRadioDisable(otInstance *aInstance) +{ + // radio will be disabled in esp_openthread_radio_deinit() + + return OT_ERROR_NONE; +} + +otError otPlatRadioSleep(otInstance *aInstance) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_sleep(idx); + + return OT_ERROR_NONE; +} + +otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_set_channel(aChannel); + esp_ieee802154_multipan_receive(idx); + + return OT_ERROR_NONE; +} + +otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + + // One in-flight + pending entries share this queue; under normal OT scheduling + // there is at most one outstanding TX per instance. + assert(s_tx_queue_info.used < OT_INSTANCE_COUNT); + + bool was_idle = (s_tx_queue_info.used == 0); + esp_openthread_radio_transmit_context_t *packet = &s_tx_queue[s_tx_queue_info.tail]; + packet->aFrame = aFrame; + packet->index = idx; + s_tx_queue_info.tail = (s_tx_queue_info.tail + 1) % OT_INSTANCE_COUNT; + s_tx_queue_info.used++; + + if (was_idle) { + radio_start_transmit(aInstance, aFrame, idx); + } + + return OT_ERROR_NONE; +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + return &s_transmit_frame[idx]; +} + +int8_t otPlatRadioGetRssi(otInstance *aInstance) +{ + return esp_ieee802154_get_recent_rssi(); +} + +otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) +{ +// FIXME: Remove `CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE` when JIRA: TZ-609 fixed. + return (otRadioCaps)(OT_RADIO_CAPS_ENERGY_SCAN | +#if CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE + OT_RADIO_CAPS_RX_ON_WHEN_IDLE | +#endif + OT_RADIO_CAPS_TRANSMIT_SEC | OT_RADIO_CAPS_RECEIVE_TIMING | OT_RADIO_CAPS_TRANSMIT_TIMING | + OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_SLEEP_TO_TX); +} + +otError otPlatRadioReceiveAt(otInstance *aInstance, uint8_t aChannel, uint32_t aStart, uint32_t aDuration) +{ + // Multipan RCP does not support receive_at yet. + esp_ieee802154_set_channel(aChannel); + esp_ieee802154_receive_at(aStart, aDuration); + return OT_ERROR_NONE; +} + +bool otPlatRadioGetPromiscuous(otInstance *aInstance) +{ + return esp_ieee802154_get_promiscuous(); +} + +void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable) +{ +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ENHANCED); +#else + esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ENABLE); +#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 +} + +otError otPlatRadioAddSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_add_pending_addr(idx, (uint8_t *)&aShortAddress, true); + return OT_ERROR_NONE; +} + +otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_add_pending_addr(idx, aExtAddress->m8, false); + return OT_ERROR_NONE; +} + +otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_clear_pending_addr(idx, (uint8_t *)&aShortAddress, true); + return OT_ERROR_NONE; +} + +otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_clear_pending_addr(idx, aExtAddress->m8, false); + return OT_ERROR_NONE; +} + +void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_reset_pending_table(idx, true); +} + +void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_reset_pending_table(idx, false); +} + +otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration) +{ + // TZ-2587 + esp_ieee802154_set_channel(aScanChannel); + esp_ieee802154_energy_detect(aScanDuration * US_PER_MS / US_PER_SYMBOL); + + return OT_ERROR_NONE; +} + +otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower) +{ + *aPower = esp_ieee802154_get_txpower(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower) +{ + esp_ieee802154_set_txpower(aPower); + + return OT_ERROR_NONE; +} + +otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t *aThreshold) +{ + *aThreshold = esp_ieee802154_get_cca_threshold(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t aThreshold) +{ + esp_ieee802154_set_cca_threshold(aThreshold); + + return OT_ERROR_NONE; +} + +int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) +{ + return esp_ieee802154_get_receive_sensitivity(); +} + +#if CONFIG_OPENTHREAD_DIAG + +void otPlatDiagSetOutputCallback(otInstance *aInstance, otPlatDiagOutputCallback aCallback, void *aContext) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aCallback); + OT_UNUSED_VARIABLE(aContext); +} + +void otPlatDiagModeSet(bool mode) +{ + s_diag_mode = mode; +} + +bool otPlatDiagModeGet(void) +{ + return s_diag_mode; +} + +void otPlatDiagTxPowerSet(int8_t tx_power) +{ + OT_UNUSED_VARIABLE(tx_power); +} + +void otPlatDiagChannelSet(uint8_t channel) +{ + OT_UNUSED_VARIABLE(channel); +} + +void otPlatDiagRadioReceived(otInstance *aInstance, otRadioFrame *frame, otError error) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(frame); + OT_UNUSED_VARIABLE(error); +} + +void otPlatDiagAlarmCallback(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); +} + +#endif // CONFIG_OPENTHREAD_DIAG + +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 +void otPlatRadioSetMacKey(otInstance *aInstance, uint8_t aKeyIdMode, uint8_t aKeyId, const otMacKeyMaterial *aPrevKey, + const otMacKeyMaterial *aCurrKey, const otMacKeyMaterial *aNextKey, otRadioKeyType aKeyType) +{ + OT_UNUSED_VARIABLE(aKeyIdMode); +#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE + assert(aKeyType == OT_KEY_TYPE_KEY_REF); +#else + assert(aKeyType == OT_KEY_TYPE_LITERAL_KEY); +#endif // OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE + assert(aPrevKey != NULL && aCurrKey != NULL && aNextKey != NULL); + + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + + s_sec_ctx[idx].key_id = aKeyId; + s_sec_ctx[idx].previous_key = *aPrevKey; + s_sec_ctx[idx].current_key = *aCurrKey; + s_sec_ctx[idx].next_key = *aNextKey; +#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE + /* Pre-export raw key bytes in task context to avoid calling psa_export_key() + * from ISR context (enh_ack_generator), which would attempt to take a mutex. */ + size_t keyLength = 0; + psa_export_key(aPrevKey->mKeyMaterial.mKeyRef, s_sec_ctx[idx].previous_key_bytes, 16, &keyLength); + psa_export_key(aCurrKey->mKeyMaterial.mKeyRef, s_sec_ctx[idx].current_key_bytes, 16, &keyLength); + psa_export_key(aNextKey->mKeyMaterial.mKeyRef, s_sec_ctx[idx].next_key_bytes, 16, &keyLength); +#endif +} + +void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + s_sec_ctx[idx].mac_frame_counter = aMacFrameCounter; +} + +void otPlatRadioSetMacFrameCounterIfLarger(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + if (aMacFrameCounter > s_sec_ctx[idx].mac_frame_counter) { + s_sec_ctx[idx].mac_frame_counter = aMacFrameCounter; + } +} +#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + +uint64_t otPlatRadioGetNow(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return otPlatTimeGet(); +} + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime) +{ + OT_UNUSED_VARIABLE(aInstance); + s_csl_sample_time = aCslSampleTime; +} + +static IRAM_ATTR uint16_t get_csl_phase(void) +{ + uint32_t cur_time = otPlatTimeGet(); + uint32_t csl_period_us = s_csl_period * OT_US_PER_TEN_SYMBOLS; + uint32_t diff = (csl_period_us - (cur_time % csl_period_us) + (s_csl_sample_time % csl_period_us)) % csl_period_us; + + return (uint16_t)(diff / OT_US_PER_TEN_SYMBOLS + 1); +} +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + +uint16_t otPlatTimeGetXtalAccuracy(void) +{ + return ESP_OPENTHREAD_XTAL_ACCURACY; +} + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE +otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance, otLinkMetrics aLinkMetrics, + const otShortAddress aShortAddress, const otExtAddress *aExtAddress) +{ + otError error = otLinkMetricsConfigureEnhAckProbing(aShortAddress, aExtAddress, aLinkMetrics); + return error; +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, otShortAddress aShortAddr, + const otExtAddress *aExtAddr) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aShortAddr); + OT_UNUSED_VARIABLE(aExtAddr); + s_csl_period = aCslPeriod; + + return OT_ERROR_NONE; +} + +#endif + +uint8_t otPlatRadioGetCslAccuracy(otInstance *aInstance) +{ + return ESP_OPENTHREAD_CSL_ACCURACY; +} + +uint8_t otPlatRadioGetCslUncertainty(otInstance *aInstance) +{ + return ESP_OPENTHREAD_CSL_UNCERTAIN; +} + +// events +static void IRAM_ATTR ot_radio_transmit_done(const uint8_t *frame, const uint8_t *ack, + esp_ieee802154_frame_info_t *ack_frame_info) +{ + int8_t tx_idx = s_tx_queue[s_tx_queue_info.head].index; + ETS_ASSERT(tx_idx >= 0 && tx_idx < OT_INSTANCE_COUNT); + ETS_ASSERT(frame == (uint8_t *)&s_transmit_psdu[tx_idx]); + + if (ack != NULL) { + s_ack_frame.mLength = (uint16_t)(*ack); + s_ack_frame.mPsdu = (uint8_t *)(ack + 1); + s_ack_frame.mChannel = ack_frame_info->channel; + s_ack_frame.mInfo.mRxInfo.mRssi = ack_frame_info->rssi; + s_ack_frame.mInfo.mRxInfo.mLqi = ack_frame_info->lqi; + s_ack_frame.mInfo.mRxInfo.mTimestamp = ack_frame_info->timestamp; + } + + set_event(EVENT_TX_DONE); +} + +static void IRAM_ATTR convert_to_ot_frame(uint8_t *data, esp_ieee802154_frame_info_t *frame_info, + otRadioFrame *radio_frame) +{ + radio_frame->mPsdu = data + 1; + radio_frame->mLength = *data; + radio_frame->mChannel = frame_info->channel; + radio_frame->mInfo.mRxInfo.mRssi = frame_info->rssi; + radio_frame->mInfo.mRxInfo.mLqi = frame_info->lqi; + radio_frame->mInfo.mRxInfo.mAckedWithFramePending = frame_info->pending; + radio_frame->mInfo.mRxInfo.mTimestamp = frame_info->timestamp; +} + +static esp_err_t IRAM_ATTR enh_ack_set_security_addr_and_key(otRadioFrame *ack_frame, int8_t idx) +{ + struct otMacKeyMaterial *key = NULL; + uint8_t key_id; + + ETS_ASSERT(otMacFrameIsSecurityEnabled(ack_frame)); + if (idx < 0 || idx >= OT_INSTANCE_COUNT) { + return ESP_FAIL; + } + key_id = otMacFrameGetKeyId(ack_frame); + if(!(otMacFrameIsKeyIdMode1(ack_frame) && key_id != 0)) { + return ESP_FAIL; + } + + if (key_id == s_sec_ctx[idx].key_id) { + key = &s_sec_ctx[idx].current_key; + } else if (key_id == s_sec_ctx[idx].key_id - 1) { + key = &s_sec_ctx[idx].previous_key; + } else if (key_id == s_sec_ctx[idx].key_id + 1) { + key = &s_sec_ctx[idx].next_key; + } else { + return ESP_FAIL; + } + s_sec_ctx[idx].ack_frame_counter = s_sec_ctx[idx].mac_frame_counter; + s_sec_ctx[idx].ack_key_id = key_id; + s_sec_ctx[idx].with_security_enh_ack = true; + if (otMacFrameIsKeyIdMode1(ack_frame)) { +#if CONFIG_IEEE802154_MULTI_PAN_ENABLE + esp_ieee802154_get_multipan_extended_address((esp_ieee802154_multipan_index_t)idx, s_sec_ctx[idx].security_addr); +#else + esp_ieee802154_get_extended_address(s_sec_ctx[idx].security_addr); +#endif + ot_set_security_key_from_key_material(idx, *key); + } + + esp_ieee802154_set_transmit_security(&ack_frame->mPsdu[-1], s_sec_ctx[idx].security_key, + s_sec_ctx[idx].security_addr); + return ESP_OK; +} + +static esp_err_t IRAM_ATTR ot_radio_enh_ack_generator(uint8_t *frame, esp_ieee802154_frame_info_t *frame_info, + uint8_t *enhack_frame) +{ + otRadioFrame ack_frame; + otRadioFrame ot_frame; + uint8_t ack_ie_data[OT_ACK_IE_MAX_SIZE] = {0}; + uint8_t offset = 0; +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE + uint8_t link_metrics_data_len = 0; + uint8_t link_metrics_data[OT_ENH_PROBING_IE_DATA_MAX_SIZE]; + otMacAddress mac_addr; +#endif + otError err; + ack_frame.mPsdu = enhack_frame + 1; + convert_to_ot_frame(frame, frame_info, &ot_frame); + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (s_csl_period > 0) { + offset += otMacFrameGenerateCslIeTemplate(ack_ie_data); + } +#endif + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE + otMacFrameGetSrcAddr(&ot_frame, &mac_addr); + link_metrics_data_len = otLinkMetricsEnhAckGenData(&mac_addr, frame_info->lqi, + frame_info->rssi, link_metrics_data); + if (link_metrics_data_len > 0) { + offset += otMacFrameGenerateEnhAckProbingIe(ack_ie_data, link_metrics_data, link_metrics_data_len); + } +#endif + err = otMacFrameGenerateEnhAck(&ot_frame, frame_info->pending, ack_ie_data, offset, &ack_frame); + + if (err != OT_ERROR_NONE) { + return ESP_FAIL; + } + enhack_frame[0] = ack_frame.mLength; + + s_enhack = enhack_frame; + + if (otMacFrameIsSecurityEnabled(&ack_frame) && !ack_frame.mInfo.mTxInfo.mIsSecurityProcessed) { + int8_t ack_idx = (int8_t)frame_info->mpf_index; + if (ack_idx < 0 || ack_idx >= OT_INSTANCE_COUNT) { + return ESP_FAIL; + } + otMacFrameSetFrameCounter(&ack_frame, s_sec_ctx[ack_idx].mac_frame_counter++); + if (enh_ack_set_security_addr_and_key(&ack_frame, ack_idx) != ESP_OK) { + return ESP_FAIL; + } + } + return ESP_OK; +} + +static void IRAM_ATTR ot_radio_receive_done(uint8_t *data, esp_ieee802154_frame_info_t *frame_info) +{ + otRadioFrame ot_frame; + ot_frame.mPsdu = data + 1; + int8_t rx_idx = (int8_t)frame_info->mpf_index; + assert((rx_idx >= 0 && rx_idx < OT_INSTANCE_COUNT) || rx_idx == ESP_IEEE802154_MULTIPAN_MAX); + + if (atomic_load(&s_recv_queue.used) == CONFIG_IEEE802154_RX_BUFFER_SIZE) { + ESP_EARLY_LOGE(OT_PLAT_LOG_TAG, "radio receive buffer full!"); + esp_ieee802154_receive_handle_done(data); + return; + } + + convert_to_ot_frame(data, frame_info, &(s_receive_frame[s_recv_queue.tail].frame)); + s_receive_frame[s_recv_queue.tail].index = rx_idx; +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + // Default to false for broadcast/unmatched frames or non-2015/non-AR data frames. + s_receive_frame[s_recv_queue.tail].frame.mInfo.mRxInfo.mAckedWithSecEnhAck = false; + if (rx_idx >= 0 && rx_idx < OT_INSTANCE_COUNT && + otMacFrameIsAckRequested(&ot_frame) && otMacFrameIsVersion2015(&ot_frame)) { + s_receive_frame[s_recv_queue.tail].frame.mInfo.mRxInfo.mAckedWithSecEnhAck = s_sec_ctx[rx_idx].with_security_enh_ack; + s_receive_frame[s_recv_queue.tail].frame.mInfo.mRxInfo.mAckFrameCounter = s_sec_ctx[rx_idx].ack_frame_counter; + s_receive_frame[s_recv_queue.tail].frame.mInfo.mRxInfo.mAckKeyId = s_sec_ctx[rx_idx].ack_key_id; + } + if (rx_idx >= 0 && rx_idx < OT_INSTANCE_COUNT) { + s_sec_ctx[rx_idx].with_security_enh_ack = false; + } +#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + s_recv_queue.tail = (s_recv_queue.tail + 1) % CONFIG_IEEE802154_RX_BUFFER_SIZE; + atomic_fetch_add(&s_recv_queue.used, 1); + set_event(EVENT_RX_DONE); +} + +static void IRAM_ATTR ot_radio_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error) +{ + int8_t tx_idx = s_tx_queue[s_tx_queue_info.head].index; + ETS_ASSERT(tx_idx >= 0 && tx_idx < OT_INSTANCE_COUNT); + ETS_ASSERT(frame == (uint8_t *)&s_transmit_psdu[tx_idx]); + + s_tx_error = error; + + set_event(EVENT_TX_FAILED); +} + +static void IRAM_ATTR ot_radio_receive_sfd_done(void) +{ +} + +static void IRAM_ATTR ot_radio_transmit_sfd_done(uint8_t *frame) +{ + int8_t idx = -1; + + if (frame != s_enhack) { + idx = s_tx_queue[s_tx_queue_info.head].index; + assert(idx >= 0 && idx < OT_INSTANCE_COUNT); + assert(frame == (uint8_t *)&s_transmit_psdu[idx]); + } +#if !OPENTHREAD_CONFIG_TIME_SYNC_ENABLE + OT_UNUSED_VARIABLE(idx); +#endif + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (s_csl_period > 0) { + otRadioFrame ot_frame; + ot_frame.mPsdu = frame + 1; + ot_frame.mLength = frame[0]; + + otMacFrameSetCslIe(&ot_frame, s_csl_period, get_csl_phase()); + } +#endif + +#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE + if ((idx >= 0) && s_transmit_frame[idx].mInfo.mTxInfo.mIeInfo->mTimeIeOffset != 0) + { + uint8_t *p_time_ie = s_transmit_frame[idx].mPsdu + s_transmit_frame[idx].mInfo.mTxInfo.mIeInfo->mTimeIeOffset; + uint64_t time = (uint64_t)((int64_t)otPlatTimeGet() + s_transmit_frame[idx].mInfo.mTxInfo.mIeInfo->mNetworkTimeOffset); + + *p_time_ie = s_transmit_frame[idx].mInfo.mTxInfo.mIeInfo->mTimeSyncSeq; + + *(++p_time_ie) = (uint8_t)(time & 0xff); + for (uint8_t i = 1; i < sizeof(uint64_t); i++) + { + time = time >> 8; + *(++p_time_ie) = (uint8_t)(time & 0xff); + } + } +#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE +} + +static void IRAM_ATTR ot_radio_energy_detect_done(int8_t power) +{ + s_ed_power = power; + + set_event(EVENT_ENERGY_DETECT_DONE); +} + +otError otPlatEntropyGet(uint8_t *aOutput, uint16_t aOutputLength) +{ + esp_fill_random(aOutput, aOutputLength); + + return OT_ERROR_NONE; +} + +otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aChannel); + OT_UNUSED_VARIABLE(aMaxPower); + + return OT_ERROR_NONE; +} + +#if CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE +void otPlatRadioSetRxOnWhenIdle(otInstance *aInstance, bool aEnable) +{ + int8_t idx = esp_openthread_get_idx_from_instance(aInstance); + esp_ieee802154_multipan_set_rx_when_idle(idx, aEnable); +} +#endif + +uint32_t otPlatRadioGetPreferredChannelMask(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return CONFIG_OPENTHREAD_PREFERRED_CHANNEL_MASK; +} + +uint32_t otPlatRadioGetSupportedChannelMask(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return CONFIG_OPENTHREAD_SUPPORTED_CHANNEL_MASK; +} + +#if (CONFIG_ESP_COEX_SW_COEXIST_ENABLE || CONFIG_EXTERNAL_COEX_ENABLE) +void esp_openthread_set_coex_config(esp_ieee802154_coex_config_t config) +{ + esp_ieee802154_set_coex_config(config); +} + +esp_ieee802154_coex_config_t esp_openthread_get_coex_config(void) +{ + return esp_ieee802154_get_coex_config(); +} +#endif + +void esp_ieee802154_receive_at_done(void) +{ + set_event(EVENT_SLEEP); +} + +otError otPlatMultipanGetActiveInstance(otInstance **aInstance) +{ + *aInstance = esp_openthread_get_instance(); + return OT_ERROR_NONE; +} + +otError otPlatMultipanSetActiveInstance(otInstance *aInstance, bool aCompletePending) +{ + OT_UNUSED_VARIABLE(aCompletePending); + esp_openthread_set_active_instance(aInstance); + return OT_ERROR_NONE; +} diff --git a/components/openthread/src/ncp/esp_openthread_ncp.cpp b/components/openthread/src/ncp/esp_openthread_ncp.cpp index 3852c9a8349..3faee575489 100644 --- a/components/openthread/src/ncp/esp_openthread_ncp.cpp +++ b/components/openthread/src/ncp/esp_openthread_ncp.cpp @@ -92,6 +92,31 @@ static esp_err_t init_console_command_worker() } #endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE +static void ncp_init_console(void) +{ +#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE + esp_err_t err = esp_console_redirect_to_otlog(); + if (err != ESP_OK) { + ESP_LOGE(NO_LOG_TAG, "Failed to redirect console to otPlatLog: %s", esp_err_to_name(err)); + } + + init_console_command_worker(); +#endif +} + +#if CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE +extern "C" void otAppNcpInitMulti(otInstance **aInstances, uint8_t aCount) +{ +#if (CONFIG_OPENTHREAD_RCP_UART || CONFIG_OPENTHREAD_RCP_USB_SERIAL_JTAG || CONFIG_OPENTHREAD_RCP_CUSTOM) + IgnoreError(otPlatUartEnable()); + otNcpHdlcInitMulti(aInstances, aCount, NcpSend); +#else +#error "Multiple instances based on SPI are not supported yet" +#endif + + ncp_init_console(); +} +#else extern "C" void otAppNcpInit(otInstance *aInstance) { #if (CONFIG_OPENTHREAD_RCP_UART || CONFIG_OPENTHREAD_RCP_USB_SERIAL_JTAG) @@ -101,15 +126,9 @@ extern "C" void otAppNcpInit(otInstance *aInstance) otNcpSpiInit(aInstance); #endif -#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE - esp_err_t err = esp_console_redirect_to_otlog(); - if (err != ESP_OK) { - ESP_LOGE(NO_LOG_TAG, "Failed to redirect console to otPlatLog: %s", esp_err_to_name(err)); - } - - init_console_command_worker(); -#endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE + ncp_init_console(); } +#endif namespace ot { namespace Ncp { diff --git a/components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp b/components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp index 6b86b6d9359..db8cd395acb 100644 --- a/components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp +++ b/components/openthread/src/ncp/esp_openthread_ncp_hdlc.cpp @@ -1,9 +1,10 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include "sdkconfig.h" #include "common/new.hpp" #include "ncp_hdlc.hpp" @@ -12,6 +13,30 @@ namespace Ncp { static OT_DEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpHdlc), uint64_t); +#if CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE +extern "C" void otNcpHdlcInitMulti(otInstance **aInstances, uint8_t aCount, otNcpHdlcSendCallback aSendCallback) +{ + NcpHdlc *ncpHdlc = nullptr; + ot::Instance *instances[SPINEL_HEADER_IID_MAX]; + + OT_ASSERT(aCount < SPINEL_HEADER_IID_MAX + 1); + OT_ASSERT(aCount > 0); + OT_ASSERT(aInstances[0] != nullptr); + + for (int i = 0; i < aCount; i++) + { + OT_ASSERT(aInstances[i] != nullptr); + instances[i] = static_cast(aInstances[i]); + } + + ncpHdlc = new (&sNcpRaw) NcpHdlc(instances, aCount, aSendCallback); + + if (ncpHdlc == nullptr || ncpHdlc != NcpBase::GetNcpInstance()) + { + OT_ASSERT(false); + } +} +#else extern "C" void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSendCallback) { NcpHdlc *ncpHdlc = nullptr; @@ -24,6 +49,7 @@ extern "C" void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSend OT_ASSERT(false); } } +#endif } // namespace Ncp } // namespace ot diff --git a/components/openthread/src/port/esp_openthread_messagepool.c b/components/openthread/src/port/esp_openthread_messagepool.c index 37046033494..0e2372ae247 100644 --- a/components/openthread/src/port/esp_openthread_messagepool.c +++ b/components/openthread/src/port/esp_openthread_messagepool.c @@ -28,11 +28,13 @@ void otPlatMessagePoolInit(otInstance *aInstance, uint16_t aMinNumFreeBuffers, s uint16_t num_buffers = aMinNumFreeBuffers; +#if CONFIG_OPENTHREAD_PLATFORM_MALLOC_CAP_SPIRAM if (!esp_psram_is_initialized() && num_buffers > OT_MSGPOOL_NUM_BUFFERS_NO_PSRAM) { ESP_LOGW(OT_PLAT_LOG_TAG, "PSRAM not available, reducing message pool from %u to %u buffers", num_buffers, OT_MSGPOOL_NUM_BUFFERS_NO_PSRAM); num_buffers = OT_MSGPOOL_NUM_BUFFERS_NO_PSRAM; } +#endif otMessageBuffer *buffer_pool = (otMessageBuffer *)heap_caps_calloc_prefer(num_buffers, aBufferSize, 3, esp_openthread_get_alloc_caps(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); s_buffer_pool_pointer = (otMessageBuffer **)heap_caps_calloc_prefer(num_buffers, sizeof(otMessageBuffer **), 3, esp_openthread_get_alloc_caps(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); diff --git a/components/openthread/src/port/esp_openthread_radio.c b/components/openthread/src/port/esp_openthread_radio.c index c1907f123ea..13b8a1fccd2 100644 --- a/components/openthread/src/port/esp_openthread_radio.c +++ b/components/openthread/src/port/esp_openthread_radio.c @@ -10,7 +10,6 @@ #include "sdkconfig.h" #include "esp_check.h" #include "esp_ieee802154.h" -#include "esp_ieee802154_types.h" #include "esp_mac.h" #include "esp_openthread_common.h" #include "esp_openthread_common_macro.h" @@ -37,7 +36,6 @@ #include "esp_coex_i154.h" #endif -#define ESP_RECEIVE_SENSITIVITY -120 #define ESP_OPENTHREAD_XTAL_ACCURACY CONFIG_OPENTHREAD_XTAL_ACCURACY #define ESP_OPENTHREAD_CSL_ACCURACY CONFIG_OPENTHREAD_CSL_ACCURACY #define ESP_OPENTHREAD_CSL_UNCERTAIN CONFIG_OPENTHREAD_CSL_UNCERTAIN @@ -484,7 +482,7 @@ otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t aTh int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) { - return ESP_RECEIVE_SENSITIVITY; + return esp_ieee802154_get_receive_sensitivity(); } #if CONFIG_OPENTHREAD_DIAG diff --git a/components/openthread/srcs_radio.cmake b/components/openthread/srcs_radio.cmake index 4c5a8680668..b0772adaa1d 100644 --- a/components/openthread/srcs_radio.cmake +++ b/components/openthread/srcs_radio.cmake @@ -61,18 +61,31 @@ set(rcp_srcs openthread/src/ncp/ncp_base_dispatcher.cpp openthread/src/ncp/ncp_base_radio.cpp openthread/examples/apps/ncp/ncp.c - # ESP port - src/port/esp_openthread_alarm.c + # ESP port (logging/misc/settings stay at the original path) src/port/esp_openthread_logging.c src/port/esp_openthread_misc.c - src/port/esp_openthread_radio.c src/port/esp_openthread_settings.c # ESP sources src/esp_openthread.cpp src/esp_openthread_lock.c src/esp_openthread_platform.cpp src/esp_openthread_task_queue.c - # ESP NCP +) + +# Single-instance files stay at the original src/ paths. Multipan RCP swaps in +# radio/alarm/instance under src/multiple_instances/. NCP is shared in src/ncp/. +if(CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE) + set(OT_PORT_DIR "src/multiple_instances/port") + set(OT_INSTANCE_SRC "src/multiple_instances/esp_openthread_instance.cpp") +else() + set(OT_PORT_DIR "src/port") + set(OT_INSTANCE_SRC "src/esp_openthread_instance.cpp") +endif() + +list(APPEND rcp_srcs + ${OT_PORT_DIR}/esp_openthread_alarm.c + ${OT_PORT_DIR}/esp_openthread_radio.c + ${OT_INSTANCE_SRC} src/ncp/esp_openthread_ncp.cpp ) @@ -88,7 +101,9 @@ elseif(CONFIG_OPENTHREAD_RCP_SPI) list(APPEND rcp_srcs openthread/src/ncp/ncp_spi.cpp src/port/esp_openthread_spi_slave.c) - if(CONFIG_OPENTHREAD_NCP_VENDOR_HOOK) + # Multipan SPI NCP is not supported; the compile-time check lives in + # src/ncp/esp_openthread_ncp.cpp. + if(CONFIG_OPENTHREAD_NCP_VENDOR_HOOK AND NOT CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE) list(APPEND rcp_srcs src/ncp/esp_openthread_ncp_spi.cpp) endif() endif() diff --git a/examples/openthread/ot_rcp/sdkconfig.ci.multipan b/examples/openthread/ot_rcp/sdkconfig.ci.multipan new file mode 100644 index 00000000000..2a5ee0a278c --- /dev/null +++ b/examples/openthread/ot_rcp/sdkconfig.ci.multipan @@ -0,0 +1,3 @@ +CONFIG_IEEE802154_MULTI_PAN_ENABLE=y +CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE=y +CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT=2