[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.
This commit is contained in:
Jonathan Hui
2026-03-03 15:32:05 -06:00
committed by GitHub
parent 26bcc07b31
commit 202d5df96d
8 changed files with 169 additions and 24 deletions
+1 -4
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+2 -2
View File
@@ -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
+34 -6
View File
@@ -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<Radio::Frame &>(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<const Mac::RxFrame &>(static_cast<const Mac::Frame &>(aNode.mRadio.mTxFrame));
ackFrame.GenerateImmAck(
static_cast<const Mac::RxFrame &>(static_cast<const Mac::Frame &>(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);
+1
View File
@@ -57,6 +57,7 @@ public:
TimeMilli GetNow(void) { return TimeMilli(static_cast<uint32_t>(mNow / 1000u)); }
TimeMicro GetNowMicro(void) { return TimeMicro(static_cast<uint32_t>(mNow)); }
uint64_t GetNowMicro64(void) const { return mNow; }
void AdvanceTime(uint32_t aDuration);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+124 -12
View File
@@ -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<Radio::Frame *>(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<uint16_t>(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
+3
View File
@@ -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<uint16_t, kMaxSrcMaatchShort> mSrcMatchShortEntries;
Array<Mac::ExtAddress, kMaxSrcMatchExt> mSrcMatchExtEntries;
};