From 0a99f34ac0edbd553d91e2cf9c8363c8e6785d49 Mon Sep 17 00:00:00 2001 From: Xu Si Yu Date: Thu, 9 Jul 2026 17:43:02 +0800 Subject: [PATCH] feat(openthread): initialize multiple_instances source directory --- .../esp_openthread_instance.cpp | 47 + .../port/esp_openthread_alarm.c | 149 +++ .../port/esp_openthread_radio.c | 877 ++++++++++++++++++ 3 files changed, 1073 insertions(+) create mode 100644 components/openthread/src/multiple_instances/esp_openthread_instance.cpp create mode 100644 components/openthread/src/multiple_instances/port/esp_openthread_alarm.c create mode 100644 components/openthread/src/multiple_instances/port/esp_openthread_radio.c 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..c52daf509c7 --- /dev/null +++ b/components/openthread/src/multiple_instances/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/multiple_instances/port/esp_openthread_alarm.c b/components/openthread/src/multiple_instances/port/esp_openthread_alarm.c new file mode 100644 index 00000000000..091e557211a --- /dev/null +++ b/components/openthread/src/multiple_instances/port/esp_openthread_alarm.c @@ -0,0 +1,149 @@ +/* + * 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_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" + +static uint32_t s_alarm_ms = 0; +static bool s_is_ms_running = false; +static uint32_t s_alarm_us = 0; +static bool s_is_us_running = false; +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) +{ + OT_UNUSED_VARIABLE(aInstance); + + s_alarm_ms = aT0 + aDt; + s_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) +{ + OT_UNUSED_VARIABLE(aInstance); + + s_is_ms_running = false; +} + +uint32_t inline otPlatAlarmMilliGetNow(void) +{ + return otPlatTimeGet() / US_PER_MS; +} + +void otPlatAlarmMicroStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) +{ + OT_UNUSED_VARIABLE(aInstance); + + s_alarm_us = aT0 + aDt; + s_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) +{ + OT_UNUSED_VARIABLE(aInstance); + s_is_us_running = false; +} + +uint32_t inline otPlatAlarmMicroGetNow(void) +{ + return otPlatTimeGet(); +} + +esp_err_t esp_openthread_alarm_init(void) +{ + 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; + if (s_is_ms_running) { + remaining_us = calculate_duration(s_alarm_ms, otPlatAlarmMilliGetNow()) * US_PER_MS; + if (remain_min_time_us > remaining_us) { + remain_min_time_us = remaining_us; + } + } + if (s_is_us_running) { + remaining_us = calculate_duration(s_alarm_us, otPlatAlarmMicroGetNow()); + if (remain_min_time_us > remaining_us) { + remain_min_time_us = remaining_us; + } + } + if (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) +{ + if (s_is_ms_running && is_expired(s_alarm_ms, otPlatAlarmMilliGetNow())) { + s_is_ms_running = false; + +#if OPENTHREAD_CONFIG_DIAG_ENABLE + if (otPlatDiagModeGet()) { + otPlatDiagAlarmFired(aInstance); + } else +#endif + { + otPlatAlarmMilliFired(aInstance); + } + + ESP_LOGD(OT_PLAT_LOG_TAG, "Millisecond timer alarm fired"); + } + if (s_is_us_running && is_expired(s_alarm_us, otPlatAlarmMicroGetNow())) { + s_is_us_running = false; + otPlatAlarmMicroFired(aInstance); + 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..e99ea7034df --- /dev/null +++ b/components/openthread/src/multiple_instances/port/esp_openthread_radio.c @@ -0,0 +1,877 @@ +/* + * 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_mac.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/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; + +static otRadioFrame s_transmit_frame; + +static esp_openthread_radio_tx_psdu s_transmit_psdu; +static uint8_t *s_enhack; +static otRadioFrame 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; +#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 +static uint32_t s_mac_frame_counter; +static uint8_t s_key_id; +static struct otMacKeyMaterial s_previous_key; +static struct otMacKeyMaterial s_current_key; +static struct otMacKeyMaterial s_next_key; +#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE +static uint8_t s_pervious_key_bytes[16]; +static uint8_t s_current_key_bytes[16]; +static uint8_t s_next_key_bytes[16]; +#endif +static bool s_with_security_enh_ack = false; +static uint32_t s_ack_frame_counter; +static uint8_t s_ack_key_id; +static uint8_t s_security_key[16]; +static uint8_t s_security_addr[8]; + +static void ot_set_security_key_from_key_material(struct otMacKeyMaterial a_key_material) +{ +#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_previous_key.mKeyMaterial.mKeyRef) { + memcpy(s_security_key, s_pervious_key_bytes, 16); + } else if (a_key_material.mKeyMaterial.mKeyRef == s_next_key.mKeyMaterial.mKeyRef) { + memcpy(s_security_key, s_next_key_bytes, 16); + } else { + memcpy(s_security_key, s_current_key_bytes, 16); + } +#else + memcpy(s_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 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); + +esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *config) +{ + 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); + + s_transmit_frame.mPsdu = s_transmit_psdu.psdu; + + for (uint8_t i = 0; i < CONFIG_IEEE802154_RX_BUFFER_SIZE; i++) { + s_receive_frame[i].mPsdu = NULL; + } + + s_ack_frame.mPsdu = NULL; + memset(&s_recv_queue, 0, sizeof(esp_openthread_circular_queue_info_t)); + +#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT + s_transmit_frame.mInfo.mTxInfo.mIeInfo = &s_transmit_ie_info; +#endif + + esp_ieee802154_enable(); + esp_ieee802154_set_promiscuous(false); + esp_ieee802154_set_rx_when_idle(true); + + return esp_openthread_platform_workflow_register(&esp_openthread_radio_update, &esp_openthread_radio_process, + s_radio_workflow); +} + +void esp_openthread_radio_deinit(void) +{ + if (s_radio_event_fd != -1) { + close(s_radio_event_fd); + s_radio_event_fd = -1; + } + + 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)); + + if (get_event(EVENT_TX_DONE)) { + clr_event(EVENT_TX_DONE); + + if (s_ack_frame.mPsdu == NULL) { + otPlatRadioTxDone(aInstance, &s_transmit_frame, NULL, OT_ERROR_NONE); + } else { + otPlatRadioTxDone(aInstance, &s_transmit_frame, &s_ack_frame, OT_ERROR_NONE); + esp_ieee802154_receive_handle_done(s_ack_frame.mPsdu - 1); + s_ack_frame.mPsdu = NULL; + } + } + + if (get_event(EVENT_TX_FAILED)) { + clr_event(EVENT_TX_FAILED); + + 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(aInstance, &s_transmit_frame, NULL, err); + } + + 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].mPsdu != NULL) { + otPlatRadioReceiveDone(aInstance, &s_receive_frame[s_recv_queue.head], OT_ERROR_NONE); + esp_ieee802154_receive_handle_done(s_receive_frame[s_recv_queue.head].mPsdu - 1); + s_receive_frame[s_recv_queue.head].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_set_panid(panid); +} + +void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress) +{ + esp_ieee802154_set_extended_address(aAddress->m8); +} + +void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t aAddress) +{ + esp_ieee802154_set_short_address(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) +{ + esp_ieee802154_sleep(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) +{ + esp_ieee802154_set_channel(aChannel); + esp_ieee802154_receive(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) +{ + 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.mInfo.mTxInfo.mIsARetx || s_csl_period > 0) { +#else + if (!s_transmit_frame.mInfo.mTxInfo.mIsARetx) { +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + otMacFrameSetFrameCounter(aFrame, s_mac_frame_counter++); + } + if (otMacFrameIsKeyIdMode1(aFrame)) { + s_transmit_frame.mInfo.mTxInfo.mAesKey = &s_current_key; + if (!s_transmit_frame.mInfo.mTxInfo.mIsARetx) { + otMacFrameSetKeyId(aFrame, s_key_id); + } + esp_ieee802154_get_extended_address(s_security_addr); + } + ot_set_security_key_from_key_material(s_current_key); + esp_ieee802154_set_transmit_security(&aFrame->mPsdu[-1], s_security_key, s_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); + + return OT_ERROR_NONE; +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) +{ + return &s_transmit_frame; +} + +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) +{ + 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) +{ + esp_ieee802154_add_pending_addr((uint8_t *)&aShortAddress, true); + return OT_ERROR_NONE; +} + +otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + esp_ieee802154_add_pending_addr(aExtAddress->m8, false); + return OT_ERROR_NONE; +} + +otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress) +{ + esp_ieee802154_clear_pending_addr((uint8_t *)&aShortAddress, true); + return OT_ERROR_NONE; +} + +otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + esp_ieee802154_clear_pending_addr(aExtAddress->m8, false); + return OT_ERROR_NONE; +} + +void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance) +{ + esp_ieee802154_reset_pending_table(true); +} + +void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance) +{ + esp_ieee802154_reset_pending_table(false); +} + +otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration) +{ + 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(aInstance); + 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); + + s_key_id = aKeyId; + s_previous_key = *aPrevKey; + s_current_key = *aCurrKey; + s_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_pervious_key_bytes, 16, &keyLength); + psa_export_key(aCurrKey->mKeyMaterial.mKeyRef, s_current_key_bytes, 16, &keyLength); + psa_export_key(aNextKey->mKeyMaterial.mKeyRef, s_next_key_bytes, 16, &keyLength); +#endif +} + +void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + OT_UNUSED_VARIABLE(aInstance); + + s_mac_frame_counter = aMacFrameCounter; +} + +void otPlatRadioSetMacFrameCounterIfLarger(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + OT_UNUSED_VARIABLE(aInstance); + + if (aMacFrameCounter > s_mac_frame_counter) { + s_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) +{ + ETS_ASSERT(frame == (uint8_t *)&s_transmit_psdu); + + 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) +{ + struct otMacKeyMaterial *key = NULL; + uint8_t key_id; + + ETS_ASSERT(otMacFrameIsSecurityEnabled(ack_frame)); + key_id = otMacFrameGetKeyId(ack_frame); + if(!(otMacFrameIsKeyIdMode1(ack_frame) && key_id != 0)) { + return ESP_FAIL; + } + + if (key_id == s_key_id) { + key = &s_current_key; + } else if (key_id == s_key_id - 1) { + key = &s_previous_key; + } else if (key_id == s_key_id + 1) { + key = &s_next_key; + } else { + return ESP_FAIL; + } + s_ack_frame_counter = s_mac_frame_counter; + s_ack_key_id = key_id; + s_with_security_enh_ack = true; + if (otMacFrameIsKeyIdMode1(ack_frame)) { + esp_ieee802154_get_extended_address(s_security_addr); + ot_set_security_key_from_key_material(*key); + } + + esp_ieee802154_set_transmit_security(&ack_frame->mPsdu[-1], s_security_key, s_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) { + otMacFrameSetFrameCounter(&ack_frame, s_mac_frame_counter++); + if (enh_ack_set_security_addr_and_key(&ack_frame) != 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; + + 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])); +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + // Inform if this frame was acknowledged with secured Enh-ACK. + if (otMacFrameIsAckRequested(&ot_frame) && otMacFrameIsVersion2015(&ot_frame)) { + s_receive_frame[s_recv_queue.tail].mInfo.mRxInfo.mAckedWithSecEnhAck = s_with_security_enh_ack; + s_receive_frame[s_recv_queue.tail].mInfo.mRxInfo.mAckFrameCounter = s_ack_frame_counter; + s_receive_frame[s_recv_queue.tail].mInfo.mRxInfo.mAckKeyId = s_ack_key_id; + } else { + s_receive_frame[s_recv_queue.tail].mInfo.mRxInfo.mAckedWithSecEnhAck = false; + } + s_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) +{ + ETS_ASSERT(frame == (uint8_t *)&s_transmit_psdu); + + 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) +{ + assert(frame == (uint8_t *)&s_transmit_psdu || frame == s_enhack); + +#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 (frame == (uint8_t *)&s_transmit_psdu && s_transmit_frame.mInfo.mTxInfo.mIeInfo->mTimeIeOffset != 0) + { + uint8_t *p_time_ie = s_transmit_frame.mPsdu + s_transmit_frame.mInfo.mTxInfo.mIeInfo->mTimeIeOffset; + uint64_t time = (uint64_t)((int64_t)otPlatTimeGet() + s_transmit_frame.mInfo.mTxInfo.mIeInfo->mNetworkTimeOffset); + + *p_time_ie = s_transmit_frame.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) +{ + OT_UNUSED_VARIABLE(aInstance); + esp_ieee802154_set_rx_when_idle(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); +}