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