/* * Copyright (c) 2023, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** * @file * This file implements the OpenThread platform abstraction for radio communication. * */ #include #include #include #include #include #include #include #include #include #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE #include #endif #include "common/code_utils.hpp" #include "common/debug.hpp" #include "common/logging.hpp" #include "utils/code_utils.h" #include "utils/link_metrics.h" #include "utils/mac_frame.h" #include "circular_queue.h" #include "em_device.h" #include "sl_core.h" #if defined _SILICON_LABS_32B_SERIES_2 #include "em_system.h" #else #include "sl_hal_system.h" #endif #include "ieee802154mac.h" #include "pa_conversions_efr32.h" #include "platform-band.h" #include "platform-efr32.h" #include "radio_coex.h" #include "radio_multi_channel.h" #include "radio_power_manager.h" #include "rail.h" #include "rail_config.h" #include "rail_ieee802154.h" #include "sl_memory_manager.h" #include "sl_multipan.h" #include "sl_packet_utils.h" #include "soft_source_match_table.h" #include "sl_openthread_radio_config.h" #ifdef SL_COMPONENT_CATALOG_PRESENT #include "sl_component_catalog.h" #endif // SL_COMPONENT_CATALOG_PRESENT #ifdef SL_CATALOG_RAIL_MULTIPLEXER_PRESENT #include "sl_rail_mux_rename.h" #endif #ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT #include "sl_rail_util_ant_div.h" #include "sl_rail_util_ant_div_config.h" #endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT #include "coexistence-802154.h" #include "coexistence-ot.h" #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT #include "sl_rail_util_ieee802154_stack_event.h" #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT #include "sl_rail_util_ieee802154_phy_select.h" #endif // #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT #include "sl_gp_interface.h" #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_FAST_CHANNEL_SWITCHING_PRESENT #include "sl_rail_util_ieee802154_fast_channel_switching_config.h" #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_FAST_CHANNEL_SWITCHING_PRESENT //------------------------------------------------------------------------------ // Enums, macros and static variables #ifndef LOW_BYTE #define LOW_BYTE(n) ((uint8_t)((n)&0xFF)) #endif // LOW_BTE #ifndef HIGH_BYTE #define HIGH_BYTE(n) ((uint8_t)(LOW_BYTE((n) >> 8))) #endif // HIGH_BYTE #ifndef BIT32 #define BIT32(x) (((uint32_t)1) << (x)) #endif // BIT32 // Intentionally maintaining separate groups for the different device series. // This gives flexibility to add new elements to be read, like CCA Thresholds. #if defined(_SILICON_LABS_32B_SERIES_2) #define USERDATA_MFG_CUSTOM_EUI_64 (2) #endif #ifndef USERDATA_END #define USERDATA_END (USERDATA_BASE + FLASH_PAGE_SIZE) #endif #if SL_OPENTHREAD_RADIO_RX_BUFFER_COUNT > CIRCULAR_QUEUE_LEN_MAX #error "Rx buffer count cannot be greater than max circular queue length." #endif // Internal flags #define FLAG_RADIO_INIT_DONE 0x00000001 #define FLAG_ONGOING_TX_DATA 0x00000002 #define FLAG_ONGOING_TX_ACK 0x00000004 #define FLAG_WAITING_FOR_ACK 0x00000008 #define FLAG_CURRENT_TX_USE_CSMA 0x00000010 #define FLAG_SCHEDULED_RX_PENDING 0x00000020 // Radio Events #define EVENT_TX_SUCCESS 0x00000100 #define EVENT_TX_CCA_FAILED 0x00000200 #define EVENT_TX_NO_ACK 0x00000400 #define EVENT_TX_SCHEDULER_ERROR 0x00000800 #define EVENT_TX_FAILED 0x00001000 #define EVENT_ACK_SENT_WITH_FP_SET 0x00002000 #define EVENT_SECURED_ACK_SENT 0x00004000 #define EVENT_SCHEDULED_RX_STARTED 0x00008000 #define EVENT_SCHEDULED_TX_STARTED 0x00010000 #define TX_WAITING_FOR_ACK 0x00 #define TX_NO_ACK 0x01 #define RADIO_TX_EVENTS \ (EVENT_TX_SUCCESS | EVENT_TX_CCA_FAILED | EVENT_TX_NO_ACK | EVENT_TX_SCHEDULER_ERROR | EVENT_TX_FAILED) #define QUARTER_DBM_IN_DBM 4 #define US_IN_MS 1000 /* FilterMask provided by RAIL is structured as follows: * | Bit:7 | Bit:6 | Bit:5 | Bit:4 | Bit:3 | Bit:2 | Bit:1 | Bit:0 | * | Addr2 | Addr1 | Addr0 | Bcast Addr | Pan Id2 | Pan Id1 | Pan Id0 | Bcast PanId | * | Matched | Matched | Matched | Matched | Matched | Matched | Matched | Matched | */ #define RADIO_BCAST_IID (0) #define RADIO_GET_FILTER_MASK(iid) (1 << (iid)) #define RADIO_PANID_FILTER_SHIFT (0) #define RADIO_ADDR_FILTER_SHIFT (4) #define RADIO_BCAST_PANID_FILTER_MASK RADIO_GET_FILTER_MASK(0) #define RADIO_INDEX0_PANID_FILTER_MASK RADIO_GET_FILTER_MASK(1) #define RADIO_INDEX1_PANID_FILTER_MASK RADIO_GET_FILTER_MASK(2) #define RADIO_INDEX2_PANID_FILTER_MASK RADIO_GET_FILTER_MASK(3) #define RADIO_GET_PANID_FILTER_MASK(filter) \ (filter \ & (RADIO_BCAST_PANID_FILTER_MASK | RADIO_INDEX0_PANID_FILTER_MASK | RADIO_INDEX1_PANID_FILTER_MASK \ | RADIO_INDEX2_PANID_FILTER_MASK)) #define RADIO_BCAST_ADDR_FILTER_MASK (RADIO_GET_FILTER_MASK(0) << RADIO_ADDR_FILTER_SHIFT) #define RADIO_INDEX0_ADDR_FILTER_MASK (RADIO_GET_FILTER_MASK(1) << RADIO_ADDR_FILTER_SHIFT) #define RADIO_INDEX1_ADDR_FILTER_MASK (RADIO_GET_FILTER_MASK(2) << RADIO_ADDR_FILTER_SHIFT) #define RADIO_INDEX2_ADDR_FILTER_MASK (RADIO_GET_FILTER_MASK(3) << RADIO_ADDR_FILTER_SHIFT) #define RADIO_GET_ADDR_FILTER_MASK(filter) \ (filter \ & (RADIO_BCAST_ADDR_FILTER_MASK | RADIO_INDEX0_ADDR_FILTER_MASK | RADIO_INDEX1_ADDR_FILTER_MASK \ | RADIO_INDEX2_ADDR_FILTER_MASK)) #define RADIO_BCAST_PANID (0xFFFF) #if defined(_SILICON_LABS_32B_SERIES_2) #define DEVICE_CAPABILITY_MCU_EN (DEVINFO->SWCAPA1 & _DEVINFO_SWCAPA1_RFMCUEN_MASK) #else #define DEVICE_CAPABILITY_MCU_EN (DEVINFO->SWCAPA & _DEVINFO_SWCAPA_RFMCUEN_MASK) #endif #define ENERGY_READS_MAX -128 #define SYMBOLS_PER_ENERGY_READING 8 // we take a reading every 8 symbols static otRadioCaps sRadioCapabilities = (OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF | OT_RADIO_CAPS_ENERGY_SCAN | OT_RADIO_CAPS_SLEEP_TO_TX #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) | OT_RADIO_CAPS_TRANSMIT_SEC // When scheduled tx is required, we support RAIL_StartScheduledCcaCsmaTx // (delay is indicated in tx frame info set in MAC) | OT_RADIO_CAPS_TRANSMIT_TIMING // When scheduled rx is required, we support RAIL_ScheduleRx in our // implementation of otPlatRadioReceiveAt | OT_RADIO_CAPS_RECEIVE_TIMING #endif ); static uint32_t sScanFrameCounter = 0; // How many 'frames' left in the current scan. static uint32_t sScanFrameCounterMax = 0; // Max 'frames' when performing scan static int8_t sEnergyReadsMax = ENERGY_READS_MAX; static RAIL_MultiTimer_t rail_timer; // Energy Scan typedef enum { ENERGY_SCAN_STATUS_IDLE, ENERGY_SCAN_STATUS_IN_PROGRESS, ENERGY_SCAN_STATUS_COMPLETED } energyScanStatus; typedef enum { ENERGY_SCAN_MODE_SYNC, ENERGY_SCAN_MODE_ASYNC } energyScanMode; typedef struct { uint8_t length; uint8_t channel; uint8_t lqi; int8_t rssi; uint8_t iid; RAIL_Time_t timestamp; } rxPacketDetails; typedef struct { rxPacketDetails packetInfo; uint8_t psdu[IEEE802154_MAX_LENGTH]; } rxBuffer; typedef uint8_t rxBufferIndex_t; static volatile energyScanStatus sEnergyScanStatus; static volatile int8_t sEnergyScanResultDbm; static energyScanMode sEnergyScanMode; // To track active interface the energy scan is being performed. static uint8_t sEnergyScanActiveInterface = INVALID_INTERFACE_INDEX; static bool sIsSrcMatchEnabled = false; #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE #define RADIO_EXT_ADDR_COUNT (RADIO_INTERFACE_COUNT - 1) // To hold transmit/energy-scan requests from the hosts i.e. one per instance/host, // if radio is busy. #define RADIO_REQUEST_BUFFER_COUNT OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM #else #define RADIO_EXT_ADDR_COUNT (RADIO_INTERFACE_COUNT) #define RADIO_REQUEST_BUFFER_COUNT 1 #endif typedef struct { otRadioFrame frame; uint8_t iid; uint8_t currentRadioTxPriority; } radioFrame; // Receive static Queue_t sRxPacketQueue; static sl_memory_pool_t sRxPacketMemPoolHandle = {0}; static uint8_t sReceiveAckPsdu[IEEE802154_MAX_LENGTH]; static radioFrame sReceive; static radioFrame sReceiveAck; static otError sReceiveError; // Transmit // One of the IID is reserved for broadcast hence we need RADIO_INTERFACE_COUNT - 1. // IID zero is for broadcast, so request from host1 (i.e. iid = 1) will use tx buffer // index 0 (i.e. iid - 1) and so on. static radioFrame sTransmitBuffer[RADIO_REQUEST_BUFFER_COUNT]; static uint8_t sTransmitPsdu[RADIO_REQUEST_BUFFER_COUNT][IEEE802154_MAX_LENGTH]; static radioFrame *sCurrentTxPacket = NULL; static uint8_t sLastLqi = 0; static int8_t sLastRssi = 0; static otExtAddress sExtAddress[RADIO_EXT_ADDR_COUNT]; // CSMA config: Should be globally scoped #define CSL_CSMA_BACKOFF_TIME_IN_US 150 RAIL_CsmaConfig_t csmaConfig = RAIL_CSMA_CONFIG_802_15_4_2003_2p4_GHz_OQPSK_CSMA; RAIL_CsmaConfig_t cslCsmaConfig = RAIL_CSMA_CONFIG_SINGLE_CCA; #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT static otRadioIeInfo sTransmitIeInfo[RADIO_REQUEST_BUFFER_COUNT]; #endif // Radio #define CCA_THRESHOLD_UNINIT 127 #define CCA_THRESHOLD_DEFAULT -75 // dBm - default for 2.4GHz 802.15.4 #define UNINITIALIZED_CHANNEL 0xFF static bool sPromiscuous = false; static efr32CommonConfig sCommonConfig; static efr32BandConfig sBandConfig; static efr32BandConfig *sCurrentBandConfig = NULL; static int8_t sCcaThresholdDbm = CCA_THRESHOLD_DEFAULT; #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT efr32RadioCounters railDebugCounters; // Create an alias for readability of RX debug tracking #define rxDebugStep (railDebugCounters.mRadioDebugData.m8[RX_DEBUG_COUNTER0]) #endif #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE // Type of commands that can be added to the pending command buffer. typedef enum { kPendingCommandTypeTransmit, kPendingCommandTypeEnergyScan, } pendingCommandType; typedef struct { // Energy scan channel. uint8_t scanChannel; // Energy scan duration. uint16_t scanDuration; } energyScanParams; // The structure representing pending transmit and energy-scan command requests. typedef struct { // The union of transmit and energy-scan requests parameters. union { // A pointer to the transmit radio frame. otRadioFrame *txFrame; // The structure of energy-scan request parameters. energyScanParams energyScan; } request; // The pending command type. pendingCommandType cmdType : 2; // The interface iid of the pending command. uint8_t iid : 2; } pendingCommandEntry; static Queue_t sPendingCommandQueue; extern otInstance *sInstances[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; static uint8_t sRailFilterMask = RADIO_BCAST_PANID_FILTER_MASK; static bool isRadioTransmittingOrScanning(void); #if FAST_CHANNEL_SWITCHING_SUPPORT static RAIL_IEEE802154_RxChannelSwitchingCfg_t sChannelSwitchingCfg; static RAIL_IEEE802154_RX_CHANNEL_SWITCHING_BUF_ALIGNMENT_TYPE sChannelSwitchingBuffer[RAIL_IEEE802154_RX_CHANNEL_SWITCHING_BUF_BYTES / RAIL_IEEE802154_RX_CHANNEL_SWITCHING_BUF_ALIGNMENT]; bool sl_is_multi_channel_enabled(void) { uint8_t firstChannel = UNINITIALIZED_CHANNEL; for (uint8_t i = 0U; i < RAIL_IEEE802154_RX_CHANNEL_SWITCHING_NUM_CHANNELS; i++) { if (sChannelSwitchingCfg.channels[i] != UNINITIALIZED_CHANNEL) { if (firstChannel == UNINITIALIZED_CHANNEL) { firstChannel = sChannelSwitchingCfg.channels[i]; } else if (firstChannel != sChannelSwitchingCfg.channels[i]) { return true; } } } return false; } otError sl_get_channel_switching_cfg(RAIL_IEEE802154_RxChannelSwitchingCfg_t *channelSwitchingCfg) { otError error = OT_ERROR_NONE; otEXPECT_ACTION(channelSwitchingCfg != NULL, error = OT_ERROR_INVALID_ARGS); memcpy(channelSwitchingCfg, &sChannelSwitchingCfg, sizeof(sChannelSwitchingCfg)); exit: return error; } static uint8_t fastChannelIndex(uint8_t aChannel) { for (uint8_t i = 0U; i < RAIL_IEEE802154_RX_CHANNEL_SWITCHING_NUM_CHANNELS; i++) { if (sChannelSwitchingCfg.channels[i] == aChannel) { return i; } } return INVALID_INTERFACE_INDEX; } #endif // FAST_CHANNEL_SWITCHING_SUPPORT #endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE static void efr32ScanTimerHandler(struct RAIL_MultiTimer *tmr, RAIL_Time_t expectedTimeOfEvent, void *cbArg); static RAIL_Status_t efr32ScanDwell(uint16_t symbols); // RAIL #ifdef SL_CATALOG_RAIL_MULTIPLEXER_PRESENT RAIL_Handle_t gRailHandle; #if SL_OPENTHREAD_COEX_COUNTER_ENABLE static void sl_ot_coex_counter_on_event(sl_rail_util_coex_event_t event); #endif // SL_OPENTHREAD_COEX_COUNTER_ENABLE #else RAIL_Handle_t emPhyRailHandle; #endif // SL_CATALOG_RAIL_MULTIPLEXER_PRESENT static const RAIL_IEEE802154_Config_t sRailIeee802154Config = { .addresses = NULL, .ackConfig = { .enable = true, .ackTimeout = 672, .rxTransitions = { .success = RAIL_RF_STATE_RX, .error = RAIL_RF_STATE_RX, }, .txTransitions = { .success = RAIL_RF_STATE_RX, .error = RAIL_RF_STATE_RX, }, }, .timings = { .idleToRx = 100, .txToRx = 192 - 10, .idleToTx = 100, #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 .rxToTx = 256, // accommodate enhanced ACKs #else .rxToTx = 192, #endif .rxSearchTimeout = 0, .txToRxSearchTimeout = 0, .txToTx = 0, }, .framesMask = RAIL_IEEE802154_ACCEPT_STANDARD_FRAMES, .promiscuousMode = false, .isPanCoordinator = false, .defaultFramePendingInOutgoingAcks = false, }; #if RADIO_CONFIG_SUBGHZ_SUPPORT #define PHY_HEADER_SIZE 2 // SHR: 4 bytes preamble, 2 bytes SFD // 802.15.4 spec describes GFSK SHR to be the same format as SUN O-QPSK // except preamble is 32 symbols (4 octets). #define SHR_SIZE 6 #else #define PHY_HEADER_SIZE 1 #define SHR_SIZE 5 // 4 bytes of preamble, 1 byte sync-word #endif #define SHR_DURATION_US 160 // Duration of SHR in us. // Misc static volatile uint32_t miscRadioState = 0; static bool emPendingData = false; #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT enum { RHO_INACTIVE = 0, RHO_EXT_ACTIVE, RHO_INT_ACTIVE, // Not used RHO_BOTH_ACTIVE, }; static uint8_t rhoActive = RHO_INACTIVE; static bool ptaGntEventReported; static bool sRadioCoexEnabled = true; #if SL_OPENTHREAD_COEX_COUNTER_ENABLE uint32_t efr32RadioCoexCounters[SL_RAIL_UTIL_COEX_EVENT_COUNT] = {0}; #endif // SL_OPENTHREAD_COEX_COUNTER_ENABLE #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT // Note: This callback can be called from ISR context. SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static bool rxPacketQueueOverflowCallback(const Queue_t *queue, void *data) { OT_UNUSED_VARIABLE(queue); OT_UNUSED_VARIABLE(data); // True to discard the queue item being considered for removal. // False for nothing to be discarded from the queue. // Do not discard the oldest entry from the queue, rather drop // the new received packet, hence, return false. return false; } #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE static bool pendingCommandQueueOverflowCallback(const Queue_t *queue, void *data) { OT_UNUSED_VARIABLE(queue); OT_UNUSED_VARIABLE(data); // We should never hit this callback because a host can only request // one command at a time. Hence, added a assert if it happens. OT_ASSERT(false); return false; } #endif #if RADIO_CONFIG_ENABLE_CUSTOM_EUI_SUPPORT && defined(_SILICON_LABS_32B_SERIES_2) /* * This API reads the UserData page on the given EFR device. */ static int readUserData(void *buffer, uint16_t index, int len, bool changeByteOrder) { uint8_t *readLocation = (uint8_t *)USERDATA_BASE + index; uint8_t *writeLocation = (uint8_t *)buffer; // Sanity check to verify if the ouput buffer is valid and the index and len are valid. // If invalid, change the len to -1 and return. otEXPECT_ACTION((writeLocation != NULL) && ((readLocation + len) <= (uint8_t *)USERDATA_END), len = -1); // Copy the contents of flash into output buffer. for (int idx = 0; idx < len; idx++) { if (changeByteOrder) { writeLocation[idx] = readLocation[(len - 1) - idx]; } else { writeLocation[idx] = readLocation[idx]; } } exit: // Return len, len was changed to -1 to indicate failure. return len; } #endif /* * This API converts the FilterMask to appropriate IID. If there are any errors, it will fallback on bcast IID. * */ SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static inline uint8_t getIidFromFilterMask(uint8_t mask) { uint8_t iid = INVALID_INTERFACE_INDEX; #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE // We need only the Pan Id masks here, as we are not matching the addresses. // Also mask all the unused indices. mask &= sRailFilterMask; // The only acceptable values for mask at this point are: // 1 - BCAST PANID - IID(0) // 2 - INDEX 0 - IID(1) // 4 - INDEX 1 - IID(2) // 8 - INDEX 2 - IID(3) // // The packet should either be directed to one of the PANs or Bcast. // (mask & (mask -1) is a simplistic way of testing if the mask is a power of 2. otEXPECT_ACTION(((mask != 0) && (mask & (mask - 1)) == 0), iid = 0); while (mask) { iid++; mask >>= 1; } exit: #else (void)mask; iid = RADIO_BCAST_IID; #endif return iid; } /* * This API validates the received FilterMask by checking if the destination address * in the received packet corresponds to destination PanID. */ SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static bool isFilterMaskValid(uint8_t mask) { bool valid = false; #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE /* Packet will be considered as a valid packet in 3 cases: * Case 1: If the packet was directed towards bcast address or bcast panid * * Case 2: If the packet was directed to one of our valid address/PANID combos * (Compare all non-bcast PANID filters against their corresponding * address filters for same IID and see if any match) * * Case 3: We don't have either the destination addressing field or destination PanId * in the received packet to determine if the dest address and dest pan match. */ if (((mask & RADIO_BCAST_PANID_FILTER_MASK) || (mask & RADIO_BCAST_ADDR_FILTER_MASK)) || // Case 1 // Find any non-broadcast PAN ID match and get ready to compare it ((((mask & (RADIO_INDEX0_PANID_FILTER_MASK | RADIO_INDEX1_PANID_FILTER_MASK | RADIO_INDEX2_PANID_FILTER_MASK)) >> RADIO_PANID_FILTER_SHIFT) & // ...To see if it coincides with any address matches for same IID (RADIO_GET_ADDR_FILTER_MASK(mask) >> RADIO_ADDR_FILTER_SHIFT)) != 0) || // Case 2 (((RADIO_GET_PANID_FILTER_MASK(mask)) == 0) || ((RADIO_GET_ADDR_FILTER_MASK(mask)) == 0)) // Case 3 ) { valid = true; } #else (void)mask; valid = true; #endif return valid; } #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) enum { MAC_KEY_PREV, MAC_KEY_CURRENT, MAC_KEY_NEXT, MAC_KEY_COUNT }; typedef struct securityMaterial { uint8_t ackKeyId; uint8_t keyId; uint32_t macFrameCounter; uint32_t ackFrameCounter; otMacKeyMaterial keys[MAC_KEY_COUNT]; } securityMaterial; // Transmit Security static securityMaterial sMacKeys[RADIO_INTERFACE_COUNT]; #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE // CSL parameters static uint32_t sCslPeriod; static uint32_t sCslSampleTime; SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static uint16_t getCslPhase(uint32_t shrTxTime) { uint32_t cslPeriodInUs = sCslPeriod * OT_US_PER_TEN_SYMBOLS; uint32_t diff; if (shrTxTime == 0U) { shrTxTime = otPlatAlarmMicroGetNow(); } diff = ((sCslSampleTime % cslPeriodInUs) - (shrTxTime % cslPeriodInUs) + cslPeriodInUs) % cslPeriodInUs; return (uint16_t)(diff / OT_US_PER_TEN_SYMBOLS); } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE // Enhanced ACK IE data static uint8_t sAckIeData[OT_ACK_IE_MAX_SIZE]; static uint8_t sAckIeDataLength = 0; SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static uint8_t generateAckIeData(uint8_t *aLinkMetricsIeData, uint8_t aLinkMetricsIeDataLen) { OT_UNUSED_VARIABLE(aLinkMetricsIeData); OT_UNUSED_VARIABLE(aLinkMetricsIeDataLen); uint8_t offset = 0; #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE if (sCslPeriod > 0) { offset += otMacFrameGenerateCslIeTemplate(sAckIeData); } #endif #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE if (aLinkMetricsIeData != NULL && aLinkMetricsIeDataLen > 0) { offset += otMacFrameGenerateEnhAckProbingIe(sAckIeData, aLinkMetricsIeData, aLinkMetricsIeDataLen); } #endif return offset; } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static otError radioProcessTransmitSecurity(otRadioFrame *aFrame, uint8_t iid) { otError error = OT_ERROR_NONE; uint8_t keyId; uint8_t keyToUse; uint8_t panIndex = efr32GetPanIndexFromIid(iid); otEXPECT(otMacFrameIsSecurityEnabled(aFrame) && otMacFrameIsKeyIdMode1(aFrame) && !aFrame->mInfo.mTxInfo.mIsSecurityProcessed); OT_ASSERT(panIndex != INVALID_INTERFACE_INDEX); if (otMacFrameIsAck(aFrame)) { keyId = otMacFrameGetKeyId(aFrame); otEXPECT_ACTION(keyId != 0, error = OT_ERROR_FAILED); if (keyId == sMacKeys[iid].keyId - 1) { keyToUse = MAC_KEY_PREV; } else if (keyId == sMacKeys[iid].keyId) { keyToUse = MAC_KEY_CURRENT; } else if (keyId == sMacKeys[iid].keyId + 1) { keyToUse = MAC_KEY_NEXT; } else { error = OT_ERROR_SECURITY; otEXPECT(false); } } else { keyId = sMacKeys[iid].keyId; keyToUse = MAC_KEY_CURRENT; } aFrame->mInfo.mTxInfo.mAesKey = &sMacKeys[iid].keys[keyToUse]; if (!aFrame->mInfo.mTxInfo.mIsHeaderUpdated) { if (otMacFrameIsAck(aFrame)) { // Store ack frame counter and ack key ID for receive frame sMacKeys[iid].ackKeyId = keyId; sMacKeys[iid].ackFrameCounter = sMacKeys[iid].macFrameCounter; } otMacFrameSetKeyId(aFrame, keyId); otMacFrameSetFrameCounter(aFrame, sMacKeys[iid].macFrameCounter++); } efr32PlatProcessTransmitAesCcm(aFrame, &sExtAddress[panIndex]); exit: return error; } #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static uint8_t readInitialPacketData(RAIL_RxPacketInfo_t *packetInfo, uint8_t expected_data_bytes_max, uint8_t expected_data_bytes_min, uint8_t *buffer, uint8_t buffer_len) { uint8_t packetBytesRead = 0; RAIL_RxPacketInfo_t adjustedPacketInfo; // Check if we have enough buffer OT_ASSERT((buffer_len >= expected_data_bytes_max) || (packetInfo != NULL)); // Read the packet info RAIL_GetRxIncomingPacketInfo(gRailHandle, packetInfo); // We are trying to get the packet info of a packet before it is completely received. // We do this to evaluate the FP bit in response and add IEs to ACK if needed. // Check to see if we have received atleast minimum number of bytes requested. otEXPECT_ACTION(packetInfo->packetBytes >= expected_data_bytes_min, packetBytesRead = 0); adjustedPacketInfo = *packetInfo; // Only extract what we care about if (packetInfo->packetBytes > expected_data_bytes_max) { adjustedPacketInfo.packetBytes = expected_data_bytes_max; // Check if the initial portion of the packet received so far exceeds the max value requested. if (packetInfo->firstPortionBytes >= expected_data_bytes_max) { // If we have received more, make sure to copy only the required bytes into the buffer. adjustedPacketInfo.firstPortionBytes = expected_data_bytes_max; adjustedPacketInfo.lastPortionData = NULL; } } // Copy number of bytes as indicated in `packetInfo->firstPortionBytes` into the buffer. RAIL_CopyRxPacket(buffer, &adjustedPacketInfo); // Put it back to packetBytes. packetBytesRead = (uint8_t)adjustedPacketInfo.packetBytes; exit: return packetBytesRead; } //------------------------------------------------------------------------------ // Forward Declarations static void RAILCb_Generic(RAIL_Handle_t aRailHandle, RAIL_Events_t aEvents); static void efr32PhyStackInit(void); #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 static void updateIeInfoTxFrame(uint32_t shrTxTime); #endif #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT static void efr32CoexInit(void); // Try to transmit the current outgoing frame subject to MAC-level PTA static void tryTxCurrentPacket(void); #else // Transmit the current outgoing frame. void txCurrentPacket(void); #define tryTxCurrentPacket txCurrentPacket #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT static void txFailedCallback(bool isAck, uint32_t status); static bool validatePacketDetails(RAIL_RxPacketHandle_t packetHandle, RAIL_RxPacketDetails_t *pPacketDetails, RAIL_RxPacketInfo_t *pPacketInfo, uint16_t *packetLength); static bool validatePacketTimestamp(RAIL_RxPacketDetails_t *pPacketDetails, uint16_t packetLength); static void updateRxFrameTimestamp(bool aIsAckFrame, RAIL_Time_t aTimestamp); static otError skipRxPacketLengthBytes(RAIL_RxPacketInfo_t *pPacketInfo); //------------------------------------------------------------------------------ // Helper Functions #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static bool phyStackEventIsEnabled(void) { bool result = false; #if (defined(SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT) && SL_RAIL_UTIL_ANT_DIV_RX_RUNTIME_PHY_SELECT) result = true; #endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT if (sRadioCoexEnabled) { result |= sl_rail_util_coex_is_enabled(); #ifdef SL_RAIL_UTIL_COEX_RUNTIME_PHY_SELECT result |= SL_RAIL_UTIL_COEX_RUNTIME_PHY_SELECT; #endif } #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT return result; } static RAIL_Events_t currentEventConfig = RAIL_EVENTS_NONE; static void updateEvents(RAIL_Events_t mask, RAIL_Events_t values) { RAIL_Status_t status; RAIL_Events_t newEventConfig = (currentEventConfig & ~mask) | (values & mask); if (newEventConfig != currentEventConfig) { currentEventConfig = newEventConfig; status = RAIL_ConfigEvents(gRailHandle, mask, values); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static sl_rail_util_ieee802154_stack_event_t handlePhyStackEvent(sl_rail_util_ieee802154_stack_event_t stackEvent, uint32_t supplement) { return (phyStackEventIsEnabled() #ifdef SL_CATALOG_RAIL_MULTIPLEXER_PRESENT ? sl_rail_mux_ieee802154_on_event(gRailHandle, stackEvent, supplement) #else ? sl_rail_util_ieee802154_on_event(stackEvent, supplement) #endif : 0); } #else static void updateEvents(RAIL_Events_t mask, RAIL_Events_t values) { RAIL_Status_t status; status = RAIL_ConfigEvents(gRailHandle, mask, values); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } #define handlePhyStackEvent(event, supplement) 0 #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT // Set or clear the passed flag. SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static inline void setInternalFlag(uint32_t flag, bool val) { CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); miscRadioState = (val ? (miscRadioState | flag) : (miscRadioState & ~flag)); CORE_EXIT_ATOMIC(); } // Returns true if the passed flag is set, false otherwise. SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static inline bool getInternalFlag(uint32_t flag) { bool isFlagSet; CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); isFlagSet = (miscRadioState & flag) ? true : false; CORE_EXIT_ATOMIC(); return isFlagSet; } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static inline bool txWaitingForAck(void) { return (getInternalFlag(FLAG_ONGOING_TX_DATA) && ((sCurrentTxPacket->frame.mPsdu[0] & IEEE802154_FRAME_FLAG_ACK_REQUIRED) != 0)); } static inline bool isRadioTransmittingOrScanning(void) { return ((sEnergyScanStatus != ENERGY_SCAN_STATUS_IDLE) || getInternalFlag(FLAG_ONGOING_TX_DATA)); } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static bool txIsDataRequest(void) { uint16_t fcf = sCurrentTxPacket->frame.mPsdu[IEEE802154_FCF_OFFSET] | (sCurrentTxPacket->frame.mPsdu[IEEE802154_FCF_OFFSET + 1] << 8); return (getInternalFlag(FLAG_ONGOING_TX_DATA) && (fcf & IEEE802154_FRAME_TYPE_MASK) == IEEE802154_FRAME_TYPE_COMMAND); } #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT static inline bool isReceivingFrame(void) { return (RAIL_GetRadioState(gRailHandle) & RAIL_RF_STATE_RX_ACTIVE) == RAIL_RF_STATE_RX_ACTIVE; } #endif SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void radioSetIdle(void) { if (RAIL_GetRadioState(gRailHandle) != RAIL_RF_STATE_IDLE) { RAIL_Idle(gRailHandle, RAIL_IDLE, true); (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_IDLED, 0U); (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_IDLED, 0U); } RAIL_YieldRadio(gRailHandle); } static otError radioSetRx(uint8_t aChannel) { otError error = OT_ERROR_NONE; RAIL_Status_t status; RAIL_SchedulerInfo_t bgRxSchedulerInfo = { .priority = SL_802154_RADIO_PRIO_BACKGROUND_RX_VALUE, // sliptime/transaction time is not used for bg rx }; #if FAST_CHANNEL_SWITCHING_SUPPORT && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE if (sl_is_multi_channel_enabled()) { // Calling RAIL_StartRx with a channel not listed in the channel // switching config is a bug. OT_ASSERT(fastChannelIndex(aChannel) != INVALID_INTERFACE_INDEX); radioSetIdle(); status = RAIL_IEEE802154_ConfigRxChannelSwitching(gRailHandle, &sChannelSwitchingCfg); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); status = RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_CHANNEL_SWITCHING, RAIL_RX_OPTION_CHANNEL_SWITCHING); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } else { status = RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_CHANNEL_SWITCHING, RAIL_RX_OPTIONS_NONE); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } #endif status = RAIL_StartRx(gRailHandle, aChannel, &bgRxSchedulerInfo); otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED); (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_LISTEN, 0U); otLogInfoPlat("State=OT_RADIO_STATE_RECEIVE"); exit: return error; } #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) static otError radioScheduleRx(uint8_t aChannel, uint32_t aStart, uint32_t aDuration) { otError error = OT_ERROR_NONE; RAIL_Status_t status; #if FAST_CHANNEL_SWITCHING_SUPPORT && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE // UID 1327639: Schedule Rx and Concurrent listening when used together // will cause undefined behavior. Therefore it was decided to assert upon detecting // this condition assert(0); #endif // FAST_CHANNEL_SWITCHING_SUPPORT && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE RAIL_SchedulerInfo_t bgRxSchedulerInfo = { .priority = SL_802154_RADIO_PRIO_BACKGROUND_RX_VALUE, // sliptime/transaction time is not used for bg rx }; // Configure scheduled receive as requested RAIL_ScheduleRxConfig_t rxCfg = {.start = aStart, .startMode = RAIL_TIME_ABSOLUTE, .end = aDuration, .endMode = RAIL_TIME_DELAY, .rxTransitionEndSchedule = 1, // This lets us idle after a scheduled-rx .hardWindowEnd = 0}; // This lets us receive a packet near a window-end-event status = RAIL_ScheduleRx(gRailHandle, aChannel, &rxCfg, &bgRxSchedulerInfo); otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED); (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_LISTEN, 0U); exit: return error; } #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) //------------------------------------------------------------------------------ // Radio Initialization static RAIL_Handle_t efr32RailInit(efr32CommonConfig *aCommonConfig) { RAIL_Status_t status; RAIL_Handle_t handle; #if !OPENTHREAD_RADIO OT_ASSERT(DEVICE_CAPABILITY_MCU_EN); #endif handle = RAIL_Init(&aCommonConfig->mRailConfig, NULL); OT_ASSERT(handle != NULL); #if defined(SL_CATALOG_POWER_MANAGER_PRESENT) status = RAIL_InitPowerManager(); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); #endif // SL_CATALOG_POWER_MANAGER_PRESENT status = RAIL_ConfigCal(handle, RAIL_CAL_ALL); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); status = RAIL_SetPtiProtocol(handle, RAIL_PTI_PROTOCOL_THREAD); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); status = RAIL_IEEE802154_Init(handle, &sRailIeee802154Config); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) // Enhanced Frame Pending status = RAIL_IEEE802154_EnableEarlyFramePending(handle, true); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); status = RAIL_IEEE802154_EnableDataFramePending(handle, true); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); // Copies of MAC keys for encrypting at the radio layer memset(sMacKeys, 0, sizeof(sMacKeys)); #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) uint16_t actualLength = 0; actualLength = RAIL_SetTxFifo(handle, aCommonConfig->mRailTxFifo.fifo, 0, sizeof(aCommonConfig->mRailTxFifo.fifo)); OT_ASSERT(actualLength == sizeof(aCommonConfig->mRailTxFifo.fifo)); // Enable RAIL multi-timer RAIL_ConfigMultiTimer(true); return handle; } static void efr32RailConfigLoad(efr32BandConfig *aBandConfig, int8_t aTxPower) { RAIL_Status_t status; RAIL_TxPowerConfig_t *txPowerConfig = NULL; if (aBandConfig->mChannelConfig != NULL) { status = RAIL_IEEE802154_SetPtiRadioConfig(gRailHandle, RAIL_IEEE802154_PTI_RADIO_CONFIG_915MHZ_R23_NA_EXT); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); uint16_t firstChannel = UNINITIALIZED_CHANNEL; firstChannel = RAIL_ConfigChannels(gRailHandle, aBandConfig->mChannelConfig, NULL); OT_ASSERT(firstChannel == aBandConfig->mChannelMin); status = RAIL_IEEE802154_ConfigGOptions(gRailHandle, RAIL_IEEE802154_G_OPTION_GB868, RAIL_IEEE802154_G_OPTION_GB868); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); txPowerConfig = sl_rail_util_pa_get_tx_power_config_subghz(); } else { #if defined(SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT) status = sl_rail_util_ieee802154_config_radio(gRailHandle); #else status = RAIL_IEEE802154_Config2p4GHzRadio(gRailHandle); #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT OT_ASSERT(status == RAIL_STATUS_NO_ERROR); txPowerConfig = sl_rail_util_pa_get_tx_power_config_2p4ghz(); } #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) // 802.15.4E support (only on platforms that support it, so error checking is disabled) // Note: This has to be called after RAIL_IEEE802154_Config2p4GHzRadio due to a bug where this call // can overwrite options set below. RAIL_IEEE802154_ConfigEOptions(gRailHandle, (RAIL_IEEE802154_E_OPTION_GB868 | RAIL_IEEE802154_E_OPTION_ENH_ACK), (RAIL_IEEE802154_E_OPTION_GB868 | RAIL_IEEE802154_E_OPTION_ENH_ACK)); #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) if (aTxPower != SL_INVALID_TX_POWER) { sli_update_tx_power_after_config_update(txPowerConfig, aTxPower); } } static efr32BandConfig *efr32RadioGetBandConfig(uint8_t aChannel) { efr32BandConfig *config = NULL; if ((sBandConfig.mChannelMin <= aChannel) && (aChannel <= sBandConfig.mChannelMax)) { config = &sBandConfig; } return config; } static void efr32ConfigInit(void (*aEventCallback)(RAIL_Handle_t railHandle, RAIL_Events_t events)) { sCommonConfig.mRailConfig.eventsCallback = aEventCallback; sCommonConfig.mRailConfig.protocol = NULL; // only used by Bluetooth stack #if RADIO_CONFIG_DMP_SUPPORT sCommonConfig.mRailConfig.scheduler = &(sCommonConfig.mRailSchedState); #else sCommonConfig.mRailConfig.scheduler = NULL; // only needed for DMP #endif #if RADIO_CONFIG_2P4GHZ_OQPSK_SUPPORT sBandConfig.mChannelConfig = NULL; #else sBandConfig.mChannelConfig = channelConfigs[0]; #endif sBandConfig.mChannelMin = SL_CHANNEL_MIN; sBandConfig.mChannelMax = SL_CHANNEL_MAX; #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT memset(&railDebugCounters, 0x00, sizeof(efr32RadioCounters)); #endif sli_init_power_manager(); gRailHandle = efr32RailInit(&sCommonConfig); OT_ASSERT(gRailHandle != NULL); updateEvents(RAIL_EVENTS_ALL, (0 | RAIL_EVENT_RX_ACK_TIMEOUT | RAIL_EVENT_RX_PACKET_RECEIVED #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 | RAIL_EVENT_SCHEDULED_TX_STARTED | RAIL_EVENT_TX_SCHEDULED_TX_MISSED | RAIL_EVENT_SCHEDULED_RX_STARTED | RAIL_EVENT_RX_SCHEDULED_RX_END | RAIL_EVENT_RX_SCHEDULED_RX_MISSED #endif | RAIL_EVENTS_TXACK_COMPLETION | RAIL_EVENTS_TX_COMPLETION | RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT || RADIO_CONFIG_DMP_SUPPORT | RAIL_EVENT_CONFIG_SCHEDULED | RAIL_EVENT_CONFIG_UNSCHEDULED | RAIL_EVENT_SCHEDULER_STATUS #endif | RAIL_EVENT_CAL_NEEDED)); efr32RailConfigLoad(&(sBandConfig), OPENTHREAD_CONFIG_DEFAULT_TRANSMIT_POWER); } void efr32RadioInit(void) { if (getInternalFlag(FLAG_RADIO_INIT_DONE)) { return; } RAIL_Status_t status; sl_status_t rxMemPoolStatus; bool queueStatus; // check if RAIL_TX_FIFO_SIZE is power of two.. OT_ASSERT((RAIL_TX_FIFO_SIZE & (RAIL_TX_FIFO_SIZE - 1)) == 0); // check the limits of the RAIL_TX_FIFO_SIZE. OT_ASSERT((RAIL_TX_FIFO_SIZE >= 64) || (RAIL_TX_FIFO_SIZE <= 4096)); efr32ConfigInit(RAILCb_Generic); setInternalFlag(FLAG_RADIO_INIT_DONE, true); status = RAIL_ConfigSleep(gRailHandle, RAIL_SLEEP_CONFIG_TIMERSYNC_ENABLED); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); sReceive.frame.mLength = 0; sReceive.frame.mPsdu = NULL; sReceiveAck.frame.mLength = 0; sReceiveAck.frame.mPsdu = sReceiveAckPsdu; #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE // Initialize the queue for received packets. queueStatus = queueInit(&sPendingCommandQueue, RADIO_REQUEST_BUFFER_COUNT); OT_ASSERT(queueStatus); // Specify a callback to be called upon queue overflow. queueStatus = queueOverflow(&sPendingCommandQueue, &pendingCommandQueueOverflowCallback); OT_ASSERT(queueStatus); #endif for (uint8_t i = 0; i < RADIO_REQUEST_BUFFER_COUNT; i++) { // Initialize the tx buffer params. sTransmitBuffer[i].iid = INVALID_INTERFACE_INDEX; sTransmitBuffer[i].frame.mLength = 0; sTransmitBuffer[i].frame.mPsdu = sTransmitPsdu[i]; #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT sTransmitBuffer[i].frame.mInfo.mTxInfo.mIeInfo = &sTransmitIeInfo[i]; #endif } #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE otLinkMetricsInit(SL_OPENTHREAD_RECEIVE_SENSITIVITY); #endif sCurrentBandConfig = efr32RadioGetBandConfig(OPENTHREAD_CONFIG_DEFAULT_CHANNEL); OT_ASSERT(sCurrentBandConfig != NULL); sl_rail_util_pa_init(); sli_set_tx_power_in_rail(OPENTHREAD_CONFIG_DEFAULT_TRANSMIT_POWER); status = RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_TRACK_ABORTED_FRAMES, RAIL_RX_OPTION_TRACK_ABORTED_FRAMES); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); efr32PhyStackInit(); efr32RadioSetCcaMode(SL_OPENTHREAD_RADIO_CCA_MODE); sEnergyScanStatus = ENERGY_SCAN_STATUS_IDLE; #if FAST_CHANNEL_SWITCHING_SUPPORT && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE sChannelSwitchingCfg.bufferBytes = RAIL_IEEE802154_RX_CHANNEL_SWITCHING_BUF_BYTES; sChannelSwitchingCfg.buffer = sChannelSwitchingBuffer; for (uint8_t i = 0U; i < RAIL_IEEE802154_RX_CHANNEL_SWITCHING_NUM_CHANNELS; i++) { sChannelSwitchingCfg.channels[i] = UNINITIALIZED_CHANNEL; } #endif // Initialize the queue for received packets. queueStatus = queueInit(&sRxPacketQueue, SL_OPENTHREAD_RADIO_RX_BUFFER_COUNT); OT_ASSERT(queueStatus); // Specify a callback to be called upon queue overflow. queueStatus = queueOverflow(&sRxPacketQueue, &rxPacketQueueOverflowCallback); OT_ASSERT(queueStatus); // Initialize the memory pool for rx packets. rxMemPoolStatus = sl_memory_create_pool(sizeof(rxBuffer), SL_OPENTHREAD_RADIO_RX_BUFFER_COUNT, &sRxPacketMemPoolHandle); OT_ASSERT(rxMemPoolStatus == SL_STATUS_OK); otLogInfoPlat("Initialized"); } void efr32RadioDeinit(void) { RAIL_Status_t status; RAIL_Idle(gRailHandle, RAIL_IDLE_ABORT, true); status = RAIL_ConfigEvents(gRailHandle, RAIL_EVENTS_ALL, 0); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); sCurrentBandConfig = NULL; sl_memory_delete_pool(&sRxPacketMemPoolHandle); } //------------------------------------------------------------------------------ // Energy Scan support SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void energyScanComplete(int8_t scanResultDbm) { sEnergyScanResultDbm = scanResultDbm; sEnergyScanStatus = ENERGY_SCAN_STATUS_COMPLETED; } static uint16_t efr32GetSymbolDurationUs(void) { uint32_t symbolRate = RAIL_GetSymbolRate(gRailHandle); if (symbolRate) { symbolRate = (uint16_t)(1000000 / symbolRate); } return (symbolRate > 0) ? (uint16_t)symbolRate : 1; } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void efr32ScanTimerHandler(struct RAIL_MultiTimer *tmr, RAIL_Time_t expectedTimeOfEvent, void *cbArg) { OT_UNUSED_VARIABLE(tmr); OT_UNUSED_VARIABLE(expectedTimeOfEvent); OT_UNUSED_VARIABLE(cbArg); int16_t currentRSSIQuarterdBm = RAIL_GetRssi(gRailHandle, (sScanFrameCounter == sScanFrameCounterMax ? true : false)); if (currentRSSIQuarterdBm != RAIL_RSSI_INVALID) { int8_t currentRSSI = (int8_t)(currentRSSIQuarterdBm / QUARTER_DBM_IN_DBM); if (sEnergyReadsMax < currentRSSI) { sEnergyReadsMax = currentRSSI; } } if (sScanFrameCounter != 0) { efr32ScanDwell(SYMBOLS_PER_ENERGY_READING); sScanFrameCounter--; } else { // done energy scan, reporting rssi value energyScanComplete(sEnergyReadsMax); OT_ASSERT(RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_DISABLE_FRAME_DETECTION, RAIL_RX_OPTIONS_NONE) == RAIL_STATUS_NO_ERROR); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailPlatRadioEnergyScanDoneCbCount++; #endif } } static RAIL_Status_t efr32ScanDwell(uint16_t symbols) { RAIL_CancelMultiTimer(&rail_timer); return RAIL_SetMultiTimer(&rail_timer, symbols * efr32GetSymbolDurationUs(), RAIL_TIME_DELAY, efr32ScanTimerHandler, NULL); } static otError efr32StartEnergyScan(energyScanMode aMode, uint16_t aChannel, RAIL_Time_t aAveragingTimeUs) { RAIL_Status_t status = RAIL_STATUS_NO_ERROR; otError error = OT_ERROR_NONE; efr32BandConfig *config = NULL; otEXPECT_ACTION(sEnergyScanStatus == ENERGY_SCAN_STATUS_IDLE, error = OT_ERROR_BUSY); sEnergyScanStatus = ENERGY_SCAN_STATUS_IN_PROGRESS; sEnergyScanMode = aMode; RAIL_Idle(gRailHandle, RAIL_IDLE, true); config = efr32RadioGetBandConfig(aChannel); otEXPECT_ACTION(config != NULL, error = OT_ERROR_INVALID_ARGS); if (sCurrentBandConfig != config) { efr32RailConfigLoad(config, SL_INVALID_TX_POWER); sCurrentBandConfig = config; } if (sScanFrameCounter != 0) { return OT_ERROR_FAILED; } sEnergyReadsMax = ENERGY_READS_MAX; sScanFrameCounterMax = aAveragingTimeUs / (efr32GetSymbolDurationUs() * SYMBOLS_PER_ENERGY_READING); sScanFrameCounter = sScanFrameCounterMax; status = RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_DISABLE_FRAME_DETECTION, RAIL_RX_OPTION_DISABLE_FRAME_DETECTION); otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED); // set radio to Rx otEXPECT_ACTION(radioSetRx(aChannel) == OT_ERROR_NONE, error = OT_ERROR_FAILED); // start energy scan status = efr32ScanDwell(SYMBOLS_PER_ENERGY_READING); otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED); exit: if (status != RAIL_STATUS_NO_ERROR) { energyScanComplete(OT_RADIO_RSSI_INVALID); } return error; } //------------------------------------------------------------------------------ // Stack support uint64_t otPlatRadioGetNow(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return otPlatTimeGet(); } void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) { OT_UNUSED_VARIABLE(aInstance); #if (RADIO_CONFIG_ENABLE_CUSTOM_EUI_SUPPORT) && defined(_SILICON_LABS_32B_SERIES_2) // Invalid EUI uint8_t nullEui[] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; // Read the Custom EUI and compare it to nullEui if ((readUserData(aIeeeEui64, USERDATA_MFG_CUSTOM_EUI_64, OT_EXT_ADDRESS_SIZE, true) == -1) || (memcmp(aIeeeEui64, nullEui, OT_EXT_ADDRESS_SIZE) == 0)) #endif { uint64_t eui64; uint8_t *eui64Ptr = NULL; #if defined(_SILICON_LABS_32B_SERIES_2) eui64 = SYSTEM_GetUnique(); #else eui64 = sl_hal_system_get_unique(); #endif eui64Ptr = (uint8_t *)&eui64; for (uint8_t i = 0; i < OT_EXT_ADDRESS_SIZE; i++) { aIeeeEui64[i] = eui64Ptr[(OT_EXT_ADDRESS_SIZE - 1) - i]; } } } void otPlatRadioSetPanId(otInstance *aInstance, uint16_t aPanId) { RAIL_Status_t status; uint8_t iid = efr32GetIidFromInstance(aInstance); uint8_t panIndex = efr32GetPanIndexFromIid(iid); otEXPECT(sl_ot_rtos_task_can_access_pal()); OT_ASSERT(panIndex != INVALID_INTERFACE_INDEX); otLogInfoPlat("PANID=%X index=%u IID=%d", aPanId, panIndex, iid); utilsSoftSrcMatchSetPanId(iid, aPanId); status = RAIL_IEEE802154_SetPanId(gRailHandle, aPanId, panIndex); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE // We already have IID 0 enabled in filtermask to track BCAST Packets, so // track only unique PanIds. if (aPanId != RADIO_BCAST_PANID) { sRailFilterMask |= RADIO_GET_FILTER_MASK(iid); } #endif exit: return; } void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress) { RAIL_Status_t status; uint8_t iid = efr32GetIidFromInstance(aInstance); uint8_t panIndex = efr32GetPanIndexFromIid(iid); otEXPECT(sl_ot_rtos_task_can_access_pal()); OT_ASSERT(panIndex != INVALID_INTERFACE_INDEX); for (size_t i = 0; i < sizeof(*aAddress); i++) { sExtAddress[panIndex].m8[i] = aAddress->m8[sizeof(*aAddress) - 1 - i]; } otLogInfoPlat("ExtAddr=%X%X%X%X%X%X%X%X index=%u", aAddress->m8[7], aAddress->m8[6], aAddress->m8[5], aAddress->m8[4], aAddress->m8[3], aAddress->m8[2], aAddress->m8[1], aAddress->m8[0], panIndex); status = RAIL_IEEE802154_SetLongAddress(gRailHandle, (uint8_t *)aAddress->m8, panIndex); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); exit: return; } void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t aAddress) { RAIL_Status_t status; uint8_t iid = efr32GetIidFromInstance(aInstance); uint8_t panIndex = efr32GetPanIndexFromIid(iid); otEXPECT(sl_ot_rtos_task_can_access_pal()); OT_ASSERT(panIndex != INVALID_INTERFACE_INDEX); otLogInfoPlat("ShortAddr=%X index=%u", aAddress, panIndex); status = RAIL_IEEE802154_SetShortAddress(gRailHandle, aAddress, panIndex); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); exit: return; } otRadioState otPlatRadioGetState(otInstance *aInstance) { otRadioState radioState = OT_RADIO_STATE_INVALID; OT_UNUSED_VARIABLE(aInstance); switch (RAIL_GetRadioState(gRailHandle)) { case RAIL_RF_STATE_RX_ACTIVE: radioState = OT_RADIO_STATE_RECEIVE; break; case RAIL_RF_STATE_TX_ACTIVE: radioState = OT_RADIO_STATE_TRANSMIT; break; case RAIL_RF_STATE_IDLE: radioState = OT_RADIO_STATE_SLEEP; break; case RAIL_RF_STATE_INACTIVE: radioState = OT_RADIO_STATE_DISABLED; break; } return radioState; } bool otPlatRadioIsEnabled(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return (getInternalFlag(FLAG_RADIO_INIT_DONE)); } otError otPlatRadioEnable(otInstance *aInstance) { otError error = OT_ERROR_NONE; otEXPECT(!otPlatRadioIsEnabled(aInstance)); otLogInfoPlat("State=OT_RADIO_STATE_SLEEP"); exit: return error; } otError otPlatRadioDisable(otInstance *aInstance) { otError error = OT_ERROR_NONE; otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); otEXPECT_ACTION(otPlatRadioIsEnabled(aInstance), error = OT_ERROR_INVALID_STATE); otLogInfoPlat("State=OT_RADIO_STATE_DISABLED"); exit: return error; } otError otPlatRadioSleep(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); otError error = OT_ERROR_NONE; otEXPECT_ACTION(!getInternalFlag(FLAG_ONGOING_TX_DATA), error = OT_ERROR_BUSY); otLogInfoPlat("State=OT_RADIO_STATE_SLEEP"); setInternalFlag(FLAG_SCHEDULED_RX_PENDING, false); radioSetIdle(); exit: return error; } otError efr32RadioLoadChannelConfig(uint8_t aChannel, int8_t aTxPower) { otError error = OT_ERROR_NONE; efr32BandConfig *config; config = efr32RadioGetBandConfig(aChannel); otEXPECT_ACTION(config != NULL, error = OT_ERROR_INVALID_ARGS); if (sCurrentBandConfig != config) { RAIL_Idle(gRailHandle, RAIL_IDLE, true); efr32RailConfigLoad(config, aTxPower); sCurrentBandConfig = config; } else { sli_set_tx_power_in_rail(aTxPower); } exit: return error; } otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) { otError error = OT_ERROR_NONE; RAIL_Status_t status; int8_t txPower; uint8_t iid = efr32GetIidFromInstance(aInstance); otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); otEXPECT_ACTION(!getInternalFlag(FLAG_ONGOING_TX_DATA) && sEnergyScanStatus != ENERGY_SCAN_STATUS_IN_PROGRESS, error = OT_ERROR_INVALID_STATE); OT_UNUSED_VARIABLE(iid); #if FAST_CHANNEL_SWITCHING_SUPPORT && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE uint8_t index = efr32GetPanIndexFromIid(iid); OT_ASSERT(index < RAIL_IEEE802154_RX_CHANNEL_SWITCHING_NUM_CHANNELS); sChannelSwitchingCfg.channels[index] = aChannel; #endif txPower = sl_get_tx_power_for_current_channel(aInstance); error = efr32RadioLoadChannelConfig(aChannel, txPower); otEXPECT(error == OT_ERROR_NONE); status = radioSetRx(aChannel); otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED); setInternalFlag(FLAG_SCHEDULED_RX_PENDING, false); sReceive.frame.mChannel = aChannel; sReceiveAck.frame.mChannel = aChannel; exit: return error; } #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) otError otPlatRadioReceiveAt(otInstance *aInstance, uint8_t aChannel, uint32_t aStart, uint32_t aDuration) { otError error = OT_ERROR_NONE; RAIL_Status_t status; int8_t txPower = sl_get_tx_power_for_current_channel(aInstance); // We can only have one schedule request i.e. either Rx or Tx as they use the // same RAIL resources. otEXPECT_ACTION(!getInternalFlag(EVENT_SCHEDULED_TX_STARTED), error = OT_ERROR_FAILED); otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); OT_UNUSED_VARIABLE(aInstance); error = efr32RadioLoadChannelConfig(aChannel, txPower); otEXPECT(error == OT_ERROR_NONE); status = radioScheduleRx(aChannel, aStart, aDuration); otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED); setInternalFlag(FLAG_SCHEDULED_RX_PENDING, true); sReceive.frame.mChannel = aChannel; sReceiveAck.frame.mChannel = aChannel; exit: return error; } #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE inline static void pushPendingCommand(pendingCommandType aCmdType, uint8_t aIid, void *aCmdParams) { pendingCommandEntry *pendingCommand = (pendingCommandEntry *)sl_malloc(sizeof(pendingCommandEntry)); OT_ASSERT(pendingCommand != NULL); pendingCommand->cmdType = aCmdType; pendingCommand->iid = aIid; if (aCmdType == kPendingCommandTypeTransmit) { otRadioFrame *txFrame = (otRadioFrame *)aCmdParams; pendingCommand->request.txFrame = txFrame; } else if (aCmdType == kPendingCommandTypeEnergyScan) { energyScanParams *energyScanReq = (energyScanParams *)aCmdParams; pendingCommand->request.energyScan.scanChannel = energyScanReq->scanChannel; pendingCommand->request.energyScan.scanDuration = energyScanReq->scanDuration; } if (!queueAdd(&sPendingCommandQueue, (void *)pendingCommand)) { sl_free(pendingCommand); } } #endif otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) { otError error = OT_ERROR_NONE; int8_t txPower = sl_get_tx_power_for_current_channel(aInstance); uint8_t iid = efr32GetIidFromInstance(aInstance); // sTransmitBuffer's index 0 corresponds to host 1 i.e. iid 1 and reason is, // iid zero is reserved for broadcast frames in multipan case. uint8_t txBufIndex = iid ? (iid - 1) : 0; otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE // Accept GP packets even if radio is not in required state. if ((sl_gp_intf_get_state() != SL_GP_STATE_SEND_RESPONSE) && sl_gp_intf_should_buffer_pkt(aInstance, aFrame, false)) { sl_gp_intf_buffer_pkt(aInstance); } else #endif { OT_ASSERT(txBufIndex < RADIO_REQUEST_BUFFER_COUNT); OT_ASSERT(aFrame == &sTransmitBuffer[txBufIndex].frame); OT_ASSERT(aFrame->mPsdu == sTransmitPsdu[txBufIndex]); if (!aFrame->mInfo.mTxInfo.mIsARetx) sTransmitBuffer[txBufIndex].currentRadioTxPriority = SL_802154_RADIO_PRIO_TX_MIN; else if (sTransmitBuffer[txBufIndex].currentRadioTxPriority > SL_802154_RADIO_PRIO_TX_STEP) sTransmitBuffer[txBufIndex].currentRadioTxPriority -= SL_802154_RADIO_PRIO_TX_STEP; // TX priority is always bounded by the maximum priority configured if (sTransmitBuffer[txBufIndex].currentRadioTxPriority < SL_802154_RADIO_PRIO_TX_MAX) sTransmitBuffer[txBufIndex].currentRadioTxPriority = SL_802154_RADIO_PRIO_TX_MAX; #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE OT_ASSERT((iid != RADIO_BCAST_IID) && (iid < RADIO_INTERFACE_COUNT)); // Push pending transmit and exit if radio is busy. if (isRadioTransmittingOrScanning()) { pushPendingCommand(kPendingCommandTypeTransmit, iid, aFrame); ExitNow(error = OT_ERROR_NONE); } #endif error = efr32RadioLoadChannelConfig(aFrame->mChannel, txPower); otEXPECT(error == OT_ERROR_NONE); OT_ASSERT(!getInternalFlag(FLAG_ONGOING_TX_DATA)); setInternalFlag(RADIO_TX_EVENTS, false); sTransmitBuffer[txBufIndex].iid = iid; sCurrentTxPacket = &sTransmitBuffer[txBufIndex]; setInternalFlag(FLAG_CURRENT_TX_USE_CSMA, aFrame->mInfo.mTxInfo.mCsmaCaEnabled); #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE if (sCslPeriod > 0 && sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelay == 0) { // Only called for CSL children (sCslPeriod > 0) // Note: Our SSEDs "schedule" transmissions to their parent in order to know // exactly when in the future the data packets go out so they can calculate // the accurate CSL phase to send to their parent. sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelayBaseTime = RAIL_GetTime(); sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelay = 3000; // Chosen after internal certification testing } #endif updateIeInfoTxFrame(sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelayBaseTime + sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelay + SHR_DURATION_US); // Note - we need to call this outside of txCurrentPacket as for Series 2, // this results in calling the SE interface from a critical section which is not permitted. radioProcessTransmitSecurity(&sCurrentTxPacket->frame, sCurrentTxPacket->iid); #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); setInternalFlag(FLAG_ONGOING_TX_DATA, true); tryTxCurrentPacket(); CORE_EXIT_ATOMIC(); if (getInternalFlag(EVENT_TX_FAILED)) { otPlatRadioTxStarted(aInstance, aFrame); } } exit: return error; } #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) void updateIeInfoTxFrame(uint32_t shrTxTime) { OT_ASSERT(sCurrentTxPacket != NULL); #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE // Seek the time sync offset and update the rendezvous time if (sCurrentTxPacket->frame.mInfo.mTxInfo.mIeInfo->mTimeIeOffset != 0) { uint8_t *timeIe = sCurrentTxPacket->frame.mPsdu + sCurrentTxPacket->frame.mInfo.mTxInfo.mIeInfo->mTimeIeOffset; uint64_t time = otPlatTimeGet() + sCurrentTxPacket->frame.mInfo.mTxInfo.mIeInfo->mNetworkTimeOffset; *timeIe = sCurrentTxPacket->frame.mInfo.mTxInfo.mIeInfo->mTimeSyncSeq; *(++timeIe) = (uint8_t)(time & 0xff); for (uint8_t i = 1; i < sizeof(uint64_t); i++) { time = time >> 8; *(++timeIe) = (uint8_t)(time & 0xff); } } #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE // Update IE data in the 802.15.4 header with the newest CSL period / phase if (sCslPeriod > 0 && !sCurrentTxPacket->frame.mInfo.mTxInfo.mIsHeaderUpdated) { otMacFrameSetCslIe(&sCurrentTxPacket->frame, (uint16_t)sCslPeriod, getCslPhase(shrTxTime)); } #else OT_UNUSED_VARIABLE(shrTxTime); #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE } #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 void txCurrentPacket(void) { OT_ASSERT(getInternalFlag(FLAG_ONGOING_TX_DATA)); OT_ASSERT(sCurrentTxPacket != NULL); RAIL_TxOptions_t txOptions = RAIL_TX_OPTIONS_DEFAULT; RAIL_Status_t status = RAIL_STATUS_INVALID_STATE; uint8_t frameLength; bool ackRequested; #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailPlatTxTriggered++; #endif // signalling this event earlier, as this event can OT_ASSERT REQ (expecially for a // non-CSMA transmit) giving the Coex master a little more time to grant or deny. if (getInternalFlag(FLAG_CURRENT_TX_USE_CSMA)) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_PENDED_PHY, (uint32_t) true); } else { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_PENDED_PHY, (uint32_t) false); } frameLength = (uint8_t)sCurrentTxPacket->frame.mLength; if (PHY_HEADER_SIZE == 1) { RAIL_WriteTxFifo(gRailHandle, &frameLength, sizeof frameLength, true); } else { // 2 byte PHR for Sub-GHz uint8_t PHRByte1 = (0x08U /*FCS=2byte*/ | 0x10U /*Whiten=enabled*/); uint8_t PHRByte2 = (uint8_t)(__RBIT(frameLength) >> 24); RAIL_WriteTxFifo(gRailHandle, &PHRByte1, sizeof PHRByte1, true); RAIL_WriteTxFifo(gRailHandle, &PHRByte2, sizeof PHRByte2, false); } RAIL_WriteTxFifo(gRailHandle, sCurrentTxPacket->frame.mPsdu, frameLength - 2, false); RAIL_SchedulerInfo_t txSchedulerInfo = { .priority = sCurrentTxPacket->currentRadioTxPriority, .slipTime = RADIO_SCHEDULER_CHANNEL_SLIP_TIME, .transactionTime = 0, // will be calculated later if DMP is used }; ackRequested = (sCurrentTxPacket->frame.mPsdu[0] & IEEE802154_FRAME_FLAG_ACK_REQUIRED); if (ackRequested) { txOptions |= RAIL_TX_OPTION_WAIT_FOR_ACK; #if RADIO_CONFIG_DMP_SUPPORT // time we wait for ACK if (RAIL_GetSymbolRate(gRailHandle) > 0) { txSchedulerInfo.transactionTime += 12 * 1e6 / RAIL_GetSymbolRate(gRailHandle); } else { txSchedulerInfo.transactionTime += 12 * RADIO_TIMING_DEFAULT_SYMBOLTIME_US; } #endif } #ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT // Update Tx options to use currently-selected antenna. // If antenna diverisity on Tx is disabled, leave both options 0 // so Tx antenna tracks Rx antenna. if (sl_rail_util_ant_div_get_tx_antenna_mode() != SL_RAIL_UTIL_ANT_DIV_DISABLED) { txOptions |= ((sl_rail_util_ant_div_get_tx_antenna_selected() == SL_RAIL_UTIL_ANTENNA_SELECT_ANTENNA1) ? RAIL_TX_OPTION_ANTENNA0 : RAIL_TX_OPTION_ANTENNA1); } #endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT #if RADIO_CONFIG_DMP_SUPPORT // time needed for the frame itself // 4B preamble, 1B SFD, 1B PHR is not counted in frameLength if (RAIL_GetBitRate(gRailHandle) > 0) { txSchedulerInfo.transactionTime += (frameLength + 4 + 1 + 1) * 8 * 1e6 / RAIL_GetBitRate(gRailHandle); } else { // assume 250kbps txSchedulerInfo.transactionTime += (frameLength + 4 + 1 + 1) * RADIO_TIMING_DEFAULT_BYTETIME_US; } #endif // We can only have one schedule request i.e. either Rx or Tx as they use the same RAIL resources. // Reject the transmit request if there is scheduled Rx. otEXPECT_ACTION(!getInternalFlag(FLAG_SCHEDULED_RX_PENDING), status = RAIL_STATUS_INVALID_STATE); if (sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelay == 0) { if (getInternalFlag(FLAG_CURRENT_TX_USE_CSMA)) { #if RADIO_CONFIG_DMP_SUPPORT // time needed for CSMA/CA txSchedulerInfo.transactionTime += RADIO_TIMING_CSMA_OVERHEAD_US; #endif csmaConfig.csmaTries = sCurrentTxPacket->frame.mInfo.mTxInfo.mMaxCsmaBackoffs; csmaConfig.ccaThreshold = sCcaThresholdDbm; status = RAIL_StartCcaCsmaTx(gRailHandle, sCurrentTxPacket->frame.mChannel, txOptions, &csmaConfig, &txSchedulerInfo); } else { status = RAIL_StartTx(gRailHandle, sCurrentTxPacket->frame.mChannel, txOptions, &txSchedulerInfo); } if (status == RAIL_STATUS_NO_ERROR) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_STARTED, 0U); } } else { // For CSL transmitters (FTDs): // mTxDelayBaseTime = rx-timestamp (end of sync word) when we received CSL-sync with IEs // mTxDelay = Delay starting from mTxDelayBaseTime // // For CSL receivers (SSEDs): // mTxDelayBaseTime = timestamp when otPlatRadioTransmit is called // mTxDelay = Chosen value in the future where transmit is scheduled, so we know exactly // when to calculate the phase (we can't do this on-the-fly as the packet is going out // due to platform limitations. see radioScheduleRx) // // Note that both use single CCA config, overriding any CCA/CSMA configs from the stack // #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 RAIL_ScheduleTxConfig_t scheduleTxOptions = {.when = sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelayBaseTime + sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelay - SHR_DURATION_US, .mode = RAIL_TIME_ABSOLUTE, .txDuringRx = RAIL_SCHEDULED_TX_DURING_RX_POSTPONE_TX}; // Set ccaBackoff to some constant value, so we have predictable radio warmup time for schedule tx. cslCsmaConfig.ccaBackoff = CSL_CSMA_BACKOFF_TIME_IN_US; scheduleTxOptions.when -= cslCsmaConfig.ccaBackoff; // CSL transmissions don't use CSMA but MAC accounts for single CCA time. // cslCsmaConfig is set to RAIL_CSMA_CONFIG_SINGLE_CCA above. status = RAIL_StartScheduledCcaCsmaTx(gRailHandle, sCurrentTxPacket->frame.mChannel, txOptions, &scheduleTxOptions, &cslCsmaConfig, &txSchedulerInfo); if (status == RAIL_STATUS_NO_ERROR) { #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventsScheduledTxTriggeredCount++; #endif (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_STARTED, 0U); } #endif } exit: if (status == RAIL_STATUS_NO_ERROR) { #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailTxStarted++; #endif } else { #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailTxStartFailed++; #endif (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)ackRequested); txFailedCallback(false, EVENT_TX_FAILED); otSysEventSignalPending(); } } // This API gets called from init procedure so instance to IID mapping does not exist // at that point. Also this api will get called sequentially so assign new transmit // buffer if aInstance does not exist in sInstances. otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); uint8_t index = 0; otRadioFrame *aRadioFrame = NULL; otEXPECT(sl_ot_rtos_task_can_access_pal()); #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE for (index = 0; index < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; index++) { if (sInstances[index] == aInstance || sInstances[index] == NULL) { break; } } #endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE aRadioFrame = &sTransmitBuffer[index].frame; exit: return aRadioFrame; } int8_t otPlatRadioGetRssi(otInstance *aInstance) { otError error; uint32_t start; int8_t rssi = OT_RADIO_RSSI_INVALID; uint8_t aChannel = sReceive.frame.mChannel; uint8_t iid = efr32GetIidFromInstance(aInstance); otEXPECT(sl_ot_rtos_task_can_access_pal()); otEXPECT(!getInternalFlag(FLAG_ONGOING_TX_DATA)); OT_UNUSED_VARIABLE(iid); #if FAST_CHANNEL_SWITCHING_SUPPORT && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE uint8_t index = efr32GetPanIndexFromIid(iid); OT_ASSERT(index < RAIL_IEEE802154_RX_CHANNEL_SWITCHING_NUM_CHANNELS); if (sChannelSwitchingCfg.channels[index] != UNINITIALIZED_CHANNEL) { aChannel = sChannelSwitchingCfg.channels[index]; } #endif error = efr32StartEnergyScan(ENERGY_SCAN_MODE_SYNC, aChannel, SL_OPENTHREAD_RSSI_AVERAGING_TIME); otEXPECT(error == OT_ERROR_NONE); start = RAIL_GetTime(); // waiting for the event RAIL_EVENT_RSSI_AVERAGE_DONE while (sEnergyScanStatus == ENERGY_SCAN_STATUS_IN_PROGRESS && ((RAIL_GetTime() - start) < SL_OPENTHREAD_RSSI_AVERAGING_TIMEOUT)) ; if (sEnergyScanStatus == ENERGY_SCAN_STATUS_COMPLETED) { rssi = sEnergyScanResultDbm; } sEnergyScanStatus = ENERGY_SCAN_STATUS_IDLE; exit: return rssi; } otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return sRadioCapabilities; } bool otPlatRadioGetPromiscuous(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return sPromiscuous; } void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable) { OT_UNUSED_VARIABLE(aInstance); RAIL_Status_t status; otEXPECT(sl_ot_rtos_task_can_access_pal()); sPromiscuous = aEnable; status = RAIL_IEEE802154_SetPromiscuousMode(gRailHandle, aEnable); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); exit: return; } void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable) { OT_UNUSED_VARIABLE(aInstance); otEXPECT(sl_ot_rtos_task_can_access_pal()); // set Frame Pending bit for all outgoing ACKs if aEnable is false sIsSrcMatchEnabled = aEnable; exit: return; } otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower) { OT_UNUSED_VARIABLE(aInstance); otError error = OT_ERROR_NONE; otEXPECT_ACTION(aPower != NULL, error = OT_ERROR_INVALID_ARGS); // RAIL_GetTxPowerDbm() returns power in deci-dBm (0.1dBm) // Divide by 10 because aPower is supposed be in units dBm *aPower = RAIL_GetTxPowerDbm(gRailHandle) / 10; exit: return error; } otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower) { return sli_set_default_tx_power(aInstance, aPower); } // Required for RCP error recovery // See src/lib/spinel/radio_spinel.cpp::RestoreProperties() otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower) { return sli_set_channel_max_tx_power(aInstance, aChannel, aMaxPower); } otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t *aThreshold) { OT_UNUSED_VARIABLE(aInstance); otError error = OT_ERROR_NONE; otEXPECT_ACTION(aThreshold != NULL, error = OT_ERROR_INVALID_ARGS); *aThreshold = sCcaThresholdDbm; exit: return error; } otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t aThreshold) { OT_UNUSED_VARIABLE(aInstance); otError error = OT_ERROR_NONE; otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); sCcaThresholdDbm = aThreshold; exit: return error; } int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return SL_OPENTHREAD_RECEIVE_SENSITIVITY; } otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration) { otError error = OT_ERROR_NONE; uint8_t iid = efr32GetIidFromInstance(aInstance); otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE OT_ASSERT((iid != RADIO_BCAST_IID) && (iid < RADIO_INTERFACE_COUNT)); // Push pending energy-scan and exit if radio is busy. if (isRadioTransmittingOrScanning()) { energyScanParams params = {aScanChannel, aScanDuration}; pushPendingCommand(kPendingCommandTypeEnergyScan, iid, ¶ms); ExitNow(error = OT_ERROR_NONE); } #endif sEnergyScanActiveInterface = iid; error = efr32StartEnergyScan(ENERGY_SCAN_MODE_ASYNC, aScanChannel, (RAIL_Time_t)aScanDuration * US_IN_MS); exit: return error; } //------------------------------------------------------------------------------ // Radio Config: Thread 1.2 transmit security support #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); OT_UNUSED_VARIABLE(aKeyType); uint8_t iid = efr32GetIidFromInstance(aInstance); otEXPECT(sl_ot_rtos_task_can_access_pal()); OT_ASSERT(aPrevKey != NULL && aCurrKey != NULL && aNextKey != NULL); sMacKeys[iid].keyId = aKeyId; memcpy(&sMacKeys[iid].keys[MAC_KEY_PREV], aPrevKey, sizeof(otMacKeyMaterial)); memcpy(&sMacKeys[iid].keys[MAC_KEY_CURRENT], aCurrKey, sizeof(otMacKeyMaterial)); memcpy(&sMacKeys[iid].keys[MAC_KEY_NEXT], aNextKey, sizeof(otMacKeyMaterial)); #if (OPENTHREAD_CONFIG_CRYPTO_LIB == OPENTHREAD_CONFIG_CRYPTO_LIB_PSA) size_t aKeyLen = 0; otError error = OT_ERROR_NONE; error = otPlatCryptoExportKey(sMacKeys[iid].keys[MAC_KEY_PREV].mKeyMaterial.mKeyRef, sMacKeys[iid].keys[MAC_KEY_PREV].mKeyMaterial.mKey.m8, sizeof(sMacKeys[iid].keys[MAC_KEY_PREV]), &aKeyLen); OT_ASSERT(error == OT_ERROR_NONE); error = otPlatCryptoExportKey(sMacKeys[iid].keys[MAC_KEY_CURRENT].mKeyMaterial.mKeyRef, sMacKeys[iid].keys[MAC_KEY_CURRENT].mKeyMaterial.mKey.m8, sizeof(sMacKeys[iid].keys[MAC_KEY_CURRENT]), &aKeyLen); OT_ASSERT(error == OT_ERROR_NONE); error = otPlatCryptoExportKey(sMacKeys[iid].keys[MAC_KEY_NEXT].mKeyMaterial.mKeyRef, sMacKeys[iid].keys[MAC_KEY_NEXT].mKeyMaterial.mKey.m8, sizeof(sMacKeys[iid].keys[MAC_KEY_NEXT]), &aKeyLen); OT_ASSERT(error == OT_ERROR_NONE); #endif exit: return; } void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter) { uint8_t iid = efr32GetIidFromInstance(aInstance); otEXPECT(sl_ot_rtos_task_can_access_pal()); CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); sMacKeys[iid].macFrameCounter = aMacFrameCounter; CORE_EXIT_ATOMIC(); exit: return; } void otPlatRadioSetMacFrameCounterIfLarger(otInstance *aInstance, uint32_t aMacFrameCounter) { uint8_t iid = efr32GetIidFromInstance(aInstance); otEXPECT(sl_ot_rtos_task_can_access_pal()); CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); if (aMacFrameCounter > sMacKeys[iid].macFrameCounter) { sMacKeys[iid].macFrameCounter = aMacFrameCounter; } CORE_EXIT_ATOMIC(); exit: return; } //------------------------------------------------------------------------------ // Radio Config: Enhanced Acks, CSL #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, otShortAddress aShortAddr, const otExtAddress *aExtAddr) { otError error = OT_ERROR_NONE; OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aShortAddr); OT_UNUSED_VARIABLE(aExtAddr); otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); sCslPeriod = aCslPeriod; exit: return error; } void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime) { OT_UNUSED_VARIABLE(aInstance); otEXPECT(sl_ot_rtos_task_can_access_pal()); sCslSampleTime = aCslSampleTime; exit: return; } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE uint8_t otPlatRadioGetCslAccuracy(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return otPlatTimeGetXtalAccuracy(); } uint8_t otPlatRadioGetCslUncertainty(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return SL_OPENTHREAD_CSL_TX_UNCERTAINTY; } //------------------------------------------------------------------------------ // Radio Config: Link Metrics #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance, otLinkMetrics aLinkMetrics, const otShortAddress aShortAddress, const otExtAddress *aExtAddress) { otError error; OT_UNUSED_VARIABLE(aInstance); otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); error = otLinkMetricsConfigureEnhAckProbing(aShortAddress, aExtAddress, aLinkMetrics); exit: return error; } #endif #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE otError otPlatRadioSetCoexEnabled(otInstance *aInstance, bool aEnabled) { otError error; OT_UNUSED_VARIABLE(aInstance); otEXPECT_ACTION(sl_ot_rtos_task_can_access_pal(), error = OT_ERROR_REJECTED); sl_status_t status = sl_rail_util_coex_set_enable(aEnabled); if (aEnabled && !sl_rail_util_coex_is_enabled()) { otLogInfoPlat("Coexistence GPIO configurations not set"); return OT_ERROR_FAILED; } sRadioCoexEnabled = aEnabled; error = (status != SL_STATUS_OK) ? OT_ERROR_FAILED : OT_ERROR_NONE; exit: return error; } bool otPlatRadioIsCoexEnabled(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); return (sRadioCoexEnabled && sl_rail_util_coex_is_enabled()); } #endif // OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) //------------------------------------------------------------------------------ // Radio implementation: Enhanced ACKs, CSL // Return false if we should generate an immediate ACK // Return true otherwise SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static bool writeIeee802154EnhancedAck(RAIL_Handle_t aRailHandle, RAIL_RxPacketInfo_t *packetInfoForEnhAck, uint32_t rxTimestamp, uint8_t *initialPktReadBytes, uint8_t *receivedPsdu) { // RAIL will generate an Immediate ACK for us. // For an Enhanced ACK, we need to generate the whole packet ourselves. // An 802.15.4 packet from RAIL should look like: // 1/2 | 1/2 | 0/1 | 0/2 | 0/2/8 | 0/2 | 0/2/8 | 14 // PHR | MacFCF | Seq# | DstPan | DstAdr | SrcPan | SrcAdr | SecHdr // With RAIL_IEEE802154_EnableEarlyFramePending(), RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND // is triggered after receiving through the SrcAdr field of Version 0/1 packets, // and after receiving through the SecHdr for Version 2 packets. otRadioFrame receivedFrame, enhAckFrame; uint8_t enhAckPsdu[IEEE802154_MAX_LENGTH]; #define EARLY_FRAME_PENDING_EXPECTED_BYTES (2U + 2U + 1U + 2U + 8U + 2U + 8U + 14U) #define FINAL_PACKET_LENGTH_WITH_IE (EARLY_FRAME_PENDING_EXPECTED_BYTES + OT_ACK_IE_MAX_SIZE) otEXPECT((packetInfoForEnhAck != NULL) && (initialPktReadBytes != NULL) && (receivedPsdu != NULL)); *initialPktReadBytes = readInitialPacketData(packetInfoForEnhAck, EARLY_FRAME_PENDING_EXPECTED_BYTES, (PHY_HEADER_SIZE + 2), receivedPsdu, FINAL_PACKET_LENGTH_WITH_IE); uint8_t iid = INVALID_INTERFACE_INDEX; if (*initialPktReadBytes == 0U) { return true; // Nothing to read, which means generating an immediate ACK is also pointless } receivedFrame.mPsdu = receivedPsdu + PHY_HEADER_SIZE; // This should be set to the expected length of the packet is being received. // We consider this while calculating the phase value below. receivedFrame.mLength = packetInfoForEnhAck->firstPortionData[0]; enhAckFrame.mPsdu = enhAckPsdu + PHY_HEADER_SIZE; if (!otMacFrameIsVersion2015(&receivedFrame)) { return false; } otMacAddress aSrcAddress; uint8_t linkMetricsDataLen = 0; uint8_t *dataPtr = NULL; bool setFramePending = false; iid = getIidFromFilterMask(packetInfoForEnhAck->filterMask); otMacFrameGetSrcAddr(&receivedFrame, &aSrcAddress); if (sIsSrcMatchEnabled && (aSrcAddress.mType != OT_MAC_ADDRESS_TYPE_NONE)) { setFramePending = (aSrcAddress.mType == OT_MAC_ADDRESS_TYPE_EXTENDED ? (utilsSoftSrcMatchExtFindEntry(iid, &aSrcAddress.mAddress.mExtAddress) >= 0) : (utilsSoftSrcMatchShortFindEntry(iid, aSrcAddress.mAddress.mShortAddress) >= 0)); } // Generate our IE header. // Write IE data for enhanced ACK (link metrics + allocate bytes for CSL) #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE uint8_t linkMetricsData[OT_ENH_PROBING_IE_DATA_MAX_SIZE]; linkMetricsDataLen = otLinkMetricsEnhAckGenData(&aSrcAddress, sLastLqi, sLastRssi, linkMetricsData); if (linkMetricsDataLen > 0) { dataPtr = linkMetricsData; } #endif sAckIeDataLength = generateAckIeData(dataPtr, linkMetricsDataLen); otEXPECT(otMacFrameGenerateEnhAck(&receivedFrame, setFramePending, sAckIeData, sAckIeDataLength, &enhAckFrame) == OT_ERROR_NONE); #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE if (sCslPeriod > 0) { // Calculate time in the future where the SHR is done being sent out uint32_t ackShrDoneTime = // Currently partially received packet's SHR time (rxTimestamp - (packetInfoForEnhAck->packetBytes * OT_RADIO_SYMBOL_TIME * 2) // PHR of this packet + (PHY_HEADER_SIZE * OT_RADIO_SYMBOL_TIME * 2) // Received frame's expected time in the PHR + (receivedFrame.mLength * OT_RADIO_SYMBOL_TIME * 2) // rxToTx turnaround time + sRailIeee802154Config.timings.rxToTx // PHR time of the ACK + (PHY_HEADER_SIZE * OT_RADIO_SYMBOL_TIME * 2) // SHR time of the ACK + (SHR_SIZE * OT_RADIO_SYMBOL_TIME * 2)); // Update IE data in the 802.15.4 header with the newest CSL period / phase otMacFrameSetCslIe(&enhAckFrame, (uint16_t)sCslPeriod, getCslPhase(ackShrDoneTime)); } #else OT_UNUSED_VARIABLE(rxTimestamp); #endif if (otMacFrameIsSecurityEnabled(&enhAckFrame)) { otEXPECT(radioProcessTransmitSecurity(&enhAckFrame, iid) == OT_ERROR_NONE); } // Before we're done, store some important info in reserved bits in the // MAC header (cleared later) // Check whether frame pending is set. // Check whether enhanced ACK is secured. otEXPECT((skipRxPacketLengthBytes(packetInfoForEnhAck)) == OT_ERROR_NONE); uint8_t *macFcfPointer = ((packetInfoForEnhAck->firstPortionBytes == 0) ? (uint8_t *)packetInfoForEnhAck->lastPortionData : (uint8_t *)packetInfoForEnhAck->firstPortionData); if (otMacFrameIsSecurityEnabled(&enhAckFrame)) { *macFcfPointer |= IEEE802154_SECURED_OUTGOING_ENHANCED_ACK; } if (setFramePending) { *macFcfPointer |= IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK; } // Fill in PHR now that we know Enh-ACK's length if (PHY_HEADER_SIZE == 2U) // Not true till SubGhz implementation is in place { enhAckPsdu[0] = (0x08U /*FCS=2byte*/ | 0x10U /*Whiten=enabled*/); enhAckPsdu[1] = (uint8_t)(__RBIT(enhAckFrame.mLength) >> 24); } else { enhAckPsdu[0] = enhAckFrame.mLength; } RAIL_Status_t enhAckStatus = RAIL_IEEE802154_WriteEnhAck(aRailHandle, enhAckPsdu, enhAckFrame.mLength); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT otEXPECT_ACTION(enhAckStatus == RAIL_STATUS_NO_ERROR, railDebugCounters.mRailEventsEnhAckTxFailed++); #else otEXPECT(enhAckStatus == RAIL_STATUS_NO_ERROR); #endif exit: return true; } #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) //------------------------------------------------------------------------------ // RAIL callbacks SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void dataRequestCommandCallback(RAIL_Handle_t aRailHandle) { #define MAX_EXPECTED_BYTES (2U + 2U + 1U) // PHR + FCF + DSN uint8_t receivedPsdu[IEEE802154_MAX_LENGTH]; uint8_t pktOffset = PHY_HEADER_SIZE; uint8_t initialPktReadBytes; RAIL_RxPacketInfo_t packetInfo; uint32_t rxCallbackTimestamp = otPlatAlarmMicroGetNow(); // This callback occurs after the address fields of an incoming // ACK-requesting CMD or DATA frame have been received and we // can do a frame pending check. We must also figure out what // kind of ACK is being requested -- Immediate or Enhanced. #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) if (writeIeee802154EnhancedAck(aRailHandle, &packetInfo, rxCallbackTimestamp, &initialPktReadBytes, receivedPsdu)) { // We also return true above if there were failures in // generating an enhanced ACK. return; } #else OT_UNUSED_VARIABLE(rxCallbackTimestamp); initialPktReadBytes = readInitialPacketData(&packetInfo, MAX_EXPECTED_BYTES, pktOffset + 2, receivedPsdu, MAX_EXPECTED_BYTES); #endif // Calculate frame pending for immediate-ACK // If not, RAIL will send an immediate ACK, but we need to do FP lookup. RAIL_Status_t status = RAIL_STATUS_NO_ERROR; // Check if we read the FCF, if not, set macFcf to 0 uint16_t macFcf = (initialPktReadBytes <= pktOffset) ? 0U : receivedPsdu[pktOffset]; bool framePendingSet = false; if (sIsSrcMatchEnabled) { RAIL_IEEE802154_Address_t sourceAddress; status = RAIL_IEEE802154_GetAddress(aRailHandle, &sourceAddress); otEXPECT(status == RAIL_STATUS_NO_ERROR); uint8_t iid = getIidFromFilterMask(packetInfo.filterMask); if ((sourceAddress.length == RAIL_IEEE802154_LongAddress && utilsSoftSrcMatchExtFindEntry(iid, (otExtAddress *)sourceAddress.longAddress) >= 0) || (sourceAddress.length == RAIL_IEEE802154_ShortAddress && utilsSoftSrcMatchShortFindEntry(iid, sourceAddress.shortAddress) >= 0)) { status = RAIL_IEEE802154_SetFramePending(aRailHandle); otEXPECT(status == RAIL_STATUS_NO_ERROR); framePendingSet = true; } } else if ((macFcf & IEEE802154_FRAME_TYPE_MASK) != IEEE802154_FRAME_TYPE_DATA) { status = RAIL_IEEE802154_SetFramePending(aRailHandle); otEXPECT(status == RAIL_STATUS_NO_ERROR); framePendingSet = true; } if (framePendingSet) { // Store whether frame pending was set in the outgoing ACK in a reserved // bit of the MAC header (cleared later) otEXPECT((skipRxPacketLengthBytes(&packetInfo)) == OT_ERROR_NONE); uint8_t *macFcfPointer = ((packetInfo.firstPortionBytes == 0) ? (uint8_t *)packetInfo.lastPortionData : (uint8_t *)packetInfo.firstPortionData); *macFcfPointer |= IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK; } exit: if (status == RAIL_STATUS_INVALID_STATE) { otLogWarnPlat("Too late to modify outgoing FP"); } else { OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void packetReceivedCallback(void) { RAIL_RxPacketInfo_t packetInfo; RAIL_RxPacketDetails_t packetDetails; uint16_t length = 0; bool framePendingInAck = false; bool dropPacket = false; uint8_t iid = 0; sl_status_t status; bool isRxPacketQueued; rxBuffer *rxPacketBuf = NULL; RAIL_RxPacketHandle_t packetHandle = RAIL_GetRxPacketInfo(gRailHandle, RAIL_RX_PACKET_HANDLE_NEWEST, &packetInfo); otEXPECT_ACTION( (packetHandle != RAIL_RX_PACKET_HANDLE_INVALID && packetInfo.packetStatus == RAIL_RX_PACKET_READY_SUCCESS), dropPacket = true); otEXPECT_ACTION(validatePacketDetails(packetHandle, &packetDetails, &packetInfo, &length), dropPacket = true); otEXPECT_ACTION((skipRxPacketLengthBytes(&packetInfo)) == OT_ERROR_NONE, dropPacket = true); uint8_t macFcf = ((packetInfo.firstPortionBytes == 0) ? packetInfo.lastPortionData[0] : packetInfo.firstPortionData[0]); iid = getIidFromFilterMask(packetInfo.filterMask); if (packetDetails.isAck) { otEXPECT_ACTION( (length >= IEEE802154_MIN_LENGTH && (macFcf & IEEE802154_FRAME_TYPE_MASK) == IEEE802154_FRAME_TYPE_ACK), dropPacket = true); // read packet RAIL_CopyRxPacket(sReceiveAck.frame.mPsdu, &packetInfo); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventAcksReceived++; #endif sReceiveAck.frame.mLength = length; (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ENDED, (uint32_t)isReceivingFrame()); if (txWaitingForAck() && (sReceiveAck.frame.mPsdu[IEEE802154_DSN_OFFSET] == sCurrentTxPacket->frame.mPsdu[IEEE802154_DSN_OFFSET])) { otEXPECT_ACTION(validatePacketTimestamp(&packetDetails, length), dropPacket = true); sReceiveAck.frame.mInfo.mRxInfo.mRssi = packetDetails.rssi; sReceiveAck.frame.mInfo.mRxInfo.mLqi = packetDetails.lqi; sReceiveAck.iid = iid; updateRxFrameTimestamp(true, packetDetails.timeReceived.packetTime); // Processing the ACK frame in ISR context avoids the Tx state to be messed up, // in case the Rx FIFO queue gets wiped out in a DMP situation. setInternalFlag(EVENT_TX_SUCCESS, true); setInternalFlag(FLAG_WAITING_FOR_ACK | FLAG_ONGOING_TX_DATA | EVENT_SCHEDULED_TX_STARTED, false); framePendingInAck = ((macFcf & IEEE802154_FRAME_FLAG_FRAME_PENDING) != 0); (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ACK_RECEIVED, (uint32_t)framePendingInAck); if (txIsDataRequest() && framePendingInAck) { emPendingData = true; } } // Yield the radio upon receiving an ACK as long as it is not related to // a data request. if (!txIsDataRequest()) { RAIL_YieldRadio(gRailHandle); } } else { otEXPECT_ACTION(sPromiscuous || (length >= IEEE802154_MIN_DATA_LENGTH), dropPacket = true); otEXPECT_ACTION(validatePacketTimestamp(&packetDetails, length), dropPacket = true); // Drop the packet if queue is full. otEXPECT_ACTION(!queueIsFull(&sRxPacketQueue), dropPacket = true); // Allocate a block from memory pool for the received packet. status = sl_memory_pool_alloc(&sRxPacketMemPoolHandle, (void **)&rxPacketBuf); // Drop the packet if no more memory block present in the pool to store it. otEXPECT_ACTION(status == SL_STATUS_OK && rxPacketBuf != NULL, dropPacket = true); // read packet RAIL_CopyRxPacket(rxPacketBuf->psdu, &packetInfo); rxPacketBuf->packetInfo.length = (uint8_t)length; rxPacketBuf->packetInfo.channel = (uint8_t)packetDetails.channel; rxPacketBuf->packetInfo.rssi = packetDetails.rssi; rxPacketBuf->packetInfo.lqi = packetDetails.lqi; rxPacketBuf->packetInfo.timestamp = packetDetails.timeReceived.packetTime; rxPacketBuf->packetInfo.iid = iid; // Queue the rx packet or drop it if queueing fails and free the memory block. isRxPacketQueued = queueAdd(&sRxPacketQueue, (void *)rxPacketBuf); otEXPECT_ACTION(isRxPacketQueued, dropPacket = true); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailPlatRadioReceiveProcessedCount++; #endif if (macFcf & IEEE802154_FRAME_FLAG_ACK_REQUIRED) { (void)handlePhyStackEvent((RAIL_IsRxAutoAckPaused(gRailHandle) ? SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_BLOCKED : SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACKING), (uint32_t)isReceivingFrame()); setInternalFlag(FLAG_ONGOING_TX_ACK, true); } else { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ENDED, (uint32_t)isReceivingFrame()); // We received a frame that does not require an ACK as result of a data // poll: we yield the radio here. if (emPendingData) { RAIL_YieldRadio(gRailHandle); emPendingData = false; } } } exit: if (dropPacket) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_CORRUPTED, (uint32_t)isReceivingFrame()); IgnoreError(sl_memory_pool_free(&sRxPacketMemPoolHandle, rxPacketBuf)); } } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void packetSentCallback(bool isAck) { if (isAck) { // We successfully sent out an ACK. setInternalFlag(FLAG_ONGOING_TX_ACK, false); // We acked the packet we received after a poll: we can yield now. if (emPendingData) { RAIL_YieldRadio(gRailHandle); emPendingData = false; } } else if (getInternalFlag(FLAG_ONGOING_TX_DATA)) { setInternalFlag(FLAG_CURRENT_TX_USE_CSMA, false); if (txWaitingForAck()) { setInternalFlag(FLAG_WAITING_FOR_ACK, true); (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ACK_WAITING, 0U); } else { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ENDED, 0U); RAIL_YieldRadio(gRailHandle); setInternalFlag(EVENT_TX_SUCCESS, true); // Broadcast packet clear the ONGOING flag here. setInternalFlag(FLAG_ONGOING_TX_DATA | EVENT_SCHEDULED_TX_STARTED, false); } #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventPacketSent++; #endif } } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void txFailedCallback(bool isAck, uint32_t status) { if (isAck) { setInternalFlag(FLAG_ONGOING_TX_ACK, false); } else if (getInternalFlag(FLAG_ONGOING_TX_DATA)) { if (status == EVENT_TX_CCA_FAILED) { setInternalFlag(EVENT_TX_CCA_FAILED, true); setInternalFlag(FLAG_CURRENT_TX_USE_CSMA, false); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventChannelBusy++; #endif } else { setInternalFlag(EVENT_TX_FAILED, true); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventTxAbort++; #endif } setInternalFlag((FLAG_ONGOING_TX_DATA | FLAG_WAITING_FOR_ACK | EVENT_SCHEDULED_TX_STARTED), false); RAIL_YieldRadio(gRailHandle); } } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void ackTimeoutCallback(void) { OT_ASSERT(txWaitingForAck()); OT_ASSERT(getInternalFlag(FLAG_WAITING_FOR_ACK)); setInternalFlag(EVENT_TX_NO_ACK, true); setInternalFlag(FLAG_ONGOING_TX_DATA | EVENT_SCHEDULED_TX_STARTED, false); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventNoAck++; #endif #ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT // If antenna diversity is enabled toggle the selected antenna. sl_rail_util_ant_div_toggle_tx_antenna(); #endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT // TO DO: Check if we have an OT function that // provides the number of mac retry attempts left (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ACK_TIMEDOUT, 0); setInternalFlag(FLAG_WAITING_FOR_ACK, false); RAIL_YieldRadio(gRailHandle); emPendingData = false; } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void schedulerEventCallback(RAIL_Handle_t aRailHandle) { RAIL_SchedulerStatus_t status = RAIL_GetSchedulerStatus(aRailHandle); switch (status) { case RAIL_SCHEDULER_STATUS_SCHEDULE_FAIL: case RAIL_SCHEDULER_STATUS_CCA_CSMA_TX_FAIL: case RAIL_SCHEDULER_STATUS_CCA_LBT_TX_FAIL: case RAIL_SCHEDULER_STATUS_SINGLE_TX_FAIL: case RAIL_SCHEDULER_STATUS_SCHEDULED_TX_FAIL: case RAIL_SCHEDULER_STATUS_UNSUPPORTED: case RAIL_SCHEDULER_STATUS_SCHEDULED_RX_FAIL: case RAIL_SCHEDULER_STATUS_INTERNAL_ERROR: case RAIL_SCHEDULER_STATUS_TASK_FAIL: case RAIL_SCHEDULER_STATUS_EVENT_INTERRUPTED: if (getInternalFlag(FLAG_ONGOING_TX_ACK)) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_ABORTED, (uint32_t)isReceivingFrame()); txFailedCallback(true, EVENT_TX_FAILED); } // We were in the process of TXing a data frame, treat it as a CCA_FAIL. if (getInternalFlag(FLAG_ONGOING_TX_DATA)) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)txWaitingForAck()); txFailedCallback(false, EVENT_TX_CCA_FAILED); } // We are waiting for an ACK: we will never get the ACK we were waiting for. // We want to call ackTimeoutCallback() only if the PACKET_SENT event // already fired (which would clear the FLAG_ONGOING_TX_DATA flag). if (getInternalFlag(FLAG_WAITING_FOR_ACK)) { ackTimeoutCallback(); } #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventSchedulerStatusError++; #endif break; } } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void RAILCb_Generic(RAIL_Handle_t aRailHandle, RAIL_Events_t aEvents) { #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT if (aEvents & (RAIL_EVENT_RX_SYNC1_DETECT | RAIL_EVENT_RX_SYNC2_DETECT)) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_STARTED, (uint32_t)isReceivingFrame()); } #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT if (aEvents & RAIL_EVENT_SIGNAL_DETECTED) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_SIGNAL_DETECTED, 0U); } #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT if ((aEvents & RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND) #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT && !RAIL_IsRxAutoAckPaused(aRailHandle) #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT ) { dataRequestCommandCallback(aRailHandle); } #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT if (aEvents & RAIL_EVENT_RX_FILTER_PASSED) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACCEPTED, (uint32_t)isReceivingFrame()); } #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT if (aEvents & RAIL_EVENT_TX_PACKET_SENT) { packetSentCallback(false); } else if (aEvents & RAIL_EVENT_TX_CHANNEL_BUSY) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)txWaitingForAck()); txFailedCallback(false, EVENT_TX_CCA_FAILED); } else if (aEvents & RAIL_EVENT_TX_BLOCKED) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)txWaitingForAck()); txFailedCallback(false, EVENT_TX_FAILED); } else if (aEvents & (RAIL_EVENT_TX_UNDERFLOW | RAIL_EVENT_TX_ABORTED)) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ABORTED, (uint32_t)txWaitingForAck()); txFailedCallback(false, EVENT_TX_FAILED); } else { // Pre-completion aEvents are processed in their logical order: #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT if (aEvents & RAIL_EVENT_TX_START_CCA) { // We are starting RXWARM for a CCA check (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_CCA_SOON, 0U); } if (aEvents & RAIL_EVENT_TX_CCA_RETRY) { // We failed a CCA check and need to retry (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_CCA_BUSY, 0U); } if (aEvents & RAIL_EVENT_TX_CHANNEL_CLEAR) { // We're going on-air (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_STARTED, 0U); } #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT } #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 // Note: RAIL_EVENT_SCHEDULED_TX_STARTED and RAIL_EVENT_SCHEDULED_RX_STARTED have the // same numerical value because one cannot schedule both RX and TX simultaneously. // Have to take due care to check for these statuses depending on whether we're // scheduling a transmit or receive. Therefore, we're using an internal flag // 'FLAG_SCHEDULED_RX_PENDING' to track our scheduled-receive state. if (getInternalFlag(FLAG_SCHEDULED_RX_PENDING)) { if (aEvents & RAIL_EVENT_SCHEDULED_RX_STARTED) { setInternalFlag(EVENT_SCHEDULED_RX_STARTED, true); } // Once done with scheduled receive, clear internal scheduled-rx flag and idle. // If we miss a scheduled receive, let application schedule another. if (aEvents & RAIL_EVENT_RX_SCHEDULED_RX_END || aEvents & RAIL_EVENT_RX_SCHEDULED_RX_MISSED) { setInternalFlag(FLAG_SCHEDULED_RX_PENDING | EVENT_SCHEDULED_RX_STARTED, false); radioSetIdle(); } } else { if (aEvents & RAIL_EVENT_SCHEDULED_TX_STARTED) { setInternalFlag(EVENT_SCHEDULED_TX_STARTED, true); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventsScheduledTxStartedCount++; #endif } else if (aEvents & RAIL_EVENT_TX_SCHEDULED_TX_MISSED) { txFailedCallback(false, EVENT_TX_SCHEDULER_ERROR); } } #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 if (aEvents & RAIL_EVENT_RX_PACKET_RECEIVED) { packetReceivedCallback(); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventPacketReceived++; #endif } #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT if (aEvents & RAIL_EVENT_RX_FRAME_ERROR) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_CORRUPTED, (uint32_t)isReceivingFrame()); } // The following 3 events cause us to not receive a packet if (aEvents & (RAIL_EVENT_RX_PACKET_ABORTED | RAIL_EVENT_RX_ADDRESS_FILTERED | RAIL_EVENT_RX_FIFO_OVERFLOW)) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_FILTERED, (uint32_t)isReceivingFrame()); } #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT if (aEvents & RAIL_EVENT_TXACK_PACKET_SENT) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_SENT, (uint32_t)isReceivingFrame()); packetSentCallback(true); } if (aEvents & (RAIL_EVENT_TXACK_ABORTED | RAIL_EVENT_TXACK_UNDERFLOW)) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_ABORTED, (uint32_t)isReceivingFrame()); txFailedCallback(true, 0xFF); } if (aEvents & RAIL_EVENT_TXACK_BLOCKED) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_BLOCKED, (uint32_t)isReceivingFrame()); txFailedCallback(true, 0xFF); } // Deal with ACK timeout after possible RX completion in case RAIL // notifies us of the ACK and the timeout simultaneously -- we want // the ACK to win over the timeout. if (aEvents & RAIL_EVENT_RX_ACK_TIMEOUT) { if (getInternalFlag(FLAG_WAITING_FOR_ACK)) { ackTimeoutCallback(); } } if (aEvents & RAIL_EVENT_CONFIG_UNSCHEDULED) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_IDLED, 0U); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventConfigUnScheduled++; #endif } if (aEvents & RAIL_EVENT_CONFIG_SCHEDULED) { #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventConfigScheduled++; #endif } if (aEvents & RAIL_EVENT_SCHEDULER_STATUS) { schedulerEventCallback(aRailHandle); } if (aEvents & RAIL_EVENT_CAL_NEEDED) { RAIL_Status_t status; status = RAIL_Calibrate(aRailHandle, NULL, RAIL_CAL_ALL_PENDING); // TODO: Non-RTOS DMP case fails #if (!defined(SL_CATALOG_BLUETOOTH_PRESENT) || defined(SL_CATALOG_KERNEL_PRESENT)) // TEMPORARY - this asserts on Mux - OT_ASSERT(status == RAIL_STATUS_NO_ERROR); OT_UNUSED_VARIABLE(status); #else OT_UNUSED_VARIABLE(status); #endif #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventCalNeeded++; #endif } // scheduled and unscheduled config events happen very often, // especially in a DMP situation where there is an active BLE connection. // Waking up the OT RTOS task on every one of these occurrences causes // a lower priority Serial task to starve and makes it appear like a code lockup // There is no reason to wake the OT task for these events! if (!(aEvents & RAIL_EVENT_CONFIG_SCHEDULED) && !(aEvents & RAIL_EVENT_CONFIG_UNSCHEDULED)) { otSysEventSignalPending(); } } //------------------------------------------------------------------------------ // Main thread packet handling SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static bool validatePacketDetails(RAIL_RxPacketHandle_t packetHandle, RAIL_RxPacketDetails_t *pPacketDetails, RAIL_RxPacketInfo_t *pPacketInfo, uint16_t *packetLength) { bool pktValid = true; RAIL_Status_t rStatus; #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT rxDebugStep = 0; #endif rStatus = RAIL_GetRxPacketDetailsAlt(gRailHandle, packetHandle, pPacketDetails); otEXPECT_ACTION(rStatus == RAIL_STATUS_NO_ERROR, pktValid = false); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT rxDebugStep++; #endif otEXPECT_ACTION(isFilterMaskValid(pPacketInfo->filterMask), pktValid = false); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT rxDebugStep++; #endif // RAIL's packetBytes includes the (1 or 2 byte) PHY header but not the 2-byte CRC. // We want *packetLength to match the PHY header length so we add 2 for CRC // and subtract the PHY header size. *packetLength = pPacketInfo->packetBytes + 2U - PHY_HEADER_SIZE; if (PHY_HEADER_SIZE == 1) { otEXPECT_ACTION(*packetLength == pPacketInfo->firstPortionData[0], pktValid = false); } else { uint8_t lengthByte = ((pPacketInfo->firstPortionBytes > 1) ? pPacketInfo->firstPortionData[1] : pPacketInfo->lastPortionData[0]); otEXPECT_ACTION(*packetLength == (uint16_t)(__RBIT(lengthByte) >> 24), pktValid = false); } #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT rxDebugStep++; #endif // check the length validity of recv packet; RAIL should take care of this. otEXPECT_ACTION((*packetLength >= IEEE802154_MIN_LENGTH && *packetLength <= IEEE802154_MAX_LENGTH), pktValid = false); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT rxDebugStep++; #endif exit: #if (OPENTHREAD_CONFIG_LOG_LEVEL == OT_LOG_LEVEL_DEBG) if (!pktValid) { otLogDebgPlat("RX Pkt Invalid: rStatus=0x%lX, filterMask=0x%2X, pktLen=%i", rStatus, pPacketInfo->filterMask, *packetLength); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT otLogDebgPlat("RX debug step=%i", rxDebugStep); #endif } #endif return pktValid; } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static bool validatePacketTimestamp(RAIL_RxPacketDetails_t *pPacketDetails, uint16_t packetLength) { bool rxTimestampValid = true; // Get the timestamp when the SFD was received otEXPECT_ACTION(pPacketDetails->timeReceived.timePosition != RAIL_PACKET_TIME_INVALID, rxTimestampValid = false); // + PHY HEADER SIZE for PHY header // We would not need this if PHR is not included and we want the MHR pPacketDetails->timeReceived.totalPacketBytes = packetLength + PHY_HEADER_SIZE; otEXPECT_ACTION((RAIL_GetRxTimeSyncWordEndAlt(gRailHandle, pPacketDetails) == RAIL_STATUS_NO_ERROR), rxTimestampValid = false); exit: return rxTimestampValid; } otError otPlatMultipanGetActiveInstance(otInstance **aInstance) { otError error = OT_ERROR_NOT_IMPLEMENTED; OT_UNUSED_VARIABLE(aInstance); return error; } otError otPlatMultipanSetActiveInstance(otInstance *aInstance, bool aCompletePending) { otError error = OT_ERROR_NOT_IMPLEMENTED; OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aCompletePending); return error; } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static void updateRxFrameTimestamp(bool aIsAckFrame, RAIL_Time_t aTimestamp) { // Current time > sync-receive timestamp // Therefore lower 32 bits of current time should always be greater than lower 32 bits // of sync-rx timestamp unless there is a overflow. In such cases, we do not want to // take overflow into consideration for sync-rx timestamp. uint64_t railUsTimeNow = otPlatTimeGet(); uint32_t railUsTimerWraps = railUsTimeNow >> 32; // Address multiple overflows, such as what would happen if the current time overflows // from 0x00000001FFFFFFFF to 0x0000000200000000 (leave the higher 32 bits as 0) if ((railUsTimeNow & 0xFFFFFFFF) <= aTimestamp) { railUsTimerWraps--; } if (aIsAckFrame) { sReceiveAck.frame.mInfo.mRxInfo.mTimestamp = aTimestamp + ((uint64_t)railUsTimerWraps << 32); } else { sReceive.frame.mInfo.mRxInfo.mTimestamp = aTimestamp + ((uint64_t)railUsTimerWraps << 32); } } SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) static otError skipRxPacketLengthBytes(RAIL_RxPacketInfo_t *pPacketInfo) { otError error = OT_ERROR_NONE; otEXPECT_ACTION(pPacketInfo->firstPortionBytes > 0, error = OT_ERROR_FAILED); pPacketInfo->firstPortionData += PHY_HEADER_SIZE; pPacketInfo->packetBytes -= PHY_HEADER_SIZE; if (PHY_HEADER_SIZE == 1 || pPacketInfo->firstPortionBytes > 1) { pPacketInfo->firstPortionBytes -= PHY_HEADER_SIZE; } else { pPacketInfo->firstPortionBytes = 0U; // Increment lastPortionData to skip the second byte of the PHY header otEXPECT_ACTION(pPacketInfo->lastPortionData != NULL, error = OT_ERROR_FAILED); pPacketInfo->lastPortionData++; } exit: return error; } // This function dequeues the rx-queue, move the content to sReceive buffer // and return the memory block which was used to store the received packet. // So that memory block can be freed after submitting the receiveDone Callback. static rxBuffer *prepareNextRxPacketforCb(void) { rxBuffer *rxPacketBuf = (rxBuffer *)queueRemove(&sRxPacketQueue); OT_ASSERT(rxPacketBuf != NULL); uint8_t *psdu = rxPacketBuf->psdu; // Check the reserved bits in the MAC header, then clear them. // If we sent an enhanced ACK, check if it was secured. // Set this flag only when the packet is really acknowledged with a secured enhanced ACK. sReceive.frame.mInfo.mRxInfo.mAckedWithSecEnhAck = ((*psdu & IEEE802154_SECURED_OUTGOING_ENHANCED_ACK) != 0); *psdu &= ~IEEE802154_SECURED_OUTGOING_ENHANCED_ACK; // Check whether frame pendinng bit was set in the outgoing ACK. // Set this flag only when the packet is really acknowledged with frame pending set. sReceive.frame.mInfo.mRxInfo.mAckedWithFramePending = ((*psdu & IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK) != 0); *psdu &= ~IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK; sReceive.frame.mChannel = rxPacketBuf->packetInfo.channel; sReceive.frame.mLength = rxPacketBuf->packetInfo.length; sReceive.frame.mPsdu = rxPacketBuf->psdu; sReceive.frame.mInfo.mRxInfo.mRssi = rxPacketBuf->packetInfo.rssi; sLastRssi = rxPacketBuf->packetInfo.rssi; sReceive.frame.mInfo.mRxInfo.mLqi = rxPacketBuf->packetInfo.lqi; sLastLqi = rxPacketBuf->packetInfo.lqi; sReceive.iid = rxPacketBuf->packetInfo.iid; #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 // Use stored values for these sReceive.frame.mInfo.mRxInfo.mAckKeyId = sMacKeys[sReceive.iid].ackKeyId; sReceive.frame.mInfo.mRxInfo.mAckFrameCounter = sMacKeys[sReceive.iid].ackFrameCounter; #endif updateRxFrameTimestamp(false, rxPacketBuf->packetInfo.timestamp); return rxPacketBuf; } static void processNextRxPacket(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); sReceiveError = OT_ERROR_NONE; uint8_t interfaceId = INVALID_INTERFACE_INDEX; otInstance *instance = NULL; rxBuffer *rxPacketBuf = NULL; rxPacketBuf = prepareNextRxPacketforCb(); // sReceive buffer gets populated from prepareNextRxPacketforCb. interfaceId = sReceive.iid; // Submit broadcast packet to all initilized instances. do { #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE instance = otPlatMultipanIidToInstance(interfaceId); #else instance = aInstance; #endif interfaceId++; if (instance == NULL) { continue; } #if OPENTHREAD_CONFIG_DIAG_ENABLE if (otPlatDiagModeGet()) { otPlatDiagRadioReceiveDone(instance, &sReceive.frame, sReceiveError); } else #endif // OPENTHREAD_CONFIG_DIAG_ENABLE { bool isGpPacket = sl_gp_intf_is_gp_pkt(&sReceive.frame); #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE // For multipan RCP, continue normal processing. OT_UNUSED_VARIABLE(isGpPacket); #else // Else, we should not receive GP packets. otEXPECT(!isGpPacket); #endif #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE (void)sl_gp_intf_should_buffer_pkt(instance, &sReceive.frame, true); #endif otPlatRadioReceiveDone(instance, &sReceive.frame, sReceiveError); } } while (sReceive.iid == RADIO_BCAST_IID && interfaceId < RADIO_INTERFACE_COUNT); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailPlatRadioReceiveDoneCbCount++; #endif #if !OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE exit: #endif IgnoreError(sl_memory_pool_free(&sRxPacketMemPoolHandle, rxPacketBuf)); otSysEventSignalPending(); } static void processRxPackets(otInstance *aInstance) { while (!queueIsEmpty(&sRxPacketQueue)) { processNextRxPacket(aInstance); } } static void processTxComplete(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); otError txStatus; otRadioFrame *ackFrame = NULL; if (getInternalFlag(RADIO_TX_EVENTS)) { if (getInternalFlag(EVENT_TX_SUCCESS)) { txStatus = OT_ERROR_NONE; if (sCurrentTxPacket->frame.mPsdu[0] & IEEE802154_FRAME_FLAG_ACK_REQUIRED) { ackFrame = &sReceiveAck.frame; } setInternalFlag(EVENT_TX_SUCCESS, false); } else if (getInternalFlag(EVENT_TX_CCA_FAILED)) { txStatus = OT_ERROR_CHANNEL_ACCESS_FAILURE; setInternalFlag(EVENT_TX_CCA_FAILED, false); } else if (getInternalFlag(EVENT_TX_NO_ACK)) { txStatus = OT_ERROR_NO_ACK; setInternalFlag(EVENT_TX_NO_ACK, false); } else { txStatus = OT_ERROR_ABORT; setInternalFlag(EVENT_TX_FAILED, false); } if (txStatus != OT_ERROR_NONE) { otLogDebgPlat("Transmit failed ErrorCode=%d", txStatus); } #if OPENTHREAD_CONFIG_DIAG_ENABLE if (otPlatDiagModeGet()) { #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE otPlatDiagRadioTransmitDone(otPlatMultipanIidToInstance(sCurrentTxPacket->iid), &sCurrentTxPacket->frame, txStatus); #else otPlatDiagRadioTransmitDone(aInstance, &sCurrentTxPacket->frame, txStatus); #endif } else #endif { // Clear any internally-set txDelays so future transmits are not affected. sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelayBaseTime = 0; sCurrentTxPacket->frame.mInfo.mTxInfo.mTxDelay = 0; #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE otPlatRadioTxDone(otPlatMultipanIidToInstance(sCurrentTxPacket->iid), &sCurrentTxPacket->frame, ackFrame, txStatus); #else otPlatRadioTxDone(aInstance, &sCurrentTxPacket->frame, ackFrame, txStatus); #endif } #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailPlatRadioTxDoneCbCount++; #endif otSysEventSignalPending(); } } #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE static inline void processPendingCommands(void) { // Check and process pending transmit and energy scan commands if radio is not busy. if (!queueIsEmpty(&sPendingCommandQueue) && (!isRadioTransmittingOrScanning())) { // Dequeue the pending command pendingCommandEntry *pendingCommand = (pendingCommandEntry *)queueRemove(&sPendingCommandQueue); OT_ASSERT(pendingCommand != NULL); uint8_t iid = pendingCommand->iid; switch (pendingCommand->cmdType) { case kPendingCommandTypeTransmit: otPlatRadioTransmit(otPlatMultipanIidToInstance(iid), pendingCommand->request.txFrame); break; case kPendingCommandTypeEnergyScan: otPlatRadioEnergyScan(otPlatMultipanIidToInstance(iid), pendingCommand->request.energyScan.scanChannel, pendingCommand->request.energyScan.scanDuration); break; default: OT_ASSERT(false); break; } // Free the allocated memory. sl_free(pendingCommand); } } #endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE void efr32RadioProcess(otInstance *aInstance) { (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TICK, 0U); // We should process the received packet first. Adding it at the end of this function, // will delay the stack notification until the next call to efr32RadioProcess() processRxPackets(aInstance); processTxComplete(aInstance); if (sEnergyScanMode == ENERGY_SCAN_MODE_ASYNC && sEnergyScanStatus == ENERGY_SCAN_STATUS_COMPLETED) { sEnergyScanStatus = ENERGY_SCAN_STATUS_IDLE; OT_ASSERT(sEnergyScanActiveInterface != INVALID_INTERFACE_INDEX); #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE otPlatRadioEnergyScanDone(otPlatMultipanIidToInstance(sEnergyScanActiveInterface), sEnergyScanResultDbm); #else otPlatRadioEnergyScanDone(aInstance, sEnergyScanResultDbm); #endif sEnergyScanActiveInterface = INVALID_INTERFACE_INDEX; otSysEventSignalPending(); #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT railDebugCounters.mRailEventEnergyScanCompleted++; #endif } #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE processPendingCommands(); #endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE } //------------------------------------------------------------------------------ // Antenna Diversity, Wifi coexistence and Runtime PHY select support RAIL_Status_t efr32RadioSetCcaMode(uint8_t aMode) { return RAIL_IEEE802154_ConfigCcaMode(gRailHandle, aMode); } RAIL_IEEE802154_PtiRadioConfig_t efr32GetPtiRadioConfig(void) { return (RAIL_IEEE802154_GetPtiRadioConfig(gRailHandle)); } #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT otError setRadioState(otRadioState state) { otError error = OT_ERROR_NONE; // Defer idling the radio if we have an ongoing TX task otEXPECT_ACTION(!getInternalFlag(FLAG_ONGOING_TX_DATA), error = OT_ERROR_FAILED); switch (state) { case OT_RADIO_STATE_RECEIVE: otEXPECT_ACTION(radioSetRx(sReceive.frame.mChannel) == OT_ERROR_NONE, error = OT_ERROR_FAILED); break; case OT_RADIO_STATE_SLEEP: radioSetIdle(); break; default: error = OT_ERROR_FAILED; } exit: return error; } void sl_ot_update_active_radio_config(void) { CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); // Proceed with PHY selection only if 2.4 GHz band is used otEXPECT(sBandConfig.mChannelConfig == NULL); otRadioState currentState = otPlatRadioGetState(NULL); otEXPECT(setRadioState(OT_RADIO_STATE_SLEEP) == OT_ERROR_NONE); sl_rail_util_ieee802154_config_radio(gRailHandle); otEXPECT(setRadioState(currentState) == OT_ERROR_NONE); exit: CORE_EXIT_ATOMIC(); return; } #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT void efr32AntennaConfigInit(void) { RAIL_Status_t status; sl_rail_util_ant_div_init(); status = sl_rail_util_ant_div_update_antenna_config(); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } #endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT static void changeDynamicEvents(void) { /* clang-format off */ const RAIL_Events_t eventMask = RAIL_EVENTS_NONE | RAIL_EVENT_RX_SYNC1_DETECT | RAIL_EVENT_RX_SYNC2_DETECT | RAIL_EVENT_RX_FRAME_ERROR | RAIL_EVENT_RX_FIFO_OVERFLOW | RAIL_EVENT_RX_ADDRESS_FILTERED | RAIL_EVENT_RX_PACKET_ABORTED | RAIL_EVENT_RX_FILTER_PASSED | RAIL_EVENT_TX_CHANNEL_CLEAR | RAIL_EVENT_TX_CCA_RETRY | RAIL_EVENT_TX_START_CCA | RAIL_EVENT_SIGNAL_DETECTED; /* clang-format on */ RAIL_Events_t eventValues = RAIL_EVENTS_NONE; if (phyStackEventIsEnabled()) { eventValues |= eventMask; } updateEvents(eventMask, eventValues); } #endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT static void efr32PhyStackInit(void) { #ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT efr32AntennaConfigInit(); #endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT efr32CoexInit(); #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT changeDynamicEvents(); #endif } #ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT static void emRadioEnableAutoAck(void) { CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); if (getInternalFlag(FLAG_RADIO_INIT_DONE)) { if ((rhoActive >= RHO_INT_ACTIVE) // Internal always holds ACKs || ((rhoActive > RHO_INACTIVE) && ((sl_rail_util_coex_get_options() & SL_RAIL_UTIL_COEX_OPT_ACK_HOLDOFF) != SL_RAIL_UTIL_COEX_OPT_DISABLED))) { RAIL_PauseRxAutoAck(gRailHandle, true); } else { RAIL_PauseRxAutoAck(gRailHandle, false); } } CORE_EXIT_ATOMIC(); } static void emRadioEnablePta(bool enable) { halInternalInitPta(); // When PTA is enabled, we want to negate PTA_REQ as soon as an incoming // frame is aborted, e.g. due to filtering. To do that we must turn off // the TRACKABFRAME feature that's normally on to benefit sniffing on PTI. RAIL_Status_t status = RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_TRACK_ABORTED_FRAMES, (enable ? RAIL_RX_OPTIONS_NONE : RAIL_RX_OPTION_TRACK_ABORTED_FRAMES)); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } static void efr32CoexInit(void) { #if SL_OPENTHREAD_COEX_COUNTER_ENABLE && defined(SL_CATALOG_RAIL_MULTIPLEXER_PRESENT) sl_rail_mux_set_coex_counter_handler(gRailHandle, &sl_ot_coex_counter_on_event); #else sli_radio_coex_reset(); #endif // SL_OPENTHREAD_COEX_COUNTER_ENABLE && defined(SL_CATALOG_RAIL_MULTIPLEXER_PRESENT) #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE #if defined(SL_RAIL_UTIL_COEX_REQ_GPIO) || defined(SL_RAIL_UTIL_COEX_REQ_PORT) || defined(SL_RAIL_UTIL_COEX_GNT_GPIO) \ || defined(SL_RAIL_UTIL_COEX_GNT_PORT) || SL_RAIL_UTIL_COEX_RUNTIME_PHY_SELECT sl_rail_util_ot_enable_coex_state_event_filter(); #endif #endif // OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE sl_rail_util_coex_options_t coexOptions = sl_rail_util_coex_get_options(); #if SL_OPENTHREAD_COEX_MAC_HOLDOFF_ENABLE coexOptions |= SL_RAIL_UTIL_COEX_OPT_MAC_HOLDOFF; #else if (sl_rail_util_coex_get_radio_holdoff()) { coexOptions |= SL_RAIL_UTIL_COEX_OPT_MAC_HOLDOFF; } #endif // SL_OPENTHREAD_COEX_MAC_HOLDOFF_ENABLE sl_rail_util_coex_set_options(coexOptions); emRadioEnableAutoAck(); // Might suspend AutoACK if RHO already in effect emRadioEnablePta(sl_rail_util_coex_is_enabled()); } // Managing radio transmission static void onPtaGrantTx(sl_rail_util_coex_req_t ptaStatus) { // Only pay attention to first PTA Grant callback, ignore any further ones if (ptaGntEventReported) { return; } ptaGntEventReported = true; OT_ASSERT(ptaStatus == SL_RAIL_UTIL_COEX_REQCB_GRANTED); // PTA is telling us we've gotten GRANT and should send ASAP *without* CSMA setInternalFlag(FLAG_CURRENT_TX_USE_CSMA, false); txCurrentPacket(); } static void tryTxCurrentPacket(void) { OT_ASSERT(getInternalFlag(FLAG_ONGOING_TX_DATA)); ptaGntEventReported = false; sl_rail_util_ieee802154_stack_event_t ptaStatus = handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_PENDED_MAC, (uint32_t)&onPtaGrantTx); if (ptaStatus == SL_RAIL_UTIL_IEEE802154_STACK_STATUS_SUCCESS) { // Normal case where PTA allows us to start the (CSMA) transmit below txCurrentPacket(); } else if (ptaStatus == SL_RAIL_UTIL_IEEE802154_STACK_STATUS_CB_PENDING) { // onPtaGrantTx() callback will take over (and might already have) } else if (ptaStatus == SL_RAIL_UTIL_IEEE802154_STACK_STATUS_HOLDOFF) { txFailedCallback(false, EVENT_TX_FAILED); } } // Managing CCA Threshold static void setCcaThreshold(void) { if (sCcaThresholdDbm == CCA_THRESHOLD_UNINIT) { sCcaThresholdDbm = CCA_THRESHOLD_DEFAULT; } CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); int8_t thresholddBm = sCcaThresholdDbm; if (getInternalFlag(FLAG_RADIO_INIT_DONE)) { if (rhoActive > RHO_INACTIVE) { thresholddBm = RAIL_RSSI_INVALID_DBM; } RAIL_Status_t status = RAIL_SetCcaThreshold(gRailHandle, thresholddBm); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } CORE_EXIT_ATOMIC(); } static void emRadioHoldOffInternalIsr(uint8_t active) { if (active != rhoActive) { rhoActive = active; // Update rhoActive early if (getInternalFlag(FLAG_RADIO_INIT_DONE)) { setCcaThreshold(); emRadioEnableAutoAck(); } } } // External API used by Coex Component SL_WEAK void emRadioHoldOffIsr(bool active) { emRadioHoldOffInternalIsr((uint8_t)active | (rhoActive & ~RHO_EXT_ACTIVE)); } #if SL_OPENTHREAD_COEX_COUNTER_ENABLE #ifdef SL_CATALOG_RAIL_MULTIPLEXER_PRESENT static void sl_ot_coex_counter_on_event(sl_rail_util_coex_event_t event) #else void sl_rail_util_coex_counter_on_event(sl_rail_util_coex_event_t event) #endif { otEXPECT(event < SL_RAIL_UTIL_COEX_EVENT_COUNT); efr32RadioCoexCounters[event] += 1; exit: return; } void efr32RadioClearCoexCounters(void) { memset((void *)efr32RadioCoexCounters, 0, sizeof(efr32RadioCoexCounters)); } #endif // SL_OPENTHREAD_COEX_COUNTER_ENABLE #endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT void efr32ClearRadioCounters(void) { memset(&railDebugCounters, 0, sizeof(railDebugCounters)); } #endif // RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT