[sub-mac] introduce SubMac::ListenScheduler (#13690)

This commit introduces `ListenScheduler` in `SubMac` to coordinate
timed receive windows (`TimedRx`) across different mechanisms (such as
CSL receiver and future Thread Direct wakeup or peer listeners).

Key changes:
- Add `NextRadioFireTime` to track the current synced time and the
  earliest target radio fire time (`Radio::Time64`), and to schedule a
  `TimerMicro` by converting radio time to local timer time.
- Update `SubMac::ProcessTimedRx()` to use `NextRadioFireTime`.
- Add `SubMac::ReceiveAt(const TimedRx &)` and `TimedRx::operator==`
  to cancel pending timed RX when passed an unspecified window and
  skip redundant updates when the pending or active `TimedRx` is
  unchanged.
- Add `ListenScheduler` (and `SchedInfo`) to evaluate active listeners,
  schedule the earliest upcoming `TimedRx`, and manage the transition
  timer.
- Refactor `CslReceiver` to maintain `mCurWindow` and `mNextWindow` and
  delegate timer and `ReceiveAt()` scheduling to `ListenScheduler`.
This commit is contained in:
Abtin Keshavarzian
2026-10-11 21:49:38 -05:00
committed by GitHub
parent 3dde010460
commit 7a845cfe80
3 changed files with 323 additions and 127 deletions
+171 -36
View File
@@ -51,6 +51,9 @@ SubMac::SubMac(Instance &aInstance)
, mRadioCaps(Get<Radio::Radio>().GetCaps())
, mTransmitFrame(Get<Radio::Radio>().GetTransmitBuffer())
, mCallbacks(aInstance)
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
, mListenScheduler(aInstance)
#endif
, mTimer(aInstance)
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
, mCslReceiver(aInstance)
@@ -89,6 +92,7 @@ void SubMac::Init(void)
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
mActiveTimedRx.Clear();
mPendingTimedRx.Clear();
mListenScheduler.Stop();
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
@@ -212,6 +216,10 @@ Error SubMac::Enable(void)
SetState(kStateSleep);
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
mListenScheduler.Start();
#endif
exit:
SuccessOrAssert(error);
return error;
@@ -224,10 +232,7 @@ Error SubMac::Disable(void)
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
mActiveTimedRx.Clear();
mPendingTimedRx.Clear();
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
mCslReceiver.Stop();
mListenScheduler.Stop();
#endif
mTimer.Stop();
@@ -847,9 +852,16 @@ void SubMac::HandleEnergyScanDone(int8_t aMaxRssi)
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
void SubMac::ReceiveAt(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aChannel)
{
TimedRx timedRx;
timedRx.Init(aStartTime, aDuration, aChannel);
ReceiveAt(timedRx);
}
void SubMac::ReceiveAt(const TimedRx &aTimedRx)
{
Radio::SyncedTime now;
TimedRx timedRx;
VerifyOrExit(mState != kStateDisabled);
@@ -860,18 +872,29 @@ void SubMac::ReceiveAt(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aCh
}
#endif
timedRx.Init(aStartTime, aDuration, aChannel);
if (!aTimedRx.IsSpecified())
{
CancelPendingReceiveAt();
ExitNow();
}
now.SetToNow(Get<Radio::Radio>());
VerifyOrExit(!timedRx.HasEnded(now));
VerifyOrExit(!aTimedRx.HasEnded(now));
if (!ShouldHandle(kCapReceiveTiming))
{
timedRx.ScheduleOnRadio(Get<Radio::Radio>());
aTimedRx.ScheduleOnRadio(Get<Radio::Radio>());
ExitNow();
}
VerifyOrExit(mPendingTimedRx != aTimedRx);
if (!mPendingTimedRx.IsSpecified())
{
VerifyOrExit(aTimedRx != mActiveTimedRx);
}
if (mPendingTimedRx.IsSpecified() && mPendingTimedRx.HasStarted(now) && !mPendingTimedRx.HasEnded(now))
{
// Before replacing `mPendingTimedRx` check if the existing one
@@ -891,7 +914,7 @@ void SubMac::ReceiveAt(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aCh
}
}
mPendingTimedRx = timedRx;
mPendingTimedRx = aTimedRx;
switch (mState)
{
@@ -948,20 +971,15 @@ void SubMac::ProcessTimedRx(void)
// timed RX, scheduling the timer for upcoming windows, or putting
// the radio to sleep when active reception window ends.
Radio::Time64 fireTime = Radio::kMaxTime64;
Radio::SyncedTime now;
mTimer.Stop();
now.SetToNow(Get<Radio::Radio>());
NextRadioFireTime nextFireTime(GetInstance());
// Start pending `TimedRx` if due, or clear it if missed.
if (mPendingTimedRx.IsSpecified())
{
if (mPendingTimedRx.HasStarted(now))
if (mPendingTimedRx.HasStarted(nextFireTime.GetNow()))
{
if (!mPendingTimedRx.HasEnded(now))
if (!mPendingTimedRx.HasEnded(nextFireTime.GetNow()))
{
StartPendingTimedRx();
}
@@ -970,13 +988,13 @@ void SubMac::ProcessTimedRx(void)
}
else
{
fireTime = mPendingTimedRx.GetStartTime();
nextFireTime.UpdateIfEarlier(mPendingTimedRx.GetStartTime());
}
}
VerifyOrExit(mActiveTimedRx.IsSpecified());
if (!mActiveTimedRx.HasEnded(now))
if (!mActiveTimedRx.HasEnded(nextFireTime.GetNow()))
{
if (mState != kStateTimedReceive)
{
@@ -984,7 +1002,7 @@ void SubMac::ProcessTimedRx(void)
SetState(kStateTimedReceive);
}
fireTime = Min(fireTime, mActiveTimedRx.GetEndTime());
nextFireTime.UpdateIfEarlier(mActiveTimedRx.GetEndTime());
ExitNow();
}
@@ -1005,22 +1023,7 @@ void SubMac::ProcessTimedRx(void)
}
exit:
if (fireTime != Radio::kMaxTime64)
{
// Schedule the timer to fire at a target radio time `fireTime`,
// using the synced reference `now` to translate radio time to
// local time.
uint32_t delay = 0;
if (fireTime > now.GetAsTime64())
{
delay = ClampToUint32(fireTime - now.GetAsTime64());
}
StartTimerAt(now.GetAsLocalTimeMicro(), delay);
}
nextFireTime.ScheduleTimer(mTimer);
}
void SubMac::TimedRx::Init(Radio::Time64 aStartTime, uint32_t aDuration, uint8_t aChannel)
@@ -1038,6 +1041,17 @@ void SubMac::TimedRx::ScheduleOnRadio(Radio::Radio &aRadio) const
LogWarnOnError(error, "Radio::ReceiveAt()");
}
bool SubMac::TimedRx::operator==(const TimedRx &aOther) const
{
bool matches = false;
VerifyOrExit(mIsSpecified && aOther.mIsSpecified);
matches = (mStartTime == aOther.mStartTime) && (mDuration == aOther.mDuration) && (mChannel == aOther.mChannel);
exit:
return matches;
}
#endif // OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
void SubMac::HandleTimer(void)
@@ -1259,5 +1273,126 @@ const char *SubMac::StateToString(State aState)
// LCOV_EXCL_STOP
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
//---------------------------------------------------------------------------------------------------------------------
// NextRadioFireTime
SubMac::NextRadioFireTime::NextRadioFireTime(Instance &aInstance)
: mFireTime(Radio::kMaxTime64)
{
mNow.SetToNow(aInstance.Get<Radio::Radio>());
}
void SubMac::NextRadioFireTime::ScheduleTimer(TimerMicro &aTimer) const
{
// Schedule `aTimer` to fire at the radio time `mFireTime`
// using the synced reference `mNow` to translate radio time to
// local time.
uint32_t delay;
if (mFireTime == Radio::kMaxTime64)
{
aTimer.Stop();
ExitNow();
}
delay = (mFireTime <= mNow.GetAsTime64()) ? 0 : ClampToUint32(mFireTime - mNow.GetAsTime64());
aTimer.StartAt(mNow.GetAsLocalTimeMicro(), delay);
exit:
return;
}
//---------------------------------------------------------------------------------------------------------------------
// ListenScheduler
SubMac::ListenScheduler::ListenScheduler(Instance &aInstance)
: InstanceLocator(aInstance)
, mTimer(aInstance)
{
}
void SubMac::ListenScheduler::Stop(void) { mTimer.Stop(); }
void SubMac::ListenScheduler::Schedule(void)
{
SchedInfo info(GetInstance());
VerifyOrExit(Get<SubMac>().IsEnabled());
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
ScheduleCsl(info);
#endif
Get<SubMac>().ReceiveAt(info.GetTimedRx());
info.ScheduleNextFireTimeOn(mTimer);
exit:
return;
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
void SubMac::ListenScheduler::ScheduleCsl(SchedInfo &aInfo)
{
CslReceiver &cslReceiver = Get<SubMac>().mCslReceiver;
VerifyOrExit(cslReceiver.IsEnabled());
// Advance the CSL window once within `kCslTransitionToNextAheadTime`
// of the next window's start time, and set the next timer fire time
// to that transition point. Both current and next windows are then
// considered for timed RX (selecting the earliest one that has not
// yet ended).
while (cslReceiver.GetNextWindow().GetStartTime() <= aInfo.GetNow().GetAsTime64() + kCslTransitionToNextAheadTime)
{
cslReceiver.AdvanceToNextWindow();
}
aInfo.UpdateNextFireTime(cslReceiver.GetNextWindow().GetStartTime() - kCslTransitionToNextAheadTime);
aInfo.UpdateTimedRxIfEarlier(cslReceiver.GetNextWindow());
aInfo.UpdateTimedRxIfEarlier(cslReceiver.GetCurWindow());
exit:
return;
}
#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
//---------------------------------------------------------------------------------------------------------------------
// ListenScheduler::SchedInfo
SubMac::ListenScheduler::SchedInfo::SchedInfo(Instance &aInstance)
: mNextFireTime(aInstance)
{
}
void SubMac::ListenScheduler::SchedInfo::UpdateTimedRxIfEarlier(const TimedRx &aTimedRx)
{
// Updates `mTimedRx` to `aTimedRx` if `aTimedRx` is specified, has
// not yet ended, and starts earlier than the current `mTimedRx`.
VerifyOrExit(aTimedRx.IsSpecified());
VerifyOrExit(!aTimedRx.HasEnded(GetNow()));
if (mTimedRx.IsSpecified())
{
VerifyOrExit(aTimedRx.GetStartTime() < mTimedRx.GetStartTime());
}
mTimedRx = aTimedRx;
exit:
return;
}
#endif // OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
} // namespace Mac
} // namespace ot
+84 -24
View File
@@ -42,6 +42,7 @@
#include "common/callback.hpp"
#include "common/clearable.hpp"
#include "common/equatable.hpp"
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/timer.hpp"
@@ -597,7 +598,27 @@ private:
#endif
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
class TimedRx : public Clearable<TimedRx>
class NextRadioFireTime
{
// Tracks the current synced time (`mNow`) and the earliest
// upcoming target radio fire time (`Radio::Time64`). Used to
// schedule a `TimerMicro` by converting the target radio time
// to local timer time relative to `mNow`.
public:
explicit NextRadioFireTime(Instance &aInstance);
const Radio::SyncedTime &GetNow(void) const { return mNow; }
void UpdateIfEarlier(Radio::Time64 aFireTime) { mFireTime = Min(mFireTime, aFireTime); }
void ScheduleTimer(TimerMicro &aTimer) const;
private:
Radio::SyncedTime mNow;
Radio::Time64 mFireTime;
};
class TimedRx : public Clearable<TimedRx>, public Unequatable<TimedRx>
{
public:
TimedRx(void) { Clear(); }
@@ -606,10 +627,12 @@ private:
bool IsSpecified(void) const { return mIsSpecified; }
Radio::Time64 GetStartTime(void) const { return mStartTime; }
Radio::Time64 GetEndTime(void) const { return mStartTime + mDuration; }
uint32_t GetDuration(void) const { return mDuration; }
uint8_t GetChannel(void) const { return mChannel; }
bool HasStarted(const Radio::SyncedTime &aNow) const { return mStartTime <= aNow.GetAsTime64(); }
bool HasEnded(const Radio::SyncedTime &aNow) const { return GetEndTime() <= aNow.GetAsTime64(); }
void ScheduleOnRadio(Radio::Radio &aRadio) const;
bool operator==(const TimedRx &aOther) const;
private:
Radio::Time64 mStartTime;
@@ -617,53 +640,90 @@ private:
uint8_t mChannel;
bool mIsSpecified;
};
#endif
void HandleListenSchedulerTimer(void) { mListenScheduler.HandleTimer(); }
class ListenScheduler : public InstanceLocator
{
public:
explicit ListenScheduler(Instance &aInstance);
void Start(void) { Schedule(); }
void Stop(void);
void HandleTimer(void) { Schedule(); }
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
void HandleCslReceiverTimer(void) { mCslReceiver.HandleTimer(); }
void UpdateOnCslChange(void) { Schedule(); }
#endif
private:
static constexpr uint32_t kCslTransitionToNextAheadTime = OPENTHREAD_CONFIG_CSL_RECEIVE_TIME_AHEAD;
class SchedInfo
{
public:
explicit SchedInfo(Instance &aInstance);
const Radio::SyncedTime &GetNow(void) const { return mNextFireTime.GetNow(); }
const TimedRx &GetTimedRx(void) const { return mTimedRx; }
void UpdateTimedRxIfEarlier(const TimedRx &aTimedRx);
void UpdateNextFireTime(Radio::Time64 aTime) { mNextFireTime.UpdateIfEarlier(aTime); }
void ScheduleNextFireTimeOn(TimerMicro &aTimer) { mNextFireTime.ScheduleTimer(aTimer); }
private:
NextRadioFireTime mNextFireTime;
TimedRx mTimedRx;
};
void Schedule(void);
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
void ScheduleCsl(SchedInfo &aInfo);
#endif
using ListenTimer = TimerMicroIn<SubMac, &SubMac::HandleListenSchedulerTimer>;
ListenTimer mTimer;
};
#endif // OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
class CslReceiver : public InstanceLocator
{
public:
explicit CslReceiver(Instance &aInstance);
void Init(void);
void Stop(void) { mTimer.Stop(); }
bool IsEnabled(void) const { return mPeriod > 0; }
void SetParams(uint16_t aPeriod, uint8_t aChannel, ShortAddress aShortAddr, const ExtAddress &aExtAddr);
void ProcessTxDone(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame);
void ProcessRxFrame(const RxFrame &aFrame);
void HandleTimer(void) { ScheduleSampleWindow(); }
void AdvanceToNextWindow(void);
const TimedRx &GetCurWindow(void) const { return mCurWindow; }
const TimedRx &GetNextWindow(void) const { return mNextWindow; }
const CslAccuracy &GetParentAccuracy(void) const { return mParentAccuracy; }
void SetParentAccuracy(const CslAccuracy &aCslAccuracy) { mParentAccuracy = aCslAccuracy; }
private:
static constexpr uint32_t kMinReceiveOnAhead = OPENTHREAD_CONFIG_MIN_RECEIVE_ON_AHEAD;
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 ScheduleSampleWindow(void);
void DetermineWindow(const Radio::SyncedTime &aSampleTime, Window &aWindow) const;
void Schedule(void);
void UpdateCurAndNextWindows(void);
void DetermineWindow(const Radio::SyncedTime &aSampleTime, TimedRx &aTimedRx) const;
uint32_t DetermineClockDrift(uint32_t aIntervalUs) const;
bool IsEnabled(void) const { return mPeriod > 0; }
void LogReceived(const RxFrame &aFrame);
using CslTimer = TimerMicroIn<SubMac, &SubMac::HandleCslReceiverTimer>;
uint16_t mPeriod;
uint8_t mChannel;
uint16_t mPeerShort;
Radio::SyncedTime mSampleTime;
CslAccuracy mParentAccuracy;
CslTimer mTimer;
TimedRx mCurWindow;
TimedRx mNextWindow;
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
TimeMicro mLastSync;
#else
@@ -674,11 +734,10 @@ private:
#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
void Init(void);
bool IsEnabled(void) const { return mState != kStateDisabled; }
bool RadioSupports(Capability aCapability) const { return (mRadioCaps & aCapability) != 0; }
bool ShouldHandle(Capability aCapability) const;
bool ShouldHandleCsmaBackoff(void) const;
void ProcessTransmitSecurity(TxFrame::ParseInfo &aFrameInfo);
void ReprocessSecurityForRetx(TxFrame::ParseInfo &aFrameInfo);
void SignalFrameCounterUsed(uint32_t aFrameCounter, uint8_t aKeyIndex);
@@ -688,7 +747,6 @@ private:
void SampleRssi(void);
void StartTimer(uint32_t aDelayUs);
void StartTimerAt(Time aStartTime, uint32_t aDelayUs);
void HandleReceiveDone(RxFrame *aFrame, Error aError);
void HandleTransmitStarted(TxFrame &aFrame);
void HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError);
@@ -697,6 +755,7 @@ private:
void HandleTimer(void);
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
void ReceiveAt(const TimedRx &aTimedRx);
void StartPendingTimedRx(void);
void ProcessTimedRx(void);
#endif
@@ -732,8 +791,9 @@ private:
uint8_t mRetxDelayBackoffExponent;
#endif
#if OT_CONFIG_MAC_TARGET_TIME_RX_ENABLE
TimedRx mActiveTimedRx;
TimedRx mPendingTimedRx;
TimedRx mActiveTimedRx;
TimedRx mPendingTimedRx;
ListenScheduler mListenScheduler;
#endif
SubMacTimer mTimer;
+68 -67
View File
@@ -44,7 +44,6 @@ RegisterLogModule("SubMac");
SubMac::CslReceiver::CslReceiver(Instance &aInstance)
: InstanceLocator(aInstance)
, mTimer(aInstance)
{
Init();
}
@@ -56,7 +55,8 @@ void SubMac::CslReceiver::Init(void)
mPeerShort = 0;
mParentAccuracy.Init();
mSampleTime.Clear();
mTimer.Stop();
mCurWindow.Clear();
mNextWindow.Clear();
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
mLastSync.SetValue(0);
#else
@@ -64,23 +64,6 @@ void SubMac::CslReceiver::Init(void)
#endif
}
void SubMac::CslReceiver::RestartTimerAfterSyncUpdate(void)
{
if (mTimer.IsRunning())
{
uint32_t periodUs = CslPeriodToUsec(mPeriod);
mTimer.Stop();
// Rewind sample times by one period. `ScheduleSampleWindow()` will add
// this period back, effectively re-evaluating the current CSL period's
// schedule using the updated mLastSync.
mSampleTime -= periodUs;
ScheduleSampleWindow();
}
}
void SubMac::CslReceiver::ProcessTxDone(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame)
{
// Actual synchronization timestamp should be from the sent frame instead of the current time.
@@ -92,7 +75,7 @@ void SubMac::CslReceiver::ProcessTxDone(const TxFrame::ParseInfo &aFrameInfo, Rx
VerifyOrExit(aFrameInfo.Has<CslIe>());
SetLastSyncToNow();
RestartTimerAfterSyncUpdate();
Schedule();
exit:
return;
@@ -114,7 +97,7 @@ void SubMac::CslReceiver::ProcessRxFrame(const RxFrame &aFrame)
#else
mLastSync = aFrame.GetTimestamp();
#endif
RestartTimerAfterSyncUpdate();
Schedule();
}
exit:
@@ -126,18 +109,23 @@ void SubMac::CslReceiver::SetParams(uint16_t aPeriod,
ShortAddress aShortAddr,
const ExtAddress &aExtAddr)
{
mChannel = aChannel;
VerifyOrExit((aPeriod != mPeriod) || (aShortAddr != mPeerShort));
if ((aPeriod == mPeriod) && (aShortAddr == mPeerShort))
{
// Handle a CSL channel change only
VerifyOrExit(mChannel != aChannel);
mChannel = aChannel;
VerifyOrExit(IsEnabled());
Schedule();
ExitNow();
}
mPeriod = aPeriod;
mChannel = aChannel;
mPeerShort = aShortAddr;
IgnoreError(Get<Radio::Radio>().EnableCsl(aPeriod, aShortAddr, aExtAddr));
mPeriod = aPeriod;
mTimer.Stop();
if (mPeriod > 0)
if (IsEnabled())
{
mSampleTime.SetToNow(Get<Radio::Radio>());
@@ -146,37 +134,60 @@ void SubMac::CslReceiver::SetParams(uint16_t aPeriod,
#else
mLastSync = mSampleTime.GetAsTime64();
#endif
ScheduleSampleWindow();
}
else
{
Get<SubMac>().CancelPendingReceiveAt();
}
Schedule();
exit:
return;
}
void SubMac::CslReceiver::ScheduleSampleWindow(void)
void SubMac::CslReceiver::Schedule(void)
{
Window window;
DetermineWindow(mSampleTime, window);
Get<SubMac>().ReceiveAt(window.mStartTime.GetAsTime64(), window.mDuration, mChannel);
LogDebg("CSL window start %lu, duration %lu", ToUlong(window.mStartTime.GetAsTime32()), ToUlong(window.mDuration));
// 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<Radio::Radio>().UpdateCslSampleTime(mSampleTime.GetAsTime32());
DetermineWindow(mSampleTime, window);
mTimer.FireAt(window.mStartTime.GetAsLocalTimeMicro() - kReceiveTimeAhead);
UpdateCurAndNextWindows();
Get<SubMac>().mListenScheduler.UpdateOnCslChange();
}
void SubMac::CslReceiver::DetermineWindow(const Radio::SyncedTime &aSampleTime, Window &aWindow) const
void SubMac::CslReceiver::AdvanceToNextWindow(void)
{
VerifyOrExit(IsEnabled());
mSampleTime += CslPeriodToUsec(mPeriod);
UpdateCurAndNextWindows();
exit:
return;
}
void SubMac::CslReceiver::UpdateCurAndNextWindows(void)
{
Radio::SyncedTime nextSampleTime;
if (!IsEnabled())
{
mCurWindow.Clear();
mNextWindow.Clear();
ExitNow();
}
DetermineWindow(mSampleTime, mCurWindow);
nextSampleTime = mSampleTime;
nextSampleTime += CslPeriodToUsec(mPeriod);
Get<Radio::Radio>().UpdateCslSampleTime(nextSampleTime.GetAsTime32());
DetermineWindow(nextSampleTime, mNextWindow);
LogDebg("CSL windows (start, dur) - cur (%lu, %lu), next (%lu, %lu)",
ToUlong(Radio::ConvertTime64To32(mCurWindow.GetStartTime())), ToUlong(mCurWindow.GetDuration()),
ToUlong(Radio::ConvertTime64To32(mNextWindow.GetStartTime())), ToUlong(mNextWindow.GetDuration()));
exit:
return;
}
void SubMac::CslReceiver::DetermineWindow(const Radio::SyncedTime &aSampleTime, TimedRx &aTimedRx) const
{
/*
* CSL sample timing diagram:
@@ -215,10 +226,7 @@ void SubMac::CslReceiver::DetermineWindow(const Radio::SyncedTime &aSampleTime,
ahead = Min(guardInterval + kMinReceiveOnAhead, halfPeriod);
after = Min(guardInterval + kMinReceiveOnAfter, halfPeriod);
aWindow.mStartTime = aSampleTime;
aWindow.mStartTime -= ahead;
aWindow.mDuration = ahead + after;
aTimedRx.Init(aSampleTime.GetAsTime64() - ahead, ahead + after, mChannel);
}
uint32_t SubMac::CslReceiver::DetermineClockDrift(uint32_t aIntervalUs) const
@@ -241,8 +249,6 @@ void SubMac::CslReceiver::SetLastSyncToNow(void)
void SubMac::CslReceiver::LogReceived(const RxFrame &aFrame)
{
RxFrame::ParseInfo frameInfo;
Radio::SyncedTime sampleTime;
Window window;
uint32_t margin;
Radio::Time64 timestamp;
uint32_t deviation;
@@ -258,28 +264,23 @@ void SubMac::CslReceiver::LogReceived(const RxFrame &aFrame)
VerifyOrExit((Get<SubMac>().mState == kStateTimedReceive) || (Get<SubMac>().mState == kStateSleep));
sampleTime = mSampleTime;
sampleTime -= CslPeriodToUsec(mPeriod);
DetermineWindow(sampleTime, window);
// The `kMinReceiveOnAhead` is not considered for the margin since
// it has no impact on understanding possible deviation errors
// between transmitter and receiver.
margin = sampleTime.GetAsTime32() - window.mStartTime.GetAsTime32();
margin = mSampleTime.GetAsTime32() - Radio::ConvertTime64To32(mCurWindow.GetStartTime());
margin -= Min(margin, kMinReceiveOnAhead);
timestamp = aFrame.GetTimestamp() + Radio::kHeaderPhrDuration;
if (timestamp >= sampleTime.GetAsTime64())
if (timestamp >= mSampleTime.GetAsTime64())
{
deviation = ClampToUint32(timestamp - sampleTime.GetAsTime64());
deviation = ClampToUint32(timestamp - mSampleTime.GetAsTime64());
signChar = '+';
}
else
{
deviation = ClampToUint32(sampleTime.GetAsTime64() - timestamp);
deviation = ClampToUint32(mSampleTime.GetAsTime64() - timestamp);
signChar = '-';
}
@@ -296,7 +297,7 @@ void SubMac::CslReceiver::LogReceived(const RxFrame &aFrame)
// 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()),
LogAt(logLevel, "Expected sample time %lu, margin ±%lu, deviation %c%lu", ToUlong(mSampleTime.GetAsTime32()),
ToUlong(margin), signChar, ToUlong(deviation));
exit: