• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2024, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements the CSL receiver of the subset of IEEE 802.15.4 MAC primitives.
32  */
33 
34 #include "sub_mac.hpp"
35 
36 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
37 
38 #include "instance/instance.hpp"
39 
40 namespace ot {
41 namespace Mac {
42 
43 RegisterLogModule("SubMac");
44 
CslInit(void)45 void SubMac::CslInit(void)
46 {
47     mCslPeriod     = 0;
48     mCslChannel    = 0;
49     mCslPeerShort  = 0;
50     mIsCslSampling = false;
51     mCslSampleTime = TimeMicro{0};
52     mCslLastSync   = TimeMicro{0};
53     mCslTimer.Stop();
54 }
55 
UpdateCslLastSyncTimestamp(TxFrame & aFrame,RxFrame * aAckFrame)56 void SubMac::UpdateCslLastSyncTimestamp(TxFrame &aFrame, RxFrame *aAckFrame)
57 {
58     // Actual synchronization timestamp should be from the sent frame instead of the current time.
59     // Assuming the error here since it is bounded and has very small effect on the final window duration.
60     if (aAckFrame != nullptr && aFrame.HasCslIe())
61     {
62         mCslLastSync = TimeMicro(GetLocalTime());
63     }
64 }
65 
UpdateCslLastSyncTimestamp(RxFrame * aFrame,Error aError)66 void SubMac::UpdateCslLastSyncTimestamp(RxFrame *aFrame, Error aError)
67 {
68     VerifyOrExit(aFrame != nullptr && aError == kErrorNone);
69 
70 #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
71     LogReceived(aFrame);
72 #endif
73 
74     // Assuming the risk of the parent missing the Enh-ACK in favor of smaller CSL receive window
75     if ((mCslPeriod > 0) && aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck)
76     {
77 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
78         mCslLastSync = TimerMicro::GetNow();
79 #else
80         mCslLastSync = TimeMicro(static_cast<uint32_t>(aFrame->mInfo.mRxInfo.mTimestamp));
81 #endif
82     }
83 
84 exit:
85     return;
86 }
87 
CslSample(void)88 void SubMac::CslSample(void)
89 {
90 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
91     VerifyOrExit(!mRadioFilterEnabled, IgnoreError(Get<Radio>().Sleep()));
92 #endif
93 
94     SetState(kStateCslSample);
95 
96     if (mIsCslSampling && !RadioSupportsReceiveTiming())
97     {
98         IgnoreError(Get<Radio>().Receive(mCslChannel));
99         ExitNow();
100     }
101 
102 #if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
103     IgnoreError(Get<Radio>().Sleep()); // Don't actually sleep for debugging
104 #endif
105 
106 exit:
107     return;
108 }
109 
UpdateCsl(uint16_t aPeriod,uint8_t aChannel,ShortAddress aShortAddr,const ExtAddress & aExtAddr)110 bool SubMac::UpdateCsl(uint16_t aPeriod, uint8_t aChannel, ShortAddress aShortAddr, const ExtAddress &aExtAddr)
111 {
112     bool diffPeriod  = aPeriod != mCslPeriod;
113     bool diffChannel = aChannel != mCslChannel;
114     bool diffPeer    = aShortAddr != mCslPeerShort;
115     bool retval      = diffPeriod || diffChannel || diffPeer;
116 
117     VerifyOrExit(retval);
118     mCslChannel = aChannel;
119 
120     VerifyOrExit(diffPeriod || diffPeer);
121     mCslPeriod    = aPeriod;
122     mCslPeerShort = aShortAddr;
123     IgnoreError(Get<Radio>().EnableCsl(aPeriod, aShortAddr, aExtAddr));
124 
125     mCslTimer.Stop();
126     if (mCslPeriod > 0)
127     {
128         mCslSampleTime = TimeMicro(static_cast<uint32_t>(Get<Radio>().GetNow()));
129         mIsCslSampling = false;
130         HandleCslTimer();
131     }
132 
133 exit:
134     return retval;
135 }
136 
HandleCslTimer(Timer & aTimer)137 void SubMac::HandleCslTimer(Timer &aTimer) { aTimer.Get<SubMac>().HandleCslTimer(); }
138 
HandleCslTimer(void)139 void SubMac::HandleCslTimer(void)
140 {
141     uint32_t timeAhead, timeAfter;
142 
143     GetCslWindowEdges(timeAhead, timeAfter);
144 
145     // The handler works in different ways when the radio supports receive-timing and doesn't.
146     if (RadioSupportsReceiveTiming())
147     {
148         HandleCslReceiveAt(timeAhead, timeAfter);
149     }
150     else
151     {
152         HandleCslReceiveOrSleep(timeAhead, timeAfter);
153     }
154 }
155 
HandleCslReceiveAt(uint32_t aTimeAhead,uint32_t aTimeAfter)156 void SubMac::HandleCslReceiveAt(uint32_t aTimeAhead, uint32_t aTimeAfter)
157 {
158     /*
159      * When the radio supports receive-timing:
160      *   The handler will be called once per CSL period. When the handler is called, it will set the timer to
161      *   fire at the next CSL sample time and call `Radio::ReceiveAt` to start sampling for the current CSL period.
162      *   The timer fires some time before the actual sample time. After `Radio::ReceiveAt` is called, the radio will
163      *   remain in sleep state until the actual sample time.
164      *   Note that it never call `Radio::Sleep` explicitly. The radio will fall into sleep after `ReceiveAt` ends. This
165      *   will be done by the platform as part of the `otPlatRadioReceiveAt` API.
166      *
167      *   Timer fires                                         Timer fires
168      *       ^                                                    ^
169      *       x-|------------|-------------------------------------x-|------------|---------------------------------------|
170      *            sample                   sleep                        sample                    sleep
171      */
172     uint32_t periodUs = mCslPeriod * kUsPerTenSymbols;
173     uint32_t winStart;
174     uint32_t winDuration;
175 
176     mCslTimer.FireAt(mCslSampleTime - aTimeAhead + periodUs);
177     aTimeAhead -= kCslReceiveTimeAhead;
178     winStart    = mCslSampleTime.GetValue() - aTimeAhead;
179     winDuration = aTimeAhead + aTimeAfter;
180     mCslSampleTime += periodUs;
181 
182     Get<Radio>().UpdateCslSampleTime(mCslSampleTime.GetValue());
183 
184     // Schedule reception window for any state except RX - so that CSL RX Window has lower priority
185     // than scanning or RX after the data poll.
186     if ((mState != kStateDisabled) && (mState != kStateReceive))
187     {
188         IgnoreError(Get<Radio>().ReceiveAt(mCslChannel, winStart, winDuration));
189     }
190 
191     LogCslWindow(winStart, winDuration);
192 }
193 
HandleCslReceiveOrSleep(uint32_t aTimeAhead,uint32_t aTimeAfter)194 void SubMac::HandleCslReceiveOrSleep(uint32_t aTimeAhead, uint32_t aTimeAfter)
195 {
196     /*
197      * When the radio doesn't support receive-timing:
198      *   The handler will be called twice per CSL period: at the beginning of sample and sleep. When the handler is
199      *   called, it will explicitly change the radio state due to the current state by calling `Radio::Receive` or
200      *   `Radio::Sleep`.
201      *
202      *   Timer fires  Timer fires                            Timer fires  Timer fires
203      *       ^            ^                                       ^            ^
204      *       |------------|---------------------------------------|------------|---------------------------------------|
205      *          sample                   sleep                        sample                    sleep
206      */
207 
208     if (mIsCslSampling)
209     {
210         mIsCslSampling = false;
211         mCslTimer.FireAt(mCslSampleTime - aTimeAhead);
212         if (mState == kStateCslSample)
213         {
214 #if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
215             IgnoreError(Get<Radio>().Sleep()); // Don't actually sleep for debugging
216 #endif
217             LogDebg("CSL sleep %lu", ToUlong(mCslTimer.GetNow().GetValue()));
218         }
219     }
220     else
221     {
222         uint32_t periodUs = mCslPeriod * kUsPerTenSymbols;
223         uint32_t winStart;
224         uint32_t winDuration;
225 
226         mCslTimer.FireAt(mCslSampleTime + aTimeAfter);
227         mIsCslSampling = true;
228         winStart       = TimerMicro::GetNow().GetValue();
229         winDuration    = aTimeAhead + aTimeAfter;
230         mCslSampleTime += periodUs;
231 
232         Get<Radio>().UpdateCslSampleTime(mCslSampleTime.GetValue());
233         if (mState == kStateCslSample)
234         {
235             IgnoreError(Get<Radio>().Receive(mCslChannel));
236         }
237 
238         LogCslWindow(winStart, winDuration);
239     }
240 }
241 
GetCslWindowEdges(uint32_t & aAhead,uint32_t & aAfter)242 void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter)
243 {
244     /*
245      * CSL sample timing diagram
246      *    |<---------------------------------Sample--------------------------------->|<--------Sleep--------->|
247      *    |                                                                          |                        |
248      *    |<--Ahead-->|<--UnCert-->|<--Drift-->|<--Drift-->|<--UnCert-->|<--MinWin-->|                        |
249      *    |           |            |           |           |            |            |                        |
250      * ---|-----------|------------|-----------|-----------|------------|------------|----------//------------|---
251      * -timeAhead                           CslPhase                             +timeAfter             -timeAhead
252      *
253      */
254     uint32_t semiPeriod = mCslPeriod * kUsPerTenSymbols / 2;
255     uint32_t curTime, elapsed, semiWindow;
256 
257     curTime = GetLocalTime();
258     elapsed = curTime - mCslLastSync.GetValue();
259 
260     semiWindow =
261         static_cast<uint32_t>(static_cast<uint64_t>(elapsed) *
262                               (Get<Radio>().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy()) / 1000000);
263     semiWindow += mCslParentAccuracy.GetUncertaintyInMicrosec() + Get<Radio>().GetCslUncertainty() * 10;
264 
265     aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead + kCslReceiveTimeAhead);
266     aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter);
267 }
268 
GetLocalTime(void)269 uint32_t SubMac::GetLocalTime(void)
270 {
271     uint32_t now;
272 
273 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
274     now = TimerMicro::GetNow().GetValue();
275 #else
276     now = static_cast<uint32_t>(Get<Radio>().GetNow());
277 #endif
278 
279     return now;
280 }
281 
282 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_DEBG)
LogCslWindow(uint32_t aWinStart,uint32_t aWinDuration)283 void SubMac::LogCslWindow(uint32_t aWinStart, uint32_t aWinDuration)
284 {
285     LogDebg("CSL window start %lu, duration %lu", ToUlong(aWinStart), ToUlong(aWinDuration));
286 }
287 #else
LogCslWindow(uint32_t,uint32_t)288 void SubMac::LogCslWindow(uint32_t, uint32_t) {}
289 #endif
290 
291 #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
LogReceived(RxFrame * aFrame)292 void SubMac::LogReceived(RxFrame *aFrame)
293 {
294     static constexpr uint8_t kLogStringSize = 72;
295 
296     String<kLogStringSize> logString;
297     Address                dst;
298     int32_t                deviation;
299     uint32_t               sampleTime, ahead, after;
300 
301     IgnoreError(aFrame->GetDstAddr(dst));
302 
303     VerifyOrExit((dst.GetType() == Address::kTypeShort && dst.GetShort() == GetShortAddress()) ||
304                  (dst.GetType() == Address::kTypeExtended && dst.GetExtended() == GetExtAddress()));
305 
306     LogDebg("Received frame in state (SubMac %s, CSL %s), timestamp %lu", StateToString(mState),
307             mIsCslSampling ? "CslSample" : "CslSleep",
308             ToUlong(static_cast<uint32_t>(aFrame->mInfo.mRxInfo.mTimestamp)));
309 
310     VerifyOrExit(mState == kStateCslSample);
311 
312     GetCslWindowEdges(ahead, after);
313     ahead -= kMinReceiveOnAhead + kCslReceiveTimeAhead;
314 
315     sampleTime = mCslSampleTime.GetValue() - mCslPeriod * kUsPerTenSymbols;
316     deviation  = aFrame->mInfo.mRxInfo.mTimestamp + kRadioHeaderPhrDuration - sampleTime;
317 
318     // This logs three values (all in microseconds):
319     // - Absolute sample time in which the CSL receiver expected the MHR of the received frame.
320     // - Allowed margin around that time accounting for accuracy and uncertainty from both devices.
321     // - Real deviation on the reception of the MHR with regards to expected sample time. This can
322     //   be due to clocks drift and/or CSL Phase rounding error.
323     // This means that a deviation absolute value greater than the margin would result in the frame
324     // not being received out of the debug mode.
325     logString.Append("Expected sample time %lu, margin ±%lu, deviation %ld", ToUlong(sampleTime), ToUlong(ahead),
326                      static_cast<long>(deviation));
327 
328     // Treat as a warning when the deviation is not within the margins. Neither kCslReceiveTimeAhead
329     // or kMinReceiveOnAhead/kMinReceiveOnAfter are considered for the margin since they have no
330     // impact on understanding possible deviation errors between transmitter and receiver. So in this
331     // case only `ahead` is used, as an allowable max deviation in both +/- directions.
332     if ((deviation + ahead > 0) && (deviation < static_cast<int32_t>(ahead)))
333     {
334         LogDebg("%s", logString.AsCString());
335     }
336     else
337     {
338         LogWarn("%s", logString.AsCString());
339     }
340 
341 exit:
342     return;
343 }
344 #endif
345 
346 } // namespace Mac
347 } // namespace ot
348 
349 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
350