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