From 749e91d6d7644dd82bc41a4015b9ec1c26981f31 Mon Sep 17 00:00:00 2001 From: Zhanglong Xia Date: Wed, 26 Jun 2024 23:29:56 +0800 Subject: [PATCH] [csl] move csl related functions to sub_mac_csl.cpp --- src/core/BUILD.gn | 1 + src/core/CMakeLists.txt | 2 + src/core/mac/sub_mac.cpp | 259 +--------------------------- src/core/mac/sub_mac.hpp | 4 + src/core/mac/sub_mac_csl.cpp | 319 +++++++++++++++++++++++++++++++++++ 5 files changed, 329 insertions(+), 256 deletions(-) create mode 100644 src/core/mac/sub_mac_csl.cpp diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 247d7d96bb99..7d6524e09284 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -504,6 +504,7 @@ openthread_core_files = [ "mac/sub_mac.cpp", "mac/sub_mac.hpp", "mac/sub_mac_callbacks.cpp", + "mac/sub_mac_csl.cpp", "meshcop/announce_begin_client.cpp", "meshcop/announce_begin_client.hpp", "meshcop/border_agent.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index dbc13b2e3d9e..b0243c995679 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -143,6 +143,7 @@ set(COMMON_SOURCES mac/mac_types.cpp mac/sub_mac.cpp mac/sub_mac_callbacks.cpp + mac/sub_mac_csl.cpp meshcop/announce_begin_client.cpp meshcop/border_agent.cpp meshcop/commissioner.cpp @@ -290,6 +291,7 @@ set(RADIO_COMMON_SOURCES mac/mac_types.cpp mac/sub_mac.cpp mac/sub_mac_callbacks.cpp + mac/sub_mac_csl.cpp radio/radio.cpp radio/radio_callbacks.cpp radio/radio_platform.cpp diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp index edeba2e8b81b..036fddd60ab6 100644 --- a/src/core/mac/sub_mac.cpp +++ b/src/core/mac/sub_mac.cpp @@ -96,13 +96,7 @@ void SubMac::Init(void) mTimer.Stop(); #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - mCslPeriod = 0; - mCslChannel = 0; - mCslPeerShort = 0; - mIsCslSampling = false; - mCslSampleTime = TimeMicro{0}; - mCslLastSync = TimeMicro{0}; - mCslTimer.Stop(); + CslInit(); #endif } @@ -275,85 +269,6 @@ Error SubMac::Receive(uint8_t aChannel) return error; } -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE -void SubMac::CslSample(void) -{ -#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE - VerifyOrExit(!mRadioFilterEnabled, IgnoreError(Get().Sleep())); -#endif - - SetState(kStateCslSample); - - if (mIsCslSampling && !RadioSupportsReceiveTiming()) - { - IgnoreError(Get().Receive(mCslChannel)); - ExitNow(); - } - -#if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE - IgnoreError(Get().Sleep()); // Don't actually sleep for debugging -#endif - -exit: - return; -} -#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - -#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE -void SubMac::LogReceived(RxFrame *aFrame) -{ - static constexpr uint8_t kLogStringSize = 72; - - String logString; - Address dst; - int32_t deviation; - uint32_t sampleTime, ahead, after; - - IgnoreError(aFrame->GetDstAddr(dst)); - - VerifyOrExit((dst.GetType() == Address::kTypeShort && dst.GetShort() == GetShortAddress()) || - (dst.GetType() == Address::kTypeExtended && dst.GetExtended() == GetExtAddress())); - - LogDebg("Received frame in state (SubMac %s, CSL %s), timestamp %lu", StateToString(mState), - mIsCslSampling ? "CslSample" : "CslSleep", - ToUlong(static_cast(aFrame->mInfo.mRxInfo.mTimestamp))); - - VerifyOrExit(mState == kStateCslSample); - - GetCslWindowEdges(ahead, after); - ahead -= kMinReceiveOnAhead + kCslReceiveTimeAhead; - - sampleTime = mCslSampleTime.GetValue() - mCslPeriod * kUsPerTenSymbols; - deviation = aFrame->mInfo.mRxInfo.mTimestamp + kRadioHeaderPhrDuration - sampleTime; - - // 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)); - - // Treat as a warning when the deviation is not within the margins. Neither kCslReceiveTimeAhead - // 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))) - { - LogDebg("%s", logString.AsCString()); - } - else - { - LogWarn("%s", logString.AsCString()); - } - -exit: - return; -} -#endif - void SubMac::HandleReceiveDone(RxFrame *aFrame, Error aError) { if (mPcapCallback.IsSet() && (aFrame != nullptr) && (aError == kErrorNone)) @@ -367,22 +282,8 @@ void SubMac::HandleReceiveDone(RxFrame *aFrame, Error aError) } #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - if (aFrame != nullptr && aError == kErrorNone) - { -#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 ((mCslPeriod > 0) && aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck) - { -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC - mCslLastSync = TimerMicro::GetNow(); -#else - mCslLastSync = TimeMicro(static_cast(aFrame->mInfo.mRxInfo.mTimestamp)); + UpdateCslLastSyncTimestamp(aFrame, aError); #endif - } - } -#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE if (!mRadioFilterEnabled) @@ -638,16 +539,7 @@ void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aErro mCallbacks.RecordCcaStatus(ccaSuccess, aFrame.GetChannel()); } #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - // 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. - if (aAckFrame != nullptr && aFrame.GetHeaderIe(CslIe::kHeaderIeId) != nullptr) - { -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC - mCslLastSync = TimerMicro::GetNow(); -#else - mCslLastSync = TimeMicro(static_cast(otPlatRadioGetNow(&GetInstance()))); -#endif - } + UpdateCslLastSyncTimestamp(aFrame, aAckFrame); #endif break; @@ -1140,150 +1032,5 @@ const char *SubMac::StateToString(State aState) // LCOV_EXCL_STOP -//--------------------------------------------------------------------------------------------------------------------- -// CSL Receiver methods - -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE -bool SubMac::UpdateCsl(uint16_t aPeriod, uint8_t aChannel, otShortAddress aShortAddr, const otExtAddress *aExtAddr) -{ - bool diffPeriod = aPeriod != mCslPeriod; - bool diffChannel = aChannel != mCslChannel; - bool diffPeer = aShortAddr != mCslPeerShort; - bool retval = diffPeriod || diffChannel || diffPeer; - - VerifyOrExit(retval); - mCslChannel = aChannel; - - VerifyOrExit(diffPeriod || diffPeer); - mCslPeriod = aPeriod; - mCslPeerShort = aShortAddr; - IgnoreError(Get().EnableCsl(aPeriod, aShortAddr, aExtAddr)); - - mCslTimer.Stop(); - if (mCslPeriod > 0) - { - mCslSampleTime = TimeMicro(static_cast(otPlatRadioGetNow(&GetInstance()))); - mIsCslSampling = false; - HandleCslTimer(); - } - -exit: - return retval; -} - -void SubMac::HandleCslTimer(Timer &aTimer) { aTimer.Get().HandleCslTimer(); } - -void SubMac::HandleCslTimer(void) -{ - /* - * CSL sample timing diagram - * |<---------------------------------Sample--------------------------------->|<--------Sleep--------->| - * | | | - * |<--Ahead-->|<--UnCert-->|<--Drift-->|<--Drift-->|<--UnCert-->|<--MinWin-->| | - * | | | | | | | | - * ---|-----------|------------|-----------|-----------|------------|------------|----------//------------|--- - * -timeAhead CslPhase +timeAfter -timeAhead - * - * The handler works in different ways when the radio supports receive-timing and doesn't. - * - * When the radio supports receive-timing: - * 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 `Radio::ReceiveAt` to start sampling for the current CSL period. - * The timer fires some time before the actual sample time. After `Radio::ReceiveAt` is called, the radio will - * remain in sleep state until the actual sample time. - * Note that it never call `Radio::Sleep` explicitly. The radio will fall into sleep after `ReceiveAt` ends. This - * will be done by the platform as part of the `otPlatRadioReceiveAt` API. - * - * Timer fires Timer fires - * ^ ^ - * x-|------------|-------------------------------------x-|------------|---------------------------------------| - * sample sleep sample sleep - * - * When the radio doesn't support receive-timing: - * The handler will be called twice per CSL period: at the beginning of sample and sleep. When the handler is - * called, it will explicitly change the radio state due to the current state by calling `Radio::Receive` or - * `Radio::Sleep`. - * - * Timer fires Timer fires Timer fires Timer fires - * ^ ^ ^ ^ - * |------------|---------------------------------------|------------|---------------------------------------| - * sample sleep sample sleep - * - */ - uint32_t periodUs = mCslPeriod * kUsPerTenSymbols; - uint32_t timeAhead, timeAfter, winStart, winDuration; - - GetCslWindowEdges(timeAhead, timeAfter); - - if (mIsCslSampling) - { - mIsCslSampling = false; - mCslTimer.FireAt(mCslSampleTime - timeAhead); - if (mState == kStateCslSample) - { -#if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE - IgnoreError(Get().Sleep()); // Don't actually sleep for debugging -#endif - LogDebg("CSL sleep %lu", ToUlong(mCslTimer.GetNow().GetValue())); - } - } - else - { - if (RadioSupportsReceiveTiming()) - { - mCslTimer.FireAt(mCslSampleTime - timeAhead + periodUs); - timeAhead -= kCslReceiveTimeAhead; - winStart = mCslSampleTime.GetValue() - timeAhead; - } - else - { - mCslTimer.FireAt(mCslSampleTime + timeAfter); - mIsCslSampling = true; - winStart = ot::TimerMicro::GetNow().GetValue(); - } - - winDuration = timeAhead + timeAfter; - mCslSampleTime += periodUs; - - Get().UpdateCslSampleTime(mCslSampleTime.GetValue()); - - // Schedule reception window for any state except RX - so that CSL RX Window has lower priority - // than scanning or RX after the data poll. - if (RadioSupportsReceiveTiming() && (mState != kStateDisabled) && (mState != kStateReceive)) - { - IgnoreError(Get().ReceiveAt(mCslChannel, winStart, winDuration)); - } - else if (mState == kStateCslSample) - { - IgnoreError(Get().Receive(mCslChannel)); - } - - LogDebg("CSL window start %lu, duration %lu", ToUlong(winStart), ToUlong(winDuration)); - } -} - -void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter) -{ - uint32_t semiPeriod = mCslPeriod * kUsPerTenSymbols / 2; - uint32_t curTime, elapsed, semiWindow; - -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC - curTime = TimerMicro::GetNow().GetValue(); -#else - curTime = static_cast(otPlatRadioGetNow(&GetInstance())); -#endif - - elapsed = curTime - mCslLastSync.GetValue(); - - semiWindow = - static_cast(static_cast(elapsed) * - (Get().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy()) / 1000000); - semiWindow += mCslParentAccuracy.GetUncertaintyInMicrosec() + Get().GetCslUncertainty() * 10; - - aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead + kCslReceiveTimeAhead); - aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter); -} -#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - } // namespace Mac } // namespace ot diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp index 211babd0b286..d015550aa0d4 100644 --- a/src/core/mac/sub_mac.hpp +++ b/src/core/mac/sub_mac.hpp @@ -526,9 +526,13 @@ class SubMac : public InstanceLocator, private NonCopyable private: #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + void CslInit(void); + void UpdateCslLastSyncTimestamp(TxFrame &aFrame, RxFrame *aAckFrame); + void UpdateCslLastSyncTimestamp(RxFrame *aFrame, Error aError); static void HandleCslTimer(Timer &aTimer); void HandleCslTimer(void); void GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter); + TimeMicro GetNow(void); #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE void LogReceived(RxFrame *aFrame); #endif diff --git a/src/core/mac/sub_mac_csl.cpp b/src/core/mac/sub_mac_csl.cpp new file mode 100644 index 000000000000..da3cd0729947 --- /dev/null +++ b/src/core/mac/sub_mac_csl.cpp @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2024, 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 CSL of the subset of IEEE 802.15.4 MAC primitives. + */ + +#include "sub_mac.hpp" + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + +#include "common/code_utils.hpp" +#include "common/locator_getters.hpp" +#include "common/log.hpp" +#include "instance/instance.hpp" + +namespace ot { +namespace Mac { + +RegisterLogModule("SubMac"); + +void SubMac::CslInit(void) +{ + mCslPeriod = 0; + mCslChannel = 0; + mCslPeerShort = 0; + mIsCslSampling = false; + mCslSampleTime = TimeMicro{0}; + mCslLastSync = TimeMicro{0}; + mCslTimer.Stop(); +} + +void SubMac::UpdateCslLastSyncTimestamp(TxFrame &aFrame, 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. + if (aAckFrame != nullptr && aFrame.GetHeaderIe(CslIe::kHeaderIeId) != nullptr) + { + mCslLastSync = GetNow(); + } +} + +void SubMac::UpdateCslLastSyncTimestamp(RxFrame *aFrame, Error aError) +{ + VerifyOrExit(aFrame != nullptr && aError == kErrorNone); + +#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 ((mCslPeriod > 0) && aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck) + { +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC + mCslLastSync = TimerMicro::GetNow(); +#else + mCslLastSync = TimeMicro(static_cast(aFrame->mInfo.mRxInfo.mTimestamp)); +#endif + } + +exit: + return; +} + +void SubMac::CslSample(void) +{ +#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE + VerifyOrExit(!mRadioFilterEnabled, IgnoreError(Get().Sleep())); +#endif + + SetState(kStateCslSample); + + if (mIsCslSampling && !RadioSupportsReceiveTiming()) + { + IgnoreError(Get().Receive(mCslChannel)); + ExitNow(); + } + +#if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE + IgnoreError(Get().Sleep()); // Don't actually sleep for debugging +#endif + +exit: + return; +} + +bool SubMac::UpdateCsl(uint16_t aPeriod, uint8_t aChannel, otShortAddress aShortAddr, const otExtAddress *aExtAddr) +{ + bool diffPeriod = aPeriod != mCslPeriod; + bool diffChannel = aChannel != mCslChannel; + bool diffPeer = aShortAddr != mCslPeerShort; + bool retval = diffPeriod || diffChannel || diffPeer; + + VerifyOrExit(retval); + mCslChannel = aChannel; + + VerifyOrExit(diffPeriod || diffPeer); + mCslPeriod = aPeriod; + mCslPeerShort = aShortAddr; + IgnoreError(Get().EnableCsl(aPeriod, aShortAddr, aExtAddr)); + + mCslTimer.Stop(); + if (mCslPeriod > 0) + { + mCslSampleTime = TimeMicro(static_cast(otPlatRadioGetNow(&GetInstance()))); + mIsCslSampling = false; + HandleCslTimer(); + } + +exit: + return retval; +} + +void SubMac::HandleCslTimer(Timer &aTimer) { aTimer.Get().HandleCslTimer(); } + +void SubMac::HandleCslTimer(void) +{ + /* + * CSL sample timing diagram + * |<---------------------------------Sample--------------------------------->|<--------Sleep--------->| + * | | | + * |<--Ahead-->|<--UnCert-->|<--Drift-->|<--Drift-->|<--UnCert-->|<--MinWin-->| | + * | | | | | | | | + * ---|-----------|------------|-----------|-----------|------------|------------|----------//------------|--- + * -timeAhead CslPhase +timeAfter -timeAhead + * + * The handler works in different ways when the radio supports receive-timing and doesn't. + * + * When the radio supports receive-timing: + * 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 `Radio::ReceiveAt` to start sampling for the current CSL period. + * The timer fires some time before the actual sample time. After `Radio::ReceiveAt` is called, the radio will + * remain in sleep state until the actual sample time. + * Note that it never call `Radio::Sleep` explicitly. The radio will fall into sleep after `ReceiveAt` ends. This + * will be done by the platform as part of the `otPlatRadioReceiveAt` API. + * + * Timer fires Timer fires + * ^ ^ + * x-|------------|-------------------------------------x-|------------|---------------------------------------| + * sample sleep sample sleep + * + * When the radio doesn't support receive-timing: + * The handler will be called twice per CSL period: at the beginning of sample and sleep. When the handler is + * called, it will explicitly change the radio state due to the current state by calling `Radio::Receive` or + * `Radio::Sleep`. + * + * Timer fires Timer fires Timer fires Timer fires + * ^ ^ ^ ^ + * |------------|---------------------------------------|------------|---------------------------------------| + * sample sleep sample sleep + * + */ + uint32_t periodUs = mCslPeriod * kUsPerTenSymbols; + uint32_t timeAhead, timeAfter, winStart, winDuration; + + GetCslWindowEdges(timeAhead, timeAfter); + + if (mIsCslSampling) + { + mIsCslSampling = false; + mCslTimer.FireAt(mCslSampleTime - timeAhead); + if (mState == kStateCslSample) + { +#if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE + IgnoreError(Get().Sleep()); // Don't actually sleep for debugging +#endif + LogDebg("CSL sleep %lu", ToUlong(mCslTimer.GetNow().GetValue())); + } + } + else + { + if (RadioSupportsReceiveTiming()) + { + mCslTimer.FireAt(mCslSampleTime - timeAhead + periodUs); + timeAhead -= kCslReceiveTimeAhead; + winStart = mCslSampleTime.GetValue() - timeAhead; + } + else + { + mCslTimer.FireAt(mCslSampleTime + timeAfter); + mIsCslSampling = true; + winStart = ot::TimerMicro::GetNow().GetValue(); + } + + winDuration = timeAhead + timeAfter; + mCslSampleTime += periodUs; + + Get().UpdateCslSampleTime(mCslSampleTime.GetValue()); + + // Schedule reception window for any state except RX - so that CSL RX Window has lower priority + // than scanning or RX after the data poll. + if (RadioSupportsReceiveTiming() && (mState != kStateDisabled) && (mState != kStateReceive)) + { + IgnoreError(Get().ReceiveAt(mCslChannel, winStart, winDuration)); + } + else if (mState == kStateCslSample) + { + IgnoreError(Get().Receive(mCslChannel)); + } + + LogDebg("CSL window start %lu, duration %lu", ToUlong(winStart), ToUlong(winDuration)); + } +} + +void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter) +{ + uint32_t semiPeriod = mCslPeriod * kUsPerTenSymbols / 2; + uint32_t curTime, elapsed, semiWindow; + + curTime = GetNow().GetValue(); + elapsed = curTime - mCslLastSync.GetValue(); + + semiWindow = + static_cast(static_cast(elapsed) * + (Get().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy()) / 1000000); + semiWindow += mCslParentAccuracy.GetUncertaintyInMicrosec() + Get().GetCslUncertainty() * 10; + + aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead + kCslReceiveTimeAhead); + aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter); +} + +TimeMicro SubMac::GetNow(void) +{ + TimeMicro now; + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC + now = TimerMicro::GetNow(); +#else + now = TimeMicro(static_cast(otPlatRadioGetNow(&GetInstance()))); +#endif + + return now; +} + +#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE +void SubMac::LogReceived(RxFrame *aFrame) +{ + static constexpr uint8_t kLogStringSize = 72; + + String logString; + Address dst; + int32_t deviation; + uint32_t sampleTime, ahead, after; + + IgnoreError(aFrame->GetDstAddr(dst)); + + VerifyOrExit((dst.GetType() == Address::kTypeShort && dst.GetShort() == GetShortAddress()) || + (dst.GetType() == Address::kTypeExtended && dst.GetExtended() == GetExtAddress())); + + LogDebg("Received frame in state (SubMac %s, CSL %s), timestamp %lu", StateToString(mState), + mIsCslSampling ? "CslSample" : "CslSleep", + ToUlong(static_cast(aFrame->mInfo.mRxInfo.mTimestamp))); + + VerifyOrExit(mState == kStateCslSample); + + GetCslWindowEdges(ahead, after); + ahead -= kMinReceiveOnAhead + kCslReceiveTimeAhead; + + sampleTime = mCslSampleTime.GetValue() - mCslPeriod * kUsPerTenSymbols; + deviation = aFrame->mInfo.mRxInfo.mTimestamp + kRadioHeaderPhrDuration - sampleTime; + + // 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)); + + // Treat as a warning when the deviation is not within the margins. Neither kCslReceiveTimeAhead + // 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))) + { + LogDebg("%s", logString.AsCString()); + } + else + { + LogWarn("%s", logString.AsCString()); + } + +exit: + return; +} +#endif + +} // namespace Mac +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE