diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp index a0d2de230..879279e54 100644 --- a/src/core/mac/sub_mac.cpp +++ b/src/core/mac/sub_mac.cpp @@ -160,6 +160,35 @@ void SubMac::SetExtAddress(const ExtAddress &aExtAddress) LogDebg("RadioExtAddress: %s", mExtAddress.ToString().AsCString()); } +bool SubMac::HasAddress(const Address &aAddress) const +{ + bool matches = false; + + switch (aAddress.GetType()) + { + case Address::kTypeNone: + break; + + case Address::kTypeShort: + if (aAddress.GetShort() == mShortAddress) + { + matches = true; + } + else if (mAlternateShortAddress != kShortAddrInvalid) + { + matches = (aAddress.GetShort() == mAlternateShortAddress); + } + + break; + + case Address::kTypeExtended: + matches = (aAddress.GetExtended() == mExtAddress); + break; + } + + return matches; +} + void SubMac::SetRxOnWhenIdle(bool aRxOnWhenIdle) { mRxOnWhenIdle = aRxOnWhenIdle; @@ -297,7 +326,10 @@ void SubMac::HandleReceiveDone(RxFrame *aFrame, Error aError) } #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - mCslReceiver.UpdateLastSyncTimestamp(aFrame, aError); + if ((aFrame != nullptr) && (aError == kErrorNone)) + { + mCslReceiver.ProcessRxFrame(*aFrame); + } #endif #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE @@ -571,7 +603,7 @@ void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aErro mCallbacks.RecordCcaStatus(ccaSuccess, aFrame.GetChannel()); } #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - mCslReceiver.UpdateLastSyncTimestamp(frameInfo, aAckFrame); + mCslReceiver.ProcessTxDone(frameInfo, aAckFrame); #endif break; diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp index e05bbd80f..6793ccf15 100644 --- a/src/core/mac/sub_mac.hpp +++ b/src/core/mac/sub_mac.hpp @@ -287,6 +287,16 @@ public: */ void SetExtAddress(const ExtAddress &aExtAddress); + /** + * Indicates whether a given MAC address matches this device's short, alternate short, or extended address. + * + * @param[in] aAddress The MAC address to check. + * + * @retval TRUE If @p aAddress matches any of this device's addresses. + * @retval FALSE If @p aAddress does not match any of this device's addresses. + */ + bool HasAddress(const Address &aAddress) const; + /** * Registers a callback to provide received packet capture for IEEE 802.15.4 frames. * @@ -642,8 +652,8 @@ private: void Init(void); void Stop(void) { mTimer.Stop(); } void SetParams(uint16_t aPeriod, uint8_t aChannel, ShortAddress aShortAddr, const ExtAddress &aExtAddr); - void UpdateLastSyncTimestamp(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame); - void UpdateLastSyncTimestamp(RxFrame *aFrame, Error aError); + void ProcessTxDone(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame); + void ProcessRxFrame(const RxFrame &aFrame); void HandleTimer(void); const CslAccuracy &GetParentAccuracy(void) const { return mParentAccuracy; } @@ -654,14 +664,18 @@ private: static constexpr uint32_t kMinReceiveOnAfter = OPENTHREAD_CONFIG_MIN_RECEIVE_ON_AFTER; static constexpr uint32_t kReceiveTimeAhead = OPENTHREAD_CONFIG_CSL_RECEIVE_TIME_AHEAD; + struct Window + { + Radio::SyncedTime mStartTime; + uint32_t mDuration; + }; + void RestartTimerAfterSyncUpdate(void); void SetLastSyncToNow(void); - void GetWindowEdges(uint32_t &aAhead, uint32_t &aAfter); + void DetermineWindow(const Radio::SyncedTime &aSampleTime, Window &aWindow) const; uint32_t DetermineClockDrift(uint32_t aIntervalUs) const; - uint32_t GetNextCycleDrift(void) const; bool IsEnabled(void) const { return mPeriod > 0; } - void LogWindow(Radio::Time64 aStart, uint32_t aDuration); - void LogReceived(RxFrame *aFrame); + void LogReceived(const RxFrame &aFrame); using CslTimer = TimerMicroIn; diff --git a/src/core/mac/sub_mac_csl_receiver.cpp b/src/core/mac/sub_mac_csl_receiver.cpp index 208a72372..b21a24fad 100644 --- a/src/core/mac/sub_mac_csl_receiver.cpp +++ b/src/core/mac/sub_mac_csl_receiver.cpp @@ -46,7 +46,6 @@ SubMac::CslReceiver::CslReceiver(Instance &aInstance) : InstanceLocator(aInstance) , mTimer(aInstance) { - mParentAccuracy.Init(); Init(); } @@ -55,6 +54,7 @@ void SubMac::CslReceiver::Init(void) mPeriod = 0; mChannel = 0; mPeerShort = 0; + mParentAccuracy.Init(); mSampleTime.Clear(); mTimer.Stop(); #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC @@ -81,11 +81,12 @@ void SubMac::CslReceiver::RestartTimerAfterSyncUpdate(void) } } -void SubMac::CslReceiver::UpdateLastSyncTimestamp(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame) +void SubMac::CslReceiver::ProcessTxDone(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame) { // Actual synchronization timestamp should be from the sent frame instead of the current time. // Assuming the error here since it is bounded and has very small effect on the final window duration. + VerifyOrExit(IsEnabled()); VerifyOrExit(aAckFrame != nullptr); VerifyOrExit(aFrameInfo.mParsedFully); VerifyOrExit(aFrameInfo.Has()); @@ -97,21 +98,21 @@ exit: return; } -void SubMac::CslReceiver::UpdateLastSyncTimestamp(RxFrame *aFrame, Error aError) +void SubMac::CslReceiver::ProcessRxFrame(const RxFrame &aFrame) { - VerifyOrExit(aFrame != nullptr && aError == kErrorNone); + VerifyOrExit(IsEnabled()); #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE LogReceived(aFrame); #endif // Assuming the risk of the parent missing the Enh-ACK in favor of smaller CSL receive window - if ((mPeriod > 0) && aFrame->IsAckedWithSecEnhAck()) + if (aFrame.IsAckedWithSecEnhAck()) { #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC SetLastSyncToNow(); #else - mLastSync = aFrame->GetTimestamp(); + mLastSync = aFrame.GetTimestamp(); #endif RestartTimerAfterSyncUpdate(); } @@ -158,70 +159,66 @@ exit: void SubMac::CslReceiver::HandleTimer(void) { - /* - * The handler will be called once per CSL period. When the handler is called, it will set the timer to - * fire at the next CSL sample time and call `ReceiveAt` to start sampling for the current CSL period. - * The timer fires some time before the next sample time. - * - * Timer fires Timer fires - * ^ ^ - * x-|------------|-------------------------------------x-|------------|---------------------------------------| - * sample sleep sample sleep - */ - uint32_t periodUs = CslPeriodToUsec(mPeriod); - uint32_t timeAhead, timeAfter; - Radio::Time64 winStart; - uint32_t winDuration; + Window window; - GetWindowEdges(timeAhead, timeAfter); + DetermineWindow(mSampleTime, window); + Get().ReceiveAt(window.mStartTime.GetAsTime64(), window.mDuration, mChannel); - mTimer.FireAt(mSampleTime.GetAsLocalTimeMicro() + periodUs - timeAhead - kReceiveTimeAhead - GetNextCycleDrift()); + LogDebg("CSL window start %lu, duration %lu", ToUlong(window.mStartTime.GetAsTime32()), ToUlong(window.mDuration)); - winStart = mSampleTime.GetAsTime64() - timeAhead; - winDuration = timeAhead + timeAfter; - - mSampleTime += periodUs; + // Advance sample time to the next CSL period, update the radio + // sample time on `Radio` and schedule the timer ahead of the + // next window's start time. + mSampleTime += CslPeriodToUsec(mPeriod); Get().UpdateCslSampleTime(mSampleTime.GetAsTime32()); - - Get().ReceiveAt(winStart, winDuration, mChannel); - - LogWindow(winStart, winDuration); + DetermineWindow(mSampleTime, window); + mTimer.FireAt(window.mStartTime.GetAsLocalTimeMicro() - kReceiveTimeAhead); } -void SubMac::CslReceiver::GetWindowEdges(uint32_t &aAhead, uint32_t &aAfter) +void SubMac::CslReceiver::DetermineWindow(const Radio::SyncedTime &aSampleTime, Window &aWindow) const { /* - * CSL sample timing diagram - * |<---------------------------------Sample--------------------------------->|<--------Sleep--------->| - * | | | - * |<--Ahead-->|<--UnCert-->|<--Drift-->|<--Drift-->|<--UnCert-->|<--MinWin-->| | - * | | | | | | | | - * ---|-----------|------------|-----------|-----------|------------|------------|----------//------------|--- - * -timeAhead CslPhase +timeAfter -timeAhead + * CSL sample timing diagram: + * + * |<------------------------------------ Sample Window ----------------------------------->| + * | | + * |<--MinAhead-->|<-- Uncert -->|<-- Drift -->|<-- Drift -->|<-- Uncert -->|<--MinAfter-->| + * | |<---------- Guard --------->|<---------- Guard --------->| | + * --|--------------|--------------|-------------|-------------|--------------|---------------|--- + * ^ ^ ^ + * mStartTime SampleTime WindowEnd + * (SampleTime - ahead) (SampleTime + after) */ - uint32_t semiPeriod = CslPeriodToUsec(mPeriod) / 2; - uint32_t elapsed = 0; - uint32_t semiWindow; + + uint32_t halfPeriod = CslPeriodToUsec(mPeriod) / 2; + uint32_t guardInterval = 0; + uint16_t uncertainty; + uint32_t ahead; + uint32_t after; #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC - if (mSampleTime.GetAsLocalTimeMicro() > mLastSync) + if (aSampleTime.GetAsLocalTimeMicro() > mLastSync) { - elapsed = mSampleTime.GetAsLocalTimeMicro() - mLastSync; + guardInterval = DetermineClockDrift(aSampleTime.GetAsLocalTimeMicro() - mLastSync); } #else - if (mSampleTime.GetAsTime64() > mLastSync) + if (aSampleTime.GetAsTime64() > mLastSync) { - elapsed = ClampToUint32(mSampleTime.GetAsTime64() - mLastSync); + guardInterval = DetermineClockDrift(ClampToUint32(aSampleTime.GetAsTime64() - mLastSync)); } #endif - semiWindow = DetermineClockDrift(elapsed); - semiWindow += - Radio::ConvertUncertaintyToUsec(mParentAccuracy.GetUncertainty() + Get().GetCslUncertainty()); + uncertainty = mParentAccuracy.GetUncertainty() + Get().GetCslUncertainty(); + guardInterval += Radio::ConvertUncertaintyToUsec(uncertainty); - aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead); - aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter); + ahead = Min(guardInterval + kMinReceiveOnAhead, halfPeriod); + after = Min(guardInterval + kMinReceiveOnAfter, halfPeriod); + + aWindow.mStartTime = aSampleTime; + aWindow.mStartTime -= ahead; + + aWindow.mDuration = ahead + after; } uint32_t SubMac::CslReceiver::DetermineClockDrift(uint32_t aIntervalUs) const @@ -231,8 +228,6 @@ uint32_t SubMac::CslReceiver::DetermineClockDrift(uint32_t aIntervalUs) const return Radio::DetermineClockDrift(clockAccuracy, aIntervalUs); } -uint32_t SubMac::CslReceiver::GetNextCycleDrift(void) const { return DetermineClockDrift(CslPeriodToUsec(mPeriod)); } - void SubMac::CslReceiver::SetLastSyncToNow(void) { #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC @@ -242,66 +237,68 @@ void SubMac::CslReceiver::SetLastSyncToNow(void) #endif } -#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_DEBG) -void SubMac::CslReceiver::LogWindow(Radio::Time64 aWinStart, uint32_t aWinDuration) -{ - LogDebg("CSL window start %lu, duration %lu", ToUlong(Radio::ConvertTime64To32(aWinStart)), ToUlong(aWinDuration)); -} -#else -void SubMac::CslReceiver::LogWindow(Radio::Time64, uint32_t) {} -#endif - #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE -void SubMac::CslReceiver::LogReceived(RxFrame *aFrame) +void SubMac::CslReceiver::LogReceived(const RxFrame &aFrame) { - static constexpr uint8_t kLogStringSize = 72; + RxFrame::ParseInfo frameInfo; + Radio::SyncedTime sampleTime; + Window window; + uint32_t margin; + Radio::Time64 timestamp; + uint32_t deviation; + char signChar; + LogLevel logLevel; - String logString; - RxFrame::ParseInfo frameInfo; - int32_t deviation; - uint32_t sampleTime, ahead, after; + SuccessOrExit(frameInfo.ParseFrom(aFrame, Frame::kParseAddrFields)); - IgnoreError(frameInfo.ParseFrom(*aFrame, Frame::kParseAddrFields)); - - VerifyOrExit((frameInfo.mAddrs.mDestination.IsShort() && - frameInfo.mAddrs.mDestination.GetShort() == Get().GetShortAddress()) || - (frameInfo.mAddrs.mDestination.IsExtended() && - frameInfo.mAddrs.mDestination.GetExtended() == Get().GetExtAddress())); + VerifyOrExit(Get().HasAddress(frameInfo.mAddrs.mDestination)); LogDebg("Received frame in state %s, timestamp %lu", StateToString(Get().mState), - ToUlong(Radio::ConvertTime64To32(aFrame->GetTimestamp()))); + ToUlong(Radio::ConvertTime64To32(aFrame.GetTimestamp()))); VerifyOrExit((Get().mState == kStateTimedReceive) || (Get().mState == kStateSleep)); - GetWindowEdges(ahead, after); - ahead -= kMinReceiveOnAhead; + sampleTime = mSampleTime; + sampleTime -= CslPeriodToUsec(mPeriod); - sampleTime = mSampleTime.GetAsTime32() - CslPeriodToUsec(mPeriod); - deviation = Radio::ConvertTime64To32(aFrame->GetTimestamp()) + Radio::kHeaderPhrDuration - sampleTime; + DetermineWindow(sampleTime, window); - // This logs three values (all in microseconds): - // - Absolute sample time in which the CSL receiver expected the MHR of the received frame. - // - Allowed margin around that time accounting for accuracy and uncertainty from both devices. - // - Real deviation on the reception of the MHR with regards to expected sample time. This can - // be due to clocks drift and/or CSL Phase rounding error. - // This means that a deviation absolute value greater than the margin would result in the frame - // not being received out of the debug mode. - logString.Append("Expected sample time %lu, margin ±%lu, deviation %ld", ToUlong(sampleTime), ToUlong(ahead), - static_cast(deviation)); + // The `kMinReceiveOnAhead` is not considered for the margin since + // it has no impact on understanding possible deviation errors + // between transmitter and receiver. - // Treat as a warning when the deviation is not within the margins. Neither kReceiveTimeAhead - // or kMinReceiveOnAhead/kMinReceiveOnAfter are considered for the margin since they have no - // impact on understanding possible deviation errors between transmitter and receiver. So in this - // case only `ahead` is used, as an allowable max deviation in both +/- directions. - if ((deviation + ahead > 0) && (deviation < static_cast(ahead))) + margin = sampleTime.GetAsTime32() - window.mStartTime.GetAsTime32(); + margin -= Min(margin, kMinReceiveOnAhead); + + timestamp = aFrame.GetTimestamp() + Radio::kHeaderPhrDuration; + + if (timestamp >= sampleTime.GetAsTime64()) { - LogDebg("%s", logString.AsCString()); + deviation = ClampToUint32(timestamp - sampleTime.GetAsTime64()); + signChar = '+'; } else { - LogWarn("%s", logString.AsCString()); + deviation = ClampToUint32(sampleTime.GetAsTime64() - timestamp); + signChar = '-'; } + // Treat as a warning when the deviation is not within the allowable margin. + + logLevel = (deviation <= margin) ? kLogLevelDebg : kLogLevelWarn; + + // The log includes three values (all in microseconds): + // - Absolute sample time at which the CSL receiver expected the MHR + // of the received frame. + // - Allowed margin around that time accounting for clock drift and + // uncertainty from both devices. + // - Real deviation on the reception of the MHR with regards to + // expected sample time. This can be due to clock drift and/or + // CSL Phase rounding error. + + LogAt(logLevel, "Expected sample time %lu, margin ±%lu, deviation %c%lu", ToUlong(sampleTime.GetAsTime32()), + ToUlong(margin), signChar, ToUlong(deviation)); + exit: return; }