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 "common/code_utils.hpp"
39 #include "common/locator_getters.hpp"
40 #include "common/log.hpp"
41 #include "instance/instance.hpp"
42 
43 namespace ot {
44 namespace Mac {
45 
46 RegisterLogModule("SubMac");
47 
CslInit(void)48 void SubMac::CslInit(void)
49 {
50     mCslPeriod     = 0;
51     mCslChannel    = 0;
52     mCslPeerShort  = 0;
53     mIsCslSampling = false;
54     mCslSampleTime = TimeMicro{0};
55     mCslLastSync   = TimeMicro{0};
56     mCslTimer.Stop();
57 }
58 
UpdateCslLastSyncTimestamp(TxFrame & aFrame,RxFrame * aAckFrame)59 void SubMac::UpdateCslLastSyncTimestamp(TxFrame &aFrame, RxFrame *aAckFrame)
60 {
61     // Actual synchronization timestamp should be from the sent frame instead of the current time.
62     // Assuming the error here since it is bounded and has very small effect on the final window duration.
63     if (aAckFrame != nullptr && aFrame.HasCslIe())
64     {
65         mCslLastSync = TimeMicro(GetLocalTime());
66     }
67 }
68 
UpdateCslLastSyncTimestamp(RxFrame * aFrame,Error aError)69 void SubMac::UpdateCslLastSyncTimestamp(RxFrame *aFrame, Error aError)
70 {
71     VerifyOrExit(aFrame != nullptr && aError == kErrorNone);
72 
73 #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
74     LogReceived(aFrame);
75 #endif
76 
77     // Assuming the risk of the parent missing the Enh-ACK in favor of smaller CSL receive window
78     if ((mCslPeriod > 0) && aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck)
79     {
80 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
81         mCslLastSync = TimerMicro::GetNow();
82 #else
83         mCslLastSync = TimeMicro(static_cast<uint32_t>(aFrame->mInfo.mRxInfo.mTimestamp));
84 #endif
85     }
86 
87 exit:
88     return;
89 }
90 
CslSample(void)91 void SubMac::CslSample(void)
92 {
93 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
94     VerifyOrExit(!mRadioFilterEnabled, IgnoreError(Get<Radio>().Sleep()));
95 #endif
96 
97     SetState(kStateCslSample);
98 
99     if (mIsCslSampling && !RadioSupportsReceiveTiming())
100     {
101         IgnoreError(Get<Radio>().Receive(mCslChannel));
102         ExitNow();
103     }
104 
105 #if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
106     IgnoreError(Get<Radio>().Sleep()); // Don't actually sleep for debugging
107 #endif
108 
109 exit:
110     return;
111 }
112 
UpdateCsl(uint16_t aPeriod,uint8_t aChannel,otShortAddress aShortAddr,const otExtAddress * aExtAddr)113 bool SubMac::UpdateCsl(uint16_t aPeriod, uint8_t aChannel, otShortAddress aShortAddr, const otExtAddress *aExtAddr)
114 {
115     bool diffPeriod  = aPeriod != mCslPeriod;
116     bool diffChannel = aChannel != mCslChannel;
117     bool diffPeer    = aShortAddr != mCslPeerShort;
118     bool retval      = diffPeriod || diffChannel || diffPeer;
119 
120     VerifyOrExit(retval);
121     mCslChannel = aChannel;
122 
123     VerifyOrExit(diffPeriod || diffPeer);
124     mCslPeriod    = aPeriod;
125     mCslPeerShort = aShortAddr;
126     IgnoreError(Get<Radio>().EnableCsl(aPeriod, aShortAddr, aExtAddr));
127 
128     mCslTimer.Stop();
129     if (mCslPeriod > 0)
130     {
131         mCslSampleTime = TimeMicro(static_cast<uint32_t>(otPlatRadioGetNow(&GetInstance())));
132         mIsCslSampling = false;
133         HandleCslTimer();
134     }
135 
136 exit:
137     return retval;
138 }
139 
HandleCslTimer(Timer & aTimer)140 void SubMac::HandleCslTimer(Timer &aTimer) { aTimer.Get<SubMac>().HandleCslTimer(); }
141 
HandleCslTimer(void)142 void SubMac::HandleCslTimer(void)
143 {
144     /*
145      * CSL sample timing diagram
146      *    |<---------------------------------Sample--------------------------------->|<--------Sleep--------->|
147      *    |                                                                          |                        |
148      *    |<--Ahead-->|<--UnCert-->|<--Drift-->|<--Drift-->|<--UnCert-->|<--MinWin-->|                        |
149      *    |           |            |           |           |            |            |                        |
150      * ---|-----------|------------|-----------|-----------|------------|------------|----------//------------|---
151      * -timeAhead                           CslPhase                             +timeAfter             -timeAhead
152      *
153      * The handler works in different ways when the radio supports receive-timing and doesn't.
154      *
155      * When the radio supports receive-timing:
156      *   The handler will be called once per CSL period. When the handler is called, it will set the timer to
157      *   fire at the next CSL sample time and call `Radio::ReceiveAt` to start sampling for the current CSL period.
158      *   The timer fires some time before the actual sample time. After `Radio::ReceiveAt` is called, the radio will
159      *   remain in sleep state until the actual sample time.
160      *   Note that it never call `Radio::Sleep` explicitly. The radio will fall into sleep after `ReceiveAt` ends. This
161      *   will be done by the platform as part of the `otPlatRadioReceiveAt` API.
162      *
163      *   Timer fires                                         Timer fires
164      *       ^                                                    ^
165      *       x-|------------|-------------------------------------x-|------------|---------------------------------------|
166      *            sample                   sleep                        sample                    sleep
167      *
168      * When the radio doesn't support receive-timing:
169      *   The handler will be called twice per CSL period: at the beginning of sample and sleep. When the handler is
170      *   called, it will explicitly change the radio state due to the current state by calling `Radio::Receive` or
171      *   `Radio::Sleep`.
172      *
173      *   Timer fires  Timer fires                            Timer fires  Timer fires
174      *       ^            ^                                       ^            ^
175      *       |------------|---------------------------------------|------------|---------------------------------------|
176      *          sample                   sleep                        sample                    sleep
177      *
178      */
179     uint32_t periodUs = mCslPeriod * kUsPerTenSymbols;
180     uint32_t timeAhead, timeAfter, winStart, winDuration;
181 
182     GetCslWindowEdges(timeAhead, timeAfter);
183 
184     if (mIsCslSampling)
185     {
186         mIsCslSampling = false;
187         mCslTimer.FireAt(mCslSampleTime - timeAhead);
188         if (mState == kStateCslSample)
189         {
190 #if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
191             IgnoreError(Get<Radio>().Sleep()); // Don't actually sleep for debugging
192 #endif
193             LogDebg("CSL sleep %lu", ToUlong(mCslTimer.GetNow().GetValue()));
194         }
195     }
196     else
197     {
198         if (RadioSupportsReceiveTiming())
199         {
200             mCslTimer.FireAt(mCslSampleTime - timeAhead + periodUs);
201             timeAhead -= kCslReceiveTimeAhead;
202             winStart = mCslSampleTime.GetValue() - timeAhead;
203         }
204         else
205         {
206             mCslTimer.FireAt(mCslSampleTime + timeAfter);
207             mIsCslSampling = true;
208             winStart       = ot::TimerMicro::GetNow().GetValue();
209         }
210 
211         winDuration = timeAhead + timeAfter;
212         mCslSampleTime += periodUs;
213 
214         Get<Radio>().UpdateCslSampleTime(mCslSampleTime.GetValue());
215 
216         // Schedule reception window for any state except RX - so that CSL RX Window has lower priority
217         // than scanning or RX after the data poll.
218         if (RadioSupportsReceiveTiming() && (mState != kStateDisabled) && (mState != kStateReceive))
219         {
220             IgnoreError(Get<Radio>().ReceiveAt(mCslChannel, winStart, winDuration));
221         }
222         else if (mState == kStateCslSample)
223         {
224             IgnoreError(Get<Radio>().Receive(mCslChannel));
225         }
226 
227         LogDebg("CSL window start %lu, duration %lu", ToUlong(winStart), ToUlong(winDuration));
228     }
229 }
230 
GetCslWindowEdges(uint32_t & aAhead,uint32_t & aAfter)231 void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter)
232 {
233     uint32_t semiPeriod = mCslPeriod * kUsPerTenSymbols / 2;
234     uint32_t curTime, elapsed, semiWindow;
235 
236     curTime = GetLocalTime();
237     elapsed = curTime - mCslLastSync.GetValue();
238 
239     semiWindow =
240         static_cast<uint32_t>(static_cast<uint64_t>(elapsed) *
241                               (Get<Radio>().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy()) / 1000000);
242     semiWindow += mCslParentAccuracy.GetUncertaintyInMicrosec() + Get<Radio>().GetCslUncertainty() * 10;
243 
244     aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead + kCslReceiveTimeAhead);
245     aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter);
246 }
247 
GetLocalTime(void)248 uint32_t SubMac::GetLocalTime(void)
249 {
250     uint32_t now;
251 
252 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
253     now = TimerMicro::GetNow().GetValue();
254 #else
255     now = static_cast<uint32_t>(otPlatRadioGetNow(&GetInstance()));
256 #endif
257 
258     return now;
259 }
260 
261 #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
LogReceived(RxFrame * aFrame)262 void SubMac::LogReceived(RxFrame *aFrame)
263 {
264     static constexpr uint8_t kLogStringSize = 72;
265 
266     String<kLogStringSize> logString;
267     Address                dst;
268     int32_t                deviation;
269     uint32_t               sampleTime, ahead, after;
270 
271     IgnoreError(aFrame->GetDstAddr(dst));
272 
273     VerifyOrExit((dst.GetType() == Address::kTypeShort && dst.GetShort() == GetShortAddress()) ||
274                  (dst.GetType() == Address::kTypeExtended && dst.GetExtended() == GetExtAddress()));
275 
276     LogDebg("Received frame in state (SubMac %s, CSL %s), timestamp %lu", StateToString(mState),
277             mIsCslSampling ? "CslSample" : "CslSleep",
278             ToUlong(static_cast<uint32_t>(aFrame->mInfo.mRxInfo.mTimestamp)));
279 
280     VerifyOrExit(mState == kStateCslSample);
281 
282     GetCslWindowEdges(ahead, after);
283     ahead -= kMinReceiveOnAhead + kCslReceiveTimeAhead;
284 
285     sampleTime = mCslSampleTime.GetValue() - mCslPeriod * kUsPerTenSymbols;
286     deviation  = aFrame->mInfo.mRxInfo.mTimestamp + kRadioHeaderPhrDuration - sampleTime;
287 
288     // This logs three values (all in microseconds):
289     // - Absolute sample time in which the CSL receiver expected the MHR of the received frame.
290     // - Allowed margin around that time accounting for accuracy and uncertainty from both devices.
291     // - Real deviation on the reception of the MHR with regards to expected sample time. This can
292     //   be due to clocks drift and/or CSL Phase rounding error.
293     // This means that a deviation absolute value greater than the margin would result in the frame
294     // not being received out of the debug mode.
295     logString.Append("Expected sample time %lu, margin ±%lu, deviation %ld", ToUlong(sampleTime), ToUlong(ahead),
296                      static_cast<long>(deviation));
297 
298     // Treat as a warning when the deviation is not within the margins. Neither kCslReceiveTimeAhead
299     // or kMinReceiveOnAhead/kMinReceiveOnAfter are considered for the margin since they have no
300     // impact on understanding possible deviation errors between transmitter and receiver. So in this
301     // case only `ahead` is used, as an allowable max deviation in both +/- directions.
302     if ((deviation + ahead > 0) && (deviation < static_cast<int32_t>(ahead)))
303     {
304         LogDebg("%s", logString.AsCString());
305     }
306     else
307     {
308         LogWarn("%s", logString.AsCString());
309     }
310 
311 exit:
312     return;
313 }
314 #endif
315 
316 } // namespace Mac
317 } // namespace ot
318 
319 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
320