From 202d5df96d8eecfd1509224d14e77007b9b8988d Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Tue, 3 Mar 2026 15:32:05 -0600 Subject: [PATCH] [nexus] implement CSL transmitter and receiver (#12595) This commit implements CSL transmitter and receiver functionality in the Nexus simulation platform. It enables the necessary OpenThread configurations and provides the platform-level support for CSL. It also implements the otPlatRadioGetNow platform API to provide a high-resolution 64-bit microsecond time base, which is required for accurate CSL timing and synchronization. Changes: - Added GetNowMicro64() to Nexus::Core to expose the raw 64-bit microsecond timer. - Implemented otPlatRadioGetNow() in nexus_radio.cpp. - Enabled OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE and OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE in Nexus config. - Added otRadioContext to Nexus::Radio to track CSL parameters and manage security material for radio operations. - Implemented otPlatRadioEnableCsl, otPlatRadioResetCsl, otPlatRadioUpdateCslSampleTime, and otPlatRadioGetCslAccuracy. - Implemented otPlatRadioSetMacKey, otPlatRadioSetMacFrameCounter, and otPlatRadioSetAlternateShortAddress to keep radio context synchronized with OpenThread stack. - Updated otPlatRadioSetExtendedAddress and otPlatRadioEnableCsl to use AsCoreType for better readability and project conventions. - Moved Radio member initializations to the initializer list in its constructor. - Refactored radio SFD processing: moved otMacFrameProcessTxSfd and UpdateFcs from otPlatRadioTransmit (radio platform) to Core::ProcessRadio (simulation engine), ensuring CSL IEs and security headers are updated exactly when transmission starts. - Enhanced Nexus::Core::ProcessRadio to support generating Enhanced ACKs with CSL IEs for 802.15.4-2015 frames. - Updated Nexus and fuzz build systems to include necessary Nexus platform and utils headers/utilities. --- src/core/radio/radio.hpp | 5 +- tests/fuzz/CMakeLists.txt | 2 + tests/nexus/CMakeLists.txt | 2 + tests/nexus/openthread-core-nexus-config.h | 4 +- tests/nexus/platform/nexus_core.cpp | 40 +++++- tests/nexus/platform/nexus_core.hpp | 1 + tests/nexus/platform/nexus_radio.cpp | 136 +++++++++++++++++++-- tests/nexus/platform/nexus_radio.hpp | 3 + 8 files changed, 169 insertions(+), 24 deletions(-) diff --git a/src/core/radio/radio.hpp b/src/core/radio/radio.hpp index b68b6c38e..31cb1d283 100644 --- a/src/core/radio/radio.hpp +++ b/src/core/radio/radio.hpp @@ -1114,10 +1114,7 @@ inline Error Radio::ReceiveAt(uint8_t, uint32_t, uint32_t) { return kErrorNone; #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE inline void Radio::UpdateCslSampleTime(uint32_t) {} -inline Error Radio::EnableCsl(uint32_t, Mac::ShortAddress aShortAddr, const Mac::ExtAddress &) -{ - return kErrorNotImplemented; -} +inline Error Radio::EnableCsl(uint32_t, Mac::ShortAddress, const Mac::ExtAddress &) { return kErrorNotImplemented; } inline Error Radio::ResetCsl(void) { return kErrorNotImplemented; } #endif diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 66e0e57dc..645f6715f 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -31,6 +31,8 @@ set(COMMON_INCLUDES ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/core ${PROJECT_SOURCE_DIR}/tests/nexus + ${PROJECT_SOURCE_DIR}/tests/nexus/platform + ${PROJECT_SOURCE_DIR}/examples/platforms/utils ) set(COMMON_COMPILE_OPTIONS diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 6d6f9e538..18bb2a680 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -32,6 +32,7 @@ set(COMMON_INCLUDES ${PROJECT_SOURCE_DIR}/src/core ${PROJECT_SOURCE_DIR}/tests/nexus ${PROJECT_SOURCE_DIR}/tests/nexus/platform + ${PROJECT_SOURCE_DIR}/examples/platforms/utils ) set(COMMON_COMPILE_OPTIONS @@ -51,6 +52,7 @@ add_library(ot-nexus-platform platform/nexus_radio.cpp platform/nexus_settings.cpp platform/nexus_trel.cpp + ../../examples/platforms/utils/mac_frame.cpp ) target_include_directories(ot-nexus-platform diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index 345ee5d1c..2a911913a 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -89,8 +89,8 @@ #define OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL 1 #define OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME 0 #define OPENTHREAD_CONFIG_LOG_SUFFIX "" -#define OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE 0 -#define OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE 0 +#define OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE 1 +#define OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE 1 #define OPENTHREAD_CONFIG_MAC_FILTER_ENABLE 1 #define OPENTHREAD_CONFIG_MAC_FILTER_SIZE 80 #define OPENTHREAD_CONFIG_MESH_DIAG_ENABLE 1 diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index fd019a567..29aa37711 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -348,6 +348,7 @@ void Core::ProcessRadio(Node &aNode) uint16_t dstPanId; bool ackRequested; AckMode ackMode = kNoAck; + Node *ackNode = nullptr; VerifyOrExit(aNode.mRadio.mState == Radio::kStateTransmit); @@ -361,7 +362,11 @@ void Core::ProcessRadio(Node &aNode) dstPanId = Mac::kPanIdBroadcast; } - ackRequested = aNode.mRadio.mTxFrame.GetAckRequest(); + ackRequested = aNode.mRadio.mTxFrame.GetAckRequest(); + aNode.mRadio.mRadioContext.mCslPresent = aNode.mRadio.mTxFrame.mInfo.mTxInfo.mCslPresent; + + SuccessOrQuit(otMacFrameProcessTxSfd(&aNode.mRadio.mTxFrame, mNow, &aNode.mRadio.mRadioContext)); + static_cast(aNode.mRadio.mTxFrame).UpdateFcs(); mPcap.WriteFrame(aNode.mRadio.mTxFrame, mNow); @@ -393,6 +398,7 @@ void Core::ProcessRadio(Node &aNode) Mac::Address srcAddr; ackMode = kSendAckNoFramePending; + ackNode = &rxNode; if ((aNode.mRadio.mTxFrame.GetSrcAddr(srcAddr) == kErrorNone) && rxNode.mRadio.HasFramePendingFor(srcAddr)) @@ -416,13 +422,35 @@ void Core::ProcessRadio(Node &aNode) aNode.mRadio.mChannel = aNode.mRadio.mTxFrame.mChannel; aNode.mRadio.mState = Radio::kStateReceive; - if (ackMode != kNoAck) + if (ackNode != nullptr) { - Radio::Frame ackFrame; + Radio::Frame ackFrame; + const Mac::RxFrame &rxFrame = + static_cast(static_cast(aNode.mRadio.mTxFrame)); - ackFrame.GenerateImmAck( - static_cast(static_cast(aNode.mRadio.mTxFrame)), - (ackMode == kSendAckFramePending)); + if (rxFrame.IsVersion2015()) + { + uint8_t ackIeData[OT_ACK_IE_MAX_SIZE]; + uint8_t ackIeDataLength = 0; + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + ackNode->mRadio.mRadioContext.mCslPresent = + (ackNode->mRadio.mRadioContext.mCslPeriod > 0) && + otMacFrameSrcAddrMatchCslReceiverPeer(&aNode.mRadio.mTxFrame, &ackNode->mRadio.mRadioContext); + + if (ackNode->mRadio.mRadioContext.mCslPresent) + { + ackIeDataLength = otMacFrameGenerateCslIeTemplate(ackIeData); + } +#endif + SuccessOrExit( + ackFrame.GenerateEnhAck(rxFrame, (ackMode == kSendAckFramePending), ackIeData, ackIeDataLength)); + SuccessOrExit(otMacFrameProcessTxSfd(&ackFrame, mNow, &ackNode->mRadio.mRadioContext)); + } + else + { + ackFrame.GenerateImmAck(rxFrame, (ackMode == kSendAckFramePending)); + } ackFrame.UpdateFcs(); mPcap.WriteFrame(ackFrame, mNow); diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp index 706d04a0e..f848cd7a7 100644 --- a/tests/nexus/platform/nexus_core.hpp +++ b/tests/nexus/platform/nexus_core.hpp @@ -57,6 +57,7 @@ public: TimeMilli GetNow(void) { return TimeMilli(static_cast(mNow / 1000u)); } TimeMicro GetNowMicro(void) { return TimeMicro(static_cast(mNow)); } + uint64_t GetNowMicro64(void) const { return mNow; } void AdvanceTime(uint32_t aDuration); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/nexus/platform/nexus_radio.cpp b/tests/nexus/platform/nexus_radio.cpp index 5c9d413bc..950edeb2f 100644 --- a/tests/nexus/platform/nexus_radio.cpp +++ b/tests/nexus/platform/nexus_radio.cpp @@ -42,7 +42,7 @@ extern "C" { otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); - return OT_RADIO_CAPS_NONE; + return OT_RADIO_CAPS_TRANSMIT_SEC; } int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) @@ -65,12 +65,23 @@ void otPlatRadioSetPanId(otInstance *aInstance, otPanId aPanId) { AsNode(aInstan void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aExtAddress) { - AsNode(aInstance).mRadio.mExtAddress.Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder); + Radio &radio = AsNode(aInstance).mRadio; + + radio.mExtAddress.Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder); + AsCoreType(&radio.mRadioContext.mExtAddress).Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder); } void otPlatRadioSetShortAddress(otInstance *aInstance, otShortAddress aShortAddress) { - AsNode(aInstance).mRadio.mShortAddress = aShortAddress; + Radio &radio = AsNode(aInstance).mRadio; + + radio.mShortAddress = aShortAddress; + radio.mRadioContext.mShortAddress = aShortAddress; +} + +void otPlatRadioSetAlternateShortAddress(otInstance *aInstance, otShortAddress aShortAddress) +{ + AsNode(aInstance).mRadio.mRadioContext.mAlternateShortAddress = aShortAddress; } bool otPlatRadioGetPromiscuous(otInstance *aInstance) { return AsNode(aInstance).mRadio.mPromiscuous; } @@ -135,8 +146,6 @@ otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) VerifyOrExit(radio.mState == Radio::kStateReceive, error = kErrorInvalidState); OT_ASSERT(aFrame == &AsNode(aInstance).mRadio.mTxFrame); - static_cast(aFrame)->UpdateFcs(); - radio.mState = Radio::kStateTransmit; Core::Get().MarkPendingAction(); @@ -145,6 +154,13 @@ exit: return error; } +uint64_t otPlatRadioGetNow(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return Core::Get().GetNowMicro64(); +} + int8_t otPlatRadioGetRssi(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); @@ -223,6 +239,47 @@ void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance) void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance) { AsNode(aInstance).mRadio.mSrcMatchExtEntries.Clear(); } +void otPlatRadioSetMacKey(otInstance *aInstance, + uint8_t aKeyIdMode, + uint8_t aKeyId, + const otMacKeyMaterial *aPrevKey, + const otMacKeyMaterial *aCurrKey, + const otMacKeyMaterial *aNextKey, + otRadioKeyType aKeyType) +{ + Radio &radio = AsNode(aInstance).mRadio; + + OT_UNUSED_VARIABLE(aKeyIdMode); + + radio.mRadioContext.mKeyId = aKeyId; + radio.mRadioContext.mKeyType = aKeyType; + + if (!radio.mMacFrameCounterReset) + { + radio.mRadioContext.mPrevMacFrameCounter = radio.mRadioContext.mMacFrameCounter; + radio.mRadioContext.mMacFrameCounter = 0; + } + + radio.mMacFrameCounterReset = false; + + memcpy(&radio.mRadioContext.mPrevKey, aPrevKey, sizeof(otMacKeyMaterial)); + memcpy(&radio.mRadioContext.mCurrKey, aCurrKey, sizeof(otMacKeyMaterial)); + memcpy(&radio.mRadioContext.mNextKey, aNextKey, sizeof(otMacKeyMaterial)); +} + +void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + Radio &radio = AsNode(aInstance).mRadio; + + if (aMacFrameCounter == 0) + { + radio.mRadioContext.mPrevMacFrameCounter = radio.mRadioContext.mMacFrameCounter; + radio.mMacFrameCounterReset = true; + } + + radio.mRadioContext.mMacFrameCounter = aMacFrameCounter; +} + // Not supported otError otPlatRadioEnergyScan(otInstance *, uint8_t, uint16_t) { return kErrorNotImplemented; } @@ -236,10 +293,53 @@ otError otPlatRadioSetFemLnaGain(otInstance *, int8_t) { return kErrorNotImpleme bool otPlatRadioIsCoexEnabled(otInstance *) { return false; } otError otPlatRadioSetCoexEnabled(otInstance *, bool) { return kErrorNotImplemented; } otError otPlatRadioGetCoexMetrics(otInstance *, otRadioCoexMetrics *) { return kErrorNotImplemented; } -otError otPlatRadioEnableCsl(otInstance *, uint32_t, otShortAddress, const otExtAddress *) { return kErrorNone; } -otError otPlatRadioResetCsl(otInstance *) { return kErrorNotImplemented; } -void otPlatRadioUpdateCslSampleTime(otInstance *, uint32_t) {} -uint8_t otPlatRadioGetCslAccuracy(otInstance *) { return 0; } +otError otPlatRadioEnableCsl(otInstance *aInstance, + uint32_t aCslPeriod, + otShortAddress aShortAddr, + const otExtAddress *aExtAddr) +{ + Error error = kErrorNone; + Radio &radio = AsNode(aInstance).mRadio; + + if (aCslPeriod > 0) + { + VerifyOrExit((aShortAddr != OT_RADIO_BROADCAST_SHORT_ADDR) && (aShortAddr != OT_RADIO_INVALID_SHORT_ADDR), + error = kErrorFailed); + VerifyOrExit(aExtAddr != nullptr, error = kErrorFailed); + } + + radio.mRadioContext.mCslPeriod = static_cast(aCslPeriod); + radio.mRadioContext.mCslShortAddress = aShortAddr; + + if (aExtAddr != nullptr) + { + AsCoreType(&radio.mRadioContext.mCslExtAddress).Set(aExtAddr->m8, Mac::ExtAddress::kReverseByteOrder); + } + else + { + AsCoreType(&radio.mRadioContext.mCslExtAddress).Clear(); + } + +exit: + return error; +} + +otError otPlatRadioResetCsl(otInstance *aInstance) +{ + AsNode(aInstance).mRadio.mRadioContext.mCslPeriod = 0; + return kErrorNone; +} + +void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime) +{ + AsNode(aInstance).mRadio.mRadioContext.mCslSampleTime = aCslSampleTime; +} + +uint8_t otPlatRadioGetCslAccuracy(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return 0; +} otError otPlatRadioSetChannelTargetPower(otInstance *, uint8_t, int16_t) { return kErrorNotImplemented; } otError otPlatRadioClearCalibratedPowers(otInstance *) { return kErrorNotImplemented; } otError otPlatRadioAddCalibratedPower(otInstance *, uint8_t, int16_t, const uint8_t *, uint16_t) @@ -256,16 +356,28 @@ Radio::Radio(void) : mState(kStateDisabled) , mPromiscuous(false) , mSrcMatchEnabled(false) + , mMacFrameCounterReset(false) + , mChannel(0) + , mPanId(0) + , mShortAddress(Mac::kShortAddrInvalid) { + mExtAddress.Clear(); + ClearAllBytes(mRadioContext); } void Radio::Reset(void) { - mState = kStateDisabled; - mPromiscuous = false; - mSrcMatchEnabled = false; + mState = kStateDisabled; + mPromiscuous = false; + mSrcMatchEnabled = false; + mMacFrameCounterReset = false; + mChannel = 0; + mPanId = 0; + mShortAddress = Mac::kShortAddrInvalid; + mExtAddress.Clear(); mSrcMatchShortEntries.Clear(); mSrcMatchExtEntries.Clear(); + ClearAllBytes(mRadioContext); } bool Radio::CanReceiveOnChannel(uint8_t aChannel) const diff --git a/tests/nexus/platform/nexus_radio.hpp b/tests/nexus/platform/nexus_radio.hpp index a87646d5e..40e702d68 100644 --- a/tests/nexus/platform/nexus_radio.hpp +++ b/tests/nexus/platform/nexus_radio.hpp @@ -29,6 +29,7 @@ #ifndef OT_NEXUS_PLATFORM_NEXUS_RADIO_HPP_ #define OT_NEXUS_PLATFORM_NEXUS_RADIO_HPP_ +#include "mac_frame.h" #include "instance/instance.hpp" namespace ot { @@ -72,11 +73,13 @@ struct Radio State mState; bool mPromiscuous : 1; bool mSrcMatchEnabled : 1; + bool mMacFrameCounterReset : 1; uint8_t mChannel; Mac::PanId mPanId; Mac::ShortAddress mShortAddress; Mac::ExtAddress mExtAddress; Frame mTxFrame; + otRadioContext mRadioContext; Array mSrcMatchShortEntries; Array mSrcMatchExtEntries; };